feat(inco): migrate to IncoBrand VO, BrandResolver domain service and BrandCode filtering
This commit is contained in:
parent
2617d41ef3
commit
8f933db9fa
38 changed files with 589 additions and 352 deletions
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
use Infra\Database\Models\IncoSheet;
|
||||
|
||||
/**
|
||||
|
|
@ -47,7 +48,7 @@ final class IncoSheetFactory extends Factory
|
|||
['code' => 1, 'label' => 'Gluten'],
|
||||
],
|
||||
'brand' => [
|
||||
'code' => 'M01',
|
||||
'code' => 1,
|
||||
'label' => 'Le Gaulois',
|
||||
],
|
||||
'is_seasonal' => false,
|
||||
|
|
@ -68,4 +69,19 @@ final class IncoSheetFactory extends Factory
|
|||
'compo_id' => (string) fake()->numberBetween(10000, 99999),
|
||||
];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function configure(): static
|
||||
{
|
||||
return $this->afterCreating(function (IncoSheet $sheet): void {
|
||||
$raw = is_array($sheet->brand) ? $sheet->brand : [];
|
||||
$code = isset($raw['code']) && is_numeric($raw['code']) ? (int) $raw['code'] : 1;
|
||||
$label = isset($raw['label']) && is_string($raw['label']) ? $raw['label'] : (string) ($raw['libelle'] ?? 'Le Gaulois');
|
||||
|
||||
ArcadiaBrand::query()->firstOrCreate(
|
||||
['code' => $code],
|
||||
['libelle' => $label]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ http:
|
|||
description: Filter INCO sheets updated since timestamp (RFC3339)
|
||||
disabled: true
|
||||
- name: brands[]
|
||||
value: M01
|
||||
value: LGL01
|
||||
type: query
|
||||
description: Filter by brand code
|
||||
disabled: true
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ http:
|
|||
type: query
|
||||
description: Pattern used with SQL-like matching rules
|
||||
- name: brands[]
|
||||
value: M01
|
||||
value: LGL01
|
||||
type: query
|
||||
description: Filter by brand code
|
||||
disabled: true
|
||||
|
|
|
|||
|
|
@ -529,7 +529,8 @@ components:
|
|||
minItems: 1
|
||||
items:
|
||||
type: string
|
||||
example: ["M01", "M02"]
|
||||
pattern: '^[A-Z]{3}\d{2}$'
|
||||
example: ["LGL01", "MCQ01"]
|
||||
|
||||
ShortProjection:
|
||||
name: short
|
||||
|
|
@ -859,10 +860,11 @@ components:
|
|||
properties:
|
||||
code:
|
||||
type: string
|
||||
example: M01
|
||||
pattern: '^[A-Z]{3}\d{2}$'
|
||||
example: "LGL01"
|
||||
label:
|
||||
type: string
|
||||
example: Le Gaulois
|
||||
example: "Le Gaulois"
|
||||
|
||||
Inco:
|
||||
type: object
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Application\Arcadia\Usecase\Brands\FindAll;
|
||||
|
||||
use Domain\Models\Entities\Brand;
|
||||
use Domain\Ports\Directories\ArcadiaBrandDirectory;
|
||||
use Domain\Services\BrandResolver;
|
||||
use Shared\DDD\Usecase\Input as BaseInput;
|
||||
use Shared\DDD\Usecase\Usecase;
|
||||
|
|
@ -14,6 +14,7 @@ use Webmozart\Assert\Assert;
|
|||
final readonly class FindAllBrands implements Usecase
|
||||
{
|
||||
public function __construct(
|
||||
private ArcadiaBrandDirectory $arcadiaBrandDirectory,
|
||||
private BrandResolver $brandResolver,
|
||||
) {
|
||||
}
|
||||
|
|
@ -23,12 +24,13 @@ final readonly class FindAllBrands implements Usecase
|
|||
{
|
||||
Assert::isInstanceOf($input, Input::class);
|
||||
|
||||
$brands = $this->brandResolver->resolveAll();
|
||||
$arcadiaBrands = $this->arcadiaBrandDirectory->findAll();
|
||||
$brandItems = [];
|
||||
|
||||
$brandItems = array_map(
|
||||
static fn (Brand $brand): BrandItem => BrandItem::fromDomain($brand),
|
||||
$brands
|
||||
);
|
||||
foreach ($arcadiaBrands as $arcadiaBrand) {
|
||||
$brand = $this->brandResolver->resolve($arcadiaBrand->id);
|
||||
$brandItems[] = BrandItem::fromDomain($brand);
|
||||
}
|
||||
|
||||
$total = count($brandItems);
|
||||
$offset = $input->pagination->from();
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
|||
namespace Application\Arcadia\Usecase\IncoSheets\Find;
|
||||
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoWeight;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
|
|
@ -34,7 +34,7 @@ final readonly class Output implements UsecaseOutput
|
|||
public Ingredients $ingredients,
|
||||
public ?PortionAmount $portions,
|
||||
public array $allergens,
|
||||
public Brand $brand,
|
||||
public IncoBrand $brand,
|
||||
public bool $isSeasonal,
|
||||
public ?IncoWeight $weight,
|
||||
public ?Nutriscore $nutriscore,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ namespace Application\Arcadia\Usecase\IncoSheets\FindAll;
|
|||
|
||||
use Domain\Models\Entities\Inco\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoWeight;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
|
|
@ -35,7 +35,7 @@ final readonly class PaginatedIncoSheet implements UsecaseOutput
|
|||
public Ingredients $ingredients,
|
||||
public ?PortionAmount $portions,
|
||||
public array $allergens,
|
||||
public Brand $brand,
|
||||
public IncoBrand $brand,
|
||||
public bool $isSeasonal,
|
||||
public ?IncoWeight $weight,
|
||||
public ?Nutriscore $nutriscore,
|
||||
|
|
|
|||
|
|
@ -4,26 +4,22 @@ declare(strict_types=1);
|
|||
|
||||
namespace Application\Arcadia\Usecase\IncoSheets\Search;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
|
||||
use Shared\DDD\Usecase\Input as UsecaseInput;
|
||||
use Shared\Pagination;
|
||||
|
||||
final readonly class Input implements UsecaseInput
|
||||
{
|
||||
/** @var array<string> */
|
||||
/** @var array<BrandCode> */
|
||||
public array $brands;
|
||||
|
||||
public function __construct(
|
||||
public IncoIdQuery $query,
|
||||
public bool $short = false,
|
||||
public ?Pagination $pagination = null,
|
||||
string ...$brands,
|
||||
BrandCode ...$brands,
|
||||
) {
|
||||
$this->brands = $brands;
|
||||
foreach ($this->brands as $brand) {
|
||||
if (trim($brand) === '') {
|
||||
throw new \InvalidArgumentException('Every item in brands array must be a non empty string.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ namespace Application\Arcadia\Usecase\IncoSheets\Search;
|
|||
|
||||
use Domain\Models\Entities\Inco\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoWeight;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
|
|
@ -35,7 +35,7 @@ final readonly class PaginatedIncoSheet implements UsecaseOutput
|
|||
public Ingredients $ingredients,
|
||||
public ?PortionAmount $portions,
|
||||
public array $allergens,
|
||||
public Brand $brand,
|
||||
public IncoBrand $brand,
|
||||
public bool $isSeasonal,
|
||||
public ?IncoWeight $weight,
|
||||
public ?Nutriscore $nutriscore,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
|||
namespace Domain\Models\Entities\Inco;
|
||||
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\Composition\IncoCompositionId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoWeight;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
|
|
@ -35,7 +35,7 @@ final readonly class IncoSheet implements Entity
|
|||
public Ingredients $ingredients,
|
||||
public ?PortionAmount $portions,
|
||||
public array $allergens,
|
||||
public Brand $brand,
|
||||
public IncoBrand $brand,
|
||||
public bool $isSeasonal,
|
||||
public ?IncoWeight $weight,
|
||||
public ?Nutriscore $nutriscore,
|
||||
|
|
@ -70,7 +70,7 @@ final readonly class IncoSheet implements Entity
|
|||
Ingredients $ingredients,
|
||||
?PortionAmount $portions,
|
||||
array $allergens,
|
||||
Brand $brand,
|
||||
IncoBrand $brand,
|
||||
bool $isSeasonal,
|
||||
?IncoWeight $weight,
|
||||
?Nutriscore $nutriscore,
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Inco;
|
||||
|
||||
final readonly class Brand
|
||||
{
|
||||
public function __construct(
|
||||
public string $code,
|
||||
public string $label,
|
||||
) {
|
||||
if (empty(trim($code))) {
|
||||
throw new \DomainException('Brand code must not be empty.');
|
||||
}
|
||||
|
||||
if (empty(trim($label))) {
|
||||
throw new \DomainException('Brand label must not be empty.');
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/Domain/Models/ValueObjects/Inco/IncoBrand.php
Normal file
19
src/Domain/Models/ValueObjects/Inco/IncoBrand.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Inco;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
|
||||
final readonly class IncoBrand
|
||||
{
|
||||
public function __construct(
|
||||
public BrandName $name,
|
||||
public BrandCode $code,
|
||||
public ArcadiaId $arcadiaBrandCode,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -4,21 +4,19 @@ declare(strict_types=1);
|
|||
|
||||
namespace Domain\Models\ValueObjects\Inco;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
|
||||
final readonly class IncoSheetFilter
|
||||
{
|
||||
/** @var array<string> */
|
||||
/** @var array<BrandCode> */
|
||||
public array $brands;
|
||||
|
||||
public function __construct(
|
||||
public ?\DateTimeInterface $since = null,
|
||||
string ...$brands,
|
||||
BrandCode ...$brands,
|
||||
) {
|
||||
$this->brands = $brands;
|
||||
|
||||
if (array_any($this->brands, static fn (string $brand): bool => trim($brand) === '')) {
|
||||
throw new \InvalidArgumentException('Every item in brands array must be a non empty string.');
|
||||
}
|
||||
|
||||
if (empty($this->brands) && $since === null) {
|
||||
throw new \DomainException('IncoSheetFilter requires at least one filtering criterion.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Domain\Ports\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;
|
||||
|
|
@ -27,7 +28,7 @@ interface IncoSheetDirectory extends Directory
|
|||
public function search(
|
||||
IncoIdQuery $query,
|
||||
Pagination $pagination,
|
||||
string ...$brands
|
||||
BrandCode ...$brands
|
||||
): Paginated;
|
||||
|
||||
public function find(IncoId $id): ?IncoSheet;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Domain\Ports\Directories;
|
||||
|
||||
use Domain\Models\Entities\Inco\IncoShortSheet;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
|
||||
|
|
@ -27,7 +28,7 @@ interface IncoShortSheetDirectory extends Directory
|
|||
public function search(
|
||||
IncoIdQuery $query,
|
||||
Pagination $pagination,
|
||||
string ...$brands
|
||||
BrandCode ...$brands
|
||||
): Paginated;
|
||||
|
||||
public function find(IncoId $id): ?IncoShortSheet;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ namespace Domain\Services;
|
|||
|
||||
use Domain\Exceptions\BrandNotFoundException;
|
||||
use Domain\Models\Entities\Brand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaBrand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Ports\Directories\ArcadiaBrandDirectory;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
|
|
@ -27,16 +26,24 @@ final readonly class BrandResolver
|
|||
}
|
||||
|
||||
/**
|
||||
* Resolve or generate/persist a Brand entity from an ArcadiaBrand.
|
||||
* Resolve or generate/persist a Brand entity given an Arcadia brand code (ArcadiaId).
|
||||
*
|
||||
* @throws BrandNotFoundException
|
||||
*/
|
||||
public function resolve(ArcadiaBrand $arcadiaBrand): Brand
|
||||
public function resolve(ArcadiaId $arcadiaBrandCode): Brand
|
||||
{
|
||||
$existingBrand = $this->brandRepository->findByArcadiaId($arcadiaBrand->id);
|
||||
$existingBrand = $this->brandRepository->findByArcadiaId($arcadiaBrandCode);
|
||||
|
||||
if ($existingBrand !== null) {
|
||||
return $existingBrand;
|
||||
}
|
||||
|
||||
$arcadiaBrand = $this->arcadiaBrandDirectory->find($arcadiaBrandCode);
|
||||
|
||||
if ($arcadiaBrand === null) {
|
||||
throw BrandNotFoundException::forArcadiaId($arcadiaBrandCode);
|
||||
}
|
||||
|
||||
do {
|
||||
$code = $this->brandCodeGenerator->generate($arcadiaBrand->name);
|
||||
} while ($this->brandRepository->findByCode($code) !== null);
|
||||
|
|
@ -66,43 +73,4 @@ final readonly class BrandResolver
|
|||
|
||||
return $brand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve or generate/persist a Brand entity given an ArcadiaId.
|
||||
*
|
||||
* @throws BrandNotFoundException
|
||||
*/
|
||||
public function resolveByArcadiaId(ArcadiaId $arcadiaId): Brand
|
||||
{
|
||||
$existingBrand = $this->brandRepository->findByArcadiaId($arcadiaId);
|
||||
|
||||
if ($existingBrand !== null) {
|
||||
return $existingBrand;
|
||||
}
|
||||
|
||||
$arcadiaBrand = $this->arcadiaBrandDirectory->find($arcadiaId);
|
||||
|
||||
if ($arcadiaBrand === null) {
|
||||
throw BrandNotFoundException::forArcadiaId($arcadiaId);
|
||||
}
|
||||
|
||||
return $this->resolve($arcadiaBrand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve all brands known in Arcadia directory.
|
||||
*
|
||||
* @return list<Brand>
|
||||
*/
|
||||
public function resolveAll(): array
|
||||
{
|
||||
$arcadiaBrands = $this->arcadiaBrandDirectory->findAll();
|
||||
$brands = [];
|
||||
|
||||
foreach ($arcadiaBrands as $arcadiaBrand) {
|
||||
$brands[] = $this->resolve($arcadiaBrand);
|
||||
}
|
||||
|
||||
return $brands;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ 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\Brand;
|
||||
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;
|
||||
|
|
@ -21,12 +23,21 @@ 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,
|
||||
|
|
@ -44,7 +55,8 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
}
|
||||
|
||||
if (! empty($filter->brands)) {
|
||||
$query->whereIn('brand->code', $filter->brands);
|
||||
$resolvedBrands = $this->resolveBrandFilterCodes(...$filter->brands);
|
||||
$query->whereIn('brand->code', $resolvedBrands);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,14 +85,15 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
public function search(
|
||||
IncoIdQuery $query,
|
||||
Pagination $pagination,
|
||||
string ...$brands
|
||||
BrandCode ...$brands
|
||||
): Paginated {
|
||||
$dbQuery = IncoSheetModel::query();
|
||||
|
||||
$dbQuery->where('id', 'LIKE', $query->value);
|
||||
|
||||
if (! empty($brands)) {
|
||||
$dbQuery->whereIn('brand->code', $brands);
|
||||
$resolvedBrands = $this->resolveBrandFilterCodes(...$brands);
|
||||
$dbQuery->whereIn('brand->code', $resolvedBrands);
|
||||
}
|
||||
|
||||
$total = (int) (clone $dbQuery)->count();
|
||||
|
|
@ -169,10 +182,18 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
{
|
||||
$raw = is_array($model->ingredients) ? $model->ingredients : [];
|
||||
|
||||
/** @var array<int|string, array<string>> $ingredientsGroups */
|
||||
$ingredientsGroups = $raw;
|
||||
/** @var array<string, list<string>> $cleanIngredients */
|
||||
$cleanIngredients = [];
|
||||
|
||||
return new Ingredients($ingredientsGroups);
|
||||
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
|
||||
|
|
@ -184,19 +205,22 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
}
|
||||
|
||||
return new PortionAmount(
|
||||
lowPart: $this->getFloat($raw, 'lowPart'),
|
||||
highPart: $this->getFloat($raw, 'highPart'),
|
||||
approximative: (bool) ($raw['approximative'] ?? false),
|
||||
lowPart: $this->getInt($raw, 'lowPart'),
|
||||
highPart: $this->getInt($raw, 'highPart'),
|
||||
approximative: $this->getBool($raw, 'approximative'),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return \Domain\Models\ValueObjects\Inco\Allergen[] */
|
||||
/**
|
||||
* @return array<Allergen>
|
||||
*/
|
||||
private function allergensFrom(IncoSheetModel $model): array
|
||||
{
|
||||
$allergensRaw = is_array($model->allergens) ? $model->allergens : [];
|
||||
$allergens = [];
|
||||
$raw = is_array($model->allergens) ? $model->allergens : [];
|
||||
|
||||
foreach ($allergensRaw as $a) {
|
||||
$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']);
|
||||
}
|
||||
|
|
@ -205,13 +229,20 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
return $allergens;
|
||||
}
|
||||
|
||||
private function brandFrom(IncoSheetModel $model): Brand
|
||||
private function brandFrom(IncoSheetModel $model): IncoBrand
|
||||
{
|
||||
$raw = is_array($model->brand) ? $model->brand : [];
|
||||
$raw = is_array($model->brand) ? $model->brand : [];
|
||||
$rawCode = isset($raw['code']) && (is_string($raw['code']) || is_numeric($raw['code']))
|
||||
? (string) $raw['code']
|
||||
: '1';
|
||||
|
||||
return new Brand(
|
||||
code: $this->getString($raw, 'code', 'M01'),
|
||||
label: $this->getString($raw, 'label', 'Standard'),
|
||||
$arcadiaId = new ArcadiaId(is_numeric($rawCode) ? (int) $rawCode : 1);
|
||||
$brand = $this->brandResolver->resolve($arcadiaId);
|
||||
|
||||
return new IncoBrand(
|
||||
name: $brand->name,
|
||||
code: $brand->code,
|
||||
arcadiaBrandCode: $brand->arcadiaId,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -238,9 +269,13 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
return null;
|
||||
}
|
||||
|
||||
return new Nutriscore(
|
||||
grade: $this->getString($raw, 'grade', 'A'),
|
||||
);
|
||||
$grade = $this->getString($raw, 'grade');
|
||||
|
||||
if ($grade === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Nutriscore($grade);
|
||||
}
|
||||
|
||||
private function storageConditionFrom(IncoSheetModel $model): ?StorageCondition
|
||||
|
|
@ -251,20 +286,27 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
return null;
|
||||
}
|
||||
|
||||
return new StorageCondition(
|
||||
instructions: $this->getString($raw, 'instructions', ''),
|
||||
);
|
||||
$instructions = $this->getString($raw, 'instructions');
|
||||
|
||||
if ($instructions === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new StorageCondition($instructions);
|
||||
}
|
||||
|
||||
/** @return \Domain\Models\ValueObjects\Inco\Additive[] */
|
||||
/**
|
||||
* @return array<Additive>
|
||||
*/
|
||||
private function additivesFrom(IncoSheetModel $model): array
|
||||
{
|
||||
$additivesRaw = is_array($model->additives) ? $model->additives : [];
|
||||
$additives = [];
|
||||
$raw = is_array($model->additives) ? $model->additives : [];
|
||||
|
||||
foreach ($additivesRaw as $ad) {
|
||||
if (is_array($ad) && isset($ad['code'], $ad['label']) && is_scalar($ad['code']) && is_scalar($ad['label'])) {
|
||||
$additives[] = new Additive((string) $ad['code'], (string) $ad['label']);
|
||||
$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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,86 +314,97 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @return array<int|string>
|
||||
*/
|
||||
private function getString(array $data, string $key, string $default = ''): string
|
||||
private function resolveBrandFilterCodes(BrandCode ...$brandCodes): array
|
||||
{
|
||||
$val = $data[$key] ?? null;
|
||||
$resolved = [];
|
||||
|
||||
return is_scalar($val) ? (string) $val : $default;
|
||||
}
|
||||
|
||||
private function toNullableString(mixed $val): ?string
|
||||
{
|
||||
return is_scalar($val) ? (string) $val : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
* @return array<string>
|
||||
*/
|
||||
private function getArrayOfStrings(array $data, string $key): array
|
||||
{
|
||||
$val = $data[$key] ?? null;
|
||||
|
||||
if (! is_array($val)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($val as $item) {
|
||||
if (is_string($item)) {
|
||||
$result[] = $item;
|
||||
foreach ($brandCodes as $brandCode) {
|
||||
$brand = $this->brandRepository->findByCode($brandCode);
|
||||
if ($brand !== null) {
|
||||
$resolved[] = $brand->arcadiaId->value;
|
||||
$resolved[] = (string) $brand->arcadiaId->value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
private function getInt(array $data, string $key, int $default = 0): int
|
||||
{
|
||||
$val = $data[$key] ?? null;
|
||||
|
||||
return is_numeric($val) ? (int) $val : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $data
|
||||
*/
|
||||
private function getFloat(array $data, string $key, float $default = 0.0): float
|
||||
{
|
||||
$val = $data[$key] ?? null;
|
||||
|
||||
return is_numeric($val) ? (float) $val : $default;
|
||||
}
|
||||
|
||||
private function toDateTime(\DateTimeInterface | string $value): \DateTimeInterface
|
||||
{
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return $value;
|
||||
if (empty($resolved) && ! empty($brandCodes)) {
|
||||
return [0];
|
||||
}
|
||||
|
||||
return new \DateTimeImmutable($value);
|
||||
}
|
||||
|
||||
private function toNullableDateTime(\DateTimeInterface | string | null $value): ?\DateTimeInterface
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
$unique = [];
|
||||
foreach ($resolved as $item) {
|
||||
if (! in_array($item, $unique, true)) {
|
||||
$unique[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->toDateTime($value);
|
||||
return $unique;
|
||||
}
|
||||
|
||||
private function toSqlColumn(IncoSheetSortColumn $sortBy): string
|
||||
private function toSqlColumn(IncoSheetSortColumn $column): string
|
||||
{
|
||||
return match ($sortBy) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ declare(strict_types=1);
|
|||
namespace Infra\Adapters\Directories;
|
||||
|
||||
use Domain\Models\Entities\Inco\IncoShortSheet;
|
||||
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\IncoShortSheetDirectory;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
use Infra\Database\Models\IncoSheet as IncoSheetModel;
|
||||
use Shared\Helpers\DatesHelper;
|
||||
use Shared\Paginated;
|
||||
|
|
@ -21,6 +23,11 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
|
|||
/** @var list<string> */
|
||||
private const array SHORT_FIELDS = ['id', 'name', 'label', 'created_at', 'updated_at'];
|
||||
|
||||
public function __construct(
|
||||
private BrandRepository $brandRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findAll(
|
||||
Pagination $pagination,
|
||||
|
|
@ -38,7 +45,8 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
|
|||
}
|
||||
|
||||
if (! empty($filter->brands)) {
|
||||
$query->whereIn('brand->code', $filter->brands);
|
||||
$resolvedBrands = $this->resolveBrandFilterCodes(...$filter->brands);
|
||||
$query->whereIn('brand->code', $resolvedBrands);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,14 +74,15 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
|
|||
public function search(
|
||||
IncoIdQuery $query,
|
||||
Pagination $pagination,
|
||||
string ...$brands
|
||||
BrandCode ...$brands
|
||||
): Paginated {
|
||||
$dbQuery = IncoSheetModel::query()->select(self::SHORT_FIELDS);
|
||||
|
||||
$dbQuery->where('id', 'LIKE', $query->value);
|
||||
|
||||
if (! empty($brands)) {
|
||||
$dbQuery->whereIn('brand->code', $brands);
|
||||
$resolvedBrands = $this->resolveBrandFilterCodes(...$brands);
|
||||
$dbQuery->whereIn('brand->code', $resolvedBrands);
|
||||
}
|
||||
|
||||
$total = (int) (clone $dbQuery)->count();
|
||||
|
|
@ -107,6 +116,35 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
|
|||
return $this->toDomain($model);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string>
|
||||
*/
|
||||
private function resolveBrandFilterCodes(BrandCode ...$brandCodes): array
|
||||
{
|
||||
$resolved = [];
|
||||
|
||||
foreach ($brandCodes as $brandCode) {
|
||||
$brand = $this->brandRepository->findByCode($brandCode);
|
||||
if ($brand !== null) {
|
||||
$resolved[] = $brand->arcadiaId->value;
|
||||
$resolved[] = (string) $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 toDomain(IncoSheetModel $model): IncoShortSheet
|
||||
{
|
||||
$rawId = $model->getAttribute('id');
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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;
|
||||
|
|
@ -57,8 +58,17 @@ final class InMemoryIncoSheetDirectory implements IncoSheetDirectory
|
|||
}
|
||||
}
|
||||
|
||||
if (! empty($filter->brands) && ! in_array($sheet->brand->code, $filter->brands, true)) {
|
||||
return false;
|
||||
if (! empty($filter->brands)) {
|
||||
$matched = false;
|
||||
foreach ($filter->brands as $brandCode) {
|
||||
if ($sheet->brand->code->value === $brandCode->value) {
|
||||
$matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $matched) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -88,7 +98,7 @@ final class InMemoryIncoSheetDirectory implements IncoSheetDirectory
|
|||
public function search(
|
||||
IncoIdQuery $query,
|
||||
Pagination $pagination,
|
||||
string ...$brands,
|
||||
BrandCode ...$brands,
|
||||
): Paginated {
|
||||
$regex = '/^' . str_replace('%', '.*', preg_quote($query->value, '/')) . '$/';
|
||||
|
||||
|
|
@ -97,8 +107,17 @@ final class InMemoryIncoSheetDirectory implements IncoSheetDirectory
|
|||
return false;
|
||||
}
|
||||
|
||||
if (! empty($brands) && ! in_array($sheet->brand->code, $brands, true)) {
|
||||
return false;
|
||||
if (! empty($brands)) {
|
||||
$matched = false;
|
||||
foreach ($brands as $brandCode) {
|
||||
if ($sheet->brand->code->value === $brandCode->value) {
|
||||
$matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $matched) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Infra\Adapters\Directories;
|
||||
|
||||
use Domain\Models\Entities\Inco\IncoShortSheet;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
|
||||
|
|
@ -84,7 +85,7 @@ final class InMemoryIncoShortSheetDirectory implements IncoShortSheetDirectory
|
|||
public function search(
|
||||
IncoIdQuery $query,
|
||||
Pagination $pagination,
|
||||
string ...$brands,
|
||||
BrandCode ...$brands,
|
||||
): Paginated {
|
||||
$regex = '/^' . str_replace('%', '.*', preg_quote($query->value, '/')) . '$/';
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,24 @@ use Domain\Models\ValueObjects\Brand\BrandCode;
|
|||
use Domain\Models\ValueObjects\Brand\BrandId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Infra\Database\Models\Brand as BrandModel;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
final class EloquentBrandRepository implements BrandRepository
|
||||
{
|
||||
private const int CACHE_TTL_SECONDS = 86400;
|
||||
|
||||
#[\Override]
|
||||
public function find(BrandId $id): ?Brand
|
||||
{
|
||||
$key = "brand:id:{$id->value}";
|
||||
$brand = $this->fromCacheData(Cache::get($key));
|
||||
|
||||
if ($brand !== null) {
|
||||
return $brand;
|
||||
}
|
||||
|
||||
$model = BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->find($id->value);
|
||||
|
|
@ -27,12 +37,22 @@ final class EloquentBrandRepository implements BrandRepository
|
|||
return null;
|
||||
}
|
||||
|
||||
return $this->toDomain($model);
|
||||
$brand = $this->toDomain($model);
|
||||
Cache::put($key, $this->toCacheData($brand), self::CACHE_TTL_SECONDS);
|
||||
|
||||
return $brand;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findByCode(BrandCode $code): ?Brand
|
||||
{
|
||||
$key = "brand:code:{$code->value}";
|
||||
$brand = $this->fromCacheData(Cache::get($key));
|
||||
|
||||
if ($brand !== null) {
|
||||
return $brand;
|
||||
}
|
||||
|
||||
$model = BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->where('uwil_public_id', $code->value)
|
||||
|
|
@ -42,12 +62,22 @@ final class EloquentBrandRepository implements BrandRepository
|
|||
return null;
|
||||
}
|
||||
|
||||
return $this->toDomain($model);
|
||||
$brand = $this->toDomain($model);
|
||||
Cache::put($key, $this->toCacheData($brand), self::CACHE_TTL_SECONDS);
|
||||
|
||||
return $brand;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findByArcadiaId(ArcadiaId $arcadiaId): ?Brand
|
||||
{
|
||||
$key = "brand:arcadia:{$arcadiaId->value}";
|
||||
$brand = $this->fromCacheData(Cache::get($key));
|
||||
|
||||
if ($brand !== null) {
|
||||
return $brand;
|
||||
}
|
||||
|
||||
$model = BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->where('arcadia_id', $arcadiaId->value)
|
||||
|
|
@ -57,19 +87,43 @@ final class EloquentBrandRepository implements BrandRepository
|
|||
return null;
|
||||
}
|
||||
|
||||
return $this->toDomain($model);
|
||||
$brand = $this->toDomain($model);
|
||||
Cache::put($key, $this->toCacheData($brand), self::CACHE_TTL_SECONDS);
|
||||
|
||||
return $brand;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findAll(): array
|
||||
{
|
||||
return array_values(
|
||||
$key = 'brand:all';
|
||||
$data = Cache::get($key);
|
||||
|
||||
if (is_array($data)) {
|
||||
$brands = [];
|
||||
foreach ($data as $item) {
|
||||
$brand = $this->fromCacheData($item);
|
||||
if ($brand !== null) {
|
||||
$brands[] = $brand;
|
||||
}
|
||||
}
|
||||
if (! empty($brands) && count($brands) === count($data)) {
|
||||
return $brands;
|
||||
}
|
||||
}
|
||||
|
||||
$brands = array_values(
|
||||
BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->get()
|
||||
->map(fn (BrandModel $model): Brand => $this->toDomain($model))
|
||||
->all()
|
||||
);
|
||||
|
||||
$cachedList = array_map(fn (Brand $brand): array => $this->toCacheData($brand), $brands);
|
||||
Cache::put($key, $cachedList, self::CACHE_TTL_SECONDS);
|
||||
|
||||
return $brands;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
|
@ -82,6 +136,8 @@ final class EloquentBrandRepository implements BrandRepository
|
|||
'uwil_public_id' => $entity->code,
|
||||
]
|
||||
);
|
||||
|
||||
$this->invalidateCache($entity);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
|
@ -94,6 +150,54 @@ final class EloquentBrandRepository implements BrandRepository
|
|||
if ($deleted === 0) {
|
||||
throw BrandNotFoundException::forBrand($entity->id());
|
||||
}
|
||||
|
||||
$this->invalidateCache($entity);
|
||||
}
|
||||
|
||||
private function invalidateCache(Brand $entity): void
|
||||
{
|
||||
Cache::forget('brand:all');
|
||||
Cache::forget("brand:id:{$entity->id()->value}");
|
||||
Cache::forget("brand:code:{$entity->code->value}");
|
||||
Cache::forget("brand:arcadia:{$entity->arcadiaId->value}");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{id: string, name: string, code: string, arcadiaId: int}
|
||||
*/
|
||||
private function toCacheData(Brand $brand): array
|
||||
{
|
||||
return [
|
||||
'id' => (string) $brand->id()->value,
|
||||
'name' => $brand->name->value,
|
||||
'code' => $brand->code->value,
|
||||
'arcadiaId' => $brand->arcadiaId->value,
|
||||
];
|
||||
}
|
||||
|
||||
private function fromCacheData(mixed $data): ?Brand
|
||||
{
|
||||
if (
|
||||
! is_array($data)
|
||||
|| ! isset($data['id'], $data['name'], $data['code'], $data['arcadiaId'])
|
||||
|| ! is_string($data['id'])
|
||||
|| ! is_string($data['name'])
|
||||
|| ! is_string($data['code'])
|
||||
|| ! is_int($data['arcadiaId'])
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Brand::hydrate(
|
||||
id: new BrandId(Uuid::fromString($data['id'])),
|
||||
name: new BrandName($data['name']),
|
||||
code: new BrandCode($data['code']),
|
||||
arcadiaId: new ArcadiaId($data['arcadiaId']),
|
||||
);
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function toDomain(BrandModel $model): Brand
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Presentation\Api\V1\Dto\IncoSheets\Parts;
|
||||
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Presentation\Api\V1\Contracts\ApiResponseDto;
|
||||
|
||||
final readonly class BrandDto implements ApiResponseDto
|
||||
|
|
@ -15,11 +15,11 @@ final readonly class BrandDto implements ApiResponseDto
|
|||
) {
|
||||
}
|
||||
|
||||
public static function fromDomain(Brand $brand): self
|
||||
public static function fromDomain(IncoBrand $brand): self
|
||||
{
|
||||
return new self(
|
||||
code: $brand->code,
|
||||
label: $brand->label,
|
||||
code: $brand->code->value,
|
||||
label: $brand->name->value,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Presentation\Api\V1\Http\Requests\IncoSheets;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
|
||||
|
|
@ -47,22 +48,22 @@ final class IncoSheetsIndexRequest extends FormRequest
|
|||
{
|
||||
$since = $this->filled('since') ? DatesHelper::parseDateTime($this->string('since')->value()) : null;
|
||||
|
||||
$brands = [];
|
||||
$rawBrands = $this->array('brands');
|
||||
$brandCodes = [];
|
||||
$rawBrands = $this->array('brands');
|
||||
foreach ($rawBrands as $b) {
|
||||
if (is_string($b) || is_numeric($b)) {
|
||||
$val = trim((string) $b);
|
||||
if (is_string($b)) {
|
||||
$val = trim($b);
|
||||
if ($val !== '') {
|
||||
$brands[] = $val;
|
||||
$brandCodes[] = new BrandCode($val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($brands) && $since === null) {
|
||||
if (empty($brandCodes) && $since === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new IncoSheetFilter($since, ...$brands);
|
||||
return new IncoSheetFilter($since, ...$brandCodes);
|
||||
}
|
||||
|
||||
public function sortBy(): IncoSheetSortColumn
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace Presentation\Api\V1\Http\Requests\IncoSheets;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* @method \Shared\Pagination pagination(int $defaultPageSize = 25)
|
||||
* @method \Uri\Rfc3986\Uri toUri()
|
||||
*/
|
||||
final class IncoSheetsSearchRequest extends FormRequest
|
||||
|
|
@ -29,16 +31,16 @@ final class IncoSheetsSearchRequest extends FormRequest
|
|||
return new IncoIdQuery($this->string('incoIdLike')->value());
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
/** @return array<BrandCode> */
|
||||
public function brands(): array
|
||||
{
|
||||
$raw = $this->array('brands');
|
||||
$result = [];
|
||||
foreach ($raw as $b) {
|
||||
if (is_string($b) || is_numeric($b)) {
|
||||
$val = trim((string) $b);
|
||||
if (is_string($b)) {
|
||||
$val = trim($b);
|
||||
if ($val !== '') {
|
||||
$result[] = $val;
|
||||
$result[] = new BrandCode($val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Tests\Feature\Api\V1\IncoSheets;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Services\BrandResolver;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
use Infra\Database\Models\IncoSheet;
|
||||
|
||||
describe('IncoSheets Index Feature API', function (): void {
|
||||
|
|
@ -75,6 +78,42 @@ describe('IncoSheets Index Feature API', function (): void {
|
|||
['updatedAt', 'Ascending'],
|
||||
]);
|
||||
|
||||
it('returns 200 OK with matching sheets filtered by BrandCode', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
|
||||
ArcadiaBrand::query()->firstOrCreate(['code' => 504], ['libelle' => 'LE GAULOIS PROFESSIONNEL']);
|
||||
$brand = app(BrandResolver::class)->resolve(new ArcadiaId(504));
|
||||
|
||||
IncoSheet::factory()->create([
|
||||
'id' => '9999001',
|
||||
'name' => 'Produit Gaulois Pro',
|
||||
'brand' => ['code' => 504, 'label' => 'LE GAULOIS PROFESSIONNEL'],
|
||||
]);
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson("/api/v1/inco-sheets?brands[]={$brand->code->value}");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonPath('incoSheets.0.id', '9999001')
|
||||
->assertJsonPath('incoSheets.0.brand.code', $brand->code->value)
|
||||
->assertJsonPath('incoSheets.0.brand.label', 'LE GAULOIS PROFESSIONNEL');
|
||||
});
|
||||
|
||||
it('returns 422 Unprocessable Entity when brand code format is invalid', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson('/api/v1/inco-sheets?brands[]=invalid');
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(422);
|
||||
});
|
||||
|
||||
it('returns 200 OK with short form inco sheets when short parameter is true', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Tests\Feature\Api\V1\IncoSheets;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Services\BrandResolver;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
use Infra\Database\Models\IncoSheet;
|
||||
|
||||
describe('IncoSheets Search Feature API', function (): void {
|
||||
|
|
@ -11,13 +14,13 @@ describe('IncoSheets Search Feature API', function (): void {
|
|||
IncoSheet::factory()->create([
|
||||
'id' => '1000101',
|
||||
'name' => 'Poulet Rôti 1kg',
|
||||
'brand' => ['code' => 'M01', 'label' => 'Standard'],
|
||||
'brand' => ['code' => 1, 'label' => 'Standard'],
|
||||
]);
|
||||
|
||||
IncoSheet::factory()->create([
|
||||
'id' => '1000102',
|
||||
'name' => 'Poulet Fermier',
|
||||
'brand' => ['code' => 'M02', 'label' => 'Bio'],
|
||||
'brand' => ['code' => 2, 'label' => 'Bio'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
@ -51,19 +54,36 @@ describe('IncoSheets Search Feature API', function (): void {
|
|||
$response->assertJsonCount(1, 'incoSheets');
|
||||
});
|
||||
|
||||
it('returns 200 OK with matching sheets filtered by brand', function (): void {
|
||||
it('returns 200 OK with matching sheets filtered by BrandCode', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%10001%');
|
||||
|
||||
ArcadiaBrand::query()->firstOrCreate(['code' => 1], ['libelle' => 'Standard']);
|
||||
$brand = app(BrandResolver::class)->resolve(new ArcadiaId(1));
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&brands[]={$brand->code->value}");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonCount(1, 'incoSheets');
|
||||
$response->assertJsonPath('incoSheets.0.id', '1000101')
|
||||
->assertJsonPath('incoSheets.0.brand.code', $brand->code->value);
|
||||
});
|
||||
|
||||
it('returns 422 Unprocessable Entity when brand code format is invalid', function (): void {
|
||||
// GIVEN
|
||||
/** @var \Tests\TestCase $test */
|
||||
$test = $this;
|
||||
$query = urlencode('%10001%');
|
||||
|
||||
// WHEN
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&brands[]=M01");
|
||||
$response = $test->getJson("/api/v1/inco-sheets/search?incoIdLike={$query}&brands[]=invalid");
|
||||
|
||||
// THEN
|
||||
$response->assertStatus(200);
|
||||
$response->assertJsonCount(1, 'incoSheets');
|
||||
$response->assertJsonPath('incoSheets.0.id', '1000101');
|
||||
$response->assertStatus(422);
|
||||
});
|
||||
|
||||
it('returns 200 OK with short form inco sheets when short parameter is true', function (): void {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ describe('FindAllBrands Usecase Unit Tests', function (): void {
|
|||
$brandRepository->add($existingBrand);
|
||||
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
$usecase = new FindAllBrands($resolver);
|
||||
$usecase = new FindAllBrands($arcadiaDirectory, $resolver);
|
||||
$input = new Input(Pagination::ofPage(1, 25));
|
||||
|
||||
// WHEN
|
||||
|
|
@ -91,7 +91,7 @@ describe('FindAllBrands Usecase Unit Tests', function (): void {
|
|||
$arcadiaDirectory->add($arcadia1, $arcadia2, $arcadia3);
|
||||
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
$usecase = new FindAllBrands($resolver);
|
||||
$usecase = new FindAllBrands($arcadiaDirectory, $resolver);
|
||||
$input = new Input(Pagination::ofPage(2, 2));
|
||||
|
||||
// WHEN
|
||||
|
|
@ -108,7 +108,7 @@ describe('FindAllBrands Usecase Unit Tests', function (): void {
|
|||
it('returns an empty list when there are no arcadia brands', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void {
|
||||
// GIVEN
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
$usecase = new FindAllBrands($resolver);
|
||||
$usecase = new FindAllBrands($arcadiaDirectory, $resolver);
|
||||
$input = new Input(Pagination::ofPage(1, 25));
|
||||
|
||||
// WHEN
|
||||
|
|
|
|||
|
|
@ -10,8 +10,11 @@ use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedIncoSheet;
|
|||
use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet;
|
||||
use Domain\Models\Entities\Inco\IncoSheet;
|
||||
use Domain\Models\Entities\Inco\IncoShortSheet;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
|
||||
|
|
@ -55,7 +58,7 @@ describe('FindAllIncoSheets DTOs Instantiation & Logic', function (): void {
|
|||
ingredients: new Ingredients(['main' => ['Viande']]),
|
||||
portions: null,
|
||||
allergens: [],
|
||||
brand: new Brand('M01', 'Le Gaulois'),
|
||||
brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)),
|
||||
isSeasonal: true,
|
||||
weight: null,
|
||||
nutriscore: null,
|
||||
|
|
|
|||
|
|
@ -10,8 +10,11 @@ use Application\Arcadia\Usecase\IncoSheets\FindAll\Output;
|
|||
use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedIncoSheet;
|
||||
use Application\Arcadia\Usecase\IncoSheets\FindAll\PaginatedShortIncoSheet;
|
||||
use Domain\Models\Entities\Inco\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetSortColumn;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetSorter;
|
||||
|
|
@ -53,7 +56,7 @@ describe('FindAllIncoSheets Usecase Unit Tests', function (): void {
|
|||
ingredients: new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]),
|
||||
portions: null,
|
||||
allergens: [],
|
||||
brand: new Brand('M01', 'Le Gaulois'),
|
||||
brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)),
|
||||
isSeasonal: true,
|
||||
weight: null,
|
||||
nutriscore: null,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@ namespace Tests\Unit\Application\Arcadia\Usecase\IncoSheets;
|
|||
|
||||
use Application\Arcadia\Usecase\IncoSheets\Find\Input;
|
||||
use Application\Arcadia\Usecase\IncoSheets\Find\Output;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
|
||||
|
|
@ -43,7 +46,7 @@ describe('FindIncoSheet DTO Instantiation', function (): void {
|
|||
ingredients: new Ingredients(['main' => ['Viande']]),
|
||||
portions: null,
|
||||
allergens: [],
|
||||
brand: new Brand('M01', 'Le Gaulois'),
|
||||
brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)),
|
||||
isSeasonal: true,
|
||||
weight: null,
|
||||
nutriscore: null,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ use Application\Arcadia\Usecase\IncoSheets\Find\FindIncoSheet;
|
|||
use Application\Arcadia\Usecase\IncoSheets\Find\Input;
|
||||
use Application\Arcadia\Usecase\IncoSheets\Find\Output;
|
||||
use Domain\Models\Entities\Inco\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
use Infra\Adapters\Directories\InMemoryIncoSheetDirectory;
|
||||
|
|
@ -44,7 +47,7 @@ describe('FindIncoSheet Usecase Unit Tests', function (): void {
|
|||
ingredients: new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]),
|
||||
portions: null,
|
||||
allergens: [],
|
||||
brand: new Brand('M01', 'Le Gaulois'),
|
||||
brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)),
|
||||
isSeasonal: true,
|
||||
weight: null,
|
||||
nutriscore: null,
|
||||
|
|
@ -67,7 +70,8 @@ describe('FindIncoSheet Usecase Unit Tests', function (): void {
|
|||
->and($result->id)->toBe($incoId)
|
||||
->and($result->name)->toBe('Poulet Rôti 1kg')
|
||||
->and($result->label)->toBe('Poulet Rôti Classe A')
|
||||
->and($result->brand->label)->toBe('Le Gaulois')
|
||||
->and($result->brand->name->value)->toBe('Le Gaulois')
|
||||
->and($result->brand->code->value)->toBe('LGL01')
|
||||
->and($result->isSeasonal)->toBeTrue();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ use Application\Arcadia\Usecase\IncoSheets\Search\PaginatedShortIncoSheet;
|
|||
use Application\Arcadia\Usecase\IncoSheets\Search\SearchIncoSheets;
|
||||
use Domain\Models\Entities\Inco\IncoSheet;
|
||||
use Domain\Models\Entities\Inco\IncoShortSheet;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
|
|
@ -52,7 +55,7 @@ describe('SearchIncoSheets Usecase Unit Tests', function (): void {
|
|||
ingredients: new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]),
|
||||
portions: null,
|
||||
allergens: [],
|
||||
brand: new Brand('M01', 'Le Gaulois'),
|
||||
brand: new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(1)),
|
||||
isSeasonal: true,
|
||||
weight: null,
|
||||
nutriscore: null,
|
||||
|
|
@ -69,7 +72,7 @@ describe('SearchIncoSheets Usecase Unit Tests', function (): void {
|
|||
query: new IncoIdQuery('12%'),
|
||||
short: false,
|
||||
pagination: Pagination::ofPage(1, 10),
|
||||
brands: 'M01',
|
||||
brands: new BrandCode('LGL01'),
|
||||
);
|
||||
|
||||
// WHEN
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ declare(strict_types=1);
|
|||
namespace Tests\Unit\Domain\Entities;
|
||||
|
||||
use Domain\Models\Entities\Inco\IncoSheet;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\Ingredients;
|
||||
use Shared\DDD\Model\Entity;
|
||||
|
|
@ -19,7 +22,7 @@ describe('IncoSheet Domain Entity Unit Tests', function (): void {
|
|||
$id = new IncoId('12345');
|
||||
$allegations = new Allegations(['Volaille française']);
|
||||
$ingredients = new Ingredients(['Ingrédients' => ['Viande de dinde (66%)']]);
|
||||
$brand = new Brand('M01', 'Le Gaulois');
|
||||
$brand = new IncoBrand(new BrandName('Le Gaulois'), new BrandCode('LGL01'), new ArcadiaId(504));
|
||||
|
||||
// WHEN
|
||||
$sheet = IncoSheet::hydrate(
|
||||
|
|
@ -50,7 +53,9 @@ describe('IncoSheet Domain Entity Unit Tests', function (): void {
|
|||
expect($sheet)->toBeInstanceOf(Entity::class)
|
||||
->and($sheet->id())->toBe($id)
|
||||
->and($sheet->name)->toBe('Poulet Rôti 1kg')
|
||||
->and($sheet->brand->label)->toBe('Le Gaulois')
|
||||
->and($sheet->brand->name->value)->toBe('Le Gaulois')
|
||||
->and($sheet->brand->code->value)->toBe('LGL01')
|
||||
->and($sheet->brand->arcadiaBrandCode->value)->toBe(504)
|
||||
->and($sheet->isSeasonal)->toBeTrue();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Domain\Models\ValueObjects\Inco;
|
||||
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
|
||||
covers(Brand::class);
|
||||
|
||||
describe('Brand Value Object Unit Tests', function (): void {
|
||||
it('instantiates valid Brand VO', function (): void {
|
||||
// GIVEN
|
||||
$code = 'M01';
|
||||
$label = 'Le Gaulois';
|
||||
|
||||
// WHEN
|
||||
$brand = new Brand($code, $label);
|
||||
|
||||
// THEN
|
||||
expect($brand->code)->toBe('M01')
|
||||
->and($brand->label)->toBe('Le Gaulois')
|
||||
->and($brand->code)->toBe('M01')
|
||||
->and($brand->label)->toBe('Le Gaulois');
|
||||
});
|
||||
|
||||
it('throws DomainException when code is empty', function (): void {
|
||||
// GIVEN / WHEN / THEN
|
||||
expect(fn () => new Brand(' ', 'Le Gaulois'))
|
||||
->toThrow(\DomainException::class, 'Brand code must not be empty.');
|
||||
});
|
||||
|
||||
it('throws DomainException when label is empty', function (): void {
|
||||
// GIVEN / WHEN / THEN
|
||||
expect(fn () => new Brand('M01', ' '))
|
||||
->toThrow(\DomainException::class, 'Brand label must not be empty.');
|
||||
});
|
||||
});
|
||||
29
tests/Unit/Domain/Models/ValueObjects/Inco/IncoBrandTest.php
Normal file
29
tests/Unit/Domain/Models/ValueObjects/Inco/IncoBrandTest.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Domain\Models\ValueObjects\Inco;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
|
||||
covers(IncoBrand::class);
|
||||
|
||||
describe('IncoBrand Value Object Unit Tests', function (): void {
|
||||
it('instantiates valid IncoBrand VO', function (): void {
|
||||
// GIVEN
|
||||
$name = new BrandName('Le Gaulois');
|
||||
$code = new BrandCode('LGL01');
|
||||
$arcadiaCode = new ArcadiaId(504);
|
||||
|
||||
// WHEN
|
||||
$brand = new IncoBrand($name, $code, $arcadiaCode);
|
||||
|
||||
// THEN
|
||||
expect($brand->name->value)->toBe('Le Gaulois')
|
||||
->and($brand->code->value)->toBe('LGL01')
|
||||
->and($brand->arcadiaBrandCode->value)->toBe(504);
|
||||
});
|
||||
});
|
||||
|
|
@ -4,34 +4,30 @@ declare(strict_types=1);
|
|||
|
||||
namespace Tests\Unit\Domain\Models\ValueObjects\Inco;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
|
||||
|
||||
covers(IncoSheetFilter::class);
|
||||
|
||||
describe('IncoSheetFilter Value Object', function (): void {
|
||||
it('can be instantiated with brand list filter', function (): void {
|
||||
it('can be instantiated with BrandCode list filter', function (): void {
|
||||
// GIVEN
|
||||
$brands = ['M01', 'M02'];
|
||||
$code1 = new BrandCode('LGL01');
|
||||
$code2 = new BrandCode('MCQ01');
|
||||
|
||||
// WHEN
|
||||
$filter = new IncoSheetFilter(null, ...$brands);
|
||||
$filter = new IncoSheetFilter(null, $code1, $code2);
|
||||
|
||||
// THEN
|
||||
expect($filter->brands)->toBe(['M01', 'M02']);
|
||||
expect($filter->brands)->toHaveCount(2)
|
||||
->and($filter->brands[0])->toBe($code1)
|
||||
->and($filter->brands[1])->toBe($code2);
|
||||
});
|
||||
|
||||
it('throws DomainException when instantiated with all null parameters', function (): void {
|
||||
// GIVEN, WHEN & THEN
|
||||
expect(fn () => new IncoSheetFilter())
|
||||
// THENE
|
||||
->toThrow(\DomainException::class, 'IncoSheetFilter requires at least one filtering criterion.');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException when brands array contains an empty string', function (): void {
|
||||
// GIVEN
|
||||
$invalidBrands = ['M01', ''];
|
||||
|
||||
// WHEN & THEN
|
||||
expect(fn () => new IncoSheetFilter(null, ...$invalidBrands))
|
||||
->toThrow(\InvalidArgumentException::class, 'Every item in brands array must be a non empty string.');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,23 +41,22 @@ describe('BrandResolver Unit Tests', function (): void {
|
|||
);
|
||||
$brandRepository->add($existing);
|
||||
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
$arcadiaBrand = new ArcadiaBrand(new ArcadiaId(1), new BrandName('Le Gaulois'));
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
|
||||
// WHEN
|
||||
$resolved = $resolver->resolve($arcadiaBrand);
|
||||
$resolved = $resolver->resolve(new ArcadiaId(1));
|
||||
|
||||
// THEN
|
||||
expect($resolved)->toBe($existing);
|
||||
});
|
||||
|
||||
it('generates, persists and logs a new brand if not in repository', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void {
|
||||
it('generates, persists and logs a new brand if not in repository but in Arcadia directory', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void {
|
||||
// GIVEN
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
$arcadiaBrand = new ArcadiaBrand(new ArcadiaId(2), new BrandName('Maître CoQ'));
|
||||
$arcadiaDirectory->add(new ArcadiaBrand(new ArcadiaId(2), new BrandName('Maître CoQ')));
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
|
||||
// WHEN
|
||||
$resolved = $resolver->resolve($arcadiaBrand);
|
||||
$resolved = $resolver->resolve(new ArcadiaId(2));
|
||||
|
||||
// THEN
|
||||
expect($resolved->arcadiaId->value)->toBe(2)
|
||||
|
|
@ -67,44 +66,12 @@ describe('BrandResolver Unit Tests', function (): void {
|
|||
expect($brandRepository->findByArcadiaId(new ArcadiaId(2)))->not->toBeNull();
|
||||
});
|
||||
|
||||
it('resolves by ArcadiaId directly', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void {
|
||||
// GIVEN
|
||||
$arcadiaBrand = new ArcadiaBrand(new ArcadiaId(3), new BrandName('Loué'));
|
||||
$arcadiaDirectory->add($arcadiaBrand);
|
||||
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
|
||||
// WHEN
|
||||
$resolved = $resolver->resolveByArcadiaId(new ArcadiaId(3));
|
||||
|
||||
// THEN
|
||||
expect($resolved->arcadiaId->value)->toBe(3)
|
||||
->and($resolved->name->value)->toBe('Loué');
|
||||
});
|
||||
|
||||
it('throws BrandNotFoundException when resolving by unknown ArcadiaId', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void {
|
||||
it('throws BrandNotFoundException when resolving an unknown ArcadiaId', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void {
|
||||
// GIVEN
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
|
||||
// WHEN & THEN
|
||||
expect(fn () => $resolver->resolveByArcadiaId(new ArcadiaId(999)))
|
||||
expect(fn () => $resolver->resolve(new ArcadiaId(999)))
|
||||
->toThrow(BrandNotFoundException::class, 'Arcadia brand with ID 999 not found.');
|
||||
});
|
||||
|
||||
it('resolves all brands from Arcadia directory', function () use ($arcadiaDirectory, $brandRepository, $codeGenerator, $logger): void {
|
||||
// GIVEN
|
||||
$arcadiaDirectory->add(
|
||||
new ArcadiaBrand(new ArcadiaId(1), new BrandName('Le Gaulois')),
|
||||
new ArcadiaBrand(new ArcadiaId(2), new BrandName('Maître CoQ')),
|
||||
);
|
||||
|
||||
$resolver = new BrandResolver($arcadiaDirectory, $brandRepository, $codeGenerator, $logger);
|
||||
|
||||
// WHEN
|
||||
$resolvedList = $resolver->resolveAll();
|
||||
|
||||
// THEN
|
||||
expect($resolvedList)->toHaveCount(2)
|
||||
->and($brandRepository->findAll())->toHaveCount(2);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ declare(strict_types=1);
|
|||
namespace Tests\Unit\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\Brand\BrandName;
|
||||
use Domain\Models\ValueObjects\Inco\Allegations;
|
||||
use Domain\Models\ValueObjects\Inco\Brand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoBrand;
|
||||
use Domain\Models\ValueObjects\Inco\IncoId;
|
||||
use Domain\Models\ValueObjects\Inco\IncoIdQuery;
|
||||
use Domain\Models\ValueObjects\Inco\IncoSheetFilter;
|
||||
|
|
@ -19,7 +22,7 @@ use Shared\Pagination;
|
|||
covers(InMemoryIncoSheetDirectory::class);
|
||||
|
||||
describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
|
||||
function createSheet(string $idStr, string $brandCode, string $brandLabel): IncoSheet
|
||||
function createSheet(string $idStr, string $brandCode, string $brandLabel, int $arcadiaId = 1): IncoSheet
|
||||
{
|
||||
return IncoSheet::hydrate(
|
||||
id: new IncoId($idStr),
|
||||
|
|
@ -34,7 +37,7 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
|
|||
ingredients: new Ingredients(['Ingrédients' => ['Test']]),
|
||||
portions: null,
|
||||
allergens: [],
|
||||
brand: new Brand($brandCode, $brandLabel),
|
||||
brand: new IncoBrand(new BrandName($brandLabel), new BrandCode($brandCode), new ArcadiaId($arcadiaId)),
|
||||
isSeasonal: true,
|
||||
weight: null,
|
||||
nutriscore: null,
|
||||
|
|
@ -49,7 +52,7 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
|
|||
it('finds an inco sheet by id', function (): void {
|
||||
// GIVEN
|
||||
$directory = new InMemoryIncoSheetDirectory();
|
||||
$sheet = createSheet('12345', 'M01', 'Le Gaulois');
|
||||
$sheet = createSheet('12345', 'LGL01', 'Le Gaulois', 1);
|
||||
$directory->add($sheet);
|
||||
|
||||
// WHEN
|
||||
|
|
@ -64,13 +67,13 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
|
|||
it('filters, sorts and paginates inco sheets', function (): void {
|
||||
// GIVEN
|
||||
$directory = new InMemoryIncoSheetDirectory();
|
||||
$sheet1 = createSheet('10001', 'M01', 'Le Gaulois');
|
||||
$sheet2 = createSheet('10002', 'M02', 'Maître CoQ');
|
||||
$sheet1 = createSheet('10001', 'LGL01', 'Le Gaulois', 1);
|
||||
$sheet2 = createSheet('10002', 'MCQ01', 'Maître CoQ', 2);
|
||||
$directory->add($sheet1, $sheet2);
|
||||
|
||||
$pagination = Pagination::ofPage(1, 10);
|
||||
$sorter = new IncoSheetSorter(IncoSheetSortColumn::incoSheetId, \SortDirection::Descending);
|
||||
$filter = new IncoSheetFilter(null, 'M01');
|
||||
$filter = new IncoSheetFilter(null, new BrandCode('LGL01'));
|
||||
|
||||
// WHEN
|
||||
$result = $directory->findAll($pagination, $sorter, $filter);
|
||||
|
|
@ -84,16 +87,16 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
|
|||
it('searches inco sheets by id query and brands', function (): void {
|
||||
// GIVEN
|
||||
$directory = new InMemoryIncoSheetDirectory();
|
||||
$sheet1 = createSheet('12345', 'M01', 'Le Gaulois');
|
||||
$sheet2 = createSheet('12999', 'M01', 'Le Gaulois');
|
||||
$sheet3 = createSheet('54321', 'M02', 'Maître CoQ');
|
||||
$sheet1 = createSheet('12345', 'LGL01', 'Le Gaulois', 1);
|
||||
$sheet2 = createSheet('12999', 'LGL01', 'Le Gaulois', 1);
|
||||
$sheet3 = createSheet('54321', 'MCQ01', 'Maître CoQ', 2);
|
||||
$directory->add($sheet1, $sheet2, $sheet3);
|
||||
|
||||
$query = new IncoIdQuery('12%');
|
||||
$pagination = Pagination::ofPage(1, 10);
|
||||
|
||||
// WHEN
|
||||
$result = $directory->search($query, $pagination, 'M01');
|
||||
$result = $directory->search($query, $pagination, new BrandCode('LGL01'));
|
||||
|
||||
// THEN
|
||||
expect($result->getTotal())->toBe(2)
|
||||
|
|
@ -103,7 +106,7 @@ describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
|
|||
it('clears all sheets', function (): void {
|
||||
// GIVEN
|
||||
$directory = new InMemoryIncoSheetDirectory();
|
||||
$sheet = createSheet('12345', 'M01', 'Le Gaulois');
|
||||
$sheet = createSheet('12345', 'LGL01', 'Le Gaulois', 1);
|
||||
$directory->add($sheet);
|
||||
|
||||
// WHEN
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue