diff --git a/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php b/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php index 21f2312..c7127b3 100644 --- a/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php +++ b/src/Application/Usecase/Employees/FindAll/FindAllEmployees.php @@ -26,7 +26,8 @@ final readonly class FindAllEmployees implements Usecase $page = $this->directory->findAll( $input->pagination, - $input->sorter + $input->sorter, + $input->filter ); return $this->toOutput($page); diff --git a/src/Application/Usecase/Employees/FindAll/Input.php b/src/Application/Usecase/Employees/FindAll/Input.php index 94e31c0..7387807 100644 --- a/src/Application/Usecase/Employees/FindAll/Input.php +++ b/src/Application/Usecase/Employees/FindAll/Input.php @@ -4,6 +4,7 @@ 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,6 +14,7 @@ final readonly class Input implements UsecaseInput public function __construct( public Pagination $pagination, public EmployeeSorter $sorter, + public ?EmployeeFilter $filter = null, ) { } } diff --git a/src/Domain/Models/ValueObjects/Employees/EmployeeFilter.php b/src/Domain/Models/ValueObjects/Employees/EmployeeFilter.php new file mode 100644 index 0000000..bc96a6b --- /dev/null +++ b/src/Domain/Models/ValueObjects/Employees/EmployeeFilter.php @@ -0,0 +1,23 @@ + */ - public function findAll(Pagination $pagination, EmployeeSorter $sorter): Paginated; + public function findAll( + Pagination $pagination, + EmployeeSorter $sorter, + ?EmployeeFilter $filter = null, + ): Paginated; public function find(ArcadiaEmployeeId $id): ?Employee; } diff --git a/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php b/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php index 7721b72..9b009a7 100644 --- a/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php +++ b/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php @@ -5,6 +5,7 @@ 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; @@ -19,10 +20,34 @@ use Shared\Pagination; final class EloquentEmployeeDirectory implements EmployeeDirectory { #[\Override] - public function findAll(Pagination $pagination, EmployeeSorter $sorter): Paginated - { + public function findAll( + Pagination $pagination, + EmployeeSorter $sorter, + ?EmployeeFilter $filter = null, + ): Paginated { $query = EmployeeModel::query(); + if ($filter !== null) { + if ($filter->establishment !== null) { + $query->where('establishment', $filter->establishment->value); + } + + if ($filter->site !== null) { + $query->where('site', $filter->site->value); + } + + if ($filter->service !== null) { + $query->where('service', $filter->service->value); + } + + if ($filter->since !== null) { + $query->where(static function ($q) use ($filter): void { + $q->where('updated_at', '>=', $filter->since) + ->orWhere('created_at', '>=', $filter->since); + }); + } + } + $total = (int) (clone $query)->count(); /** @var \Illuminate\Support\Collection $rows */ diff --git a/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php b/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php index 2921dc4..ea25050 100644 --- a/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php +++ b/src/Presentation/Api/V1/Http/Controllers/Employees/EmployeesIndexController.php @@ -28,6 +28,7 @@ final readonly class EmployeesIndexController $input = new Input( pagination: $request->pagination(), sorter: $request->sorter(), + filter: $request->filter(), ); /** @var \Presentation\Api\V1\Presenters\Employees\EmployeesIndexView $response */ diff --git a/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php b/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php index 0760c51..6685e1c 100644 --- a/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php +++ b/src/Presentation/Api/V1/Http/Requests/Employees/EmployeesIndexRequest.php @@ -4,10 +4,15 @@ 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 Domain\Models\ValueObjects\EstablishmentCode; +use Domain\Models\ValueObjects\ServiceId; +use Domain\Models\ValueObjects\SiteId; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; +use Shared\Helpers\DatesHelper; use Shared\Pagination; final class EmployeesIndexRequest extends FormRequest @@ -57,6 +62,25 @@ final class EmployeesIndexRequest extends FormRequest return new EmployeeSorter($this->sortBy(), $this->direction()); } + public function filter(): ?EmployeeFilter + { + $since = $this->filled('since') ? DatesHelper::parseDateTime($this->string('since')->value()) : null; + $establishment = $this->filled('establishment') ? new EstablishmentCode($this->string('establishment')->value()) : null; + $site = $this->filled('siteId') ? new SiteId($this->string('siteId')->value()) : null; + $service = $this->filled('serviceId') ? new ServiceId($this->string('serviceId')->value()) : null; + + if ($establishment === null && $site === null && $service === null && $since === null) { + return null; + } + + return new EmployeeFilter( + establishment: $establishment, + site: $site, + service: $service, + since: $since, + ); + } + public function sortBy(): EmployeeSortColumn { $value = $this->string('sortBy', EmployeeSortColumn::arcadiaId->name)->value(); diff --git a/tests/Feature/Api/V1/Employee/EmployeesIndexTest.php b/tests/Feature/Api/V1/Employee/EmployeesIndexTest.php index 32d2a50..2af30a8 100644 --- a/tests/Feature/Api/V1/Employee/EmployeesIndexTest.php +++ b/tests/Feature/Api/V1/Employee/EmployeesIndexTest.php @@ -87,8 +87,24 @@ describe('Employees Index Feature API', function (): void { $response->assertStatus(200); }); - it('returns 200 OK for establishment, site, and service query filters', function (): void { + it('returns 200 OK and filters results for establishment, site, and service query filters', function (): void { // GIVEN + Employee::query()->delete(); + + Employee::factory()->create([ + 'public_employee_number' => '01155902', + 'establishment' => '01', + 'site' => 'LDCSER', + 'service' => '097', + ]); + + Employee::factory()->create([ + 'public_employee_number' => '1234567', + 'establishment' => 'LY', + 'site' => 'LDCSER', + 'service' => '101', + ]); + /** @var \Tests\TestCase $test */ $test = $this; @@ -97,5 +113,7 @@ describe('Employees Index Feature API', function (): void { // THEN $response->assertStatus(200); + $response->assertJsonCount(1, 'employees'); + $response->assertJsonPath('employees.0.id', '01155902'); }); }); diff --git a/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php b/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php index b7fb105..d881010 100644 --- a/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php +++ b/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php @@ -5,9 +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 Illuminate\Foundation\Testing\RefreshDatabase; use Infra\Adapters\Directories\EloquentEmployeeDirectory; use Infra\Database\Models\Employee as EmployeeModel; @@ -110,6 +114,48 @@ describe('EloquentEmployeeDirectory Integration Tests', function (): void { ->toThrow(\UnexpectedValueException::class, 'Employee created_at cannot be null.'); }); + it('filters employees when EmployeeFilter parameters are provided', function (): void { + // GIVEN + $directory = new EloquentEmployeeDirectory(); + + EmployeeModel::factory()->create([ + 'public_employee_number' => '1000001', + 'establishment' => '01', + 'site' => 'LDCSER', + 'service' => '097', + 'created_at' => '2024-01-01 08:00:00', + 'updated_at' => '2024-01-01 09:00:00', + ]); + + EmployeeModel::factory()->create([ + 'public_employee_number' => '1000002', + 'establishment' => 'LY', + 'site' => 'LDCSER', + 'service' => '101', + 'created_at' => '2024-01-01 08:00:00', + 'updated_at' => '2024-01-01 09:00:00', + ]); + + $filter = new EmployeeFilter( + establishment: new EstablishmentCode('01'), + site: new SiteId('LDCSER'), + service: new ServiceId('097'), + since: new \DateTimeImmutable('2024-01-01 00:00:00') + ); + + $pagination = Pagination::ofPage(1, 10); + $sorter = new EmployeeSorter(EmployeeSortColumn::arcadiaId, \SortDirection::Ascending); + + // WHEN + $result = $directory->findAll($pagination, $sorter, $filter); + + // THEN + expect($result)->toBeInstanceOf(Paginated::class) + ->and($result->getTotal())->toBe(1) + ->and(count($result->getItems()))->toBe(1) + ->and($result->getItems()[0]->id()->value)->toBe('1000001'); + }); + it('returns paginated employees sorted by specified columns', function (EmployeeSortColumn $sortColumn, \SortDirection $direction): void { // GIVEN $directory = new EloquentEmployeeDirectory(); diff --git a/tests/Unit/Domain/Models/ValueObjects/Employees/EmployeeFilterTest.php b/tests/Unit/Domain/Models/ValueObjects/Employees/EmployeeFilterTest.php new file mode 100644 index 0000000..6724b9e --- /dev/null +++ b/tests/Unit/Domain/Models/ValueObjects/Employees/EmployeeFilterTest.php @@ -0,0 +1,56 @@ + new EmployeeFilter()) + ->toThrow(\DomainException::class, 'EmployeeFilter requires at least one filtering criterion.'); + }); + + it('instantiates filter with a single parameter', function (): void { + // GIVEN + $establishment = new EstablishmentCode('01'); + + // WHEN + $filter = new EmployeeFilter(establishment: $establishment); + + // THEN + expect($filter->establishment)->toBe($establishment) + ->and($filter->site)->toBeNull() + ->and($filter->service)->toBeNull() + ->and($filter->since)->toBeNull(); + }); + + it('instantiates filter with all parameters', function (): void { + // GIVEN + $establishment = new EstablishmentCode('01'); + $site = new SiteId('LDCSER'); + $service = new ServiceId('097'); + $since = new \DateTimeImmutable('2024-01-01'); + + // WHEN + $filter = new EmployeeFilter( + establishment: $establishment, + site: $site, + service: $service, + since: $since, + ); + + // THEN + expect($filter->establishment)->toBe($establishment) + ->and($filter->site)->toBe($site) + ->and($filter->service)->toBe($service) + ->and($filter->since)->toBe($since); + }); +});