refactor(infra): replace Eloquent directory adapters with direct QueryBuilder adapters and mappers

- Replace Eloquent models for replicated Oracle tables with direct QueryBuilder directory adapters
- Bind ArcadiaEmployeeDirectory, ArcadiaIncoSheetDirectory, ArcadiaIncoShortSheetDirectory, ArcadiaIncoCompositionDirectory, and ArcadiaMarqueBrandDirectory
- Remove obsolete Eloquent directory classes and models
This commit is contained in:
Jérémy Munsch 2026-08-26 17:29:08 +02:00
parent cf918ccd30
commit cec8d1ea66
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
20 changed files with 411 additions and 1716 deletions

View file

@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Employee;
use Domain\Models\ValueObjects\Employees\ArcadiaEmployeeId;
use Domain\Models\ValueObjects\Employees\EmployeeFilter;
use Domain\Models\ValueObjects\Employees\EmployeeSortColumn;
use Domain\Models\ValueObjects\Employees\EmployeeSorter;
use Domain\Models\ValueObjects\SiteId;
use Domain\Ports\Directories\EmployeeDirectory;
use Illuminate\Database\Connection;
use Illuminate\Database\Query\Builder;
use Infra\Adapters\Mappers\EmployeeMapper;
use Shared\Paginated;
use Shared\Pagination;
final class ArcadiaEmployeeDirectory implements EmployeeDirectory
{
public function __construct(
private readonly Connection $connection,
) {
}
#[\Override]
public function findAll(
Pagination $pagination,
EmployeeSorter $sorter,
?EmployeeFilter $filter = null,
): Paginated {
$query = $this->connection->table('RU_PERSONNE_SIRH');
if ($filter !== null) {
if ($filter->establishment !== null) {
$query->where('ETAB_FIC', $filter->establishment->value);
}
if (! empty($filter->sites)) {
$siteValues = array_map(static fn (SiteId $s): string => $s->value, $filter->sites);
$query->whereIn('SITE_FIC', $siteValues);
}
if ($filter->service !== null) {
$query->where('SERVICE_FIC', $filter->service->value);
}
if ($filter->since !== null) {
$query->where(static function (Builder $q) use ($filter): void {
$q->where('DAT_MAJ', '>=', $filter->since)
->orWhere('DAT_CRE', '>=', $filter->since);
});
}
}
$total = (int) (clone $query)->count();
$rows = (clone $query)
->orderBy(
$this->toSqlColumn($sorter->sortBy),
$sorter->direction
)
->offset($pagination->from())
->limit($pagination->pageSize())
->get();
$items = [];
foreach ($rows as $row) {
$items[] = EmployeeMapper::toDomain($row);
}
/** @var Employee[] $items */
return new Paginated($pagination, $items, $total);
}
#[\Override]
public function find(ArcadiaEmployeeId $id): ?Employee
{
$row = $this->connection
->table('RU_PERSONNE_SIRH')
->where('MATRICULE_FIC', $id->value)
->first();
if ($row === null) {
return null;
}
return EmployeeMapper::toDomain($row);
}
private function toSqlColumn(EmployeeSortColumn $sortBy): string
{
return match ($sortBy) {
EmployeeSortColumn::arcadiaId => 'MATRICULE_FIC',
EmployeeSortColumn::startDate => 'DATE_DEB_FIC',
EmployeeSortColumn::endDate => 'DATE_FIN_FIC',
EmployeeSortColumn::plannedEndDate => 'DF_CARRIERE',
EmployeeSortColumn::createdAt => 'DAT_CRE',
EmployeeSortColumn::updatedAt => 'DAT_MAJ',
};
}
}

View file

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\Composition\IncoComposition;
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Ports\Directories\IncoCompositionDirectory;
use Illuminate\Database\Connection;
use Infra\Adapters\Mappers\IncoCompositionMapper;
final class ArcadiaIncoCompositionDirectory implements IncoCompositionDirectory
{
public function __construct(
private readonly Connection $connection,
) {
}
#[\Override]
public function find(IncoCompositionId $id): ?IncoComposition
{
$row = $this->connection
->table('INCO_COMPO')
->where('ID', $id->value)
->first();
if ($row === null) {
return null;
}
return IncoCompositionMapper::toDomain($row);
}
#[\Override]
public function findByIncoId(IncoId $incoId): ?IncoComposition
{
$row = $this->connection
->table('INCO_COMPO')
->where('ID_INCO', $incoId->value)
->first();
if ($row === null) {
return null;
}
return IncoCompositionMapper::toDomain($row);
}
}

View file

@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoSheet;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Ports\Directories\IncoSheetDirectory;
use Domain\Ports\Repositories\BrandRepository;
use Illuminate\Database\Connection;
use Illuminate\Database\Query\Builder;
use Infra\Adapters\Mappers\IncoMapper;
use Shared\Paginated;
use Shared\Pagination;
final class ArcadiaIncoSheetDirectory implements IncoSheetDirectory
{
public function __construct(
private readonly Connection $connection,
private readonly BrandRepository $brandRepository,
private readonly IncoMapper $mapper,
) {
}
#[\Override]
public function findAll(
Pagination $pagination,
IncoSheetSorter $sorter,
?IncoSheetFilter $filter = null
): Paginated {
$query = $this->connection
->table('INCO')
->leftJoin('FAM_VENTE', 'INCO.FAMILLE_VTE', '=', 'FAM_VENTE.COD_FAMVTE')
->select(['INCO.*', 'FAM_VENTE.LIB_FAMVTE as FAMILLE_VTE_LIBELLE']);
if ($filter !== null) {
if ($filter->since !== null) {
$query->where(static function (Builder $q) use ($filter): void {
$q->where('INCO.DAT_MAJ', '>=', $filter->since)
->orWhere('INCO.DAT_CRE', '>=', $filter->since);
});
}
if (! empty($filter->brands)) {
$resolvedBrands = $this->resolveBrandFilterCodes(...$filter->brands);
$query->whereIn('INCO.MARQUE', $resolvedBrands);
}
}
$total = (int) (clone $query)->count();
$rows = (clone $query)
->orderBy(
$this->toSqlColumn($sorter->sortBy),
$sorter->direction
)
->offset($pagination->from())
->limit($pagination->pageSize())
->get();
$items = [];
foreach ($rows as $row) {
$items[] = $this->mapper->toDomain($row);
}
/** @var IncoSheet[] $items */
return new Paginated($pagination, $items, $total);
}
#[\Override]
public function search(
IncoIdQuery $query,
Pagination $pagination,
BrandCode ...$brands
): Paginated {
$dbQuery = $this->connection
->table('INCO')
->leftJoin('FAM_VENTE', 'INCO.FAMILLE_VTE', '=', 'FAM_VENTE.COD_FAMVTE')
->select(['INCO.*', 'FAM_VENTE.LIB_FAMVTE as FAMILLE_VTE_LIBELLE']);
$dbQuery->where('INCO.NO_ART', 'LIKE', $query->value);
if (! empty($brands)) {
$resolvedBrands = $this->resolveBrandFilterCodes(...$brands);
$dbQuery->whereIn('INCO.MARQUE', $resolvedBrands);
}
$total = (int) (clone $dbQuery)->count();
$rows = $dbQuery
->offset($pagination->from())
->limit($pagination->pageSize())
->get();
$items = [];
foreach ($rows as $row) {
$items[] = $this->mapper->toDomain($row);
}
/** @var IncoSheet[] $items */
return new Paginated($pagination, $items, $total);
}
#[\Override]
public function find(IncoId $id): ?IncoSheet
{
$row = $this->connection
->table('INCO')
->leftJoin('FAM_VENTE', 'INCO.FAMILLE_VTE', '=', 'FAM_VENTE.COD_FAMVTE')
->select(['INCO.*', 'FAM_VENTE.LIB_FAMVTE as FAMILLE_VTE_LIBELLE'])
->where('INCO.NO_ART', $id->value)
->first();
if ($row === null) {
return null;
}
return $this->mapper->toDomain($row);
}
/**
* @return array<int|string>
*/
private function resolveBrandFilterCodes(BrandCode ...$brandCodes): array
{
$resolved = [];
foreach ($brandCodes as $brandCode) {
$brand = $this->brandRepository->findByCode($brandCode);
if ($brand !== null) {
$resolved[] = (int) $brand->arcadiaId->value;
$resolved[] = $brand->arcadiaId->value;
}
}
if (empty($resolved) && ! empty($brandCodes)) {
return [0];
}
$unique = [];
foreach ($resolved as $item) {
if (! in_array($item, $unique, true)) {
$unique[] = $item;
}
}
return $unique;
}
private function toSqlColumn(IncoSheetSortColumn $column): string
{
return match ($column) {
IncoSheetSortColumn::incoSheetId => 'NO_ART',
IncoSheetSortColumn::createdAt => 'DAT_CRE',
IncoSheetSortColumn::updatedAt => 'DAT_MAJ',
};
}
}

