Merge branch 'changes-after-review' into 'refonte'

Changes after review

See merge request ldc-web/projets/mesbr!56
This commit is contained in:
Corentin HUET 2026-10-02 15:18:07 +00:00
commit 4d3c778d17
7 changed files with 68 additions and 100 deletions

View file

@ -13,7 +13,7 @@ final readonly class Input implements UseCaseInput
{
public function __construct(
public Pagination $pagination,
public ?CampaignRequestSortCriteria $sort = null,
public CampaignRequestSortCriteria $sort,
public ?CampaignRequestFilters $filters = null,
) {
}

View file

@ -12,9 +12,18 @@ use Shared\ValueObjects\Money;
*/
final readonly class MoneyAmount implements \Stringable, Equatable
{
private const int MIN_FACE_VALUE_IN_CENTS = 1;
private const int MAX_FACE_VALUE_IN_CENTS = 300;
public function __construct(
public Money $value,
) {
if ($this->value->value < self::MIN_FACE_VALUE_IN_CENTS || $this->value->value > self::MAX_FACE_VALUE_IN_CENTS) {
throw new \DomainException(sprintf(
'Money amount must be between 0.01€ and 3.00€, %s given.',
$this->value,
));
}
}
#[\Override]

View file

@ -17,9 +17,6 @@ use Shared\Enums\Country;
final readonly class RequestedCoupon
{
private const int MIN_FACE_VALUE_IN_CENTS = 1;
private const int MAX_FACE_VALUE_IN_CENTS = 300;
/**
* @param array<CouponValidityInformation> $couponValidityInformation
* @param array<IncoCode> $products
@ -36,21 +33,12 @@ final readonly class RequestedCoupon
public ?PrintRules $printRules = null,
public ?GS1 $gs1 = null,
) {
$money = $this->moneyAmount->value;
foreach ($this->couponValidityInformation as $validityInformation) {
if (!$validityInformation instanceof CouponValidityInformation) {
throw new \DomainException('Coupon validity information must be CouponValidityInformation instances.');
}
}
if ($money->value < self::MIN_FACE_VALUE_IN_CENTS || $money->value > self::MAX_FACE_VALUE_IN_CENTS) {
throw new \DomainException(sprintf(
'Coupon face value must be between 0.01€ and 3.00€, %s given.',
$money,
));
}
if (!$this->hasValidityInformationFor(Country::FRANCE)) {
throw new \DomainException('A coupon requires validity information for France.');
}

View file

@ -25,7 +25,7 @@ interface CampaignRequestRepository extends Repository
*/
public function findAll(
Pagination $pagination,
?CampaignRequestSortCriteria $sortCriteria = null,
CampaignRequestSortCriteria $sortCriteria,
?CampaignRequestFilters $filters = null,
): Paginated;
}

View file

@ -71,11 +71,18 @@ describe('list campaigns request use case', function (): void {
new PrintRules(maxPerCustomer: 3),
new GS1('01234567890128', BarcodeFormat::EAN13)
);
$this->pagination = Pagination::firstPage();
$this->sort = new CampaignRequestSortCriteria(
clauses: [
new CampaignRequestSortClause(field: CampaignRequestSortField::CreatedAt, direction: \SortDirection::Descending),
]
);
});
it('maps repository campaigns request to list items, preserving pagination and sort', function (): void {
// GIVEN
$pagination = Pagination::firstPage();
$campaignRequest1 = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
@ -94,7 +101,7 @@ describe('list campaigns request use case', function (): void {
$this->couponB,
CampaignRequestStatus::CREATED,
);
$paginated = new Paginated($pagination, [$campaignRequest1, $campaignRequest2], 2);
$paginated = new Paginated($this->pagination, [$campaignRequest1, $campaignRequest2], 2);
$sort = new CampaignRequestSortCriteria(
clauses: [
@ -103,12 +110,12 @@ describe('list campaigns request use case', function (): void {
]
);
$filters = new CampaignRequestFilters($campaignRequest2->brandCode, CampaignRequestStatus::CREATED);
$input = new Input(pagination: $pagination, sort: $sort, filters: $filters);
$input = new Input(pagination: $this->pagination, sort: $sort, filters: $filters);
$repository = \Mockery::mock(CampaignRequestRepository::class);
$repository->shouldReceive('findAll')
->once()
->with($pagination, CampaignRequestSortCriteria::class, CampaignRequestFilters::class)
->with($this->pagination, CampaignRequestSortCriteria::class, CampaignRequestFilters::class)
->andReturn($paginated);
$useCase = new ListCampaignsRequest($repository);
@ -128,12 +135,11 @@ describe('list campaigns request use case', function (): void {
->and($items[0]->campaignType)->toBe($campaignRequest1->campaignType)
->and($items[0]->status)->toBe($campaignRequest1->status)
->and($result->campaignsRequest->getTotal())->toBe(2)
->and($result->campaignsRequest->getPagination())->toBe($pagination);
->and($result->campaignsRequest->getPagination())->toBe($this->pagination);
});
it('lists campaigns request with filters but without sort', function (): void {
it('lists campaigns request with filters', function (): void {
// GIVEN
$pagination = Pagination::firstPage();
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
@ -143,15 +149,15 @@ describe('list campaigns request use case', function (): void {
$this->couponA,
CampaignRequestStatus::CREATED,
);
$paginated = new Paginated($pagination, [$campaignRequest], 1);
$paginated = new Paginated($this->pagination, [$campaignRequest], 1);
$filters = new CampaignRequestFilters($campaignRequest->brandCode, CampaignRequestStatus::CREATED);
$input = new Input(pagination: $pagination, filters: $filters);
$input = new Input(pagination: $this->pagination, sort: $this->sort, filters: $filters);
$repository = \Mockery::mock(CampaignRequestRepository::class);
$repository->shouldReceive('findAll')
->once()
->with($pagination, null, $filters)
->with($this->pagination, $this->sort, $filters)
->andReturn($paginated);
$useCase = new ListCampaignsRequest($repository);
@ -167,12 +173,11 @@ describe('list campaigns request use case', function (): void {
->and($items[0]->name)->toBe($campaignRequest->name)
->and($items[0]->status)->toBe($campaignRequest->status)
->and($result->campaignsRequest->getTotal())->toBe(1)
->and($result->campaignsRequest->getPagination())->toBe($pagination);
->and($result->campaignsRequest->getPagination())->toBe($this->pagination);
});
it('lists campaigns request with sort but without filters', function (): void {
// GIVEN
$pagination = Pagination::firstPage();
$campaignRequest1 = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
@ -191,19 +196,19 @@ describe('list campaigns request use case', function (): void {
$this->couponB,
CampaignRequestStatus::CREATED,
);
$paginated = new Paginated($pagination, [$campaignRequest2, $campaignRequest1], 2);
$paginated = new Paginated($this->pagination, [$campaignRequest2, $campaignRequest1], 2);
$sort = new CampaignRequestSortCriteria(
clauses: [
new CampaignRequestSortClause(field: CampaignRequestSortField::BrandCode, direction: \SortDirection::Descending),
]
);
$input = new Input(pagination: $pagination, sort: $sort);
$input = new Input(pagination: $this->pagination, sort: $sort);
$repository = \Mockery::mock(CampaignRequestRepository::class);
$repository->shouldReceive('findAll')
->once()
->with($pagination, $sort, null)
->with($this->pagination, $sort, null)
->andReturn($paginated);
$useCase = new ListCampaignsRequest($repository);
@ -217,19 +222,18 @@ describe('list campaigns request use case', function (): void {
->and($items[0]->brandCode)->toBe($campaignRequest2->brandCode)
->and($items[1]->brandCode)->toBe($campaignRequest1->brandCode)
->and($result->campaignsRequest->getTotal())->toBe(2)
->and($result->campaignsRequest->getPagination())->toBe($pagination);
->and($result->campaignsRequest->getPagination())->toBe($this->pagination);
});
it('returns an empty list without error when there are no campaigns request', function (): void {
// GIVEN
$pagination = Pagination::firstPage();
$paginated = new Paginated($pagination, [], 0);
$input = new Input(pagination: $pagination);
$paginated = new Paginated($this->pagination, [], 0);
$input = new Input(pagination: $this->pagination, sort: $this->sort);
$repository = \Mockery::mock(CampaignRequestRepository::class);
$repository->shouldReceive('findAll')
->once()
->with($pagination, null, null)
->with($this->pagination, $this->sort, null)
->andReturn($paginated);
$useCase = new ListCampaignsRequest($repository);

View file

@ -12,8 +12,11 @@ covers(MoneyAmount::class);
describe('money amount', function (): void {
beforeEach(function (): void {
$this->money = new Money(Currency::EUR, 300);
$this->secondMoney = new Money(Currency::EUR, 500);
$this->money = new Money(Currency::EUR, 300);
$this->secondMoney = new Money(Currency::EUR, 200);
$this->minimumMoney = new Money(Currency::EUR, 1);
$this->zeroMoney = new Money(Currency::EUR, 0);
$this->aboveLimitMoney = new Money(Currency::EUR, 301);
});
it('wraps a Money value', function (): void {
@ -54,4 +57,32 @@ describe('money amount', function (): void {
// THEN
expect((string) $amount)->toBe((string) $amount->value);
});
it('accepts the minimum amount of 0.01€', function (): void {
// GIVEN / WHEN
$amount = new MoneyAmount($this->minimumMoney);
// THEN
expect($amount->value)->toBe($this->minimumMoney);
});
it('accepts the maximum amount of 3.00€', function (): void {
// GIVEN / WHEN
$amount = new MoneyAmount($this->money);
// THEN
expect($amount->value)->toBe($this->money);
});
it('throws when the amount is zero', function (): void {
// GIVEN / WHEN / THEN
expect(fn () => new MoneyAmount($this->zeroMoney))
->toThrow(\DomainException::class, 'Money amount must be between 0.01€ and 3.00€');
});
it('throws when the amount is above 3.00€', function (): void {
// GIVEN / WHEN / THEN
expect(fn () => new MoneyAmount($this->aboveLimitMoney))
->toThrow(\DomainException::class, 'Money amount must be between 0.01€ and 3.00€');
});
});

View file

@ -188,70 +188,6 @@ describe('requested coupon', function (): void {
))->toThrow(\DomainException::class, 'Coupon validity information must be CouponValidityInformation instances.');
});
it('accepts the minimum face value of 0.01€', function (): void {
// GIVEN
$moneyAmount = new MoneyAmount(new Money(Currency::EUR, 1));
// WHEN
$coupon = new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: $moneyAmount,
processingCode: $this->processingCode,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
);
// THEN
expect($coupon->moneyAmount)->toBe($moneyAmount);
});
it('accepts the maximum face value of 3.00€', function (): void {
// GIVEN
$moneyAmount = new MoneyAmount(new Money(Currency::EUR, 300));
// WHEN
$coupon = new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: $moneyAmount,
processingCode: $this->processingCode,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
);
// THEN
expect($coupon->moneyAmount)->toBe($moneyAmount);
});
it('throws when the face value is zero', function (): void {
// GIVEN / WHEN / THEN
expect(fn () => new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: new MoneyAmount(new Money(Currency::EUR, 0)),
processingCode: $this->processingCode,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
))->toThrow(\DomainException::class, 'Coupon face value must be between 0.01€ and 3.00€');
});
it('throws when the face value is above 3.00€', function (): void {
// GIVEN / WHEN / THEN
expect(fn () => new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: new MoneyAmount(new Money(Currency::EUR, 301)),
processingCode: $this->processingCode,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
))->toThrow(\DomainException::class, 'Coupon face value must be between 0.01€ and 3.00€');
});
it('throws when a gs1 coupon has no benelux validity information', function (): void {
// GIVEN / WHEN / THEN
expect(fn () => new RequestedCoupon(