feat(absences) : fondation backend du module de gestion des absences
Module type Payfit (étapes 1+2 de la spec V1) : demande d'absence, validation admin, soldes à jour. - Enums : AbsenceType, AbsenceStatus, HalfDay, ContractType, FamilySituation - Entités : AbsencePolicy, AbsenceBalance, AbsenceRequest + champs RH sur User - Services : PublicHolidayProvider (fériés FR métropole en PHP pur, Computus), AbsenceDayCalculator (décompte jours ouvrés/ouvrables + demi-journées, TDD), AbsenceBalanceService (périodes + pending/taken/recrédit) - API Platform : providers/processors (création, approve/reject/cancel) + RBAC me/admin, contrôleurs preview (dry-run), upload/download justificatif, calendrier - Migrations : une par table + colonnes RH user (DEFAULT puis DROP DEFAULT) - Fixtures : 5 policies par défaut, salariés démo, soldes et demandes - Tests unitaires : PublicHolidayProvider, AbsenceDayCalculator (12 tests) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,8 @@ use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Enum\ContractType;
|
||||
use App\Enum\FamilySituation;
|
||||
use App\Repository\UserRepository;
|
||||
use App\State\MeProvider;
|
||||
use App\State\UserPasswordHasherProcessor;
|
||||
@@ -48,11 +50,11 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
#[Groups(['me:read', 'task:read', 'user:list', 'time_entry:read', 'client_ticket:read'])]
|
||||
#[Groups(['me:read', 'task:read', 'user:list', 'time_entry:read', 'client_ticket:read', 'absence_request:read', 'absence_balance:read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 180, unique: true)]
|
||||
#[Groups(['me:read', 'task:read', 'user:list', 'user:write', 'time_entry:read', 'client_ticket:read'])]
|
||||
#[Groups(['me:read', 'task:read', 'user:list', 'user:write', 'time_entry:read', 'client_ticket:read', 'absence_request:read', 'absence_balance:read'])]
|
||||
private ?string $username = null;
|
||||
|
||||
/** @var list<string> */
|
||||
@@ -87,6 +89,54 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private Collection $allowedProjects;
|
||||
|
||||
// --- HR / absence management fields ---
|
||||
|
||||
/** Whether this user is an employee subject to absence management. */
|
||||
#[ORM\Column]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private bool $isEmployee = false;
|
||||
|
||||
/** Hiring date — start of paid-leave acquisition. */
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?DateTimeImmutable $hireDate = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?DateTimeImmutable $endDate = null;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 16, nullable: true, enumType: ContractType::class)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?ContractType $contractType = null;
|
||||
|
||||
/** Work-time ratio: 1.0 = full time, 0.8 = 4 days out of 5. */
|
||||
#[ORM\Column(type: Types::FLOAT)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private float $workTimeRatio = 1.0;
|
||||
|
||||
/** Yearly paid-leave entitlement in worked days (default 25 = jours ouvrés). */
|
||||
#[ORM\Column(type: Types::FLOAT)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private float $annualLeaveDays = 25.0;
|
||||
|
||||
/** Reference period start as MM-DD (default 06-01, 1st of June). */
|
||||
#[ORM\Column(length: 5)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private string $referencePeriodStart = '06-01';
|
||||
|
||||
/** Paid-leave already acquired when the module is rolled out. */
|
||||
#[ORM\Column(type: Types::FLOAT)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private float $initialLeaveBalance = 0.0;
|
||||
|
||||
#[ORM\Column(type: Types::STRING, length: 16, nullable: true, enumType: FamilySituation::class)]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private ?FamilySituation $familySituation = null;
|
||||
|
||||
#[ORM\Column]
|
||||
#[Groups(['me:read', 'user:list', 'user:write'])]
|
||||
private int $nbChildren = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new DateTimeImmutable();
|
||||
@@ -217,7 +267,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[Groups(['me:read', 'task:read', 'user:list', 'time_entry:read', 'client_ticket:read'])]
|
||||
#[Groups(['me:read', 'task:read', 'user:list', 'time_entry:read', 'client_ticket:read', 'absence_request:read', 'absence_balance:read'])]
|
||||
public function getAvatarUrl(): ?string
|
||||
{
|
||||
if (null === $this->avatarFileName) {
|
||||
@@ -243,4 +293,124 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
$this->plainPassword = null;
|
||||
}
|
||||
|
||||
public function isEmployee(): bool
|
||||
{
|
||||
return $this->isEmployee;
|
||||
}
|
||||
|
||||
public function setIsEmployee(bool $isEmployee): static
|
||||
{
|
||||
$this->isEmployee = $isEmployee;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHireDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->hireDate;
|
||||
}
|
||||
|
||||
public function setHireDate(?DateTimeImmutable $hireDate): static
|
||||
{
|
||||
$this->hireDate = $hireDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEndDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function setEndDate(?DateTimeImmutable $endDate): static
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContractType(): ?ContractType
|
||||
{
|
||||
return $this->contractType;
|
||||
}
|
||||
|
||||
public function setContractType(?ContractType $contractType): static
|
||||
{
|
||||
$this->contractType = $contractType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWorkTimeRatio(): float
|
||||
{
|
||||
return $this->workTimeRatio;
|
||||
}
|
||||
|
||||
public function setWorkTimeRatio(float $workTimeRatio): static
|
||||
{
|
||||
$this->workTimeRatio = $workTimeRatio;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAnnualLeaveDays(): float
|
||||
{
|
||||
return $this->annualLeaveDays;
|
||||
}
|
||||
|
||||
public function setAnnualLeaveDays(float $annualLeaveDays): static
|
||||
{
|
||||
$this->annualLeaveDays = $annualLeaveDays;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getReferencePeriodStart(): string
|
||||
{
|
||||
return $this->referencePeriodStart;
|
||||
}
|
||||
|
||||
public function setReferencePeriodStart(string $referencePeriodStart): static
|
||||
{
|
||||
$this->referencePeriodStart = $referencePeriodStart;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInitialLeaveBalance(): float
|
||||
{
|
||||
return $this->initialLeaveBalance;
|
||||
}
|
||||
|
||||
public function setInitialLeaveBalance(float $initialLeaveBalance): static
|
||||
{
|
||||
$this->initialLeaveBalance = $initialLeaveBalance;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFamilySituation(): ?FamilySituation
|
||||
{
|
||||
return $this->familySituation;
|
||||
}
|
||||
|
||||
public function setFamilySituation(?FamilySituation $familySituation): static
|
||||
{
|
||||
$this->familySituation = $familySituation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNbChildren(): int
|
||||
{
|
||||
return $this->nbChildren;
|
||||
}
|
||||
|
||||
public function setNbChildren(int $nbChildren): static
|
||||
{
|
||||
$this->nbChildren = $nbChildren;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user