Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
- 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>
454 lines
12 KiB
PHP
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;
|
|
}
|
|
}
|