Compare commits

..

7 Commits

Author SHA1 Message Date
deploy 562ea93d78 add jwt secret to prod env file
Build and Deploy Docker / build (push) Successful in 5m31s
Build and Deploy Docker / deploy (push) Successful in 47s
2026-07-14 16:40:46 +02:00
deploy 30f50dcfb7 change config from mariadb to mysql
Build and Deploy Docker / build (push) Successful in 5m48s
Build and Deploy Docker / deploy (push) Successful in 46s
2026-07-14 15:42:57 +02:00
deploy d8022ab205 change docker container name to backend
Build and Deploy Docker / build (push) Successful in 8m35s
Build and Deploy Docker / deploy (push) Successful in 39s
2026-07-14 14:53:45 +02:00
deploy 71c14b0625 add env prod 2026-07-14 14:14:42 +02:00
deploy d9bf86598b add cicd
Build and Deploy Docker / build (push) Failing after 14m19s
Build and Deploy Docker / deploy (push) Has been skipped
2026-07-14 08:05:08 +02:00
deploy 4388a7ec93 add client from rest api 2026-07-14 07:56:35 +02:00
deploy 22da85872d initial rest api for react.js 2026-06-29 18:54:40 +02:00
24 changed files with 2131 additions and 660 deletions
+52
View File
@@ -0,0 +1,52 @@
name: Build and Deploy Docker
on:
push:
branches:
- masterApi
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: git.rhost.ovh
username: ${{ github.actor }}
password: ${{ secrets.TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./services/www/Dockerfile
push: true
tags: git.rhost.ovh/${{ github.repository }}:latest
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.2.0
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SERVER_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /home/ryjek/docker/sprawozdania
docker compose pull backend
docker compose up -d backend
docker compose exec --workdir /var/www/html backend APP_RUNTIME_ENV=prod php bin/console secrets:decrypt-to-local --force
docker compose exec --workdir /var/www/html backend php ./bin/console doctrine:migration:migrate --dry-run
docker compose exec --workdir /var/www/html backend php ./bin/console lexik:jwt:generate-keypair --skip-if-exists
docker compose exec --workdir /var/www/html backend php ./bin/console cache:clear
-10
View File
@@ -38,16 +38,6 @@ services:
- internal - internal
volumes: volumes:
- ./storage/redis:/data - ./storage/redis:/data
# wait_for_it:
# build:
# context: .
# dockerfile: services/wait_for_it/Dockerfile
# networks:
# - internal
# depends_on:
# - database
# command: sh -c "/wait -t 30 database:5432 -- echo 123;"
networks: networks:
internal: internal:
internal: true internal: true
+35
View File
@@ -0,0 +1,35 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=prod
APP_SECRET=$env(APP_SECRET)$
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
#DATABASE_URL="mysql://app:%env(DATABASE_PASSWORD)%@mariadb:3306/app?serverVersion=8.0.32&charset=utf8mb4"
DATABASE_URL="mysql://app:%env(DATABASE_PASSWORD)%@mysql:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
#DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ###
REDIS_URL=redis://redis:6379?timeout=5
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=%env(JWT_PASSPHRASE)%
+2 -1
View File
@@ -1,11 +1,12 @@
###> symfony/framework-bundle ### ###> symfony/framework-bundle ###
.env .env
.env.prod
/.env.local /.env.local
/.env.local.php /.env.local.php
/.env.*.local /.env.*.local
/config/secrets/prod/prod.decrypt.private.php /config/secrets/prod/prod.decrypt.private.php
config/jwt
!config/jwt/.gitkeep
/public/bundles/ /public/bundles/
/var/ /var/
/vendor/ /vendor/
+3
View File
@@ -12,6 +12,8 @@
"doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.2", "doctrine/orm": "^3.2",
"league/commonmark": "^2.4", "league/commonmark": "^2.4",
"lexik/jwt-authentication-bundle": "^3.2",
"nelmio/cors-bundle": "^2.6",
"predis/predis": "^2.2", "predis/predis": "^2.2",
"symfony/console": "6.4.*", "symfony/console": "6.4.*",
"symfony/dotenv": "6.4.*", "symfony/dotenv": "6.4.*",
@@ -20,6 +22,7 @@
"symfony/http-client": "^7.1", "symfony/http-client": "^7.1",
"symfony/runtime": "6.4.*", "symfony/runtime": "6.4.*",
"symfony/security-bundle": "6.4.*", "symfony/security-bundle": "6.4.*",
"symfony/serializer-pack": "^1.3",
"symfony/stimulus-bundle": "^2.18", "symfony/stimulus-bundle": "^2.18",
"symfony/twig-bundle": "6.4.*", "symfony/twig-bundle": "6.4.*",
"symfony/ux-turbo": "^2.18", "symfony/ux-turbo": "^2.18",
+1716 -637
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -12,4 +12,6 @@ return [
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
]; ];
View File
@@ -0,0 +1,5 @@
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
clock_skew: 0
+8
View File
@@ -0,0 +1,8 @@
nelmio_cors:
defaults:
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_headers: ['Content-Type', 'Authorization']
allow_methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
max_age: 3600
paths:
'^/api/': ~
+16 -11
View File
@@ -13,6 +13,20 @@ security:
dev: dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/ pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false security: false
login:
pattern: ^/api/auth/login
stateless: true
json_login:
check_path: /api/auth/login
username_path: username
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ^/api
stateless: true
jwt: ~
main: main:
lazy: true lazy: true
provider: app_user_provider provider: app_user_provider
@@ -24,22 +38,13 @@ security:
always_use_default_target_path: true always_use_default_target_path: true
logout: logout:
path: app_logout path: app_logout
# where to redirect after logout
# target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site # Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used # Note: Only the *first* access control that matches will be used
access_control: access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/user/activate, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER } - { path: ^/, roles: ROLE_USER }
- { path: ^/api/auth, roles: PUBLIC_ACCESS }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
when@test: when@test:
security: security:
@@ -0,0 +1,3 @@
<?php // dev.JWT_PASSPHRASE.0269d8 on Tue, 14 Jul 2026 16:39:52 +0200
return "\x5D\x0FW\xCF\xA8\x28\xCDN\x8Fk\xE2\x17\xA3\x5Cd\xF7\x15\xA7KeJ\x1A\x27\xF4\x10\xEB\x1A\xFF6U\xAFx\x3C\x84\x5B\xE7\x9D\x01f\xF7\x90\xAC\xAAJ\x82\xA9\xBE\xEDBS2\x16\xA4\x95\x24\x1D\x218\xDF\xF33\x93\xB9\x9A\xAF\xB3\xE7\x02\x22\xABC\xE8\xEDM\x98\x5B\xF7\xFD\x0E\xE8s\x8D\x06\x00\x40m\x9B_\x8F\x27\xF5t3\x97\x8E\xF6y\xAA\xE0\xB7\xF4\xD67\x06\xD0Q\x0B\xFF\x90\x1A\xAC\xA1\x2B\xDA\xA2\x8F6\xF5n\x18.\xDA\x18\x1A\x18\xAC3\xE5\xA28\x9C\xE7\x09\xFF\xCC\x99\x88\x16\x83\x06\x2C\x09z\xF2\x9D\x18\xACT2\xBCqZD\xE9\x93\x96\xCF\xCC\xDDwnp\xFE\xE1o\xA9R\x21\x9B\x5E\x0F\x7C\x0B\xA4\xC9\x11\xCF";
+1
View File
@@ -3,4 +3,5 @@
return [ return [
'API_FAKTUROWNIA' => null, 'API_FAKTUROWNIA' => null,
'DATABASE_PASSWORD' => null, 'DATABASE_PASSWORD' => null,
'JWT_PASSPHRASE' => null,
]; ];
@@ -0,0 +1,3 @@
<?php // prod.JWT_PASSPHRASE.0269d8 on Tue, 14 Jul 2026 16:40:05 +0200
return "\xAF\xFA\xD0CAH\x1B\x27\xB0\x12\x3B\x05\x06\xF5\x18\x90q\xBCr\xE7\x2A\x830\x1A\x19\x02\x2F\xCE\x8CR\xC7\x1C\xA5V\xBA\xC0\xC3\xAC-\x83\x5D\xCB\x87~\xD1\x1E\x95\x2AY\xB8\xA1\x02\xAF\xE7\x60.\xBF\x2F\xD1\xCF3\xB1\xE3\x842\x92\x136\x91\xD1\xFC\xAD\xEE\x2FsC\xFC\x0B\x93\x8AQ\x18\x8F\x93\xBC\x00q\xA3\x93jD\xBF\x1DI\xF3\x98\x94~\x3F\x27\xBB\x9B\x84\x0B\xB0\xBDB\x19\xCC7\xDE\xCF\x2A2\x1C\xAC\x04\xB9\x86\x11\x83C\x3B\x18\xE8\xBBdQ\xC0\xA4\x15\xBCM\x28~\xF4\x0BG\x26\x5Dp\x04\xA5B\xCE\x0F\x5D~\xD8H\xC2\xBE\x18mfep\x07\x1CZ\x2C\x8B7\xBAz\x7C\xF3\x8CuY~\xB5y\xFD\x12\xC7\x9F";
@@ -3,4 +3,5 @@
return [ return [
'APP_SECRET' => null, 'APP_SECRET' => null,
'DATABASE_PASSWORD' => null, 'DATABASE_PASSWORD' => null,
'JWT_PASSPHRASE' => null,
]; ];
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260507143656 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE UNIQUE INDEX report_todo ON reports_todo (report_id, name)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX report_todo ON reports_todo');
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Controller\Api;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class AuthController extends AbstractController
{
#[Route('/api/auth/login', name: 'api_login', methods: ['POST'])]
public function login(): JsonResponse
{
throw new \LogicException('Intercepted by firewall.');
}
}
@@ -0,0 +1,98 @@
<?php
namespace App\Controller\Api;
use App\Entity\Clients;
use App\Repository\ClientsRepository;
use App\Services\AddReports;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/clients', name: 'api_')]
class ClientsController extends AbstractController
{
public function __construct(
private ClientsRepository $repo,
private SerializerInterface $serializer,
) {}
#[Route('', name: 'list', methods: ['GET'])]
public function list(): JsonResponse
{
$products = $this->repo->findAll();
$prepare = [];
foreach ($products as $product) {
$prepare[] = [
'id' => $product->getId(),
'strName' => $product->getStrName(),
'strAddress' => $product->getStrAddress(),
'dateLegitimacy' => $product->getDateLegitimacy()->format('Y-m-d'),
'dateProclamation' => $product->getDateProclamation()->format('Y-m-d'),
'extendInfo' => [
'intNip' => $product->getIntNIP(),
'intPhone' => $product->getIntPhone(),
'strEmail' => $product->getStrEmail(),
]
];
}
return $this->json($prepare, context: ['groups' => ['client:read']]);
}
#[Route('/add', name: 'add_client', methods: ['POST'])]
public function add(Request $request, EntityManagerInterface $entityManager, AddReports $addReports): JsonResponse
{
$need = [
"strName" => $request->getPayload()->get('name'),
"strAddress" => $request->getPayload()->get('address'),
"dateLegitimacy" => $request->getPayload()->get('legitimacy'),
"dateProclamation" => $request->getPayload()->get('proclamation'),
"intNIP" => $request->getPayload()->get('nip'),
"intPhone" => $request->getPayload()->get('phone'),
"strEmail" => $request->getPayload()->get('email'),
];
$newClient = new Clients();
foreach ($need as $key => $value) {
$cleared = $this->clearType($key, $value);
$method = 'set'.ucfirst($key);
$newClient->{$method}($cleared);
}
$entityManager->persist($newClient);
$entityManager->flush();
$entityManager->getCache()->evictEntityRegion(Clients::class);
$addReports->setClient($newClient);
$addReports->init();
return $this->json(['success' => true]);
}
private function clearType(string $key, mixed $value)
{
if (str_contains($key, 'str')) {
return addslashes($value);
}
if (str_contains($key, 'int')) {
return intval($value);
}
if (str_contains($key, 'date')) {
return new \DateTime($value);
}
}
// #[Route('/{id}', name: 'show', methods: ['GET'])]
// public function show(Product $product): JsonResponse
// {
// return $this->json($product, context: ['groups' => ['product:read']]);
// }
//
// #[Route('', name: 'create', methods: ['POST'])]
// public function create(Request $request): JsonResponse
// {
// $data = $request->toArray();
// // walidacja + zapis...
// return $this->json($product, 201, context: ['groups' => ['product:read']]);
// }
}
@@ -0,0 +1,72 @@
<?php
namespace App\Controller\Api;
use App\Entity\Reports;
use App\Entity\ReportsTodo;
use App\Enum\ReportStatus;
use App\Enum\ReportTodoEnum;
use App\Helper\MonthHelper;
use App\Repository\ReportsRepository;
use App\Services\AddReports;
use App\Services\OverdueReports;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/reports', name: 'api_reports')]
class ReportsController extends AbstractController
{
public function __construct(
private readonly ReportsRepository $repo,
private readonly OverdueReports $overdueReports
)
{
}
#[Route('/{monthId}', name: 'api_reports_all', methods: ['GET'])]
public function getReports(int $monthId): JsonResponse
{
$monthHelper = new MonthHelper($this->overdueReports);
$clientId=0;
$monthCurrentDate = $monthHelper->getCurrentMonthDate($monthId);
$reportsAll = $this->repo->findAllInMonth($monthCurrentDate,$clientId);
return $this->json($reportsAll, context: ['groups' => ['reportsAll:read','reportstodo:read']]);
}
#[Route('/todo', name: 'api_reports_todo', methods: ['POST'])]
public function updateReportTodo(Request $request, AddReports $reportsService, EntityManagerInterface $entityManager): JsonResponse
{
$reportId = $request->toArray()['reportId'];
$todoName = $request->toArray()['todoName'];
$nameObj = ReportTodoEnum::tryFromNameStatic($todoName);
if (!$nameObj) {
throw $this->createNotFoundException();
}
$reportReference = $entityManager->getReference(Reports::class, $reportId);
$todo = $entityManager->getRepository(ReportsTodo::class)->findOneBy(['name' => $nameObj, 'report' => $reportReference]);
if($todo){
$todo->setDeleted(!$todo->isDeleted());
} else {
$todo = new ReportsTodo();
$todo->setName($nameObj);
$todo->setReport($reportReference);
}
$reportReference->addReportsTodo($todo);
$entityManager->persist($todo);
$entityManager->flush();
$entityManager->getCache()->evictCollection(Reports::class,"reportsTodos",$reportReference->getId());
return $this->json([
'reportId' => $reportId,
'reportStatusTodo' => $reportReference->getStatusTodo(),
'todoItem' => $todo
], context: ['groups' => ['reportstodo:read']]);
}
}
+14
View File
@@ -8,6 +8,8 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: ClientsRepository::class)] #[ORM\Entity(repositoryClass: ClientsRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'client_cache')] #[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'client_cache')]
@@ -16,41 +18,53 @@ class Clients
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
#[Groups(['client:read'])]
private ?int $id = null; private ?int $id = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Groups(['client:read','reportsAll:read'])]
private ?string $strName = null; private ?string $strName = null;
#[ORM\Column(type: Types::BIGINT)] #[ORM\Column(type: Types::BIGINT)]
#[Groups(['client:read'])]
private ?int $intNIP = null; private ?int $intNIP = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Groups(['client:read'])]
private ?string $strAddress = null; private ?string $strAddress = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Groups(['client:read'])]
private ?string $strEmail = null; private ?string $strEmail = null;
#[ORM\Column(type: Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_MUTABLE)]
#[Groups(['client:read'])]
private ?\DateTimeInterface $dateLegitimacy = null; private ?\DateTimeInterface $dateLegitimacy = null;
#[ORM\Column(type: Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_MUTABLE)]
#[Groups(['client:read'])]
private ?\DateTimeInterface $dateProclamation = null; private ?\DateTimeInterface $dateProclamation = null;
/** /**
* @var Collection<int, Schedule> * @var Collection<int, Schedule>
*/ */
#[Groups(['client:read'])]
#[ORM\OneToMany(targetEntity: Schedule::class, mappedBy: 'client')] #[ORM\OneToMany(targetEntity: Schedule::class, mappedBy: 'client')]
private Collection $schedules; private Collection $schedules;
#[Groups(['client:read'])]
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
private bool $isDeleted = false; private bool $isDeleted = false;
#[Groups(['client:read'])]
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
private bool $isComplete = false; private bool $isComplete = false;
#[Groups(['client:read'])]
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
private ?int $intPhone = null; private ?int $intPhone = null;
#[Groups(['client:read'])]
#[ORM\Column(enumType: ReportHighlight::class, options: ["default" => "0"])] #[ORM\Column(enumType: ReportHighlight::class, options: ["default" => "0"])]
private ReportHighlight $highlight = ReportHighlight::STANDARD; private ReportHighlight $highlight = ReportHighlight::STANDARD;
+28
View File
@@ -13,6 +13,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: ReportsRepository::class)] #[ORM\Entity(repositoryClass: ReportsRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'report_cache')] #[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'report_cache')]
@@ -22,13 +23,16 @@ class Reports
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
#[Groups(['report:read','reportsAll:read'])]
private ?int $id = null; private ?int $id = null;
#[ORM\ManyToOne] #[ORM\ManyToOne]
#[ORM\JoinColumn(referencedColumnName: "id", nullable: false)] #[ORM\JoinColumn(referencedColumnName: "id", nullable: false)]
#[Groups(['report:read','reportsAll:read'])]
private ?Clients $client = null; private ?Clients $client = null;
#[ORM\Column(type: Types::DATE_MUTABLE)] #[ORM\Column(type: Types::DATE_MUTABLE)]
#[Groups(['report:read'])]
private ?\DateTimeInterface $dateStart = null; private ?\DateTimeInterface $dateStart = null;
/** /**
@@ -36,21 +40,26 @@ class Reports
*/ */
#[ORM\OneToMany(targetEntity: ReportsTodo::class, mappedBy: 'report', orphanRemoval: true)] #[ORM\OneToMany(targetEntity: ReportsTodo::class, mappedBy: 'report', orphanRemoval: true)]
#[ORM\Cache(usage: 'READ_ONLY', region: 'report_cache')] #[ORM\Cache(usage: 'READ_ONLY', region: 'report_cache')]
#[Groups(['report:read','reportsAll:read'])]
private Collection $reportsTodos; private Collection $reportsTodos;
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
#[Groups(['report:read'])]
private bool $isDelete = false; private bool $isDelete = false;
private int $numberOfReport = 0; private int $numberOfReport = 0;
#[ORM\Column(options: ["default" => "0"])] #[ORM\Column(options: ["default" => "0"])]
#[Groups(['report:read'])]
private bool $isDone = false; private bool $isDone = false;
#[ORM\Column] #[ORM\Column]
#[Groups(['report:read','reportsAll:read'])]
private ?int $number = null; private ?int $number = null;
#[ORM\Column(enumType: ReportHighlight::class, options: ["default" => "0"])] #[ORM\Column(enumType: ReportHighlight::class, options: ["default" => "0"])]
#[Groups(['report:read'])]
private ReportHighlight $highlight = ReportHighlight::STANDARD; private ReportHighlight $highlight = ReportHighlight::STANDARD;
public function __construct() public function __construct()
@@ -150,6 +159,25 @@ class Reports
return $count; return $count;
} }
#[Groups(['reportsAll:read'])]
public function getStatusTodo(): ReportStatus
{
if ($this->countTodoReports() === 0) {
return ReportStatus::INSERT;
}
$status = ReportStatus::WORKING;
foreach ($this->getReportsTodos() as $reportsTodo) {
if ($reportsTodo->isDeleted()) {
continue;
}
if ($reportsTodo->getName() === ReportTodoEnum::COMPLETE) {
$status = ReportStatus::COMPLETE;
break;
}
}
return $status;
}
public function takeStatusTodo(): ReportStatus public function takeStatusTodo(): ReportStatus
{ {
if ($this->countTodoReports() === 0) { if ($this->countTodoReports() === 0) {
+21
View File
@@ -7,17 +7,29 @@ use App\Enum\ReportTodoEnum;
use App\Repository\ReportsTodoRepository; use App\Repository\ReportsTodoRepository;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: ReportsTodoRepository::class)] #[ORM\Entity(repositoryClass: ReportsTodoRepository::class)]
#[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'report_cache')] #[ORM\Cache(usage: 'NONSTRICT_READ_WRITE', region: 'report_cache')]
#[ORM\Table(
name: 'reports_todo',
uniqueConstraints: [
new ORM\UniqueConstraint(
name: 'report_todo',
columns: ['report_id', 'name'],
)
]
)]
class ReportsTodo class ReportsTodo
{ {
#[ORM\Id] #[ORM\Id]
#[ORM\GeneratedValue] #[ORM\GeneratedValue]
#[ORM\Column] #[ORM\Column]
#[Groups(['reportstodo:read'])]
private ?int $id = null; private ?int $id = null;
#[ORM\Column(type: Types::SMALLINT, enumType: ReportTodoEnum::class)] #[ORM\Column(type: Types::SMALLINT, enumType: ReportTodoEnum::class)]
#[Groups(['reportstodo:read'])]
private ?ReportTodoEnum $name = null; private ?ReportTodoEnum $name = null;
#[ORM\ManyToOne(inversedBy: 'reportsTodos')] #[ORM\ManyToOne(inversedBy: 'reportsTodos')]
@@ -25,8 +37,12 @@ class ReportsTodo
private ?Reports $report = null; private ?Reports $report = null;
#[ORM\Column(options: ["default" => false])] #[ORM\Column(options: ["default" => false])]
#[Groups(['reportstodo:read'])]
private ?bool $deleted = false; private ?bool $deleted = false;
#[Groups(['reportstodo:read'])]
private ?string $nameString = null;
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@@ -67,4 +83,9 @@ class ReportsTodo
return $this; return $this;
} }
public function getNameString(): ?string
{
return $this->name->name;
}
} }
+4
View File
@@ -36,6 +36,10 @@ enum ReportTodoEnum: int {
} }
public function tryFromName(string $name): ?ReportTodoEnum public function tryFromName(string $name): ?ReportTodoEnum
{
return self::tryFromNameStatic($name);
}
public static function tryFromNameStatic(string $name): ?ReportTodoEnum
{ {
$name = strtoupper($name); $name = strtoupper($name);
foreach (self::cases() as $v) { foreach (self::cases() as $v) {