diff --git a/src/Application/Usecase/Employees/FindAll/Filter.php b/src/Application/Usecase/Employees/FindAll/Filter.php new file mode 100644 index 0000000..fff65cc --- /dev/null +++ b/src/Application/Usecase/Employees/FindAll/Filter.php @@ -0,0 +1,28 @@ + */ + public array $sites; + + public function __construct( + public ?EstablishmentCode $establishment = null, + public ?ServiceId $service = null, + public ?\DateTimeInterface $since = null, + SiteId ...$sites, + ) { + $this->sites = $sites; + + if ($establishment === null && empty($this->sites) && $service === null && $since === null) { + throw new \DomainException('Filter requires at least one filtering criterion.'); + } + } +} diff --git a/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php b/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php index c7127b3..d64f62b 100644 --- a/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php +++ b/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php @@ -6,6 +6,9 @@ namespace Application\Usecase\Employees\FindAll; use Domain\Models\Entities\Employee; use Domain\Ports\Directories\EmployeeDirectory; +use Domain\Models\ValueObjects\Employees\EmployeeFilter as DirectoryEmployeeFilter; +use Domain\Models\ValueObjects\Employees\EmployeeSortColumn as DirectoryEmployeeSortColumn; +use Domain\Models\ValueObjects\Employees\EmployeeSorter as DirectoryEmployeeSorter; use Shared\DDD\Usecase\Input as BaseInput; use Shared\DDD\Usecase\Usecase; use Shared\Paginated; @@ -24,10 +27,24 @@ final readonly class FindAllEmployees implements Usecase { Assert::isInstanceOf($input, Input::class); + $filter = $input->filter === null + ? null + : new DirectoryEmployeeFilter( + establishment: $input->filter->establishment, + sites: $input->filter->sites, + service: $input->filter->service, + since: $input->filter->since, + ); + + $sorter = new DirectoryEmployeeSorter( + sortBy: DirectoryEmployeeSortColumn::from($input->sorter->sortBy->value), + direction: $input->sorter->direction, + ); + $page = $this->directory->findAll( $input->pagination, - $input->sorter, - $input->filter + $sorter, + $filter ); return $this->toOutput($page); diff --git a/src/Application/Usecase/Employees/FindAll/Input.php b/src/Application/Usecase/Employees/FindAll/Input.php index 7387807..011613a 100644 --- a/src/Application/Usecase/Employees/FindAll/Input.php +++ b/src/Application/Usecase/Employees/FindAll/Input.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace Application\Usecase\Employees\FindAll; -use Domain\Models\ValueObjects\Employees\EmployeeFilter; -use Domain\Models\ValueObjects\Employees\EmployeeSorter; use Shared\DDD\Usecase\Input as UsecaseInput; use Shared\Pagination; @@ -13,8 +11,8 @@ final readonly class Input implements UsecaseInput { public function __construct( public Pagination $pagination, - public EmployeeSorter $sorter, - public ?EmployeeFilter $filter = null, + public Sorter $sorter, + public ?Filter $filter = null, ) { } } diff --git a/src/Application/Usecase/Employees/FindAll/SortColumn.php b/src/Application/Usecase/Employees/FindAll/SortColumn.php new file mode 100644 index 0000000..83f6dc9 --- /dev/null +++ b/src/Application/Usecase/Employees/FindAll/SortColumn.php @@ -0,0 +1,15 @@ + $sites */ + /** + * @param array $sites + */ public function __construct( public ?EstablishmentCode $establishment = null, public array $sites = [], public ?ServiceId $service = null, public ?\DateTimeInterface $since = null, ) { - foreach ($this->sites as $site) { - if (! $site instanceof SiteId) { - throw new \InvalidArgumentException('Every item in sites array must be an instance of SiteId.'); - } - } - - if ($establishment === null && empty($this->sites) && $service === null && $since === null) { + if ($establishment === null && empty($sites) && $service === null && $since === null) { throw new \DomainException('EmployeeFilter requires at least one filtering criterion.'); } + + Assert::allIsInstanceOf($sites, SiteId::class, 'Every item in sites array must be an instance of SiteId.'); } } diff --git a/src/Domain/Ports/Directories/EmployeeDirectory.php b/src/Domain/Ports/Directories/EmployeeDirectory.php index 1bb759f..76038ec 100644 --- a/src/Domain/Ports/Directories/EmployeeDirectory.php +++ b/src/Domain/Ports/Directories/EmployeeDirectory.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Domain\Ports\Directories; use Domain\Models\Entities\Employee; +use Domain\Models\ValueObjects\Entities\ArcadiaEmployeeId; use Domain\Models\ValueObjects\Employees\EmployeeFilter; use Domain\Models\ValueObjects\Employees\EmployeeSorter; -use Domain\Models\ValueObjects\Entities\ArcadiaEmployeeId; use Shared\DDD\Directory; use Shared\Paginated; use Shared\Pagination; diff --git a/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php b/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php index 456dc70..21f86e5 100644 --- a/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php +++ b/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php @@ -5,14 +5,14 @@ declare(strict_types=1); namespace Infra\Adapters\Directories; use Domain\Models\Entities\Employee; -use Domain\Models\ValueObjects\Employees\EmployeeFilter; -use Domain\Models\ValueObjects\Employees\EmployeeSortColumn; -use Domain\Models\ValueObjects\Employees\EmployeeSorter; use Domain\Models\ValueObjects\Entities\ArcadiaEmployeeId; use Domain\Models\ValueObjects\EstablishmentCode; use Domain\Models\ValueObjects\ServiceId; use Domain\Models\ValueObjects\SiteId; use Domain\Ports\Directories\EmployeeDirectory; +use Domain\Models\ValueObjects\Employees\EmployeeFilter; +use Domain\Models\ValueObjects\Employees\EmployeeSortColumn; +use Domain\Models\ValueObjects\Employees\EmployeeSorter; use Infra\Database\Models\Employee as EmployeeModel; use Shared\Paginated; use Shared\Pagination; diff --git a/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php b/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php index cada6bd..28e54df 100644 --- a/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php +++ b/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php @@ -4,9 +4,9 @@ declare(strict_types=1); namespace Presentation\Api\V1\Http\Requests\Employees; -use Domain\Models\ValueObjects\Employees\EmployeeFilter; -use Domain\Models\ValueObjects\Employees\EmployeeSortColumn; -use Domain\Models\ValueObjects\Employees\EmployeeSorter; +use Application\Usecase\Employees\FindAll\Filter; +use Application\Usecase\Employees\FindAll\SortColumn; +use Application\Usecase\Employees\FindAll\Sorter; use Domain\Models\ValueObjects\EstablishmentCode; use Domain\Models\ValueObjects\ServiceId; use Domain\Models\ValueObjects\SiteId; @@ -26,8 +26,8 @@ final class EmployeesIndexRequest extends FormRequest 'page' => ['sometimes', 'integer', 'min:1'], 'pageSize' => ['sometimes', 'integer', 'min:1', 'max:' . Pagination::MAX_PAGE_SIZE], 'sortBy' => ['sometimes', Rule::in(array_map( - static fn (EmployeeSortColumn $case): string => $case->name, - EmployeeSortColumn::cases() + static fn (SortColumn $case): string => $case->name, + SortColumn::cases() ))], 'sortDirection' => ['sometimes', Rule::in(['Ascending', 'Descending'])], 'since' => ['sometimes', 'date'], @@ -59,12 +59,12 @@ final class EmployeesIndexRequest extends FormRequest ); } - public function sorter(): EmployeeSorter + public function sorter(): Sorter { - return new EmployeeSorter($this->sortBy(), $this->direction()); + return new Sorter($this->sortBy(), $this->direction()); } - public function filter(): ?EmployeeFilter + public function filter(): ?Filter { $since = $this->filled('since') ? DatesHelper::parseDateTime($this->string('since')->value()) : null; $establishment = $this->filled('establishment') ? new EstablishmentCode($this->string('establishment')->value()) : null; @@ -88,25 +88,20 @@ final class EmployeesIndexRequest extends FormRequest return null; } - return new EmployeeFilter( - establishment: $establishment, - sites: $sites, - service: $service, - since: $since, - ); + return new Filter($establishment, $service, $since, ...$sites); } - public function sortBy(): EmployeeSortColumn + public function sortBy(): SortColumn { - $value = $this->string('sortBy', EmployeeSortColumn::arcadiaId->name)->value(); + $value = $this->string('sortBy', SortColumn::arcadiaId->name)->value(); return match ($value) { - 'startDate' => EmployeeSortColumn::startDate, - 'endDate' => EmployeeSortColumn::endDate, - 'plannedEndDate' => EmployeeSortColumn::plannedEndDate, - 'createdAt' => EmployeeSortColumn::createdAt, - 'updatedAt' => EmployeeSortColumn::updatedAt, - default => EmployeeSortColumn::arcadiaId, + 'startDate' => SortColumn::startDate, + 'endDate' => SortColumn::endDate, + 'plannedEndDate' => SortColumn::plannedEndDate, + 'createdAt' => SortColumn::createdAt, + 'updatedAt' => SortColumn::updatedAt, + default => SortColumn::arcadiaId, }; } diff --git a/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php b/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php index 3dd8373..cee942a 100644 --- a/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php +++ b/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php @@ -5,13 +5,13 @@ declare(strict_types=1); namespace Tests\Integration\Infra\Adapters\Directories; use Domain\Models\Entities\Employee; -use Domain\Models\ValueObjects\Employees\EmployeeFilter; -use Domain\Models\ValueObjects\Employees\EmployeeSortColumn; -use Domain\Models\ValueObjects\Employees\EmployeeSorter; use Domain\Models\ValueObjects\Entities\ArcadiaEmployeeId; use Domain\Models\ValueObjects\EstablishmentCode; use Domain\Models\ValueObjects\ServiceId; use Domain\Models\ValueObjects\SiteId; +use Domain\Models\ValueObjects\Employees\EmployeeFilter; +use Domain\Models\ValueObjects\Employees\EmployeeSortColumn; +use Domain\Models\ValueObjects\Employees\EmployeeSorter; use Illuminate\Foundation\Testing\RefreshDatabase; use Infra\Adapters\Directories\EloquentEmployeeDirectory; use Infra\Database\Models\Employee as EmployeeModel; diff --git a/tests/Unit/Application/Usecase/Employees/FindAllEmployeesTest.php b/tests/Unit/Application/Usecase/Employees/FindAllEmployeesTest.php new file mode 100644 index 0000000..68ee087 --- /dev/null +++ b/tests/Unit/Application/Usecase/Employees/FindAllEmployeesTest.php @@ -0,0 +1,74 @@ +shouldReceive('findAll') + ->once() + ->withArgs(function (Pagination $actualPagination, DirectoryEmployeeSorter $actualSorter, mixed $actualFilter) use ($pagination): bool { + return $actualPagination === $pagination + && $actualSorter->sortBy === DirectoryEmployeeSortColumn::arcadiaId + && $actualSorter->direction === \SortDirection::Ascending + && $actualFilter === null; + }) + ->andReturn($paginatedResult); + + // WHEN + $result = $usecase->execute($input); + + // THEN + expect($result)->toBeInstanceOf(Output::class) + ->and($result->total)->toBe(1) + ->and($result->page)->toBe(1) + ->and($result->perPage)->toBe(10) + ->and($result->employees)->toHaveCount(1) + ->and($result->employees[0]->firstName)->toBe('John'); + }); +}); diff --git a/tests/Unit/Application/Usecase/Employees/FindEmployeeTest.php b/tests/Unit/Application/Usecase/Employees/FindEmployeeTest.php new file mode 100644 index 0000000..7f849f0 --- /dev/null +++ b/tests/Unit/Application/Usecase/Employees/FindEmployeeTest.php @@ -0,0 +1,76 @@ +shouldReceive('find') + ->once() + ->with($employeeId) + ->andReturn($employee); + + // WHEN + $result = $usecase->execute($input); + + // THEN + expect($result)->toBeInstanceOf(Output::class) + ->and($result->id)->toBe($employeeId) + ->and($result->firstName)->toBe('John') + ->and($result->lastName)->toBe('DOE') + ->and($result->establishment)->toBe('EST01') + ->and($result->site)->toBe('LDCSER') + ->and($result->service)->toBe('097'); + }); + + it('throws NotFoundException when employee does not exist', function (): void { + // GIVEN + $employeeId = new ArcadiaEmployeeId('01155902'); + $input = new Input($employeeId); + $employeeDirectory = \Mockery::mock(EmployeeDirectory::class); + $usecase = new FindEmployee($employeeDirectory); + + $employeeDirectory->shouldReceive('find') + ->once() + ->with($employeeId) + ->andReturnNull(); + + // WHEN & THEN + expect(fn () => $usecase->execute($input)) + ->toThrow(NotFoundException::class, "Employee '01155902' not found."); + }); +});