feat(employees): add filtering capabilities for employee retrieval
- Introduced EmployeeFilter value object for filtering criteria. - Updated EmployeeDirectory and EloquentEmployeeDirectory to support filtering. - Modified FindAllEmployees use case to accept filter input. - Enhanced EmployeesIndexRequest to handle filter parameters. - Added tests for filtering functionality in EmployeesIndexTest and EloquentEmployeeDirectoryTest.
This commit is contained in:
parent
b7f2de4f92
commit
b64c1ef366
10 changed files with 206 additions and 5 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
23
src/Domain/Models/ValueObjects/Employees/EmployeeFilter.php
Normal file
23
src/Domain/Models/ValueObjects/Employees/EmployeeFilter.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Employees;
|
||||
|
||||
use Domain\Models\ValueObjects\EstablishmentCode;
|
||||
use Domain\Models\ValueObjects\ServiceId;
|
||||
use Domain\Models\ValueObjects\SiteId;
|
||||
|
||||
final readonly class EmployeeFilter
|
||||
{
|
||||
public function __construct(
|
||||
public ?EstablishmentCode $establishment = null,
|
||||
public ?SiteId $site = null,
|
||||
public ?ServiceId $service = null,
|
||||
public ?\DateTimeInterface $since = null,
|
||||
) {
|
||||
if ($establishment === null && $site === null && $service === null && $since === null) {
|
||||
throw new \DomainException('EmployeeFilter requires at least one filtering criterion.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Domain\Ports\Directories;
|
||||
|
||||
use Domain\Models\Entities\Employee;
|
||||
use Domain\Models\ValueObjects\Employees\EmployeeFilter;
|
||||
use Domain\Models\ValueObjects\Employees\EmployeeSorter;
|
||||
use Domain\Models\ValueObjects\Entities\ArcadiaEmployeeId;
|
||||
use Shared\DDD\Directory;
|
||||
|
|
@ -15,7 +16,11 @@ use Shared\Pagination;
|
|||
interface EmployeeDirectory extends Directory
|
||||
{
|
||||
/** @return Paginated<Employee> */
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<int, EmployeeModel> $rows */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Domain\Models\ValueObjects\Employees;
|
||||
|
||||
use Domain\Models\ValueObjects\Employees\EmployeeFilter;
|
||||
use Domain\Models\ValueObjects\EstablishmentCode;
|
||||
use Domain\Models\ValueObjects\ServiceId;
|
||||
use Domain\Models\ValueObjects\SiteId;
|
||||
|
||||
covers(EmployeeFilter::class);
|
||||
|
||||
describe('EmployeeFilter Value Object Unit Tests', function (): void {
|
||||
it('throws DomainException when instantiated with all null parameters', function (): void {
|
||||
// GIVEN, WHEN & THEN
|
||||
expect(fn () => 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);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue