feat(employees): add support for filtering by multiple site IDs
- Updated EmployeeFilter to accept an array of SiteId. - Modified EloquentEmployeeDirectory to handle multiple site filters. - Enhanced EmployeesIndexRequest to validate siteIds as an array. - Added tests for siteIds filtering in EmployeesIndexTest and EmployeeOpenApiTest.
This commit is contained in:
parent
b64c1ef366
commit
58749030bf
9 changed files with 115 additions and 12 deletions
|
|
@ -40,6 +40,16 @@ http:
|
|||
type: query
|
||||
description: Filter by site ID (e.g. LDCSER, SLELA)
|
||||
disabled: true
|
||||
- name: siteIds[]
|
||||
value: LDCSER
|
||||
type: query
|
||||
description: Filter by multiple site IDs
|
||||
disabled: true
|
||||
- name: siteIds[]
|
||||
value: SLELA
|
||||
type: query
|
||||
description: Filter by multiple site IDs
|
||||
disabled: true
|
||||
- name: serviceId
|
||||
value: "097"
|
||||
type: query
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ info:
|
|||
title: Arcadia API
|
||||
description: |
|
||||
Arcadia API in order to abstract corporate metamodel to a proper and stable implemented API.
|
||||
Architecture Clean Architecture, Domain-Driven Design (DDD) & Ports & Adapters (Hexagonale).
|
||||
contact:
|
||||
name: Uwil Engineering Team
|
||||
email: digital@ldc.fr
|
||||
|
|
@ -45,6 +44,7 @@ paths:
|
|||
- $ref: '#/components/parameters/Since'
|
||||
- $ref: '#/components/parameters/Establishment'
|
||||
- $ref: '#/components/parameters/SiteId'
|
||||
- $ref: '#/components/parameters/SiteIds'
|
||||
- $ref: '#/components/parameters/ServiceId'
|
||||
- $ref: '#/components/parameters/SortBy'
|
||||
- $ref: '#/components/parameters/SortDirection'
|
||||
|
|
@ -207,6 +207,21 @@ components:
|
|||
pattern: '^[A-Z]{2,}$'
|
||||
example: 'LDCSER'
|
||||
|
||||
SiteIds:
|
||||
name: siteIds
|
||||
in: query
|
||||
required: false
|
||||
description: "Filtrer par un ou plusieurs codes site (ex: siteIds[]=LDCSER&siteIds[]=SLELA)."
|
||||
style: form
|
||||
explode: true
|
||||
schema:
|
||||
type: array
|
||||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
pattern: '^[A-Z]{2,}$'
|
||||
example: ['LDCSER', 'SLELA']
|
||||
|
||||
ServiceId:
|
||||
name: serviceId
|
||||
in: query
|
||||
|
|
|
|||
|
|
@ -10,13 +10,20 @@ use Domain\Models\ValueObjects\SiteId;
|
|||
|
||||
final readonly class EmployeeFilter
|
||||
{
|
||||
/** @param array<SiteId> $sites */
|
||||
public function __construct(
|
||||
public ?EstablishmentCode $establishment = null,
|
||||
public ?SiteId $site = null,
|
||||
public array $sites = [],
|
||||
public ?ServiceId $service = null,
|
||||
public ?\DateTimeInterface $since = null,
|
||||
) {
|
||||
if ($establishment === null && $site === null && $service === null && $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) {
|
||||
throw new \DomainException('EmployeeFilter requires at least one filtering criterion.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ final class EloquentEmployeeDirectory implements EmployeeDirectory
|
|||
$query->where('establishment', $filter->establishment->value);
|
||||
}
|
||||
|
||||
if ($filter->site !== null) {
|
||||
$query->where('site', $filter->site->value);
|
||||
if (! empty($filter->sites)) {
|
||||
$siteValues = array_map(static fn (SiteId $s): string => $s->value, $filter->sites);
|
||||
$query->whereIn('site', $siteValues);
|
||||
}
|
||||
|
||||
if ($filter->service !== null) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ final class EmployeesIndexRequest extends FormRequest
|
|||
'since' => ['sometimes', 'date'],
|
||||
'establishment' => ['sometimes', 'string'],
|
||||
'siteId' => ['sometimes', 'string'],
|
||||
'siteIds' => ['sometimes', 'array'],
|
||||
'siteIds.*' => ['sometimes', 'string'],
|
||||
'serviceId' => ['sometimes', 'string'],
|
||||
];
|
||||
}
|
||||
|
|
@ -66,16 +68,29 @@ final class EmployeesIndexRequest extends FormRequest
|
|||
{
|
||||
$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) {
|
||||
$sites = [];
|
||||
$rawSiteIds = $this->input('siteIds');
|
||||
if (is_array($rawSiteIds)) {
|
||||
foreach ($rawSiteIds as $s) {
|
||||
if (is_string($s) && $s !== '') {
|
||||
$sites[] = new SiteId($s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->filled('siteId')) {
|
||||
$sites[] = new SiteId($this->string('siteId')->value());
|
||||
}
|
||||
|
||||
if ($establishment === null && empty($sites) && $service === null && $since === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new EmployeeFilter(
|
||||
establishment: $establishment,
|
||||
site: $site,
|
||||
sites: $sites,
|
||||
service: $service,
|
||||
since: $since,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -116,4 +116,34 @@ describe('Employees Index Feature API', function (): void {
|
|||
$response->assertJsonCount(1, 'employees');
|
||||
$response->assertJsonPath('employees.0.id', '01155902');
|
||||
});
|
||||
|
||||
it('returns 200 OK and filters results for siteIds array query filter', function (): void {
|
||||
// GIVEN
|
||||
Employee::query()->delete();
|
||||
|
||||
Employee::factory()->create([
|
||||
'public_employee_number' => '01155902',
|
||||
'site' => 'LDCSER',
|
||||
]);
|
||||
|
||||
Employee::factory()->create([
|
||||
'public_employee_number' => '01155903',
|
||||
'site' => 'SLELA',
|
||||
]);
|
||||
|
||||
Employee::factory()->create([
|
||||
'public_employee_number' => '01155904',
|
||||
'site' => 'OTHER',
|
||||
]);
|
||||
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson('/api/v1/employees?siteIds[]=LDCSER&siteIds[]=SLELA');
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonCount(2, 'employees');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,6 +39,21 @@ describe('Employee OpenAPI Conformance', function (): void {
|
|||
$response->assertValidResponse(200);
|
||||
});
|
||||
|
||||
it('validates GET /api/v1/employees list response with siteIds filter against openapi spec', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
|
||||
// WHEN
|
||||
/** @var \Illuminate\Testing\TestResponse<\Symfony\Component\HttpFoundation\Response> $response */
|
||||
$response = $test->getJson('/api/v1/employees?siteIds[]=LDCSER&siteIds[]=SLELA');
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertValidRequest();
|
||||
$response->assertValidResponse(200);
|
||||
});
|
||||
|
||||
it('validates GET /api/v1/employees/{id} detail response against openapi spec', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ describe('EloquentEmployeeDirectory Integration Tests', function (): void {
|
|||
|
||||
$filter = new EmployeeFilter(
|
||||
establishment: new EstablishmentCode('01'),
|
||||
site: new SiteId('LDCSER'),
|
||||
sites: [new SiteId('LDCSER')],
|
||||
service: new ServiceId('097'),
|
||||
since: new \DateTimeImmutable('2024-01-01 00:00:00')
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,16 @@ describe('EmployeeFilter Value Object Unit Tests', function (): void {
|
|||
->toThrow(\DomainException::class, 'EmployeeFilter requires at least one filtering criterion.');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException when sites array contains an item that is not a SiteId', function (): void {
|
||||
// GIVEN
|
||||
$invalidSites = [new SiteId('LDCSER'), 'invalid-string'];
|
||||
|
||||
// WHEN & THEN
|
||||
/** @phpstan-ignore-next-line */
|
||||
expect(fn () => new EmployeeFilter(sites: $invalidSites))
|
||||
->toThrow(\InvalidArgumentException::class, 'Every item in sites array must be an instance of SiteId.');
|
||||
});
|
||||
|
||||
it('instantiates filter with a single parameter', function (): void {
|
||||
// GIVEN
|
||||
$establishment = new EstablishmentCode('01');
|
||||
|
|
@ -27,7 +37,7 @@ describe('EmployeeFilter Value Object Unit Tests', function (): void {
|
|||
|
||||
// THEN
|
||||
expect($filter->establishment)->toBe($establishment)
|
||||
->and($filter->site)->toBeNull()
|
||||
->and($filter->sites)->toBeEmpty()
|
||||
->and($filter->service)->toBeNull()
|
||||
->and($filter->since)->toBeNull();
|
||||
});
|
||||
|
|
@ -42,14 +52,14 @@ describe('EmployeeFilter Value Object Unit Tests', function (): void {
|
|||
// WHEN
|
||||
$filter = new EmployeeFilter(
|
||||
establishment: $establishment,
|
||||
site: $site,
|
||||
sites: [$site],
|
||||
service: $service,
|
||||
since: $since,
|
||||
);
|
||||
|
||||
// THEN
|
||||
expect($filter->establishment)->toBe($establishment)
|
||||
->and($filter->site)->toBe($site)
|
||||
->and($filter->sites)->toBe([$site])
|
||||
->and($filter->service)->toBe($service)
|
||||
->and($filter->since)->toBe($since);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue