Files
SIRH/src/Entity/Employee.php
tristan 1fe7f2cdde
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
feat : agence d'intérim sur les contrats INTERIM + renommage Types d'absence en Types de statut + colonne Absence en Statut
- Nouvelle entité InterimAgency (table interim_agencies, API lecture seule)
- Sélecteur agence conditionnel dans les formulaires création employé et ajout contrat
- Affichage "Intérim (NomAgence)" sur la liste employés et l'historique contrat
- Date de fin obligatoire côté frontend pour CDD et INTERIM (aligné backend)
- Renommage "Types d'absence" → "Types de statut" (sidebar, page, titre)
- Renommage en-tête "Absence" → "Statut" sur les vues jour heures et conducteurs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 11:47:14 +02:00

454 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use App\Dto\Employees\ContractHistoryItem;
use App\Enum\ContractNature;
use App\Repository\EmployeeRepository;
use App\State\EmployeeWriteProcessor;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
#[ApiResource(
normalizationContext: ['groups' => ['employee:read', 'site:read']],
denormalizationContext: ['groups' => ['employee:write']],
paginationEnabled: false,
security: "is_granted('ROLE_ADMIN')",
processor: EmployeeWriteProcessor::class,
order: ['site.name' => 'ASC', 'displayOrder' => 'ASC', 'lastName' => 'ASC', 'firstName' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: EmployeeRepository::class)]
#[ORM\Table(name: 'employees')]
class Employee
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
#[Groups(['absence:read', 'employee:read'])]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 100)]
#[Groups(['absence:read', 'employee:read', 'employee:write'])]
private string $firstName = '';
#[ORM\Column(type: 'string', length: 100)]
#[Groups(['absence:read', 'employee:read', 'employee:write'])]
private string $lastName = '';
#[ApiProperty(readableLink: true)]
#[ORM\ManyToOne(targetEntity: Site::class)]
#[ORM\JoinColumn(nullable: true)]
#[Groups(['employee:read', 'employee:write'])]
private ?Site $site = null;
#[ApiProperty(readableLink: true)]
#[ORM\ManyToOne(targetEntity: Contract::class)]
#[ORM\JoinColumn(nullable: false)]
#[Groups(['employee:read', 'employee:write'])]
private ?Contract $contract = null;
#[ORM\Column(type: 'integer', options: ['default' => 0])]
#[Groups(['employee:read', 'employee:write'])]
private int $displayOrder = 0;
#[ORM\Column(type: 'date_immutable', nullable: true)]
#[Groups(['employee:read'])]
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
private ?DateTimeImmutable $entryDate = null;
#[ORM\Column(type: 'datetime_immutable')]
private DateTimeImmutable $createdAt;
/**
* @var Collection<int, EmployeeContractPeriod>
*/
#[ORM\OneToMany(mappedBy: 'employee', targetEntity: EmployeeContractPeriod::class)]
private Collection $contractPeriods;
#[Groups(['employee:write'])]
private ?string $contractNature = null;
#[Groups(['employee:write'])]
private ?string $contractStartDate = null;
#[Groups(['employee:write'])]
private ?string $contractEndDate = null;
#[Groups(['employee:write'])]
private ?bool $contractPaidLeaveSettled = null;
#[Groups(['employee:write'])]
private ?string $contractComment = null;
#[Groups(['employee:write'])]
private ?bool $isDriverInput = null;
/**
* @var null|array<int, int> iso-day → minutes, write-only (propagated to EmployeeContractPeriod)
*/
#[Groups(['employee:write'])]
private ?array $workDaysHoursInput = null;
#[Groups(['employee:write'])]
private ?int $interimAgencyId = null;
public function __construct()
{
$this->createdAt = new DateTimeImmutable();
$this->contractPeriods = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getFirstName(): string
{
return $this->firstName;
}
public function setFirstName(string $firstName): self
{
$this->firstName = $firstName;
return $this;
}
public function getLastName(): string
{
return $this->lastName;
}
public function setLastName(string $lastName): self
{
$this->lastName = $lastName;
return $this;
}
#[Groups(['employee:read'])]
public function getInitials(): string
{
$first = mb_strtoupper(mb_substr(trim($this->firstName), 0, 1));
$last = mb_strtoupper(mb_substr(trim($this->lastName), 0, 1));
return $first.$last;
}
public function getSite(): ?Site
{
return $this->site;
}
public function setSite(?Site $site): self
{
$this->site = $site;
return $this;
}
public function getContract(): ?Contract
{
return $this->contract;
}
public function setContract(?Contract $contract): self
{
$this->contract = $contract;
return $this;
}
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
public function getDisplayOrder(): int
{
return $this->displayOrder;
}
public function setDisplayOrder(int $displayOrder): self
{
$this->displayOrder = $displayOrder;
return $this;
}
public function getEntryDate(): ?DateTimeImmutable
{
return $this->entryDate;
}
public function setEntryDate(?DateTimeImmutable $entryDate): self
{
$this->entryDate = $entryDate;
return $this;
}
public function getContractNature(): ?string
{
return $this->contractNature;
}
public function setContractNature(?string $contractNature): self
{
$this->contractNature = $contractNature;
return $this;
}
public function getContractStartDate(): ?string
{
return $this->contractStartDate;
}
public function setContractStartDate(?string $contractStartDate): self
{
$this->contractStartDate = $contractStartDate;
return $this;
}
public function getContractEndDate(): ?string
{
return $this->contractEndDate;
}
public function setContractEndDate(?string $contractEndDate): self
{
$this->contractEndDate = $contractEndDate;
return $this;
}
public function getContractPaidLeaveSettled(): ?bool
{
return $this->contractPaidLeaveSettled;
}
public function setContractPaidLeaveSettled(?bool $contractPaidLeaveSettled): self
{
$this->contractPaidLeaveSettled = $contractPaidLeaveSettled;
return $this;
}
public function getContractComment(): ?string
{
return $this->contractComment;
}
public function setContractComment(?string $contractComment): self
{
$this->contractComment = $contractComment;
return $this;
}
public function getIsDriverInput(): ?bool
{
return $this->isDriverInput;
}
public function setIsDriverInput(?bool $isDriverInput): self
{
$this->isDriverInput = $isDriverInput;
return $this;
}
/**
* @return null|array<int, int>
*/
public function getWorkDaysHoursInput(): ?array
{
return $this->workDaysHoursInput;
}
/**
* @param null|array<int|string, mixed> $workDaysHoursInput
*/
public function setWorkDaysHoursInput(?array $workDaysHoursInput): self
{
if (null === $workDaysHoursInput) {
$this->workDaysHoursInput = null;
return $this;
}
$normalized = [];
foreach ($workDaysHoursInput as $key => $value) {
$normalized[(int) $key] = (int) $value;
}
$this->workDaysHoursInput = $normalized;
return $this;
}
public function getInterimAgencyId(): ?int
{
return $this->interimAgencyId;
}
public function setInterimAgencyId(?int $interimAgencyId): self
{
$this->interimAgencyId = $interimAgencyId;
return $this;
}
#[Groups(['employee:read'])]
public function getCurrentInterimAgencyId(): ?int
{
return $this->resolveCurrentContractPeriod()?->getInterimAgency()?->getId();
}
#[Groups(['employee:read'])]
public function getCurrentInterimAgencyName(): ?string
{
return $this->resolveCurrentContractPeriod()?->getInterimAgency()?->getName();
}
#[Groups(['employee:read'])]
public function getHasActiveContract(): bool
{
return null !== $this->resolveCurrentContractPeriod();
}
#[Groups(['employee:read'])]
public function getIsDriver(): bool
{
return $this->resolveCurrentContractPeriod()?->getIsDriver() ?? false;
}
#[Groups(['employee:read'])]
public function getCurrentContractNature(): string
{
return $this->resolveCurrentContractPeriod()?->getContractNatureEnum()->value ?? ContractNature::CDI->value;
}
#[Groups(['employee:read'])]
public function getCurrentContractStartDate(): ?string
{
return $this->resolveCurrentContractPeriod()?->getStartDate()->format('Y-m-d');
}
#[Groups(['employee:read'])]
public function getCurrentContractEndDate(): ?string
{
return $this->resolveCurrentContractPeriod()?->getEndDate()?->format('Y-m-d');
}
/**
* @return list<array{id: null|int, startDate: string, endDate: null|string, comment: null|string}>
*/
#[Groups(['employee:read'])]
public function getCurrentSuspensions(): array
{
$currentPeriod = $this->resolveCurrentContractPeriod();
if (null === $currentPeriod) {
return [];
}
return array_values(array_map(
static fn (ContractSuspension $s): array => [
'id' => $s->getId(),
'startDate' => $s->getStartDate()->format('Y-m-d'),
'endDate' => $s->getEndDate()?->format('Y-m-d'),
'comment' => $s->getComment(),
],
$currentPeriod->getSuspensions()->toArray()
));
}
/**
* @return Collection<int, EmployeeContractPeriod>
*/
public function getContractPeriods(): Collection
{
return $this->contractPeriods;
}
/**
* @return list<ContractHistoryItem>
*/
#[Groups(['employee:read'])]
public function getContractHistory(): array
{
$periods = $this->contractPeriods->toArray();
usort(
$periods,
static fn (EmployeeContractPeriod $a, EmployeeContractPeriod $b): int => $b->getStartDate() <=> $a->getStartDate()
);
return array_map(
static function (EmployeeContractPeriod $period): ContractHistoryItem {
$contract = $period->getContract();
$suspensionData = array_map(
static fn (ContractSuspension $s): array => [
'id' => $s->getId(),
'startDate' => $s->getStartDate()->format('Y-m-d'),
'endDate' => $s->getEndDate()?->format('Y-m-d'),
'comment' => $s->getComment(),
],
$period->getSuspensions()->toArray()
);
return new ContractHistoryItem(
contractId: $contract?->getId(),
contractName: $contract?->getName(),
weeklyHours: $contract?->getWeeklyHours(),
contractNature: $period->getContractNatureEnum()->value,
startDate: $period->getStartDate()->format('Y-m-d'),
endDate: $period->getEndDate()?->format('Y-m-d'),
comment: $period->getComment(),
periodId: $period->getId(),
suspensions: $suspensionData,
isDriver: $period->getIsDriver(),
workDaysHours: $period->getWorkDaysHours(),
interimAgencyId: $period->getInterimAgency()?->getId(),
interimAgencyName: $period->getInterimAgency()?->getName(),
);
},
$periods
);
}
private function resolveCurrentContractPeriod(): ?EmployeeContractPeriod
{
$today = new DateTimeImmutable('today');
$current = null;
foreach ($this->contractPeriods as $period) {
if ($period->getStartDate() > $today) {
continue;
}
$endDate = $period->getEndDate();
if (null !== $endDate && $endDate < $today) {
continue;
}
if (null === $current || $period->getStartDate() > $current->getStartDate()) {
$current = $period;
}
}
return $current;
}
}