View file

@ -13,18 +13,17 @@ use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Ports\Directories\IncoShortSheetDirectory;
use Domain\Ports\Repositories\BrandRepository;
use Infra\Database\Models\IncoSheet as IncoSheetModel;
use Shared\Helpers\DatesHelper;
use Illuminate\Database\Connection;
use Illuminate\Database\Query\Builder;
use Infra\Adapters\Mappers\IncoShortMapper;
use Shared\Paginated;
use Shared\Pagination;
final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
final class ArcadiaIncoShortSheetDirectory implements IncoShortSheetDirectory
{
/** @var list<string> */
private const array SHORT_FIELDS = ['id', 'name', 'label', 'created_at', 'updated_at'];
public function __construct(
private BrandRepository $brandRepository,
private readonly Connection $connection,
private readonly BrandRepository $brandRepository,
) {
}
@ -34,19 +33,21 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
IncoSheetSorter $sorter,
?IncoSheetFilter $filter = null
): Paginated {
$query = IncoSheetModel::query()->select(self::SHORT_FIELDS);
$query = $this->connection
->table('INCO')
->select(IncoShortMapper::SELECT_COLUMNS);
if ($filter !== null) {
if ($filter->since !== null) {
$query->where(static function ($q) use ($filter): void {
$q->where('updated_at', '>=', $filter->since)
->orWhere('created_at', '>=', $filter->since);
$query->where(static function (Builder $q) use ($filter): void {
$q->where('DAT_MAJ', '>=', $filter->since)
->orWhere('DAT_CRE', '>=', $filter->since);
});
}
if (! empty($filter->brands)) {
$resolvedBrands = $this->resolveBrandFilterCodes(...$filter->brands);
$query->whereIn('brand->code', $resolvedBrands);
$query->whereIn('MARQUE', $resolvedBrands);
}
}
@ -63,7 +64,7 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
$items = [];
foreach ($rows as $row) {
$items[] = $this->toDomain($row);
$items[] = IncoShortMapper::toDomain($row);
}
/** @var IncoShortSheet[] $items */
@ -76,13 +77,15 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
Pagination $pagination,
BrandCode ...$brands
): Paginated {
$dbQuery = IncoSheetModel::query()->select(self::SHORT_FIELDS);
$dbQuery = $this->connection
->table('INCO')
->select(IncoShortMapper::SELECT_COLUMNS);
$dbQuery->where('id', 'LIKE', $query->value);
$dbQuery->where('NO_ART', 'LIKE', $query->value);
if (! empty($brands)) {
$resolvedBrands = $this->resolveBrandFilterCodes(...$brands);
$dbQuery->whereIn('brand->code', $resolvedBrands);
$dbQuery->whereIn('MARQUE', $resolvedBrands);
}
$total = (int) (clone $dbQuery)->count();
@ -94,7 +97,7 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
$items = [];
foreach ($rows as $row) {
$items[] = $this->toDomain($row);
$items[] = IncoShortMapper::toDomain($row);
}
/** @var IncoShortSheet[] $items */
@ -104,16 +107,17 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
#[\Override]
public function find(IncoId $id): ?IncoShortSheet
{
$model = IncoSheetModel::query()
->select(self::SHORT_FIELDS)
->where('id', $id->value)
$row = $this->connection
->table('INCO')
->select(IncoShortMapper::SELECT_COLUMNS)
->where('NO_ART', $id->value)
->first();
if ($model === null) {
if ($row === null) {
return null;
}
return $this->toDomain($model);
return IncoShortMapper::toDomain($row);
}
/**
@ -145,35 +149,12 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
return $unique;
}
private function toDomain(IncoSheetModel $model): IncoShortSheet
{
$rawId = $model->getAttribute('id');
$rawName = $model->getAttribute('name');
$rawLabel = $model->getAttribute('label');
$rawCreatedAt = $model->getAttribute('created_at');
$rawUpdatedAt = $model->getAttribute('updated_at');
$id = is_string($rawId) || is_numeric($rawId) ? (string) $rawId : '';
$name = is_string($rawName) ? $rawName : '';
$label = is_string($rawLabel) ? $rawLabel : '';
$createdAt = is_string($rawCreatedAt) ? DatesHelper::parseDateTime($rawCreatedAt) : null;
$updatedAt = is_string($rawUpdatedAt) ? DatesHelper::parseDateTime($rawUpdatedAt) : null;
return IncoShortSheet::hydrate(
id: new IncoId($id),
name: $name,
label: $label,
createdAt: $createdAt,
updatedAt: $updatedAt,
);
}
private function toSqlColumn(IncoSheetSortColumn $column): string
{
return match ($column) {
IncoSheetSortColumn::incoSheetId => 'id',
IncoSheetSortColumn::createdAt => 'created_at',
IncoSheetSortColumn::updatedAt => 'updated_at',
IncoSheetSortColumn::incoSheetId => 'NO_ART',
IncoSheetSortColumn::createdAt => 'DAT_CRE',
IncoSheetSortColumn::updatedAt => 'DAT_MAJ',
};
}
}

View file

@ -6,23 +6,31 @@ namespace Infra\Adapters\Directories;
use Domain\Models\ValueObjects\Brand\ArcadiaBrand;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Domain\Models\ValueObjects\Brand\BrandName;
use Domain\Ports\Directories\ArcadiaBrandDirectory;
use Infra\Database\Models\ArcadiaBrand as ArcadiaBrandModel;
use Domain\Ports\Directories\ArcadiaBrandDirectory as ArcadiaBrandDirectoryPort;
use Illuminate\Database\Connection;
use Infra\Adapters\Mappers\ArcadiaBrandMapper;
final class EloquentArcadiaBrandDirectory implements ArcadiaBrandDirectory
final class ArcadiaMarqueBrandDirectory implements ArcadiaBrandDirectoryPort
{
/** @var array<string, ?ArcadiaBrand> */
private array $cache = [];
public function __construct(
private readonly Connection $connection,
) {
}
#[\Override]
public function findAll(): array
{
$models = ArcadiaBrandModel::query()->get();
$items = [];
$rows = $this->connection
->table('MARQUE')
->get();
foreach ($models as $model) {
$domain = $this->toDomain($model);
$items = [];
foreach ($rows as $row) {
$domain = ArcadiaBrandMapper::toDomain($row);
$this->cache[$domain->id->value] = $domain;
$items[] = $domain;
}
@ -37,25 +45,20 @@ final class EloquentArcadiaBrandDirectory implements ArcadiaBrandDirectory
return $this->cache[$id->value];
}
$model = ArcadiaBrandModel::query()->find($id->value);
$row = $this->connection
->table('MARQUE')
->where('COD_MARQ', $id->value)
->first();
if ($model === null) {
if ($row === null) {
$this->cache[$id->value] = null;
return null;
}
$domain = $this->toDomain($model);
$domain = ArcadiaBrandMapper::toDomain($row);
$this->cache[$id->value] = $domain;
return $domain;
}
private function toDomain(ArcadiaBrandModel $model): ArcadiaBrand
{
return new ArcadiaBrand(
id: new ArcadiaId((string) $model->code),
name: new BrandName((string) $model->libelle),
);
}
}

View file

@ -1,120 +0,0 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Employee;
use Domain\Models\ValueObjects\Employees\ArcadiaEmployeeId;
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 Domain\Ports\Directories\EmployeeDirectory;
use Infra\Database\Models\Employee as EmployeeModel;
use Shared\Paginated;
use Shared\Pagination;
final class EloquentEmployeeDirectory implements EmployeeDirectory
{
#[\Override]
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 (! empty($filter->sites)) {
$siteValues = array_map(static fn (SiteId $s): string => $s->value, $filter->sites);
$query->whereIn('site', $siteValues);
}
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 */
$rows = (clone $query)
->orderBy(
$this->toSqlColumn($sorter->sortBy),
$sorter->direction
)
->offset($pagination->from())
->limit($pagination->pageSize())
->get();
$items = [];
foreach ($rows as $row) {
$items[] = $this->toDomain($row);
}
/** @var Employee[] $items */
return new Paginated($pagination, $items, $total);
}
#[\Override]
public function find(ArcadiaEmployeeId $id): ?Employee
{
$model = EmployeeModel::query()
->where('public_employee_number', $id->value)
->first();
if ($model === null) {
return null;
}
return $this->toDomain($model);
}
private function toDomain(EmployeeModel $employee): Employee
{
if ($employee->created_at === null) {
throw new \UnexpectedValueException('Employee created_at cannot be null.');
}
return Employee::hydrate(
publicEmployeeNumber: new ArcadiaEmployeeId((string) $employee->public_employee_number),
lastName: (string) $employee->last_name,
firstName: (string) $employee->first_name,
startDate: $employee->start_date,
endDate: $employee->end_date,
plannedEndDate: $employee->planned_end_date,
establishment: new EstablishmentCode((string) $employee->establishment),
site: new SiteId((string) $employee->site),
service: new ServiceId((string) $employee->service),
createdAt: $employee->created_at,
updatedAt: $employee->updated_at,
);
}
private function toSqlColumn(EmployeeSortColumn $sortBy): string
{
return match ($sortBy) {
EmployeeSortColumn::arcadiaId => 'public_employee_number',
EmployeeSortColumn::startDate => 'start_date',
EmployeeSortColumn::endDate => 'end_date',
EmployeeSortColumn::plannedEndDate => 'planned_end_date',
EmployeeSortColumn::createdAt => 'created_at',
EmployeeSortColumn::updatedAt => 'updated_at',
};
}
}

View file

@ -1,392 +0,0 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\Composition\IncoComposition;
use Domain\Models\ValueObjects\Inco\Composition\AminoAcids;
use Domain\Models\ValueObjects\Inco\Composition\CarbohydratesDetail;
use Domain\Models\ValueObjects\Inco\Composition\Energy;
use Domain\Models\ValueObjects\Inco\Composition\EnergyValues;
use Domain\Models\ValueObjects\Inco\Composition\FattyAcids;
use Domain\Models\ValueObjects\Inco\Composition\FreshProduceAndNuts;
use Domain\Models\ValueObjects\Inco\Composition\FruitAndNutAmount;
use Domain\Models\ValueObjects\Inco\Composition\Identification;
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
use Domain\Models\ValueObjects\Inco\Composition\Minerals;
use Domain\Models\ValueObjects\Inco\Composition\NutrientValue;
use Domain\Models\ValueObjects\Inco\Composition\NutriScore;
use Domain\Models\ValueObjects\Inco\Composition\NutriScorePoints;
use Domain\Models\ValueObjects\Inco\Composition\PercentageValue;
use Domain\Models\ValueObjects\Inco\Composition\PortionMacronutrients;
use Domain\Models\ValueObjects\Inco\Composition\ReferenceIntakes;
use Domain\Models\ValueObjects\Inco\Composition\ReferenceMacronutrients;
use Domain\Models\ValueObjects\Inco\Composition\SpecialCompounds;
use Domain\Models\ValueObjects\Inco\Composition\Vitamins;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Ports\Directories\IncoCompositionDirectory;
use Infra\Database\Models\IncoCompo as IncoCompoModel;
final class EloquentIncoCompositionDirectory implements IncoCompositionDirectory
{
#[\Override]
public function find(IncoCompositionId $id): ?IncoComposition
{
$model = IncoCompoModel::query()
->where('id', $id->value)
->first();
if ($model === null) {
return null;
}
return $this->toDomain($model);
}
#[\Override]
public function findByIncoId(IncoId $incoId): ?IncoComposition
{
$model = IncoCompoModel::query()
->where('id_inco', $incoId->value)
->first();
if ($model === null) {
return null;
}
return $this->toDomain($model);
}
private function toDomain(IncoCompoModel $model): IncoComposition
{
return IncoComposition::builder()
->withId(new IncoCompositionId((string) $model->id))
->withIdInco(new IncoId((string) $model->id_inco))
->withIdentification($this->identificationFrom($model))
->withEnergy($this->energyFrom($model))
->withReferenceMacronutrients($this->refMacronutrientsFrom($model))
->withPortionMacronutrients($this->portionMacronutrientsFrom($model))
->withFreshProduceAndNuts($this->freshProduceAndNutsFrom($model))
->withReferenceIntakes($this->refIntakesFrom($model))
->withNutriScore($this->nutriScoreFrom($model))
->withNutriScorePoints($this->nutriScorePointsFrom($model))
->withMinerals($this->mineralsFrom($model))
->withVitamins($this->vitaminsFrom($model))
->withCarbohydratesDetail($this->carbohydratesDetailFrom($model))
->withFattyAcids($this->fattyAcidsFrom($model))
->withAminoAcids($this->aminoAcidsFrom($model))
->withSpecialCompounds($this->specialCompoundsFrom($model))
->build();
}
private function identificationFrom(IncoCompoModel $model): Identification
{
$raw = is_array($model->identification) ? $model->identification : [];
return new Identification(
name: $this->getString($raw, 'name', 'Inconnu'),
number: $this->getString($raw, 'number', (string) $model->id_inco),
source: $this->getString($raw, 'source', 'System'),
);
}
private function energyFrom(IncoCompoModel $model): Energy
{
$energyRaw = is_array($model->energy) ? $model->energy : [];
$refArr = is_array($energyRaw['reference'] ?? null) ? $energyRaw['reference'] : [];
$porArr = is_array($energyRaw['portion'] ?? null) ? $energyRaw['portion'] : [];
$refEnergy = new EnergyValues(
size: $this->getStringWithFallback($refArr, 'size', 'portionAmount', '100g'),
energyKj: $this->getStringWithFallback($refArr, 'energyKj', 'kilojoules'),
energyKcal: $this->getStringWithFallback($refArr, 'energyKcal', 'kilocalories'),
);
$porEnergy = new EnergyValues(
size: $this->getStringWithFallback($porArr, 'size', 'portionAmount', '100g'),
energyKj: $this->getStringWithFallback($porArr, 'energyKj', 'kilojoules'),
energyKcal: $this->getStringWithFallback($porArr, 'energyKcal', 'kilocalories'),
);
return new Energy($refEnergy, $porEnergy);
}
private function refMacronutrientsFrom(IncoCompoModel $model): ReferenceMacronutrients
{
$raw = is_array($model->reference_macronutrients) ? $model->reference_macronutrients : [];
return new ReferenceMacronutrients(
fat: $this->getNutrient($raw, 'fat'),
saturatedFat: $this->getNutrient($raw, 'saturatedFat'),
carbohydrates: $this->getNutrient($raw, 'carbohydrates'),
sugars: $this->getNutrient($raw, 'sugars'),
fiber: $this->getNutrient($raw, 'fiber'),
protein: $this->getNutrient($raw, 'protein'),
salt: $this->getNutrient($raw, 'salt'),
);
}
private function portionMacronutrientsFrom(IncoCompoModel $model): PortionMacronutrients
{
$raw = is_array($model->portion_macronutrients) ? $model->portion_macronutrients : [];
return new PortionMacronutrients(
fat: $this->getNutrient($raw, 'fat'),
saturatedFat: $this->getNutrient($raw, 'saturatedFat'),
carbohydrates: $this->getNutrient($raw, 'carbohydrates'),
sugars: $this->getNutrient($raw, 'sugars'),
fiber: $this->getNutrient($raw, 'fiber'),
protein: $this->getNutrient($raw, 'protein'),
salt: $this->getNutrient($raw, 'salt'),
);
}
private function freshProduceAndNutsFrom(IncoCompoModel $model): FreshProduceAndNuts
{
$raw = is_array($model->fresh_produce_and_nuts) ? $model->fresh_produce_and_nuts : [];
$fruitAmount = new FruitAndNutAmount();
return new FreshProduceAndNuts(
reference: $fruitAmount,
portion: $fruitAmount,
freshFnlPercentage: $this->getPercentage($raw, 'freshFnlPercentage'),
fruitsPercentage: $this->getPercentage($raw, 'fruitsPercentage'),
);
}
private function refIntakesFrom(IncoCompoModel $model): ReferenceIntakes
{
$raw = is_array($model->reference_intakes) ? $model->reference_intakes : [];
return new ReferenceIntakes(
energyE14: $this->getPercentage($raw, 'energyE14'),
energyKj: $this->getPercentage($raw, 'energyKj'),
fat: $this->getPercentage($raw, 'fat'),
saturatedFat: $this->getPercentage($raw, 'saturatedFat'),
carbohydrates: $this->getPercentage($raw, 'carbohydrates'),
sugars: $this->getPercentage($raw, 'sugars'),
protein: $this->getPercentage($raw, 'protein'),
salt: $this->getPercentage($raw, 'salt'),
portionAmount: $this->getString($raw, 'portionAmount', '100g'),
);
}
private function nutriScoreFrom(IncoCompoModel $model): NutriScore
{
$raw = is_array($model->nutri_score) ? $model->nutri_score : [];
return new NutriScore(
negativeComponent: $this->getString($raw, 'negativeComponent'),
score: $this->getString($raw, 'score'),
grade: $this->getString($raw, 'grade', 'A'),
);
}
private function nutriScorePointsFrom(IncoCompoModel $model): NutriScorePoints
{
$raw = is_array($model->nutri_score_points) ? $model->nutri_score_points : [];
return new NutriScorePoints(
energyPoints: $this->getStringWithFallback($raw, 'energyPoints', 'energy'),
saturatedFatPoints: $this->getStringWithFallback($raw, 'saturatedFatPoints', 'saturatedFat'),
sugarsPoints: $this->getStringWithFallback($raw, 'sugarsPoints', 'sugars'),
saltPoints: $this->getStringWithFallback($raw, 'saltPoints', 'sodium'),
fiberPoints: $this->getStringWithFallback($raw, 'fiberPoints', 'fiber'),
proteinPoints: $this->getStringWithFallback($raw, 'proteinPoints', 'proteins'),
freshFnlPoints: $this->getStringWithFallback($raw, 'freshFnlPoints', 'fruitsNuts'),
);
}
private function mineralsFrom(IncoCompoModel $model): Minerals
{
$raw = is_array($model->minerals) ? $model->minerals : [];
return new Minerals(
calcium: $this->getString($raw, 'calcium'),
magnesium: $this->getString($raw, 'magnesium'),
potassium: $this->getString($raw, 'potassium'),
phosphorus: $this->getString($raw, 'phosphorus'),
chloride: $this->getString($raw, 'chloride'),
iron: $this->getString($raw, 'iron'),
zinc: $this->getString($raw, 'zinc'),
copper: $this->getString($raw, 'copper'),
manganese: $this->getString($raw, 'manganese'),
selenium: $this->getString($raw, 'selenium'),
iodine: $this->getString($raw, 'iodine'),
chromium: $this->getString($raw, 'chromium'),
molybdenum: $this->getString($raw, 'molybdenum'),
sulfur: $this->getString($raw, 'sulfur'),
nitrates: $this->getString($raw, 'nitrates'),
);
}
private function vitaminsFrom(IncoCompoModel $model): Vitamins
{
$raw = is_array($model->vitamins) ? $model->vitamins : [];
return new Vitamins(
vitaminA: $this->getString($raw, 'vitaminA'),
vitaminB6: $this->getString($raw, 'vitaminB6'),
vitaminB12: $this->getString($raw, 'vitaminB12'),
vitaminC: $this->getString($raw, 'vitaminC'),
vitaminD: $this->getString($raw, 'vitaminD'),
vitaminE: $this->getString($raw, 'vitaminE'),
vitaminK: $this->getString($raw, 'vitaminK'),
cholecalciferol: $this->getString($raw, 'cholecalciferol'),
niacin: $this->getString($raw, 'niacin'),
riboflavin: $this->getString($raw, 'riboflavin'),
thiamine: $this->getString($raw, 'thiamine'),
pantothenicAcid: $this->getString($raw, 'pantothenicAcid'),
biotin: $this->getString($raw, 'biotin'),
folates: $this->getString($raw, 'folates'),
);
}
private function carbohydratesDetailFrom(IncoCompoModel $model): CarbohydratesDetail
{
$raw = is_array($model->carbohydrates_detail) ? $model->carbohydrates_detail : [];
return new CarbohydratesDetail(
starch: $this->getString($raw, 'starch'),
maltodextrin: $this->getString($raw, 'maltodextrin'),
sucrose: $this->getString($raw, 'sucrose'),
glucose: $this->getString($raw, 'glucose'),
lactose: $this->getString($raw, 'lactose'),
maltose: $this->getString($raw, 'maltose'),
galactose: $this->getString($raw, 'galactose'),
carbohydrateHydrates: $this->getString($raw, 'carbohydrateHydrates'),
boundFructose: $this->getString($raw, 'boundFructose'),
freeFructose: $this->getString($raw, 'freeFructose'),
oligosaccharides: $this->getString($raw, 'oligosaccharides'),
polyols: $this->getString($raw, 'polyols'),
solubleFiber: $this->getString($raw, 'solubleFiber'),
inositol: $this->getString($raw, 'inositol'),
);
}
private function fattyAcidsFrom(IncoCompoModel $model): FattyAcids
{
$raw = is_array($model->fatty_acids) ? $model->fatty_acids : [];
return new FattyAcids(
fd: $this->getString($raw, 'fd'),
cholesterol: $this->getString($raw, 'cholesterol'),
transFat: $this->getString($raw, 'transFat'),
formic: $this->getString($raw, 'formic'),
acetic: $this->getString($raw, 'acetic'),
propionic: $this->getString($raw, 'propionic'),
butyric: $this->getString($raw, 'butyric'),
valeric: $this->getString($raw, 'valeric'),
caproic: $this->getString($raw, 'caproic'),
enanthic: $this->getString($raw, 'enanthic'),
caprylic: $this->getString($raw, 'caprylic'),
pelargonic: $this->getString($raw, 'pelargonic'),
capric: $this->getString($raw, 'capric'),
undecylic: $this->getString($raw, 'undecylic'),
lauric: $this->getString($raw, 'lauric'),
tridecylic: $this->getString($raw, 'tridecylic'),
myristic: $this->getString($raw, 'myristic'),
pentadecylic: $this->getString($raw, 'pentadecylic'),
f16d0: $this->getString($raw, 'f16d0'),
margaric: $this->getString($raw, 'margaric'),
stearic: $this->getString($raw, 'stearic'),
arachidic: $this->getString($raw, 'arachidic'),
heneicosylic: $this->getString($raw, 'heneicosylic'),
behenic: $this->getString($raw, 'behenic'),
tricosylic: $this->getString($raw, 'tricosylic'),
lignoceric: $this->getString($raw, 'lignoceric'),
carboceric: $this->getString($raw, 'carboceric'),
cerotic: $this->getString($raw, 'cerotic'),
ceroplastic: $this->getString($raw, 'ceroplastic'),
montanic: $this->getString($raw, 'montanic'),
nonacosylic: $this->getString($raw, 'nonacosylic'),
melissic: $this->getString($raw, 'melissic'),
laceric: $this->getString($raw, 'laceric'),
hentriacontylic: $this->getString($raw, 'hentriacontylic'),
hextriacontylic: $this->getString($raw, 'hextriacontylic'),
heptatriacontylic: $this->getString($raw, 'heptatriacontylic'),
octatriacontylic: $this->getString($raw, 'octatriacontylic'),
nonadylie: $this->getString($raw, 'nonadylie'),
psyllic: $this->getString($raw, 'psyllic'),
palmitic: $this->getString($raw, 'palmitic'),
oleic: $this->getString($raw, 'oleic'),
f18d: $this->getString($raw, 'f18d'),
f19d: $this->getString($raw, 'f19d'),
f11eico: $this->getString($raw, 'f11eico'),
geddic: $this->getString($raw, 'geddic'),
erucic: $this->getString($raw, 'erucic'),
nervonic: $this->getString($raw, 'nervonic'),
hyenic: $this->getString($raw, 'hyenic'),
amscis: $this->getString($raw, 'amscis'),
f18d2cn6: $this->getString($raw, 'f18d2cn6'),
f18d3n3: $this->getString($raw, 'f18d3n3'),
f20d4: $this->getString($raw, 'f20d4'),
f20d5n3: $this->getString($raw, 'f20d5n3'),
f22d6n3: $this->getString($raw, 'f22d6n3'),
fapun3: $this->getString($raw, 'fapun3'),
fapun6: $this->getString($raw, 'fapun6'),
fapun9: $this->getString($raw, 'fapun9'),
fapucis: $this->getString($raw, 'fapucis'),
fapulc: $this->getString($raw, 'fapulc'),
);
}
private function aminoAcidsFrom(IncoCompoModel $model): AminoAcids
{
$raw = is_array($model->amino_acids) ? $model->amino_acids : [];
return new AminoAcids(
essentialAminoAcids: $this->getString($raw, 'essentialAminoAcids'),
caseins: $this->getString($raw, 'caseins'),
wheyProtein: $this->getString($raw, 'wheyProtein'),
);
}
private function specialCompoundsFrom(IncoCompoModel $model): SpecialCompounds
{
$raw = is_array($model->special_compounds) ? $model->special_compounds : [];
return new SpecialCompounds(
choline: $this->getString($raw, 'choline'),
carnitine: $this->getString($raw, 'carnitine'),
taurine: $this->getString($raw, 'taurine'),
nucleotides: $this->getString($raw, 'nucleotides'),
);
}
/**
* @param array<mixed> $data
*/
private function getString(array $data, string $key, string $default = '0'): string
{
$val = $data[$key] ?? null;
return is_scalar($val) ? (string) $val : $default;
}
/**
* @param array<mixed> $data
*/
private function getStringWithFallback(array $data, string $key, string $fallbackKey, string $default = '0'): string
{
$val = $data[$key] ?? $data[$fallbackKey] ?? null;
return is_scalar($val) ? (string) $val : $default;
}
/**
* @param array<mixed> $data
*/
private function getNutrient(array $data, string $key, string $default = '0'): NutrientValue
{
return new NutrientValue($this->getString($data, $key, $default));
}
/**
* @param array<mixed> $data
*/
private function getPercentage(array $data, string $key, string $default = '0'): PercentageValue
{
return new PercentageValue($this->getString($data, $key, $default));
}
}

View file

@ -1,410 +0,0 @@
<?php
declare(strict_types=1);
namespace Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoSheet;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Inco\Additive;
use Domain\Models\ValueObjects\Inco\Allegations;
use Domain\Models\ValueObjects\Inco\Allergen;
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
use Domain\Models\ValueObjects\Inco\IncoBrand;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Models\ValueObjects\Inco\IncoWeight;
use Domain\Models\ValueObjects\Inco\Ingredients;
use Domain\Models\ValueObjects\Inco\Nutriscore;
use Domain\Models\ValueObjects\Inco\PortionAmount;
use Domain\Models\ValueObjects\Inco\StorageCondition;
use Domain\Ports\Directories\IncoSheetDirectory;
use Domain\Ports\Repositories\BrandRepository;
use Domain\Services\BrandResolver;
use Infra\Database\Models\IncoSheet as IncoSheetModel;
use Shared\Helpers\DatesHelper;
use Shared\Paginated;
use Shared\Pagination;
final class EloquentIncoSheetDirectory implements IncoSheetDirectory
{
public function __construct(
private BrandResolver $brandResolver,
private BrandRepository $brandRepository,
) {
}
#[\Override]
public function findAll(
Pagination $pagination,
IncoSheetSorter $sorter,
?IncoSheetFilter $filter = null
): Paginated {
$query = IncoSheetModel::query();
if ($filter !== null) {
if ($filter->since !== null) {
$query->where(static function ($q) use ($filter): void {
$q->where('updated_at', '>=', $filter->since)
->orWhere('created_at', '>=', $filter->since);
});
}
if (! empty($filter->brands)) {
$resolvedBrands = $this->resolveBrandFilterCodes(...$filter->brands);
$query->whereIn('brand->code', $resolvedBrands);
}
}
$total = (int) (clone $query)->count();
$rows = (clone $query)
->orderBy(
$this->toSqlColumn($sorter->sortBy),
$sorter->direction
)
->offset($pagination->from())
->limit($pagination->pageSize())
->get();
$items = [];
foreach ($rows as $row) {
$items[] = $this->toDomain($row);
}
/** @var IncoSheet[] $items */
return new Paginated($pagination, $items, $total);
}
#[\Override]
public function search(
IncoIdQuery $query,
Pagination $pagination,
BrandCode ...$brands
): Paginated {
$dbQuery = IncoSheetModel::query();
$dbQuery->where('id', 'LIKE', $query->value);
if (! empty($brands)) {
$resolvedBrands = $this->resolveBrandFilterCodes(...$brands);
$dbQuery->whereIn('brand->code', $resolvedBrands);
}
$total = (int) (clone $dbQuery)->count();
$rows = $dbQuery
->offset($pagination->from())
->limit($pagination->pageSize())
->get();
$items = [];
foreach ($rows as $row) {
$items[] = $this->toDomain($row);
}
/** @var IncoSheet[] $items */
return new Paginated($pagination, $items, $total);
}
#[\Override]
public function find(IncoId $id): ?IncoSheet
{
$model = IncoSheetModel::query()
->where('id', $id->value)
->first();
if ($model === null) {
return null;
}
return $this->toDomain($model);
}
private function toDomain(IncoSheetModel $model): IncoSheet
{
$allegations = $this->allegationsFrom($model);
$ingredients = $this->ingredientsFrom($model);
$portions = $this->portionsFrom($model);
$allergens = $this->allergensFrom($model);
$brand = $this->brandFrom($model);
$weight = $this->weightFrom($model);
$nutriscore = $this->nutriscoreFrom($model);
$storageCondition = $this->storageConditionFrom($model);
$additives = $this->additivesFrom($model);
$compo = $model->compo_id !== null ? new IncoCompositionId((string) $model->compo_id) : null;
return IncoSheet::builder()
->withId(new IncoId((string) $model->id))
->withName((string) $model->name)
->withLabel((string) $model->label)
->withDescription((string) $model->description)
->withAllegations($allegations)
->withSellArguments((string) $model->sell_arguments)
->withPreparationAdvices((string) $model->preparation_advices)
->withTips($this->toNullableString($model->tips))
->withSellFamilly($this->toNullableString($model->sell_familly))
->withIngredients($ingredients)
->withPortions($portions)
->withAllergens(...$allergens)
->withBrand($brand)
->withIsSeasonal((bool) $model->is_seasonal)
->withWeight($weight)
->withNutriscore($nutriscore)
->withStorageCondition($storageCondition)
->withAdditives(...$additives)
->withCompo($compo)
->withCreatedAt($this->toNullableDateTime($model->created_at))
->withUpdatedAt($this->toNullableDateTime($model->updated_at))
->build();
}
private function allegationsFrom(IncoSheetModel $model): Allegations
{
$raw = is_array($model->allegations) ? $model->allegations : [];
return new Allegations(
allegations: $this->getArrayOfStrings($raw, 'allegations'),
health: $this->getArrayOfStrings($raw, 'health'),
nutrition: $this->getArrayOfStrings($raw, 'nutrition'),
other: $this->getArrayOfStrings($raw, 'other'),
);
}
private function ingredientsFrom(IncoSheetModel $model): Ingredients
{
$raw = is_array($model->ingredients) ? $model->ingredients : [];
/** @var array<string, list<string>> $cleanIngredients */
$cleanIngredients = [];
foreach ($raw as $key => $values) {
if (is_string($key) && is_array($values)) {
$cleanIngredients[$key] = array_values(
array_filter($values, static fn ($item): bool => is_string($item))
);
}
}
return new Ingredients($cleanIngredients);
}
private function portionsFrom(IncoSheetModel $model): ?PortionAmount
{
$raw = is_array($model->portions) ? $model->portions : null;
if ($raw === null) {
return null;
}
return new PortionAmount(
lowPart: $this->getInt($raw, 'lowPart'),
highPart: $this->getInt($raw, 'highPart'),
approximative: $this->getBool($raw, 'approximative'),
);
}
/**
* @return array<Allergen>
*/
private function allergensFrom(IncoSheetModel $model): array
{
$raw = is_array($model->allergens) ? $model->allergens : [];
$allergens = [];
foreach ($raw as $a) {
if (is_array($a) && isset($a['code'], $a['label']) && is_numeric($a['code']) && is_string($a['label'])) {
$allergens[] = new Allergen((int) $a['code'], $a['label']);
}
}
return $allergens;
}
private function brandFrom(IncoSheetModel $model): IncoBrand
{
$raw = is_array($model->brand) ? $model->brand : [];
$rawCode = isset($raw['code']) && (is_string($raw['code']) || is_numeric($raw['code']))
? (string) $raw['code']
: '1';
$arcadiaId = new ArcadiaId($rawCode);
$brand = $this->brandResolver->resolve($arcadiaId);
return new IncoBrand(
name: $brand->name,
code: $brand->code,
arcadiaBrandCode: $brand->arcadiaId,
);
}
private function weightFrom(IncoSheetModel $model): ?IncoWeight
{
$raw = is_array($model->weight) ? $model->weight : null;
if ($raw === null) {
return null;
}
return new IncoWeight(
netWeight: $this->getInt($raw, 'netWeight'),
grossWeight: $this->getInt($raw, 'grossWeight'),
theoreticalAverage: $this->getInt($raw, 'theoreticalAverage'),
);
}
private function nutriscoreFrom(IncoSheetModel $model): ?Nutriscore
{
$raw = is_array($model->nutriscore) ? $model->nutriscore : null;
if ($raw === null) {
return null;
}
$grade = $this->getString($raw, 'grade');
if ($grade === '') {
return null;
}
return new Nutriscore($grade);
}
private function storageConditionFrom(IncoSheetModel $model): ?StorageCondition
{
$raw = is_array($model->storage_condition) ? $model->storage_condition : null;
if ($raw === null) {
return null;
}
$instructions = $this->getString($raw, 'instructions');
if ($instructions === '') {
return null;
}
return new StorageCondition($instructions);
}
/**
* @return array<Additive>
*/
private function additivesFrom(IncoSheetModel $model): array
{
$raw = is_array($model->additives) ? $model->additives : [];
$additives = [];
foreach ($raw as $a) {
if (is_array($a) && isset($a['code'], $a['label']) && is_string($a['code']) && is_string($a['label'])) {
$additives[] = new Additive($a['code'], $a['label']);
}
}
return $additives;
}
/**
* @return array<int|string>
*/
private function resolveBrandFilterCodes(BrandCode ...$brandCodes): array
{
$resolved = [];
foreach ($brandCodes as $brandCode) {
$brand = $this->brandRepository->findByCode($brandCode);
if ($brand !== null) {
$resolved[] = (int) $brand->arcadiaId->value;
$resolved[] = $brand->arcadiaId->value;
}
}
if (empty($resolved) && ! empty($brandCodes)) {
return [0];
}
$unique = [];
foreach ($resolved as $item) {
if (! in_array($item, $unique, true)) {
$unique[] = $item;
}
}
return $unique;
}
private function toSqlColumn(IncoSheetSortColumn $column): string
{
return match ($column) {
IncoSheetSortColumn::incoSheetId => 'id',
IncoSheetSortColumn::createdAt => 'created_at',
IncoSheetSortColumn::updatedAt => 'updated_at',
};
}
/**
* @param array<array-key, mixed> $data
* @return array<string>
*/
private function getArrayOfStrings(array $data, string $key): array
{
if (! isset($data[$key]) || ! is_array($data[$key])) {
return [];
}
return array_values(
array_filter($data[$key], static fn ($item): bool => is_string($item))
);
}
/**
* @param array<array-key, mixed> $data
*/
private function getString(array $data, string $key, string $default = ''): string
{
return isset($data[$key]) && is_string($data[$key]) ? $data[$key] : $default;
}
/**
* @param array<array-key, mixed> $data
*/
private function getInt(array $data, string $key, int $default = 0): int
{
return isset($data[$key]) && is_numeric($data[$key]) ? (int) $data[$key] : $default;
}
/**
* @param array<array-key, mixed> $data
*/
private function getBool(array $data, string $key, bool $default = false): bool
{
return isset($data[$key]) ? (bool) $data[$key] : $default;
}
private function toNullableString(mixed $value): ?string
{
return is_string($value) && trim($value) !== '' ? $value : null;
}
private function toNullableDateTime(mixed $value): ?\DateTimeInterface
{
if ($value instanceof \DateTimeInterface) {
return $value;
}
if (is_string($value)) {
return DatesHelper::parseDateTime($value);
}
return null;
}
}

View file

@ -9,10 +9,9 @@ use Application\Arcadia\Config\ThrottlingConfig;
use Application\Health\Contracts\HealthChecker;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request as IlluminateRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Infra\Http\Helpers\RequestHelper;
@ -77,11 +76,11 @@ class AppServiceProvider extends ServiceProvider
protected function configureRateLimiting(ThrottlingConfig $throttlingConfig): void
{
RateLimiter::for('web', function (IlluminateRequest $request) use ($throttlingConfig) {
RateLimiter::for('web', function (Request $request) use ($throttlingConfig) {
return Limit::perMinute($throttlingConfig->webLimit->toInt())->by($request->ip() ?? '127.0.0.1');
});
RateLimiter::for('api.v1', function (IlluminateRequest $request) use ($throttlingConfig) {
RateLimiter::for('api.v1', function (Request $request) use ($throttlingConfig) {
return Limit::perMinute($throttlingConfig->apiV1Limit->toInt())->by($request->ip() ?? '127.0.0.1');
});
}

View file

@ -12,15 +12,18 @@ use Domain\Ports\Directories\IncoSheetDirectory;
use Domain\Ports\Directories\IncoShortSheetDirectory;
use Domain\Ports\Repositories\BrandRepository;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
use Infra\Adapters\Directories\EloquentArcadiaBrandDirectory;
use Infra\Adapters\Directories\ArcadiaEmployeeDirectory;
use Infra\Adapters\Directories\ArcadiaIncoCompositionDirectory;
use Infra\Adapters\Directories\ArcadiaIncoSheetDirectory;
use Infra\Adapters\Directories\ArcadiaIncoShortSheetDirectory;
use Infra\Adapters\Directories\ArcadiaMarqueBrandDirectory;
use Infra\Adapters\Directories\EloquentClientAuditLogDirectory;
use Infra\Adapters\Directories\EloquentEmployeeDirectory;
use Infra\Adapters\Directories\EloquentIncoCompositionDirectory;
use Infra\Adapters\Directories\EloquentIncoSheetDirectory;
use Infra\Adapters\Directories\EloquentIncoShortSheetDirectory;
use Infra\Adapters\Events\LaravelTransactionalDomainEventDispatcher;
use Infra\Adapters\Mappers\IncoMapper;
use Infra\Adapters\Repositories\EloquentBrandRepository;
use Infra\Cache\BrandCache;
use Infra\Config\ArcadiaStorageConfig;
@ -35,11 +38,35 @@ class DomainServiceProvider extends ServiceProvider
#[\Override]
public function register(): void
{
$this->app->bind(EmployeeDirectory::class, EloquentEmployeeDirectory::class);
$this->app->bind(IncoSheetDirectory::class, EloquentIncoSheetDirectory::class);
$this->app->bind(IncoShortSheetDirectory::class, EloquentIncoShortSheetDirectory::class);
$this->app->bind(IncoCompositionDirectory::class, EloquentIncoCompositionDirectory::class);
$this->app->scoped(ArcadiaBrandDirectory::class, EloquentArcadiaBrandDirectory::class);
$this->app->bind('arcadiaConnection', static function (Application $app): Connection {
$storageConfig = $app->make(ArcadiaStorageConfig::class);
return DB::connection($storageConfig->databaseConnection);
});
$this->app->bind(
EmployeeDirectory::class,
fn (Application $app) => new ArcadiaEmployeeDirectory($app->make('arcadiaConnection'))
);
$this->app->bind(
IncoSheetDirectory::class,
fn (Application $app) => new ArcadiaIncoSheetDirectory(
$app->make('arcadiaConnection'),
$app->make(BrandRepository::class),
$app->make(IncoMapper::class)
)
);
$this->app->bind(
IncoShortSheetDirectory::class,
fn (Application $app) => new ArcadiaIncoShortSheetDirectory($app->make('arcadiaConnection'), $app->make(BrandRepository::class))
);
$this->app->bind(
IncoCompositionDirectory::class,
fn (Application $app) => new ArcadiaIncoCompositionDirectory($app->make('arcadiaConnection'))
);
$this->app->bind(
ArcadiaBrandDirectory::class,
fn (Application $app) => new ArcadiaMarqueBrandDirectory($app->make('arcadiaConnection'))
);
$this->app->bind(BrandRepository::class, EloquentBrandRepository::class);
$this->app->bind(ClientAuditLogDirectory::class, EloquentClientAuditLogDirectory::class);

View file

@ -19,14 +19,6 @@ describe('Infra Architecture Rules', function (): void {
->expect('Infra\Database\Models')
->toBeFinal();
arch('Infra adapters implement Domain Ports')
->expect('Infra\Adapters\Directories\EloquentEmployeeDirectory')
->toImplement('Domain\Ports\Directories\EmployeeDirectory');
arch('Infra EloquentEmployeeRepository implements Domain EmployeeRepository port')
->expect('Infra\Adapters\Repositories\EloquentEmployeeRepository')
->toImplement('Domain\Ports\Repositories\EmployeeRepository');
arch('Infra layer uses strict types')
->expect('Infra')
->toUseStrictTypes();

View file

@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Database\Seeders;
use Database\Seeders\EmployeeSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Infra\Database\Models\Employee;
use Tests\TestCase;
covers(Employee::class);
uses(TestCase::class, RefreshDatabase::class);
describe('EmployeeSeeder Integration Test', function (): void {
it('seeds default employee records into the database', function (): void {
// GIVEN
$seeder = new EmployeeSeeder();
// WHEN
$seeder->run();
// THEN
expect(Employee::count())->toBe(4);
});
});

View file

@ -1,98 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Adapters\Directories;
use Domain\Models\ValueObjects\Brand\ArcadiaBrand as ArcadiaBrandDomain;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Infra\Adapters\Directories\EloquentArcadiaBrandDirectory;
use Infra\Database\Models\ArcadiaBrand;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
covers(EloquentArcadiaBrandDirectory::class);
describe('EloquentArcadiaBrandDirectory Integration Tests', function (): void {
beforeEach(function (): void {
ArcadiaBrand::query()->delete();
ArcadiaBrand::query()->create(['code' => 1, 'libelle' => 'Le Gaulois']);
ArcadiaBrand::query()->create(['code' => 2, 'libelle' => 'Maître CoQ']);
});
it('finds an arcadia brand by ArcadiaId and memoizes the result in memory', function (): void {
// GIVEN
$directory = new EloquentArcadiaBrandDirectory();
$arcadiaId = new ArcadiaId('1');
// WHEN: First lookup (hits database)
$first = $directory->find($arcadiaId);
// THEN
expect($first)->toBeInstanceOf(ArcadiaBrandDomain::class)
->and($first?->id->value)->toBe('1')
->and($first?->name->value)->toBe('Le Gaulois');
// WHEN: Second lookup - DB query count should not increase because of in-memory caching
$queryCount = 0;
DB::listen(function () use (&$queryCount): void {
$queryCount++;
});
$second = $directory->find($arcadiaId);
// THEN: Second call returns the exact memoized entity with 0 extra queries
expect($second)->toBe($first)
->and($queryCount)->toBe(0);
});
it('returns null when arcadia brand is not found and caches the null result', function (): void {
// GIVEN
$directory = new EloquentArcadiaBrandDirectory();
$unknownId = new ArcadiaId('999');
// WHEN: First lookup for non-existent brand
$first = $directory->find($unknownId);
// THEN
expect($first)->toBeNull();
// WHEN: Second lookup
$queryCount = 0;
DB::listen(function () use (&$queryCount): void {
$queryCount++;
});
$second = $directory->find($unknownId);
// THEN
expect($second)->toBeNull()
->and($queryCount)->toBe(0);
});
it('finds all arcadia brands and populates the cache', function (): void {
// GIVEN
$directory = new EloquentArcadiaBrandDirectory();
// WHEN
$all = $directory->findAll();
// THEN
expect($all)->toHaveCount(2);
// WHEN: Calling find() on cached items
$queryCount = 0;
DB::listen(function () use (&$queryCount): void {
$queryCount++;
});
$brand1 = $directory->find(new ArcadiaId('1'));
// THEN
expect($brand1)->not->toBeNull()
->and($brand1?->name->value)->toBe('Le Gaulois')
->and($queryCount)->toBe(0);
});
});

View file

@ -1,186 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Adapters\Directories;
use Domain\Models\Entities\Employee;
use Domain\Models\ValueObjects\Employees\ArcadiaEmployeeId;
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\Testing\RefreshDatabase;
use Infra\Adapters\Directories\EloquentEmployeeDirectory;
use Infra\Database\Models\Employee as EmployeeModel;
use Shared\Paginated;
use Shared\Pagination;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
covers(EloquentEmployeeDirectory::class);
beforeEach(function (): void {
EmployeeModel::query()->delete();
});
describe('EloquentEmployeeDirectory Integration Tests', function (): void {
it('finds an employee by public employee number and maps to domain entity', function (): void {
// GIVEN
$directory = new EloquentEmployeeDirectory();
EmployeeModel::factory()->create([
'public_employee_number' => '1234567',
'last_name' => 'DUPONT',
'first_name' => 'Jean',
'start_date' => '2024-01-01',
'end_date' => '2025-01-01',
'planned_end_date' => '2025-06-01',
'establishment' => '01',
'site' => 'LDCSER',
'service' => '097',
'created_at' => '2024-01-01 10:00:00',
'updated_at' => '2024-01-02 12:00:00',
]);
$employeeId = new ArcadiaEmployeeId('1234567');
// WHEN
$result = $directory->find($employeeId);
// THEN
expect($result)->toBeInstanceOf(Employee::class)
->and($result?->id()->value)->toBe('1234567')
->and($result?->lastName)->toBe('DUPONT')
->and($result?->firstName)->toBe('Jean')
->and($result?->startDate->format('Y-m-d'))->toBe('2024-01-01')
->and($result?->endDate?->format('Y-m-d'))->toBe('2025-01-01')
->and($result?->plannedEndDate?->format('Y-m-d'))->toBe('2025-06-01')
->and($result?->establishment->value)->toBe('01')
->and($result?->site->value)->toBe('LDCSER')
->and($result?->service->value)->toBe('097')
->and($result?->createdAt->format('Y-m-d H:i:s'))->toBe('2024-01-01 10:00:00')
->and($result?->updatedAt?->format('Y-m-d H:i:s'))->toBe('2024-01-02 12:00:00');
});
it('returns null when employee is not found', function (): void {
// GIVEN
$directory = new EloquentEmployeeDirectory();
$employeeId = new ArcadiaEmployeeId('9999999');
// WHEN
$result = $directory->find($employeeId);
// THEN
expect($result)->toBeNull();
});
it('throws UnexpectedValueException when created_at is null', function (): void {
// GIVEN
$directory = new EloquentEmployeeDirectory();
EmployeeModel::query()->insert([
'public_employee_number' => '7654321',
'last_name' => 'MARTIN',
'first_name' => 'Alice',
'start_date' => '2024-01-01',
'establishment' => '01',
'site' => 'LDCSER',
'service' => '097',
'created_at' => null,
'updated_at' => null,
]);
$employeeId = new ArcadiaEmployeeId('7654321');
// WHEN & THEN
expect(fn () => $directory->find($employeeId))
->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'),
sites: [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();
EmployeeModel::factory()->create([
'public_employee_number' => '1000001',
'start_date' => '2024-01-01',
'end_date' => '2024-06-01',
'planned_end_date' => '2024-12-01',
'created_at' => '2024-01-01 08:00:00',
'updated_at' => '2024-01-01 09:00:00',
]);
EmployeeModel::factory()->create([
'public_employee_number' => '1000002',
'start_date' => '2024-02-01',
'end_date' => '2024-07-01',
'planned_end_date' => '2024-12-15',
'created_at' => '2024-02-01 08:00:00',
'updated_at' => '2024-02-01 09:00:00',
]);
$pagination = Pagination::ofPage(1, 10);
$sorter = new EmployeeSorter($sortColumn, $direction);
// WHEN
$result = $directory->findAll($pagination, $sorter);
// THEN
expect($result)->toBeInstanceOf(Paginated::class)
->and($result->getTotal())->toBe(2)
->and(count($result->getItems()))->toBe(2);
})->with([
'arcadiaId asc' => [EmployeeSortColumn::arcadiaId, \SortDirection::Ascending],
'arcadiaId desc' => [EmployeeSortColumn::arcadiaId, \SortDirection::Descending],
'startDate asc' => [EmployeeSortColumn::startDate, \SortDirection::Ascending],
'endDate desc' => [EmployeeSortColumn::endDate, \SortDirection::Descending],
'plannedEndDate asc' => [EmployeeSortColumn::plannedEndDate, \SortDirection::Ascending],
'createdAt desc' => [EmployeeSortColumn::createdAt, \SortDirection::Descending],
'updatedAt asc' => [EmployeeSortColumn::updatedAt, \SortDirection::Ascending],
]);
});

View file

@ -1,160 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoSheet;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Ports\Repositories\BrandRepository;
use Domain\Services\BrandResolver;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Infra\Adapters\Directories\EloquentIncoSheetDirectory;
use Infra\Database\Models\ArcadiaBrand;
use Infra\Database\Models\IncoSheet as IncoSheetModel;
use Shared\Pagination;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
covers(EloquentIncoSheetDirectory::class);
describe('EloquentIncoSheetDirectory Integration Tests', function (): void {
beforeEach(function (): void {
IncoSheetModel::query()->delete();
ArcadiaBrand::query()->firstOrCreate(['code' => 1], ['libelle' => 'Le Gaulois']);
ArcadiaBrand::query()->firstOrCreate(['code' => 2], ['libelle' => 'Maître CoQ']);
});
it('finds an inco sheet by IncoId and resolves its IncoBrand', function (): void {
// GIVEN
$resolver = app(BrandResolver::class);
$repository = app(BrandRepository::class);
$directory = new EloquentIncoSheetDirectory($resolver, $repository);
IncoSheetModel::factory()->create([
'id' => '1000101',
'name' => 'Poulet Rôti',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
$incoId = new IncoId('1000101');
// WHEN
$result = $directory->find($incoId);
// THEN
expect($result)->toBeInstanceOf(IncoSheet::class)
->and((string) $result?->id->value)->toBe('1000101')
->and($result?->name)->toBe('Poulet Rôti')
->and($result?->brand->name->value)->toBe('Le Gaulois')
->and($result?->brand->arcadiaBrandCode->value)->toBe('1')
->and($result?->brand->code->value)->toMatch('/^[A-Z]{3}\d{2}$/');
});
it('returns null when inco sheet is not found', function (): void {
// GIVEN
$resolver = app(BrandResolver::class);
$repository = app(BrandRepository::class);
$directory = new EloquentIncoSheetDirectory($resolver, $repository);
// WHEN
$result = $directory->find(new IncoId('9999999'));
// THEN
expect($result)->toBeNull();
});
it('finds all inco sheets filtered by BrandCode', function (): void {
// GIVEN
$resolver = app(BrandResolver::class);
$repository = app(BrandRepository::class);
$directory = new EloquentIncoSheetDirectory($resolver, $repository);
$brand1 = $resolver->resolve(new ArcadiaId('1'));
$brand2 = $resolver->resolve(new ArcadiaId('2'));
IncoSheetModel::factory()->create([
'id' => '1000101',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
IncoSheetModel::factory()->create([
'id' => '1000102',
'brand' => ['code' => 2, 'label' => 'Maître CoQ'],
]);
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Ascending);
$filter = new IncoSheetFilter(null, $brand1->code);
// WHEN
$result = $directory->findAll($pagination, $sorter, $filter);
// THEN
expect($result->getTotal())->toBe(1)
->and($result->getItems())->toHaveCount(1)
->and((string) $result->getItems()[0]->id->value)->toBe('1000101');
});
it('searches inco sheets by id query and BrandCode', function (): void {
// GIVEN
$resolver = app(BrandResolver::class);
$repository = app(BrandRepository::class);
$directory = new EloquentIncoSheetDirectory($resolver, $repository);
$brand1 = $resolver->resolve(new ArcadiaId('1'));
IncoSheetModel::factory()->create([
'id' => '1234501',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
IncoSheetModel::factory()->create([
'id' => '1234502',
'brand' => ['code' => 2, 'label' => 'Maître CoQ'],
]);
IncoSheetModel::factory()->create([
'id' => '9999901',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
$query = new IncoIdQuery('1234%');
$pagination = Pagination::ofPage(1, 10);
// WHEN
$result = $directory->search($query, $pagination, $brand1->code);
// THEN
expect($result->getTotal())->toBe(1)
->and($result->getItems())->toHaveCount(1)
->and((string) $result->getItems()[0]->id->value)->toBe('1234501');
});
it('returns empty paginated result when filtering by non-existent BrandCode', function (): void {
// GIVEN
$resolver = app(BrandResolver::class);
$repository = app(BrandRepository::class);
$directory = new EloquentIncoSheetDirectory($resolver, $repository);
IncoSheetModel::factory()->create([
'id' => '1000101',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
$unknownCode = new BrandCode('ZZZ01');
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Ascending);
$filter = new IncoSheetFilter(null, $unknownCode);
// WHEN
$result = $directory->findAll($pagination, $sorter, $filter);
// THEN
expect($result->getTotal())->toBe(0)
->and($result->getItems())->toBeEmpty();
});
});

View file

@ -1,148 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Adapters\Directories;
use Domain\Models\Entities\Inco\IncoShortSheet;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Inco\IncoId;
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
use Domain\Ports\Repositories\BrandRepository;
use Domain\Services\BrandResolver;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Infra\Adapters\Directories\EloquentIncoShortSheetDirectory;
use Infra\Database\Models\ArcadiaBrand;
use Infra\Database\Models\IncoSheet as IncoSheetModel;
use Shared\Pagination;
use Tests\TestCase;
uses(TestCase::class, RefreshDatabase::class);
covers(EloquentIncoShortSheetDirectory::class);
describe('EloquentIncoShortSheetDirectory Integration Tests', function (): void {
beforeEach(function (): void {
IncoSheetModel::query()->delete();
ArcadiaBrand::query()->firstOrCreate(['code' => 1], ['libelle' => 'Le Gaulois']);
ArcadiaBrand::query()->firstOrCreate(['code' => 2], ['libelle' => 'Maître CoQ']);
});
it('finds an inco short sheet by IncoId', function (): void {
// GIVEN
$repository = app(BrandRepository::class);
$directory = new EloquentIncoShortSheetDirectory($repository);
IncoSheetModel::factory()->create([
'id' => '1000101',
'name' => 'Poulet Rôti Court',
]);
$incoId = new IncoId('1000101');
// WHEN
$result = $directory->find($incoId);
// THEN
expect($result)->toBeInstanceOf(IncoShortSheet::class)
->and((string) $result?->id->value)->toBe('1000101')
->and($result?->name)->toBe('Poulet Rôti Court');
});
it('returns null when inco short sheet is not found', function (): void {
// GIVEN
$repository = app(BrandRepository::class);
$directory = new EloquentIncoShortSheetDirectory($repository);
// WHEN
$result = $directory->find(new IncoId('9999999'));
// THEN
expect($result)->toBeNull();
});
it('finds all inco short sheets filtered by BrandCode', function (): void {
// GIVEN
$resolver = app(BrandResolver::class);
$repository = app(BrandRepository::class);
$directory = new EloquentIncoShortSheetDirectory($repository);
$brand1 = $resolver->resolve(new ArcadiaId('1'));
IncoSheetModel::factory()->create([
'id' => '1000101',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
IncoSheetModel::factory()->create([
'id' => '1000102',
'brand' => ['code' => 2, 'label' => 'Maître CoQ'],
]);
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Ascending);
$filter = new IncoSheetFilter(null, $brand1->code);
// WHEN
$result = $directory->findAll($pagination, $sorter, $filter);
// THEN
expect($result->getTotal())->toBe(1)
->and($result->getItems())->toHaveCount(1)
->and((string) $result->getItems()[0]->id->value)->toBe('1000101');
});
it('searches inco short sheets by id query and BrandCode', function (): void {
// GIVEN
$resolver = app(BrandResolver::class);
$repository = app(BrandRepository::class);
$directory = new EloquentIncoShortSheetDirectory($repository);
$brand1 = $resolver->resolve(new ArcadiaId('1'));
IncoSheetModel::factory()->create([
'id' => '1234501',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
IncoSheetModel::factory()->create([
'id' => '1234502',
'brand' => ['code' => 2, 'label' => 'Maître CoQ'],
]);
$query = new IncoIdQuery('1234%');
$pagination = Pagination::ofPage(1, 10);
// WHEN
$result = $directory->search($query, $pagination, $brand1->code);
// THEN
expect($result->getTotal())->toBe(1)
->and($result->getItems())->toHaveCount(1)
->and((string) $result->getItems()[0]->id->value)->toBe('1234501');
});
it('returns empty paginated short sheets when filtering by non-existent BrandCode', function (): void {
// GIVEN
$repository = app(BrandRepository::class);
$directory = new EloquentIncoShortSheetDirectory($repository);
IncoSheetModel::factory()->create([
'id' => '1000101',
'brand' => ['code' => 1, 'label' => 'Le Gaulois'],
]);
$unknownCode = new BrandCode('ZZZ01');
$pagination = Pagination::ofPage(1, 10);
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Ascending);
$filter = new IncoSheetFilter(null, $unknownCode);
// WHEN
$result = $directory->findAll($pagination, $sorter, $filter);
// THEN
expect($result->getTotal())->toBe(0)
->and($result->getItems())->toBeEmpty();
});
});

View file

@ -13,7 +13,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Infra\Adapters\Repositories\EloquentBrandRepository;
use Infra\Cache\BrandCache;
use Infra\Database\Models\ArcadiaBrand;
use Infra\Database\Models\Brand as BrandModel;
use Ramsey\Uuid\Uuid;
use Tests\TestCase;
@ -24,7 +23,6 @@ covers(EloquentBrandRepository::class);
beforeEach(function (): void {
Cache::flush();
BrandModel::query()->delete();
ArcadiaBrand::query()->delete();
});
describe('EloquentBrandRepository Integration Tests', function (): void {

View file

@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Database\Models;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Infra\Database\Models\Employee;
use Tests\TestCase;
covers(Employee::class);
uses(TestCase::class, RefreshDatabase::class);
describe('Employee Eloquent Model & Factory', function (): void {
it('creates and persists an Employee record via factory', function (): void {
// GIVEN
$employee = Employee::factory()->create();
// WHEN
$retrieved = Employee::find($employee->public_employee_number);
// THEN
expect($retrieved)->not->toBeNull()
->and($retrieved->public_employee_number)->toBe($employee->public_employee_number);
});
});

View file

@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Database\Models;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Infra\Database\Models\IncoCompo;
use Tests\TestCase;
covers(IncoCompo::class);
uses(TestCase::class, RefreshDatabase::class);
describe('IncoCompo Eloquent Model & Factory', function (): void {
it('creates and persists an IncoCompo record via factory', function (): void {
// GIVEN
$compo = IncoCompo::factory()->create();
// WHEN
$retrieved = IncoCompo::find($compo->id);
// THEN
expect($retrieved)->not->toBeNull()
->and($retrieved->id)->toBe($compo->id);
});
});

View file

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Integration\Infra\Database\Models;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Infra\Database\Models\IncoSheet;
use Tests\TestCase;
covers(IncoSheet::class);
uses(TestCase::class, RefreshDatabase::class);
describe('IncoSheet Eloquent Model & Factory', function (): void {
it('creates and persists an IncoSheet record via factory', function (): void {
// GIVEN
$sheet = IncoSheet::factory()->create([
'name' => 'Poulet Rôti 1kg Test',
]);
// WHEN
$retrieved = IncoSheet::find($sheet->id);
// THEN
expect($retrieved)->not->toBeNull()
->and($retrieved->id)->toBe($sheet->id)
->and($retrieved->name)->toBe('Poulet Rôti 1kg Test');
});
});