Ajout des notification + page employé (#6)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled

| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #6
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #6.
This commit is contained in:
2026-03-10 12:35:17 +00:00
committed by Autin
parent ae42c70d50
commit f493ea237b
126 changed files with 9215 additions and 935 deletions

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Service\Contracts;
use App\Enum\ContractNature;
use DateTimeImmutable;
final readonly class EmployeeContractChangeRequest
{
public function __construct(
public ?ContractNature $contractNature,
public ?DateTimeImmutable $contractStartDate,
public ?DateTimeImmutable $contractEndDate,
public ?bool $contractPaidLeaveSettled,
public ?string $contractComment,
) {}
public function hasPeriodChangeRequest(): bool
{
return null !== $this->contractNature
|| null !== $this->contractStartDate
|| null !== $this->contractEndDate
|| null !== $this->contractPaidLeaveSettled
|| null !== $this->contractComment;
}
public function isCloseOnlyRequest(bool $contractChanged): bool
{
return !$contractChanged
&& null === $this->contractStartDate
&& null === $this->contractNature
&& null !== $this->contractEndDate;
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Service\Contracts;
use App\Entity\Employee;
use App\Enum\ContractNature;
use DateTimeImmutable;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
final class EmployeeContractChangeRequestFactory
{
public function fromEmployee(Employee $employee): EmployeeContractChangeRequest
{
return new EmployeeContractChangeRequest(
contractNature: $this->resolveContractNature($employee->getContractNature()),
contractStartDate: $this->parseOptionalYmd($employee->getContractStartDate(), 'contractStartDate'),
contractEndDate: $this->parseOptionalYmd($employee->getContractEndDate(), 'contractEndDate'),
contractPaidLeaveSettled: $employee->getContractPaidLeaveSettled(),
contractComment: $employee->getContractComment(),
);
}
private function resolveContractNature(?string $raw): ?ContractNature
{
if (null === $raw || '' === trim($raw)) {
return null;
}
return ContractNature::tryFrom(trim($raw))
?? throw new UnprocessableEntityHttpException('contractNature must be one of CDI, CDD, INTERIM.');
}
private function parseOptionalYmd(?string $raw, string $field): ?DateTimeImmutable
{
if (null === $raw || '' === trim($raw)) {
return null;
}
$value = trim($raw);
$date = DateTimeImmutable::createFromFormat('Y-m-d', $value);
if (!$date || $date->format('Y-m-d') !== $value) {
throw new UnprocessableEntityHttpException(sprintf('%s must use Y-m-d format.', $field));
}
return $date;
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace App\Service\Contracts;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Enum\ContractNature;
use DateTimeImmutable;
final class EmployeeContractPeriodBuilder
{
public function build(
Employee $employee,
Contract $contract,
DateTimeImmutable $startDate,
?DateTimeImmutable $endDate,
ContractNature $nature,
): EmployeeContractPeriod {
return new EmployeeContractPeriod()
->setEmployee($employee)
->setContract($contract)
->setStartDate($startDate)
->setEndDate($endDate)
->setContractNature($nature)
;
}
}

View File

@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace App\Service\Contracts;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Enum\ContractNature;
use App\Repository\EmployeeContractPeriodRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
final readonly class EmployeeContractPeriodManager implements EmployeeContractPeriodManagerInterface
{
public function __construct(
private EntityManagerInterface $entityManager,
private EmployeeContractPeriodRepository $periodRepository,
private EmployeeContractPeriodBuilder $periodBuilder,
private EmployeeContractPeriodValidator $periodValidator,
) {}
public function ensureContractPeriodExists(
Employee $employee,
Contract $contract,
DateTimeImmutable $startDate,
?DateTimeImmutable $endDate,
ContractNature $nature,
): void {
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
$covered = $this->periodRepository->findOneCoveringDate($employee, $startDate);
if (null !== $covered) {
return;
}
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature);
$this->entityManager->flush();
}
public function closeCurrentPeriod(
?EmployeeContractPeriod $todayPeriod,
DateTimeImmutable $requestedEndDate,
bool $paidLeaveSettled,
?string $comment = null
): void {
if (null === $todayPeriod) {
throw new UnprocessableEntityHttpException('No active contract period to close.');
}
$this->periodValidator->assertCloseEndDateCanBeApplied(
$todayPeriod->getStartDate(),
$todayPeriod->getEndDate(),
$requestedEndDate,
$todayPeriod->getContractNatureEnum()
);
$todayPeriod->setEndDate($requestedEndDate);
$todayPeriod->setPaidLeaveSettled($paidLeaveSettled);
$todayPeriod->setComment($comment);
$this->entityManager->flush();
}
public function createNextPeriod(
Employee $employee,
Contract $contract,
DateTimeImmutable $startDate,
?DateTimeImmutable $endDate,
ContractNature $nature,
?EmployeeContractPeriod $todayPeriod
): void {
$this->periodValidator->assertPeriodDates($startDate, $endDate, $nature);
if (null !== $todayPeriod) {
$this->periodValidator->assertNextStartDateCompatible($startDate, $todayPeriod);
if (null === $todayPeriod->getEndDate()) {
$todayPeriod->setEndDate($startDate->modify('-1 day'));
}
}
$this->persistNewPeriod($employee, $contract, $startDate, $endDate, $nature);
$this->entityManager->flush();
}
private function persistNewPeriod(
Employee $employee,
Contract $contract,
DateTimeImmutable $startDate,
?DateTimeImmutable $endDate,
ContractNature $nature,
): void {
$period = $this->periodBuilder->build($employee, $contract, $startDate, $endDate, $nature);
$this->entityManager->persist($period);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\Service\Contracts;
use App\Entity\Contract;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use App\Enum\ContractNature;
use DateTimeImmutable;
interface EmployeeContractPeriodManagerInterface
{
public function ensureContractPeriodExists(
Employee $employee,
Contract $contract,
DateTimeImmutable $startDate,
?DateTimeImmutable $endDate,
ContractNature $nature,
): void;
public function closeCurrentPeriod(
?EmployeeContractPeriod $todayPeriod,
DateTimeImmutable $requestedEndDate,
bool $paidLeaveSettled,
?string $comment = null
): void;
public function createNextPeriod(
Employee $employee,
Contract $contract,
DateTimeImmutable $startDate,
?DateTimeImmutable $endDate,
ContractNature $nature,
?EmployeeContractPeriod $todayPeriod
): void;
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace App\Service\Contracts;
use App\Entity\EmployeeContractPeriod;
use App\Enum\ContractNature;
use DateTimeImmutable;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
final class EmployeeContractPeriodValidator
{
public function assertPeriodDates(
DateTimeImmutable $startDate,
?DateTimeImmutable $endDate,
ContractNature $nature,
bool $allowCdiEndDate = false
): void {
if (null !== $endDate && $endDate < $startDate) {
throw new UnprocessableEntityHttpException('contractEndDate cannot be before contractStartDate.');
}
if ($nature->requiresEndDate() && null === $endDate) {
throw new UnprocessableEntityHttpException('contractEndDate is required for CDD and INTERIM.');
}
if (!$allowCdiEndDate && ContractNature::CDI === $nature && null !== $endDate) {
throw new UnprocessableEntityHttpException('contractEndDate must be empty for CDI.');
}
}
public function assertCloseEndDateCanBeApplied(
DateTimeImmutable $startDate,
?DateTimeImmutable $currentEndDate,
DateTimeImmutable $requestedEndDate,
ContractNature $nature
): void {
$this->assertPeriodDates($startDate, $requestedEndDate, $nature, true);
if (null !== $currentEndDate && $requestedEndDate > $currentEndDate) {
throw new UnprocessableEntityHttpException('contractEndDate cannot be increased on current contract.');
}
}
public function assertNextStartDateCompatible(
DateTimeImmutable $startDate,
EmployeeContractPeriod $currentPeriod
): void {
$currentEndDate = $currentPeriod->getEndDate();
if (null === $currentEndDate) {
if ($startDate <= $currentPeriod->getStartDate()) {
throw new UnprocessableEntityHttpException('contractStartDate must be after current contract start date.');
}
return;
}
if ($startDate <= $currentEndDate) {
throw new UnprocessableEntityHttpException('contractStartDate must be after current contract end date.');
}
}
}