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:
@@ -7,6 +7,7 @@ declare(strict_types=1);
|
||||
* Activer/désactiver un module = ajouter/commenter sa ligne. Exposé par GET /api/modules.
|
||||
*/
|
||||
|
||||
use App\Module\Absence\AbsenceModule;
|
||||
use App\Module\Core\CoreModule;
|
||||
use App\Module\ProjectManagement\ProjectManagementModule;
|
||||
use App\Module\TimeTracking\TimeTrackingModule;
|
||||
@@ -15,4 +16,5 @@ return [
|
||||
CoreModule::class,
|
||||
TimeTrackingModule::class,
|
||||
ProjectManagementModule::class,
|
||||
AbsenceModule::class,
|
||||
];
|
||||
|
||||
@@ -48,6 +48,11 @@ doctrine:
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Module/ProjectManagement/Domain/Entity'
|
||||
prefix: 'App\Module\ProjectManagement\Domain\Entity'
|
||||
Absence:
|
||||
type: attribute
|
||||
is_bundle: false
|
||||
dir: '%kernel.project_dir%/src/Module/Absence/Domain/Entity'
|
||||
prefix: 'App\Module\Absence\Domain\Entity'
|
||||
controller_resolver:
|
||||
auto_mapping: false
|
||||
|
||||
|
||||
@@ -57,11 +57,11 @@ services:
|
||||
arguments:
|
||||
$avatarUploadDir: '%avatar_upload_dir%'
|
||||
|
||||
App\Controller\Absence\AbsenceJustificationUploadController:
|
||||
App\Module\Absence\Infrastructure\Controller\AbsenceJustificationUploadController:
|
||||
arguments:
|
||||
$uploadDir: '%absence_justification_upload_dir%'
|
||||
|
||||
App\Controller\Absence\AbsenceJustificationDownloadController:
|
||||
App\Module\Absence\Infrastructure\Controller\AbsenceJustificationDownloadController:
|
||||
arguments:
|
||||
$uploadDir: '%absence_justification_upload_dir%'
|
||||
|
||||
@@ -93,4 +93,10 @@ services:
|
||||
|
||||
App\Module\ProjectManagement\Domain\Repository\TaskRecurrenceRepositoryInterface: '@App\Module\ProjectManagement\Infrastructure\Doctrine\DoctrineTaskRecurrenceRepository'
|
||||
|
||||
App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface: '@App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceRequestRepository'
|
||||
|
||||
App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface: '@App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsencePolicyRepository'
|
||||
|
||||
App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface: '@App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceBalanceRepository'
|
||||
|
||||
App\Shared\Domain\Contract\NotifierInterface: '@App\Module\Core\Infrastructure\Notifier'
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ return [
|
||||
'icon' => 'mdi:cog-outline',
|
||||
'roles' => ['ROLE_ADMIN'],
|
||||
'items' => [
|
||||
['label' => 'sidebar.admin.teamAbsences', 'to' => '/team-absences', 'icon' => 'mdi:calendar-account-outline'],
|
||||
['label' => 'sidebar.admin.teamAbsences', 'to' => '/team-absences', 'icon' => 'mdi:calendar-account-outline', 'module' => 'absence'],
|
||||
['label' => 'sidebar.admin.administration', 'to' => '/admin', 'icon' => 'mdi:cog-outline', 'permission' => 'core.users.view'],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Absence module: add Timestampable/Blamable columns to absence_balance and
|
||||
* absence_policy.
|
||||
*
|
||||
* AbsenceBalance and AbsencePolicy adopt TimestampableBlamableTrait.
|
||||
* AbsenceRequest is intentionally untouched (it already carries createdAt /
|
||||
* reviewedAt). This migration is purely additive — nullable columns + nullable
|
||||
* FK to "user" with ON DELETE SET NULL. No DROP/ALTER on existing data. Columns
|
||||
* are lowercase snake_case. Hand-written to guarantee zero destructive
|
||||
* instruction.
|
||||
*/
|
||||
final class Version20260620170000 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Absence: add timestampable/blamable columns to absence_balance and absence_policy (additive)';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// absence_balance
|
||||
$this->addSql('ALTER TABLE absence_balance ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_balance ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_balance ADD created_by INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_balance ADD updated_by INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_balance ADD CONSTRAINT FK_65723A76DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE absence_balance ADD CONSTRAINT FK_65723A7616FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
|
||||
$this->addSql('CREATE INDEX IDX_65723A76DE12AB56 ON absence_balance (created_by)');
|
||||
$this->addSql('CREATE INDEX IDX_65723A7616FE72E1 ON absence_balance (updated_by)');
|
||||
$this->addSql("COMMENT ON COLUMN absence_balance.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
|
||||
$this->addSql("COMMENT ON COLUMN absence_balance.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
|
||||
$this->addSql("COMMENT ON COLUMN absence_balance.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
|
||||
$this->addSql("COMMENT ON COLUMN absence_balance.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
|
||||
|
||||
// absence_policy
|
||||
$this->addSql('ALTER TABLE absence_policy ADD created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_policy ADD updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_policy ADD created_by INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_policy ADD updated_by INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE absence_policy ADD CONSTRAINT FK_7A780B65DE12AB56 FOREIGN KEY (created_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
|
||||
$this->addSql('ALTER TABLE absence_policy ADD CONSTRAINT FK_7A780B6516FE72E1 FOREIGN KEY (updated_by) REFERENCES "user" (id) ON DELETE SET NULL NOT DEFERRABLE');
|
||||
$this->addSql('CREATE INDEX IDX_7A780B65DE12AB56 ON absence_policy (created_by)');
|
||||
$this->addSql('CREATE INDEX IDX_7A780B6516FE72E1 ON absence_policy (updated_by)');
|
||||
$this->addSql("COMMENT ON COLUMN absence_policy.created_at IS 'Creation timestamp (Timestampable, set on prePersist)'");
|
||||
$this->addSql("COMMENT ON COLUMN absence_policy.updated_at IS 'Last update timestamp (Timestampable, set on prePersist/preUpdate)'");
|
||||
$this->addSql("COMMENT ON COLUMN absence_policy.created_by IS 'User who created the entry (Blamable, FK user.id, SET NULL on delete)'");
|
||||
$this->addSql("COMMENT ON COLUMN absence_policy.updated_by IS 'User who last updated the entry (Blamable, FK user.id, SET NULL on delete)'");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// absence_balance
|
||||
$this->addSql('ALTER TABLE absence_balance DROP CONSTRAINT FK_65723A76DE12AB56');
|
||||
$this->addSql('ALTER TABLE absence_balance DROP CONSTRAINT FK_65723A7616FE72E1');
|
||||
$this->addSql('DROP INDEX IDX_65723A76DE12AB56');
|
||||
$this->addSql('DROP INDEX IDX_65723A7616FE72E1');
|
||||
$this->addSql('ALTER TABLE absence_balance DROP created_at');
|
||||
$this->addSql('ALTER TABLE absence_balance DROP updated_at');
|
||||
$this->addSql('ALTER TABLE absence_balance DROP created_by');
|
||||
$this->addSql('ALTER TABLE absence_balance DROP updated_by');
|
||||
|
||||
// absence_policy
|
||||
$this->addSql('ALTER TABLE absence_policy DROP CONSTRAINT FK_7A780B65DE12AB56');
|
||||
$this->addSql('ALTER TABLE absence_policy DROP CONSTRAINT FK_7A780B6516FE72E1');
|
||||
$this->addSql('DROP INDEX IDX_7A780B65DE12AB56');
|
||||
$this->addSql('DROP INDEX IDX_7A780B6516FE72E1');
|
||||
$this->addSql('ALTER TABLE absence_policy DROP created_at');
|
||||
$this->addSql('ALTER TABLE absence_policy DROP updated_at');
|
||||
$this->addSql('ALTER TABLE absence_policy DROP created_by');
|
||||
$this->addSql('ALTER TABLE absence_policy DROP updated_by');
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\AbsenceBalance;
|
||||
use App\Entity\AbsencePolicy;
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Entity\Client;
|
||||
use App\Entity\MailConfiguration;
|
||||
use App\Entity\ZimbraConfiguration;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Enum\ContractType;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceBalance;
|
||||
use App\Module\Absence\Domain\Entity\AbsencePolicy;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Core\Application\Rbac\RbacSeeder;
|
||||
use App\Module\Core\Domain\Entity\User;
|
||||
use App\Module\ProjectManagement\Domain\Entity\Project;
|
||||
|
||||
@@ -4,10 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool;
|
||||
|
||||
use App\Entity\AbsenceBalance;
|
||||
use App\Entity\AbsencePolicy;
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Entity\Client;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceBalance;
|
||||
use App\Module\Absence\Domain\Entity\AbsencePolicy;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Core\Domain\Entity\User;
|
||||
use App\Module\ProjectManagement\Domain\Entity\Project;
|
||||
use App\Module\ProjectManagement\Domain\Entity\Task;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Absence;
|
||||
|
||||
use App\Shared\Domain\Module\ModuleInterface;
|
||||
|
||||
final class AbsenceModule implements ModuleInterface
|
||||
{
|
||||
public static function id(): string
|
||||
{
|
||||
return 'absence';
|
||||
}
|
||||
|
||||
public static function label(): string
|
||||
{
|
||||
return 'Absences';
|
||||
}
|
||||
|
||||
public static function isRequired(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Permissions RBAC fin du Module Absence.
|
||||
*
|
||||
* Additif : alimente le catalogue RBAC. La sécurité des opérations API
|
||||
* reste en ROLE_USER/ROLE_ADMIN (non recâblée ici).
|
||||
*
|
||||
* @return list<array{code: string, label: string}>
|
||||
*/
|
||||
public static function permissions(): array
|
||||
{
|
||||
return [
|
||||
['code' => 'absence.requests.view', 'label' => 'Voir les demandes d\'absence'],
|
||||
['code' => 'absence.requests.manage', 'label' => 'Gérer les demandes d\'absence'],
|
||||
['code' => 'absence.policies.manage', 'label' => 'Gérer les règles d\'absence'],
|
||||
['code' => 'absence.balances.manage', 'label' => 'Gérer les soldes d\'absence'],
|
||||
];
|
||||
}
|
||||
}
|
||||
+17
-13
@@ -2,13 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
namespace App\Module\Absence\Application\Service;
|
||||
|
||||
use App\Entity\AbsenceBalance;
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Module\Core\Domain\Entity\User;
|
||||
use App\Repository\AbsenceBalanceRepository;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceBalance;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
|
||||
use App\Shared\Domain\Contract\LeaveProfileInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
@@ -21,21 +22,24 @@ final readonly class AbsenceBalanceService
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private AbsenceBalanceRepository $balanceRepository,
|
||||
private AbsenceBalanceRepositoryInterface $balanceRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Reference period string for a request: paid leave follows the employee's
|
||||
* reference period (e.g. "2025-2026"), other types are tracked yearly.
|
||||
*/
|
||||
public function periodFor(User $user, AbsenceType $type, DateTimeInterface $date): string
|
||||
public function periodFor(UserInterface $user, AbsenceType $type, DateTimeInterface $date): string
|
||||
{
|
||||
if (AbsenceType::PaidLeave !== $type) {
|
||||
return $date->format('Y');
|
||||
}
|
||||
|
||||
$year = (int) $date->format('Y');
|
||||
$startMonthDay = $user->getReferencePeriodStart(); // e.g. "06-01"
|
||||
$year = (int) $date->format('Y');
|
||||
// The reference-period start (e.g. "06-01") is an HR profile field,
|
||||
// accessed through the LeaveProfileInterface contract to keep the
|
||||
// Absence module decoupled from the concrete Core User entity.
|
||||
$startMonthDay = $user instanceof LeaveProfileInterface ? $user->getReferencePeriodStart() : '01-01';
|
||||
$currentMonthDay = $date->format('m-d');
|
||||
|
||||
$startYear = $currentMonthDay >= $startMonthDay ? $year : $year - 1;
|
||||
@@ -43,7 +47,7 @@ final readonly class AbsenceBalanceService
|
||||
return sprintf('%d-%d', $startYear, $startYear + 1);
|
||||
}
|
||||
|
||||
public function getOrCreateBalance(User $user, AbsenceType $type, string $period): AbsenceBalance
|
||||
public function getOrCreateBalance(UserInterface $user, AbsenceType $type, string $period): AbsenceBalance
|
||||
{
|
||||
$balance = $this->balanceRepository->findOneForPeriod($user, $type, $period);
|
||||
|
||||
@@ -85,7 +89,7 @@ final readonly class AbsenceBalanceService
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var User $user */
|
||||
/** @var UserInterface $user */
|
||||
$user = $request->getUser();
|
||||
$period = $this->periodFor($user, $request->getType(), $request->getStartDate());
|
||||
$balance = $this->balanceRepository->findOneForPeriod($user, $request->getType(), $period);
|
||||
@@ -128,7 +132,7 @@ final readonly class AbsenceBalanceService
|
||||
|
||||
private function balanceForRequest(AbsenceRequest $request): AbsenceBalance
|
||||
{
|
||||
/** @var User $user */
|
||||
/** @var UserInterface $user */
|
||||
$user = $request->getUser();
|
||||
$type = $request->getType();
|
||||
$period = $this->periodFor($user, $type, $request->getStartDate());
|
||||
+11
-6
@@ -2,16 +2,19 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
namespace App\Module\Absence\Domain\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Repository\AbsenceBalanceRepository;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Infrastructure\ApiPlatform\State\AbsenceBalanceProvider;
|
||||
use App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceBalanceRepository;
|
||||
use App\Shared\Domain\Contract\BlamableInterface;
|
||||
use App\Shared\Domain\Contract\TimestampableInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use App\State\AbsenceBalanceProvider;
|
||||
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
@@ -35,11 +38,13 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
normalizationContext: ['groups' => ['absence_balance:read']],
|
||||
denormalizationContext: ['groups' => ['absence_balance:write']],
|
||||
)]
|
||||
#[ORM\Entity(repositoryClass: AbsenceBalanceRepository::class)]
|
||||
#[ORM\Entity(repositoryClass: DoctrineAbsenceBalanceRepository::class)]
|
||||
#[ORM\Table(name: 'absence_balance')]
|
||||
#[ORM\UniqueConstraint(name: 'uniq_absence_balance_user_type_period', columns: ['user_id', 'type', 'period'])]
|
||||
class AbsenceBalance
|
||||
class AbsenceBalance implements TimestampableInterface, BlamableInterface
|
||||
{
|
||||
use TimestampableBlamableTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
@@ -2,14 +2,17 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
namespace App\Module\Absence\Domain\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Repository\AbsencePolicyRepository;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsencePolicyRepository;
|
||||
use App\Shared\Domain\Contract\BlamableInterface;
|
||||
use App\Shared\Domain\Contract\TimestampableInterface;
|
||||
use App\Shared\Domain\Trait\TimestampableBlamableTrait;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Attribute\Groups;
|
||||
@@ -31,11 +34,13 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
denormalizationContext: ['groups' => ['absence_policy:write']],
|
||||
order: ['type' => 'ASC'],
|
||||
)]
|
||||
#[ORM\Entity(repositoryClass: AbsencePolicyRepository::class)]
|
||||
#[ORM\Entity(repositoryClass: DoctrineAbsencePolicyRepository::class)]
|
||||
#[ORM\Table(name: 'absence_policy')]
|
||||
#[ORM\UniqueConstraint(name: 'uniq_absence_policy_type', columns: ['type'])]
|
||||
class AbsencePolicy
|
||||
class AbsencePolicy implements TimestampableInterface, BlamableInterface
|
||||
{
|
||||
use TimestampableBlamableTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
+10
-10
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
namespace App\Module\Absence\Domain\Entity;
|
||||
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Delete;
|
||||
@@ -10,15 +10,15 @@ use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Enum\HalfDay;
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Enum\HalfDay;
|
||||
use App\Module\Absence\Infrastructure\ApiPlatform\State\AbsenceCancelProcessor;
|
||||
use App\Module\Absence\Infrastructure\ApiPlatform\State\AbsenceRequestProcessor;
|
||||
use App\Module\Absence\Infrastructure\ApiPlatform\State\AbsenceRequestProvider;
|
||||
use App\Module\Absence\Infrastructure\ApiPlatform\State\AbsenceReviewProcessor;
|
||||
use App\Module\Absence\Infrastructure\Doctrine\DoctrineAbsenceRequestRepository;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use App\State\AbsenceCancelProcessor;
|
||||
use App\State\AbsenceRequestProcessor;
|
||||
use App\State\AbsenceRequestProvider;
|
||||
use App\State\AbsenceReviewProcessor;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -64,7 +64,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
denormalizationContext: ['groups' => ['absence_request:write']],
|
||||
order: ['createdAt' => 'DESC'],
|
||||
)]
|
||||
#[ORM\Entity(repositoryClass: AbsenceRequestRepository::class)]
|
||||
#[ORM\Entity(repositoryClass: DoctrineAbsenceRequestRepository::class)]
|
||||
#[ORM\Table(name: 'absence_request')]
|
||||
class AbsenceRequest
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enum;
|
||||
namespace App\Module\Absence\Domain\Enum;
|
||||
|
||||
enum AbsenceStatus: string
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enum;
|
||||
namespace App\Module\Absence\Domain\Enum;
|
||||
|
||||
enum AbsenceType: string
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enum;
|
||||
namespace App\Module\Absence\Domain\Enum;
|
||||
|
||||
enum HalfDay: string
|
||||
{
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Absence\Domain\Repository;
|
||||
|
||||
use App\Module\Absence\Domain\Entity\AbsenceBalance;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
|
||||
interface AbsenceBalanceRepositoryInterface
|
||||
{
|
||||
public function findById(int $id): ?AbsenceBalance;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $criteria
|
||||
* @param null|array<string, string> $orderBy
|
||||
*
|
||||
* @return AbsenceBalance[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneForPeriod(UserInterface $user, AbsenceType $type, string $period): ?AbsenceBalance;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Absence\Domain\Repository;
|
||||
|
||||
use App\Module\Absence\Domain\Entity\AbsencePolicy;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
|
||||
interface AbsencePolicyRepositoryInterface
|
||||
{
|
||||
public function findById(int $id): ?AbsencePolicy;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $criteria
|
||||
* @param null|array<string, string> $orderBy
|
||||
*
|
||||
* @return AbsencePolicy[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneByType(AbsenceType $type): ?AbsencePolicy;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Module\Absence\Domain\Repository;
|
||||
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeInterface;
|
||||
|
||||
interface AbsenceRequestRepositoryInterface
|
||||
{
|
||||
public function findById(int $id): ?AbsenceRequest;
|
||||
|
||||
/**
|
||||
* Whether the user already has a PENDING or APPROVED absence that overlaps
|
||||
* the given date range.
|
||||
*/
|
||||
public function hasOverlap(
|
||||
UserInterface $user,
|
||||
DateTimeInterface $startDate,
|
||||
DateTimeInterface $endDate,
|
||||
?int $excludeId = null,
|
||||
): bool;
|
||||
|
||||
/**
|
||||
* Absences (approved or pending) overlapping a date range, all employees.
|
||||
*
|
||||
* @return AbsenceRequest[]
|
||||
*/
|
||||
public function findInRange(DateTimeInterface $from, DateTimeInterface $to): array;
|
||||
|
||||
/**
|
||||
* @return AbsenceRequest[]
|
||||
*/
|
||||
public function findFiltered(
|
||||
?UserInterface $user = null,
|
||||
?AbsenceStatus $status = null,
|
||||
?AbsenceType $type = null,
|
||||
?DateTimeInterface $from = null,
|
||||
?DateTimeInterface $to = null,
|
||||
): array;
|
||||
}
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
namespace App\Module\Absence\Domain\Service;
|
||||
|
||||
use App\Enum\HalfDay;
|
||||
use App\Module\Absence\Domain\Enum\HalfDay;
|
||||
use DateInterval;
|
||||
use DatePeriod;
|
||||
use DateTimeImmutable;
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
namespace App\Module\Absence\Domain\Service;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
+7
-5
@@ -2,11 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State;
|
||||
namespace App\Module\Absence\Infrastructure\ApiPlatform\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Entity\AbsenceBalance;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceBalance;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
@@ -18,6 +19,7 @@ final readonly class AbsenceBalanceProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private AbsenceBalanceRepositoryInterface $balanceRepository,
|
||||
private Security $security,
|
||||
) {}
|
||||
|
||||
@@ -26,11 +28,10 @@ final readonly class AbsenceBalanceProvider implements ProviderInterface
|
||||
$user = $this->security->getUser();
|
||||
assert($user instanceof UserInterface);
|
||||
|
||||
$repo = $this->entityManager->getRepository(AbsenceBalance::class);
|
||||
$isAdmin = $this->security->isGranted('ROLE_ADMIN');
|
||||
|
||||
if (isset($uriVariables['id'])) {
|
||||
$balance = $repo->find($uriVariables['id']);
|
||||
$balance = $this->balanceRepository->findById((int) $uriVariables['id']);
|
||||
if (null === $balance) {
|
||||
return null;
|
||||
}
|
||||
@@ -41,7 +42,8 @@ final readonly class AbsenceBalanceProvider implements ProviderInterface
|
||||
return $balance;
|
||||
}
|
||||
|
||||
$qb = $repo->createQueryBuilder('b')
|
||||
$qb = $this->entityManager->getRepository(AbsenceBalance::class)
|
||||
->createQueryBuilder('b')
|
||||
->orderBy('b.type', 'ASC')
|
||||
;
|
||||
|
||||
+4
-4
@@ -2,13 +2,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State;
|
||||
namespace App\Module\Absence\Infrastructure\ApiPlatform\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
+10
-10
@@ -2,17 +2,17 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State;
|
||||
namespace App\Module\Absence\Infrastructure\ApiPlatform\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Repository\AbsencePolicyRepository;
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Service\AbsenceDayCalculator;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -32,8 +32,8 @@ final readonly class AbsenceRequestProcessor implements ProcessorInterface
|
||||
private EntityManagerInterface $entityManager,
|
||||
private Security $security,
|
||||
private AbsenceDayCalculator $calculator,
|
||||
private AbsencePolicyRepository $policyRepository,
|
||||
private AbsenceRequestRepository $requestRepository,
|
||||
private AbsencePolicyRepositoryInterface $policyRepository,
|
||||
private AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private AbsenceBalanceService $balanceService,
|
||||
) {}
|
||||
|
||||
+7
-5
@@ -2,11 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State;
|
||||
namespace App\Module\Absence\Infrastructure\ApiPlatform\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
@@ -18,6 +19,7 @@ final readonly class AbsenceRequestProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private Security $security,
|
||||
) {}
|
||||
|
||||
@@ -26,12 +28,11 @@ final readonly class AbsenceRequestProvider implements ProviderInterface
|
||||
$user = $this->security->getUser();
|
||||
assert($user instanceof UserInterface);
|
||||
|
||||
$repo = $this->entityManager->getRepository(AbsenceRequest::class);
|
||||
$isAdmin = $this->security->isGranted('ROLE_ADMIN');
|
||||
|
||||
// Single item: owner or admin only
|
||||
if (isset($uriVariables['id'])) {
|
||||
$request = $repo->find($uriVariables['id']);
|
||||
$request = $this->requestRepository->findById((int) $uriVariables['id']);
|
||||
if (null === $request) {
|
||||
return null;
|
||||
}
|
||||
@@ -42,7 +43,8 @@ final readonly class AbsenceRequestProvider implements ProviderInterface
|
||||
return $request;
|
||||
}
|
||||
|
||||
$qb = $repo->createQueryBuilder('a')
|
||||
$qb = $this->entityManager->getRepository(AbsenceRequest::class)
|
||||
->createQueryBuilder('a')
|
||||
->orderBy('a.createdAt', 'DESC')
|
||||
;
|
||||
|
||||
+4
-4
@@ -2,13 +2,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\State;
|
||||
namespace App\Module\Absence\Infrastructure\ApiPlatform\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
+18
-10
@@ -2,12 +2,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
namespace App\Module\Absence\Infrastructure\Command;
|
||||
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
|
||||
use App\Repository\AbsenceBalanceRepository;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
|
||||
use App\Module\Core\Domain\Repository\UserRepositoryInterface;
|
||||
use App\Shared\Domain\Contract\LeaveProfileInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
@@ -37,8 +38,8 @@ use function sprintf;
|
||||
class AccrueLeaveCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DoctrineUserRepository $userRepository,
|
||||
private readonly AbsenceBalanceRepository $balanceRepository,
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
private readonly AbsenceBalanceRepositoryInterface $balanceRepository,
|
||||
private readonly AbsenceBalanceService $balanceService,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
@@ -88,7 +89,14 @@ class AccrueLeaveCommand extends Command
|
||||
$skipped = 0;
|
||||
|
||||
foreach ($employees as $user) {
|
||||
$rate = ($user->getAnnualLeaveDays() / 12) * $user->getWorkTimeRatio();
|
||||
// RH leave profile fields are read through the contract to keep the
|
||||
// Absence module decoupled from the concrete Core User entity.
|
||||
$profile = $user instanceof LeaveProfileInterface ? $user : null;
|
||||
if (null === $profile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rate = ($profile->getAnnualLeaveDays() / 12) * $profile->getWorkTimeRatio();
|
||||
$period = $this->balanceService->periodFor($user, AbsenceType::PaidLeave, $firstDay);
|
||||
|
||||
$balance = $this->balanceRepository->findOneForPeriod($user, AbsenceType::PaidLeave, $period);
|
||||
@@ -104,7 +112,7 @@ class AccrueLeaveCommand extends Command
|
||||
? $this->balanceRepository->findOneForPeriod($user, AbsenceType::PaidLeave, $previousPeriod)
|
||||
: null;
|
||||
$balance->setAcquired(
|
||||
null !== $previousBalance ? $previousBalance->getAcquiring() : $user->getInitialLeaveBalance(),
|
||||
null !== $previousBalance ? $previousBalance->getAcquiring() : $profile->getInitialLeaveBalance(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,7 +127,7 @@ class AccrueLeaveCommand extends Command
|
||||
$balance->setLastAccruedMonth($monthKey);
|
||||
++$accrued;
|
||||
|
||||
$seeded = $isNew && (null !== self::previousPeriod($period) || $user->getInitialLeaveBalance() > 0);
|
||||
$seeded = $isNew && (null !== self::previousPeriod($period) || $profile->getInitialLeaveBalance() > 0);
|
||||
$rows[] = [
|
||||
$user->getUsername(),
|
||||
$period,
|
||||
+3
-3
@@ -2,9 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Controller;
|
||||
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
@@ -19,7 +19,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
class AbsenceCalendarController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsenceRequestRepository $requestRepository,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
) {}
|
||||
|
||||
#[Route('/api/admin/absences/calendar', name: 'absence_calendar', methods: ['GET'], priority: 1)]
|
||||
+4
-5
@@ -2,10 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Controller;
|
||||
|
||||
use App\Entity\AbsenceRequest;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
@@ -21,7 +20,7 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
class AbsenceJustificationDownloadController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly Security $security,
|
||||
private readonly string $uploadDir,
|
||||
) {}
|
||||
@@ -30,7 +29,7 @@ class AbsenceJustificationDownloadController extends AbstractController
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function __invoke(int $id): BinaryFileResponse
|
||||
{
|
||||
$absence = $this->entityManager->getRepository(AbsenceRequest::class)->find($id);
|
||||
$absence = $this->requestRepository->findById($id);
|
||||
if (null === $absence) {
|
||||
throw new NotFoundHttpException('Absence request not found.');
|
||||
}
|
||||
+4
-3
@@ -2,9 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Controller;
|
||||
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
@@ -34,6 +34,7 @@ class AbsenceJustificationUploadController extends AbstractController
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly Security $security,
|
||||
private readonly string $uploadDir,
|
||||
) {}
|
||||
@@ -42,7 +43,7 @@ class AbsenceJustificationUploadController extends AbstractController
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function __invoke(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$absence = $this->entityManager->getRepository(AbsenceRequest::class)->find($id);
|
||||
$absence = $this->requestRepository->findById($id);
|
||||
if (null === $absence) {
|
||||
throw new NotFoundHttpException('Absence request not found.');
|
||||
}
|
||||
+9
-9
@@ -2,14 +2,14 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Controller;
|
||||
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Enum\HalfDay;
|
||||
use App\Repository\AbsenceBalanceRepository;
|
||||
use App\Repository\AbsencePolicyRepository;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Service\AbsenceDayCalculator;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Enum\HalfDay;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
|
||||
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
|
||||
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -30,8 +30,8 @@ class AbsencePreviewController extends AbstractController
|
||||
public function __construct(
|
||||
private readonly Security $security,
|
||||
private readonly AbsenceDayCalculator $calculator,
|
||||
private readonly AbsencePolicyRepository $policyRepository,
|
||||
private readonly AbsenceBalanceRepository $balanceRepository,
|
||||
private readonly AbsencePolicyRepositoryInterface $policyRepository,
|
||||
private readonly AbsenceBalanceRepositoryInterface $balanceRepository,
|
||||
private readonly AbsenceBalanceService $balanceService,
|
||||
) {}
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Controller;
|
||||
|
||||
use App\Service\PublicHolidayProvider;
|
||||
use App\Module\Absence\Domain\Service\PublicHolidayProvider;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
+10
-4
@@ -2,10 +2,11 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
namespace App\Module\Absence\Infrastructure\Doctrine;
|
||||
|
||||
use App\Entity\AbsenceBalance;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceBalance;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
@@ -13,13 +14,18 @@ use Doctrine\Persistence\ManagerRegistry;
|
||||
/**
|
||||
* @extends ServiceEntityRepository<AbsenceBalance>
|
||||
*/
|
||||
class AbsenceBalanceRepository extends ServiceEntityRepository
|
||||
class DoctrineAbsenceBalanceRepository extends ServiceEntityRepository implements AbsenceBalanceRepositoryInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, AbsenceBalance::class);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?AbsenceBalance
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
public function findOneForPeriod(UserInterface $user, AbsenceType $type, string $period): ?AbsenceBalance
|
||||
{
|
||||
return $this->findOneBy([
|
||||
+10
-4
@@ -2,23 +2,29 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
namespace App\Module\Absence\Infrastructure\Doctrine;
|
||||
|
||||
use App\Entity\AbsencePolicy;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Entity\AbsencePolicy;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<AbsencePolicy>
|
||||
*/
|
||||
class AbsencePolicyRepository extends ServiceEntityRepository
|
||||
class DoctrineAbsencePolicyRepository extends ServiceEntityRepository implements AbsencePolicyRepositoryInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, AbsencePolicy::class);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?AbsencePolicy
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
public function findOneByType(AbsenceType $type): ?AbsencePolicy
|
||||
{
|
||||
return $this->findOneBy(['type' => $type]);
|
||||
+11
-5
@@ -2,11 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
namespace App\Module\Absence\Infrastructure\Doctrine;
|
||||
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
@@ -15,13 +16,18 @@ use Doctrine\Persistence\ManagerRegistry;
|
||||
/**
|
||||
* @extends ServiceEntityRepository<AbsenceRequest>
|
||||
*/
|
||||
class AbsenceRequestRepository extends ServiceEntityRepository
|
||||
class DoctrineAbsenceRequestRepository extends ServiceEntityRepository implements AbsenceRequestRepositoryInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, AbsenceRequest::class);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?AbsenceRequest
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user already has a PENDING or APPROVED absence that overlaps
|
||||
* the given date range. Two ranges overlap when start_a <= end_b and
|
||||
+6
-6
@@ -2,12 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
@@ -21,7 +21,7 @@ class CancelAbsenceRequestTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly AbsenceRequestRepository $requestRepository,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly AbsenceBalanceService $balanceService,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
@@ -32,7 +32,7 @@ class CancelAbsenceRequestTool
|
||||
throw new AccessDeniedException('Access denied: ROLE_USER required.');
|
||||
}
|
||||
|
||||
$request = $this->requestRepository->find($id);
|
||||
$request = $this->requestRepository->findById($id);
|
||||
if (null === $request) {
|
||||
throw new InvalidArgumentException(sprintf('AbsenceRequest with ID %d not found.', $id));
|
||||
}
|
||||
+14
-14
@@ -2,18 +2,18 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Entity\AbsenceRequest;
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Enum\HalfDay;
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
|
||||
use App\Repository\AbsencePolicyRepository;
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Service\AbsenceDayCalculator;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Entity\AbsenceRequest;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Enum\HalfDay;
|
||||
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use App\Module\Absence\Domain\Service\AbsenceDayCalculator;
|
||||
use App\Module\Core\Domain\Repository\UserRepositoryInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
@@ -28,9 +28,9 @@ class CreateAbsenceRequestTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly DoctrineUserRepository $userRepository,
|
||||
private readonly AbsencePolicyRepository $policyRepository,
|
||||
private readonly AbsenceRequestRepository $requestRepository,
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
private readonly AbsencePolicyRepositoryInterface $policyRepository,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly AbsenceDayCalculator $calculator,
|
||||
private readonly AbsenceBalanceService $balanceService,
|
||||
private readonly Security $security,
|
||||
@@ -49,7 +49,7 @@ class CreateAbsenceRequestTool
|
||||
throw new AccessDeniedException('Access denied: ROLE_USER required.');
|
||||
}
|
||||
|
||||
$user = $this->userRepository->find($userId)
|
||||
$user = $this->userRepository->findById($userId)
|
||||
?? throw new InvalidArgumentException(sprintf('User with ID %d not found.', $userId));
|
||||
|
||||
$typeEnum = AbsenceType::tryFrom($type)
|
||||
+4
-4
@@ -2,9 +2,9 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
@@ -17,7 +17,7 @@ use function sprintf;
|
||||
class DeleteAbsenceRequestTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsenceRequestRepository $requestRepository,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
@@ -28,7 +28,7 @@ class DeleteAbsenceRequestTool
|
||||
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
|
||||
}
|
||||
|
||||
$request = $this->requestRepository->find($id);
|
||||
$request = $this->requestRepository->findById($id);
|
||||
if (null === $request) {
|
||||
throw new InvalidArgumentException(sprintf('AbsenceRequest with ID %d not found.', $id));
|
||||
}
|
||||
+4
-4
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
@@ -17,7 +17,7 @@ use function sprintf;
|
||||
class GetAbsenceRequestTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsenceRequestRepository $requestRepository,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
|
||||
@@ -27,7 +27,7 @@ class GetAbsenceRequestTool
|
||||
throw new AccessDeniedException('Access denied: ROLE_USER required.');
|
||||
}
|
||||
|
||||
$request = $this->requestRepository->find($id);
|
||||
$request = $this->requestRepository->findById($id);
|
||||
if (null === $request) {
|
||||
throw new InvalidArgumentException(sprintf('AbsenceRequest with ID %d not found.', $id));
|
||||
}
|
||||
+7
-7
@@ -2,12 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
|
||||
use App\Repository\AbsenceBalanceRepository;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
|
||||
use App\Module\Core\Domain\Repository\UserRepositoryInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
@@ -19,8 +19,8 @@ use function sprintf;
|
||||
class ListAbsenceBalancesTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsenceBalanceRepository $balanceRepository,
|
||||
private readonly DoctrineUserRepository $userRepository,
|
||||
private readonly AbsenceBalanceRepositoryInterface $balanceRepository,
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
|
||||
@@ -32,7 +32,7 @@ class ListAbsenceBalancesTool
|
||||
|
||||
$criteria = [];
|
||||
if (null !== $userId) {
|
||||
$user = $this->userRepository->find($userId);
|
||||
$user = $this->userRepository->findById($userId);
|
||||
if (null === $user) {
|
||||
throw new InvalidArgumentException(sprintf('User with ID %d not found.', $userId));
|
||||
}
|
||||
+3
-3
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Repository\AbsencePolicyRepository;
|
||||
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
@@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
class ListAbsencePoliciesTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsencePolicyRepository $policyRepository,
|
||||
private readonly AbsencePolicyRepositoryInterface $policyRepository,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
|
||||
+8
-8
@@ -2,13 +2,13 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Enum\AbsenceType;
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceType;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use App\Module\Core\Domain\Repository\UserRepositoryInterface;
|
||||
use DateTimeImmutable;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
@@ -21,8 +21,8 @@ use function sprintf;
|
||||
class ListAbsenceRequestsTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsenceRequestRepository $requestRepository,
|
||||
private readonly DoctrineUserRepository $userRepository,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
|
||||
@@ -39,7 +39,7 @@ class ListAbsenceRequestsTool
|
||||
|
||||
$user = null;
|
||||
if (null !== $userId) {
|
||||
$user = $this->userRepository->find($userId)
|
||||
$user = $this->userRepository->findById($userId)
|
||||
?? throw new InvalidArgumentException(sprintf('User with ID %d not found.', $userId));
|
||||
}
|
||||
|
||||
+6
-6
@@ -2,12 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Enum\AbsenceStatus;
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Repository\AbsenceRequestRepository;
|
||||
use App\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Application\Service\AbsenceBalanceService;
|
||||
use App\Module\Absence\Domain\Enum\AbsenceStatus;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceRequestRepositoryInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -24,7 +24,7 @@ class ReviewAbsenceRequestTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly AbsenceRequestRepository $requestRepository,
|
||||
private readonly AbsenceRequestRepositoryInterface $requestRepository,
|
||||
private readonly AbsenceBalanceService $balanceService,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
@@ -39,7 +39,7 @@ class ReviewAbsenceRequestTool
|
||||
throw new InvalidArgumentException('decision must be "approve" or "reject".');
|
||||
}
|
||||
|
||||
$request = $this->requestRepository->find($id);
|
||||
$request = $this->requestRepository->findById($id);
|
||||
if (null === $request) {
|
||||
throw new InvalidArgumentException(sprintf('AbsenceRequest with ID %d not found.', $id));
|
||||
}
|
||||
+4
-4
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Repository\AbsenceBalanceRepository;
|
||||
use App\Module\Absence\Domain\Repository\AbsenceBalanceRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
@@ -18,7 +18,7 @@ use function sprintf;
|
||||
class UpdateAbsenceBalanceTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsenceBalanceRepository $balanceRepository,
|
||||
private readonly AbsenceBalanceRepositoryInterface $balanceRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
@@ -33,7 +33,7 @@ class UpdateAbsenceBalanceTool
|
||||
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
|
||||
}
|
||||
|
||||
$balance = $this->balanceRepository->find($id);
|
||||
$balance = $this->balanceRepository->findById($id);
|
||||
if (null === $balance) {
|
||||
throw new InvalidArgumentException(sprintf('AbsenceBalance with ID %d not found.', $id));
|
||||
}
|
||||
+4
-4
@@ -2,10 +2,10 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Tool\Absence;
|
||||
namespace App\Module\Absence\Infrastructure\Mcp\Tool;
|
||||
|
||||
use App\Mcp\Tool\Serializer;
|
||||
use App\Repository\AbsencePolicyRepository;
|
||||
use App\Module\Absence\Domain\Repository\AbsencePolicyRepositoryInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use InvalidArgumentException;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
@@ -18,7 +18,7 @@ use function sprintf;
|
||||
class UpdateAbsencePolicyTool
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AbsencePolicyRepository $policyRepository,
|
||||
private readonly AbsencePolicyRepositoryInterface $policyRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly Security $security,
|
||||
) {}
|
||||
@@ -36,7 +36,7 @@ class UpdateAbsencePolicyTool
|
||||
throw new AccessDeniedException('Access denied: ROLE_ADMIN required.');
|
||||
}
|
||||
|
||||
$policy = $this->policyRepository->find($id);
|
||||
$policy = $this->policyRepository->findById($id);
|
||||
if (null === $policy) {
|
||||
throw new InvalidArgumentException(sprintf('AbsencePolicy with ID %d not found.', $id));
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use App\Module\Core\Infrastructure\ApiPlatform\State\UserPasswordHasherProcessor
|
||||
use App\Module\Core\Infrastructure\Doctrine\DoctrineUserRepository;
|
||||
use App\Shared\Domain\Attribute\Auditable;
|
||||
use App\Shared\Domain\Attribute\AuditIgnore;
|
||||
use App\Shared\Domain\Contract\LeaveProfileInterface;
|
||||
use App\Shared\Domain\Contract\UserInterface as SharedUserInterface;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
@@ -63,7 +64,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
||||
#[Auditable]
|
||||
#[ORM\Entity(repositoryClass: DoctrineUserRepository::class)]
|
||||
#[ORM\Table(name: '`user`')]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface, SharedUserInterface
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface, SharedUserInterface, LeaveProfileInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
|
||||
@@ -9,6 +9,8 @@ use DateTimeInterface;
|
||||
|
||||
interface UserRepositoryInterface
|
||||
{
|
||||
public function findById(int $id): ?UserInterface;
|
||||
|
||||
/**
|
||||
* @return list<UserInterface>
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,11 @@ class DoctrineUserRepository extends ServiceEntityRepository implements UserRepo
|
||||
parent::__construct($registry, User::class);
|
||||
}
|
||||
|
||||
public function findById(int $id): ?UserInterface
|
||||
{
|
||||
return $this->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<UserInterface>
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Shared\Domain\Contract;
|
||||
|
||||
/**
|
||||
* HR leave profile of an employee, consumed by the Absence module without
|
||||
* coupling it to the concrete Core User entity.
|
||||
*
|
||||
* Exposes only the RH getters actually used by AbsenceBalanceService and
|
||||
* AccrueLeaveCommand. This is a service-level typing contract, not a Doctrine
|
||||
* relation (no resolve_target_entities entry).
|
||||
*/
|
||||
interface LeaveProfileInterface
|
||||
{
|
||||
public function getWorkTimeRatio(): float;
|
||||
|
||||
public function getAnnualLeaveDays(): float;
|
||||
|
||||
public function getReferencePeriodStart(): string;
|
||||
|
||||
public function getInitialLeaveBalance(): float;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user