diff --git a/src/Infra/Adapters/Directories/ArcadiaEmployeeDirectory.php b/src/Infra/Adapters/Directories/ArcadiaEmployeeDirectory.php new file mode 100644 index 0000000..9aed126 --- /dev/null +++ b/src/Infra/Adapters/Directories/ArcadiaEmployeeDirectory.php @@ -0,0 +1,104 @@ +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', + }; + } +} diff --git a/src/Infra/Adapters/Directories/ArcadiaIncoCompositionDirectory.php b/src/Infra/Adapters/Directories/ArcadiaIncoCompositionDirectory.php new file mode 100644 index 0000000..c52b8e0 --- /dev/null +++ b/src/Infra/Adapters/Directories/ArcadiaIncoCompositionDirectory.php @@ -0,0 +1,50 @@ +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); + } +} diff --git a/src/Infra/Adapters/Directories/ArcadiaIncoSheetDirectory.php b/src/Infra/Adapters/Directories/ArcadiaIncoSheetDirectory.php new file mode 100644 index 0000000..02593f1 --- /dev/null +++ b/src/Infra/Adapters/Directories/ArcadiaIncoSheetDirectory.php @@ -0,0 +1,166 @@ +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 + */ + 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', + }; + } +} diff --git a/src/Infra/Adapters/Directories/EloquentIncoShortSheetDirectory.php b/src/Infra/Adapters/Directories/ArcadiaIncoShortSheetDirectory.php similarity index 58% rename from src/Infra/Adapters/Directories/EloquentIncoShortSheetDirectory.php rename to src/Infra/Adapters/Directories/ArcadiaIncoShortSheetDirectory.php index fac2e34..5b1241c 100644 --- a/src/Infra/Adapters/Directories/EloquentIncoShortSheetDirectory.php +++ b/src/Infra/Adapters/Directories/ArcadiaIncoShortSheetDirectory.php @@ -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 */ - 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', }; } } diff --git a/src/Infra/Adapters/Directories/EloquentArcadiaBrandDirectory.php b/src/Infra/Adapters/Directories/ArcadiaMarqueBrandDirectory.php similarity index 50% rename from src/Infra/Adapters/Directories/EloquentArcadiaBrandDirectory.php rename to src/Infra/Adapters/Directories/ArcadiaMarqueBrandDirectory.php index 8f99db8..846753b 100644 --- a/src/Infra/Adapters/Directories/EloquentArcadiaBrandDirectory.php +++ b/src/Infra/Adapters/Directories/ArcadiaMarqueBrandDirectory.php @@ -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 */ 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), - ); - } } diff --git a/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php b/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php deleted file mode 100644 index cf17252..0000000 --- a/src/Infra/Adapters/Directories/EloquentEmployeeDirectory.php +++ /dev/null @@ -1,120 +0,0 @@ -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 $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', - }; - } -} diff --git a/src/Infra/Adapters/Directories/EloquentIncoCompositionDirectory.php b/src/Infra/Adapters/Directories/EloquentIncoCompositionDirectory.php deleted file mode 100644 index 0da6a28..0000000 --- a/src/Infra/Adapters/Directories/EloquentIncoCompositionDirectory.php +++ /dev/null @@ -1,392 +0,0 @@ -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 $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 $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 $data - */ - private function getNutrient(array $data, string $key, string $default = '0'): NutrientValue - { - return new NutrientValue($this->getString($data, $key, $default)); - } - - /** - * @param array $data - */ - private function getPercentage(array $data, string $key, string $default = '0'): PercentageValue - { - return new PercentageValue($this->getString($data, $key, $default)); - } -} diff --git a/src/Infra/Adapters/Directories/EloquentIncoSheetDirectory.php b/src/Infra/Adapters/Directories/EloquentIncoSheetDirectory.php deleted file mode 100644 index 8b9387d..0000000 --- a/src/Infra/Adapters/Directories/EloquentIncoSheetDirectory.php +++ /dev/null @@ -1,410 +0,0 @@ -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> $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 - */ - 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 - */ - 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 - */ - 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 $data - * @return array - */ - 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 $data - */ - private function getString(array $data, string $key, string $default = ''): string - { - return isset($data[$key]) && is_string($data[$key]) ? $data[$key] : $default; - } - - /** - * @param array $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 $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; - } -} diff --git a/src/Infra/Providers/AppServiceProvider.php b/src/Infra/Providers/AppServiceProvider.php index f9e75e9..71a1299 100644 --- a/src/Infra/Providers/AppServiceProvider.php +++ b/src/Infra/Providers/AppServiceProvider.php @@ -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'); }); } diff --git a/src/Infra/Providers/DomainServiceProvider.php b/src/Infra/Providers/DomainServiceProvider.php index c913975..a90d89b 100644 --- a/src/Infra/Providers/DomainServiceProvider.php +++ b/src/Infra/Providers/DomainServiceProvider.php @@ -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); diff --git a/tests/Architecture/InfraTest.php b/tests/Architecture/InfraTest.php index 2b26705..1b7616f 100644 --- a/tests/Architecture/InfraTest.php +++ b/tests/Architecture/InfraTest.php @@ -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(); diff --git a/tests/Integration/Database/Seeders/EmployeeSeederTest.php b/tests/Integration/Database/Seeders/EmployeeSeederTest.php deleted file mode 100644 index 03af7d1..0000000 --- a/tests/Integration/Database/Seeders/EmployeeSeederTest.php +++ /dev/null @@ -1,27 +0,0 @@ -run(); - - // THEN - expect(Employee::count())->toBe(4); - }); -}); diff --git a/tests/Integration/Infra/Adapters/Directories/EloquentArcadiaBrandDirectoryTest.php b/tests/Integration/Infra/Adapters/Directories/EloquentArcadiaBrandDirectoryTest.php deleted file mode 100644 index a892b3b..0000000 --- a/tests/Integration/Infra/Adapters/Directories/EloquentArcadiaBrandDirectoryTest.php +++ /dev/null @@ -1,98 +0,0 @@ -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); - }); -}); diff --git a/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php b/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php deleted file mode 100644 index 96b957f..0000000 --- a/tests/Integration/Infra/Adapters/Directories/EloquentEmployeeDirectoryTest.php +++ /dev/null @@ -1,186 +0,0 @@ -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], - ]); -}); diff --git a/tests/Integration/Infra/Adapters/Directories/EloquentIncoSheetDirectoryTest.php b/tests/Integration/Infra/Adapters/Directories/EloquentIncoSheetDirectoryTest.php deleted file mode 100644 index ccb4783..0000000 --- a/tests/Integration/Infra/Adapters/Directories/EloquentIncoSheetDirectoryTest.php +++ /dev/null @@ -1,160 +0,0 @@ -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(); - }); -}); diff --git a/tests/Integration/Infra/Adapters/Directories/EloquentIncoShortSheetDirectoryTest.php b/tests/Integration/Infra/Adapters/Directories/EloquentIncoShortSheetDirectoryTest.php deleted file mode 100644 index 2030cd0..0000000 --- a/tests/Integration/Infra/Adapters/Directories/EloquentIncoShortSheetDirectoryTest.php +++ /dev/null @@ -1,148 +0,0 @@ -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(); - }); -}); diff --git a/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php b/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php index f161d09..34a8755 100644 --- a/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php +++ b/tests/Integration/Infra/Adapters/Repositories/EloquentBrandRepositoryTest.php @@ -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 { diff --git a/tests/Integration/Infra/Database/Models/EmployeeModelTest.php b/tests/Integration/Infra/Database/Models/EmployeeModelTest.php deleted file mode 100644 index e782b7c..0000000 --- a/tests/Integration/Infra/Database/Models/EmployeeModelTest.php +++ /dev/null @@ -1,27 +0,0 @@ -create(); - - // WHEN - $retrieved = Employee::find($employee->public_employee_number); - - // THEN - expect($retrieved)->not->toBeNull() - ->and($retrieved->public_employee_number)->toBe($employee->public_employee_number); - }); -}); diff --git a/tests/Integration/Infra/Database/Models/IncoCompoModelTest.php b/tests/Integration/Infra/Database/Models/IncoCompoModelTest.php deleted file mode 100644 index 9df0c4c..0000000 --- a/tests/Integration/Infra/Database/Models/IncoCompoModelTest.php +++ /dev/null @@ -1,27 +0,0 @@ -create(); - - // WHEN - $retrieved = IncoCompo::find($compo->id); - - // THEN - expect($retrieved)->not->toBeNull() - ->and($retrieved->id)->toBe($compo->id); - }); -}); diff --git a/tests/Integration/Infra/Database/Models/IncoSheetModelTest.php b/tests/Integration/Infra/Database/Models/IncoSheetModelTest.php deleted file mode 100644 index de94bfe..0000000 --- a/tests/Integration/Infra/Database/Models/IncoSheetModelTest.php +++ /dev/null @@ -1,30 +0,0 @@ -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'); - }); -});