feat(absence) : migrate Absence domain into module (back)

LST-66 (2.3) backend. Behaviour-preserving move of the absences domain into
src/Module/Absence/. API operations, securities, routes and the 10 MCP tool
names are unchanged.

- 3 entities + 3 enums moved to Domain/{Entity,Enum}; user relations stay on
  UserInterface. 3 repositories split into Domain/Repository interfaces +
  Doctrine impls (bound in services.yaml); find() kept off interfaces
  (findById instead).
- Pure services (AbsenceDayCalculator, PublicHolidayProvider) -> Domain/Service;
  AbsenceBalanceService -> Application/Service; State (5), controllers (5),
  10 MCP tools and AccrueLeaveCommand -> Infrastructure/.
- New LeaveProfileInterface contract (Shared) exposes the HR getters used by
  AbsenceBalanceService/AccrueLeaveCommand; User implements it -> Absence no
  longer imports the concrete Core User. MCP tools/command inject
  UserRepositoryInterface (findById) instead of the concrete repository.
- Timestampable/Blamable added to AbsenceBalance and AbsencePolicy (additive
  migration: created_at/updated_at + created_by/updated_by FK ON DELETE SET
  NULL + COMMENT). AbsenceRequest untouched (already has createdAt/reviewedAt).
- AbsenceModule registered (id absence, 4 RBAC perms, not re-wired); doctrine
  mapping added; team-absences sidebar item gated by the module.

161 tests green, mapping valid, no API route regression, cs-fixer clean.
This commit is contained in:
Matthieu
2026-06-20 18:32:02 +02:00
parent 7446b7dca9
commit 306cfd34cd
51 changed files with 514 additions and 209 deletions
@@ -4,19 +4,19 @@ declare(strict_types=1);
namespace App\Tests\Functional\Mcp;
use App\Entity\AbsenceBalance;
use App\Entity\AbsencePolicy;
use App\Enum\AbsenceType;
use App\Mcp\Tool\Absence\CancelAbsenceRequestTool;
use App\Mcp\Tool\Absence\CreateAbsenceRequestTool;
use App\Mcp\Tool\Absence\ReviewAbsenceRequestTool;
use App\Module\Absence\Application\Service\AbsenceBalanceService;
use App\Module\Absence\Domain\Entity\AbsenceBalance;
use App\Module\Absence\Domain\Entity\AbsencePolicy;
use App\Module\Absence\Domain\Enum\AbsenceType;
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
use App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceBalanceRepository;
use App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsencePolicyRepository;
use App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceRequestRepository;
use App\Module\Absence\Infrastructure\Mcp\Tool\CancelAbsenceRequestTool;
use App\Module\Absence\Infrastructure\Mcp\Tool\CreateAbsenceRequestTool;
use App\Module\Absence\Infrastructure\Mcp\Tool\ReviewAbsenceRequestTool;
use App\Module\Core\Domain\Entity\User;
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
use App\Repository\AbsenceBalanceRepository;
use App\Repository\AbsencePolicyRepository;
use App\Repository\AbsenceRequestRepository;
use App\Service\AbsenceBalanceService;
use App\Service\AbsenceDayCalculator;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
@@ -93,7 +93,7 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
self::assertSame(5.0, (float) $data['countedDays']);
self::assertSame($this->employee->getId(), $data['user']['id']);
$balance = self::getContainer()->get(AbsenceBalanceRepository::class)
$balance = self::getContainer()->get(DoctrineAbsenceBalanceRepository::class)
->findOneForPeriod($this->employee, AbsenceType::PaidLeave, '2026-2027')
;
self::assertNotNull($balance);
@@ -113,7 +113,7 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
self::assertSame('approved', $data['status']);
self::assertSame($this->admin->getId(), $data['reviewedBy']['id']);
$balance = self::getContainer()->get(AbsenceBalanceRepository::class)
$balance = self::getContainer()->get(DoctrineAbsenceBalanceRepository::class)
->findOneForPeriod($this->employee, AbsenceType::PaidLeave, '2026-2027')
;
self::assertSame(0.0, $balance->getPending());
@@ -128,7 +128,7 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
);
// Shrink the entitlement below the 5 requested days.
$balance = self::getContainer()->get(AbsenceBalanceRepository::class)
$balance = self::getContainer()->get(DoctrineAbsenceBalanceRepository::class)
->findOneForPeriod($this->employee, AbsenceType::PaidLeave, '2026-2027')
;
$balance->setAcquired(2.0);
@@ -158,7 +158,7 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
$data = json_decode(($this->cancelTool($this->admin))($created['id']), true);
self::assertSame('cancelled', $data['status']);
$balance = self::getContainer()->get(AbsenceBalanceRepository::class)
$balance = self::getContainer()->get(DoctrineAbsenceBalanceRepository::class)
->findOneForPeriod($this->employee, AbsenceType::PaidLeave, '2026-2027')
;
self::assertSame(0.0, $balance->getTaken());
@@ -195,7 +195,7 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
);
self::assertSame('pending', $data['status']);
$balance = self::getContainer()->get(AbsenceBalanceRepository::class)
$balance = self::getContainer()->get(DoctrineAbsenceBalanceRepository::class)
->findOneForPeriod($this->employee, AbsenceType::Bereavement, '2026')
;
self::assertNull($balance, 'Bereavement must not create or touch any balance.');
@@ -217,8 +217,8 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
return new CreateAbsenceRequestTool(
$c->get(EntityManagerInterface::class),
$c->get(DoctrineUserRepository::class),
$c->get(AbsencePolicyRepository::class),
$c->get(AbsenceRequestRepository::class),
$c->get(DoctrineAbsencePolicyRepository::class),
$c->get(DoctrineAbsenceRequestRepository::class),
$c->get(AbsenceDayCalculator::class),
$c->get(AbsenceBalanceService::class),
$this->securityFor($actor),
@@ -231,7 +231,7 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
return new ReviewAbsenceRequestTool(
$c->get(EntityManagerInterface::class),
$c->get(AbsenceRequestRepository::class),
$c->get(DoctrineAbsenceRequestRepository::class),
$c->get(AbsenceBalanceService::class),
$this->securityFor($actor),
);
@@ -243,7 +243,7 @@ class AbsenceRequestLifecycleTest extends KernelTestCase
return new CancelAbsenceRequestTool(
$c->get(EntityManagerInterface::class),
$c->get(AbsenceRequestRepository::class),
$c->get(DoctrineAbsenceRequestRepository::class),
$c->get(AbsenceBalanceService::class),
$this->securityFor($actor),
);
@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace App\Tests\Unit\Service;
use App\Enum\HalfDay;
use App\Service\AbsenceDayCalculator;
use App\Service\PublicHolidayProvider;
use App\Module\Absence\Domain\Enum\HalfDay;
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
use App\Module\Absence\Domain\Service\PublicHolidayProvider;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Tests\Unit\Service;
use App\Service\PublicHolidayProvider;
use App\Module\Absence\Domain\Service\PublicHolidayProvider;
use DateTimeImmutable;
use PHPUnit\Framework\TestCase;