diff --git a/src/Application/UseCases/Coupon/Update/Input.php b/src/Application/UseCases/Coupon/Update/Input.php index fc8f41b..421b17a 100644 --- a/src/Application/UseCases/Coupon/Update/Input.php +++ b/src/Application/UseCases/Coupon/Update/Input.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace Application\UseCases\Coupon\Update; use Domain\Models\ValueObjects\Coupon\CouponCode; +use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray; use Domain\Models\ValueObjects\Coupon\CouponPicture; +use Domain\Models\ValueObjects\Coupon\CouponValidityInformationArray; use Shared\DDD\UseCases\Input as UseCaseInput; use Webmozart\Assert\Assert; @@ -14,9 +16,13 @@ final readonly class Input implements UseCaseInput public function __construct( public CouponCode $code, public ?CouponPicture $picture = null, + public ?CouponValidityInformationArray $validityInformations = null, + public ?CouponLegalInformationArray $legalInformations = null, ) { Assert::true( - $this->picture instanceof CouponPicture, + $this->picture instanceof CouponPicture + || $this->validityInformations instanceof CouponValidityInformationArray + || $this->legalInformations instanceof CouponLegalInformationArray, 'At least one field must be provided to update the coupon.', ); } diff --git a/src/Application/UseCases/Coupon/Update/UpdateCoupon.php b/src/Application/UseCases/Coupon/Update/UpdateCoupon.php index a3b50ed..6d44e0b 100644 --- a/src/Application/UseCases/Coupon/Update/UpdateCoupon.php +++ b/src/Application/UseCases/Coupon/Update/UpdateCoupon.php @@ -8,7 +8,9 @@ use Application\UseCases\Coupon\Update\Output\CouponNotFound; use Application\UseCases\Coupon\Update\Output\CouponUpdated; use Application\UseCases\Coupon\Update\Output\Output; use Domain\Models\Entities\Coupon; +use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray; use Domain\Models\ValueObjects\Coupon\CouponPicture; +use Domain\Models\ValueObjects\Coupon\CouponValidityInformationArray; use Domain\Ports\Repositories\CouponRepository; use Shared\DDD\UseCases\Input as BaseInput; use Shared\DDD\UseCases\Transactional; @@ -38,6 +40,16 @@ final readonly class UpdateCoupon implements UseCase, Transactional $coupon = $coupon->changePicture($input->picture); } + if ( + $input->validityInformations instanceof CouponValidityInformationArray + || $input->legalInformations instanceof CouponLegalInformationArray + ) { + $coupon = $coupon->changeInformations( + $input->validityInformations ?? $coupon->validityInformations, + $input->legalInformations ?? $coupon->legalInformations, + ); + } + $this->repository->save($coupon); return new CouponUpdated( diff --git a/src/Domain/Models/Entities/Coupon.php b/src/Domain/Models/Entities/Coupon.php index f7d8fc9..6648fb4 100644 --- a/src/Domain/Models/Entities/Coupon.php +++ b/src/Domain/Models/Entities/Coupon.php @@ -165,4 +165,26 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier updatedAt: DatesHelper::now(), ); } + + public function changeInformations(CouponValidityInformationArray $validityInformations, CouponLegalInformationArray $legalInformations): self + { + return new self( + id: $this->id, + code: $this->code, + name: $this->name, + amount: $this->amount, + picture: $this->picture, + diffusion: $this->diffusion, + validityInformations: $validityInformations, + legalInformations: $legalInformations, + processingCode: $this->processingCode, + brandCode: $this->brandCode, + createdBy: $this->createdBy, + products: $this->products, + movements: $this->movements, + quantity: $this->quantity, + availabilityCalendar: $this->availabilityCalendar, + updatedAt: DatesHelper::now(), + ); + } } diff --git a/src/Domain/Models/ValueObjects/Coupon/CouponCountryCoverage.php b/src/Domain/Models/ValueObjects/Coupon/CouponCountryCoverage.php index b00aca7..f336808 100644 --- a/src/Domain/Models/ValueObjects/Coupon/CouponCountryCoverage.php +++ b/src/Domain/Models/ValueObjects/Coupon/CouponCountryCoverage.php @@ -29,6 +29,14 @@ final readonly class CouponCountryCoverage throw new \DomainException(sprintf('A coupon requires legal information for %s.', $country->name)); } } + + if (count($validityInformations->value) !== count($processingCodes->value)) { + throw new \DomainException('A coupon requires exactly one validity information per diffusion country.'); + } + + if (count($legalInformations->value) !== count($processingCodes->value)) { + throw new \DomainException('A coupon requires exactly one legal information per diffusion country.'); + } } private static function hasInformationFor( diff --git a/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php b/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php index 0ef1d32..e37cdca 100644 --- a/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php +++ b/tests/Unit/Application/UseCases/Coupon/Update/UpdateCouponTest.php @@ -142,6 +142,46 @@ describe('update coupon use case', function (): void { expect($result)->toBeInstanceOf(CouponNotFound::class); }); + it('updates the legal and validity information and keeps the other fields', function () use ($makeCoupon): void { + // GIVEN + $coupon = $makeCoupon(); + $newValidityInformations = new CouponValidityInformationArray([ + new CouponValidityInformation('New validity information.', Country::FRANCE), + ]); + $newLegalInformations = new CouponLegalInformationArray([ + new CouponLegalInformation('New legal information.', Country::FRANCE), + ]); + $input = new Input( + code: $coupon->code, + validityInformations: $newValidityInformations, + legalInformations: $newLegalInformations, + ); + + $repository = \Mockery::mock(CouponRepository::class); + $repository->shouldReceive('findByCode')->once()->with($coupon->code)->andReturn($coupon); + $repository->shouldReceive('save')->once()->with(\Mockery::on( + fn (Coupon $saved): bool => $saved->validityInformations === $newValidityInformations + && $saved->legalInformations === $newLegalInformations + && $saved->id === $coupon->id + && $saved->name === $coupon->name + && $saved->amount === $coupon->amount + && $saved->picture === $coupon->picture + && $saved->diffusion === $coupon->diffusion + && $saved->movements === $coupon->movements + && $saved->updatedAt instanceof \DateTimeInterface, + )); + + $useCase = new UpdateCoupon($repository); + + // WHEN + $result = $useCase->execute($input); + + // THEN + expect($result)->toBeInstanceOf(CouponUpdated::class) + ->and($result->validityInformations)->toBe($newValidityInformations) + ->and($result->legalInformations)->toBe($newLegalInformations); + }); + it('rejects an input with nothing to update', function (): void { // GIVEN $code = new CouponCode('COU01'); diff --git a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCountryCoverageTest.php b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCountryCoverageTest.php index 1a7bcf0..d6dbe67 100644 --- a/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCountryCoverageTest.php +++ b/tests/Unit/Domain/Models/ValueObjects/Coupon/CouponCountryCoverageTest.php @@ -90,6 +90,40 @@ describe('coupon country coverage', function (): void { expect($action)->toThrow(\DomainException::class, 'A coupon requires legal information for BENELUX.'); }); + it('rejects validity information for a country where the coupon is not diffused', function () use ( + $franceCode, + $franceValidity, + $beneluxValidity, + $franceLegal, + ): void { + // GIVEN / WHEN + $action = fn () => CouponCountryCoverage::assert( + new ProcessingCodeArray([$franceCode]), + new CouponValidityInformationArray([$franceValidity, $beneluxValidity]), + new CouponLegalInformationArray([$franceLegal]), + ); + + // THEN + expect($action)->toThrow(\DomainException::class, 'A coupon requires exactly one validity information per diffusion country.'); + }); + + it('rejects legal information for a country where the coupon is not diffused', function () use ( + $franceCode, + $franceValidity, + $franceLegal, + $beneluxLegal, + ): void { + // GIVEN / WHEN + $action = fn () => CouponCountryCoverage::assert( + new ProcessingCodeArray([$franceCode]), + new CouponValidityInformationArray([$franceValidity]), + new CouponLegalInformationArray([$franceLegal, $beneluxLegal]), + ); + + // THEN + expect($action)->toThrow(\DomainException::class, 'A coupon requires exactly one legal information per diffusion country.'); + }); + it('cannot be instantiated from the outside', function (): void { // GIVEN $reflection = new \ReflectionClass(CouponCountryCoverage::class);