refactor: ♻️ update entities, related VOs and tests

This commit is contained in:
Corentin Huet 2026-09-29 17:12:16 +02:00
parent d97ca22d67
commit 2bfdadb8cb
32 changed files with 1054 additions and 722 deletions

View file

@ -44,9 +44,9 @@ final readonly class FindCampaignRequest implements UseCase
return new CampaignRequestFound(
brand: $brand,
name: $campaignRequest->name,
applicant: $campaignRequest->applicant,
campaignType: $campaignRequest->campaignType,
printRules: $campaignRequest->printRules,
requestedCoupon: $campaignRequest->requestedCoupon,
status: $campaignRequest->status,
rejectionReason: $campaignRequest->rejectionReason,

View file

@ -4,21 +4,21 @@ declare(strict_types=1);
namespace Application\UseCases\CampaignRequest\Find\Output;
use Domain\Models\Entities\Brand;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Models\Entities\Brand;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
final readonly class CampaignRequestFound implements Output
{
public function __construct(
public Brand $brand,
public CampaignName $name,
public Applicant $applicant,
public CampaignRequestType $campaignType,
public ?PrintRules $printRules,
public RequestedCoupon $requestedCoupon,
public CampaignRequestStatus $status,
public ?RejectionReason $rejectionReason,

View file

@ -7,6 +7,7 @@ namespace Application\UseCases\CampaignRequest\List;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
final readonly class CampaignRequestListItem

View file

@ -33,6 +33,7 @@ final readonly class ListCampaignsRequest implements UseCase
$items = array_map(
static fn (CampaignRequest $campaignRequest): CampaignRequestListItem => new CampaignRequestListItem(
brandCode: $campaignRequest->brandCode,
name: $campaignRequest->name,
applicant: $campaignRequest->applicant,
campaignType: $campaignRequest->campaignType,
status: $campaignRequest->status

View file

@ -11,24 +11,23 @@ use Domain\Models\ValueObjects\Campaign\CampaignId;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Shared\DDD\Models\AggregateRoot;
use Shared\DDD\Models\HasTimestampIdentifier;
use Shared\Enums\Country;
/**
* @implements \Shared\DDD\Models\AggregateRoot<\Domain\Models\ValueObjects\Campaign\CampaignId>
*/
final readonly class Campaign implements AggregateRoot, HasTimestampIdentifier
{
/**
* @param array<CampaignCoupon> $coupons
*/
private function __construct(
public CampaignId $id,
public CampaignCode $code,
public Tag $tag,
public CampaignName $name,
public bool $publication,
public bool $printAllAtOnce,
public Country $availableIn,
/** @var array<int, CampaignCoupon> $coupons */
public array $coupons,
public BrandCode $brand,
public BrandCode $brandCode,
public ?\DateTimeInterface $updatedAt = null,
) {
foreach ($this->coupons as $coupon) {
@ -54,59 +53,50 @@ final readonly class Campaign implements AggregateRoot, HasTimestampIdentifier
}
/**
* @param array<int, CampaignCoupon> $coupons
* @param array<CampaignCoupon> $coupons
*/
public static function create(
CampaignCode $code,
Tag $tag,
CampaignName $name,
bool $publication,
bool $printAllAtOnce,
Country $availableIn,
/** @var array<int, CampaignCoupon> $coupons */
array $coupons,
BrandCode $brand,
BrandCode $brandCode,
?\DateTimeInterface $updatedAt = null,
): Campaign {
return new self(
id: CampaignId::generate(),
code: $code,
tag: $tag,
name: $name,
publication: $publication,
printAllAtOnce: $printAllAtOnce,
availableIn: $availableIn,
coupons: $coupons,
brand: $brand,
brandCode: $brandCode,
updatedAt: $updatedAt,
);
}
/**
* @param array<int, CampaignCoupon> $coupons
* @param array<CampaignCoupon> $coupons
*/
public static function hydrate(
CampaignId $id,
CampaignCode $code,
Tag $tag,
CampaignName $name,
bool $publication,
bool $printAllAtOnce,
Country $availableIn,
array $coupons,
BrandCode $brand,
BrandCode $brandCode,
?\DateTimeInterface $updatedAt = null,
): self {
return new self(
id: $id,
code: $code,
tag: $tag,
name: $name,
publication: $publication,
printAllAtOnce: $printAllAtOnce,
availableIn: $availableIn,
coupons: $coupons,
brand: $brand,
brandCode: $brandCode,
updatedAt: $updatedAt,
);
}

View file

@ -7,10 +7,10 @@ namespace Domain\Models\Entities;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Shared\DDD\Models\AggregateRoot;
use Shared\DDD\Models\HasTimestampIdentifier;
@ -24,9 +24,9 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
private function __construct(
public CampaignRequestId $id,
public BrandCode $brandCode,
public CampaignName $name,
public Applicant $applicant,
public CampaignRequestType $campaignType,
public ?PrintRules $printRules,
public RequestedCoupon $requestedCoupon,
public CampaignRequestStatus $status,
public ?RejectionReason $rejectionReason = null,
@ -60,9 +60,9 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
public static function hydrate(
CampaignRequestId $id,
BrandCode $brandCode,
CampaignName $name,
Applicant $applicant,
CampaignRequestType $campaignType,
?PrintRules $printRules,
RequestedCoupon $requestedCoupon,
CampaignRequestStatus $status,
?RejectionReason $rejectionReason = null,
@ -71,9 +71,9 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
return new self(
id: $id,
brandCode: $brandCode,
name: $name,
applicant: $applicant,
campaignType: $campaignType,
printRules: $printRules,
requestedCoupon: $requestedCoupon,
status: $status,
rejectionReason: $rejectionReason,
@ -90,9 +90,9 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
return new self(
id: $this->id,
brandCode: $this->brandCode,
name: $this->name,
applicant: $this->applicant,
campaignType: $this->campaignType,
printRules: $this->printRules,
requestedCoupon: $this->requestedCoupon,
status: CampaignRequestStatus::REJECTED,
rejectionReason: $reason,

View file

@ -7,15 +7,16 @@ namespace Domain\Models\Entities;
use Domain\Models\ValueObjects\Actor\ActorId;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Coupon\CouponAvailabilityCalendar;
use Domain\Models\ValueObjects\Coupon\CouponCode;
use Domain\Models\ValueObjects\Coupon\CouponDiffusion;
use Domain\Models\ValueObjects\Coupon\CouponId;
use Domain\Models\ValueObjects\Coupon\CouponLegalInformation;
use Domain\Models\ValueObjects\Coupon\CouponProduct;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\ProcessingCode;
use Shared\DDD\Models\AggregateRoot;
@ -26,27 +27,37 @@ use Shared\DDD\Models\HasTimestampIdentifier;
*/
final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier
{
/**
* @param array<CouponValidityInformation> $couponValidityInformation
* @param array<CouponLegalInformation> $couponLegalInformation
* @param array<IncoCode> $products
*/
private function __construct(
public CouponId $id,
public CouponCode $code,
public CouponName $name,
public bool $status,
public CouponProductName $productName,
public MoneyAmount $amount,
public CouponPicture $picture,
public CouponProduct $product,
public CouponDiffusion $diffusion,
public CouponAvailabilityCalendar $availability,
public CouponValidity $validity,
/** @var array<int, CouponLegalInformation> $legalInformation */
public array $legalInformation,
public ProcessingCode $code,
public array $couponValidityInformation,
public array $couponLegalInformation,
public ProcessingCode $processingCode,
public BrandCode $brandCode,
public ActorId $createdBy,
public array $products = [],
public ?CouponAvailabilityCalendar $availabilityCalendar = null,
public ?GS1 $gtin = null,
public ?\DateTimeInterface $updatedAt = null,
) {
foreach ($this->legalInformation as $legal) {
if (!$legal instanceof CouponLegalInformation) {
foreach ($this->couponValidityInformation as $validityInformation) {
if (!$validityInformation instanceof CouponValidityInformation) {
throw new \DomainException('Coupon validity information must be CouponValidityInformation instances.');
}
}
foreach ($this->couponLegalInformation as $legalInformation) {
if (!$legalInformation instanceof CouponLegalInformation) {
throw new \DomainException('Coupon legal information must be CouponLegalInformation instances.');
}
}
@ -68,81 +79,85 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier
}
/**
* @param array<int, CouponLegalInformation> $legalInformation
* @param array<CouponValidityInformation> $couponValidityInformation
* @param array<CouponLegalInformation> $couponLegalInformation
* @param array<IncoCode> $products
*/
public static function create(
CouponCode $code,
CouponName $name,
bool $status,
CouponProductName $productName,
MoneyAmount $amount,
CouponPicture $picture,
CouponProduct $product,
CouponDiffusion $diffusion,
CouponAvailabilityCalendar $availability,
CouponValidity $validity,
array $legalInformation,
ProcessingCode $code,
array $couponValidityInformation,
array $couponLegalInformation,
ProcessingCode $processingCode,
BrandCode $brandCode,
ActorId $createdBy,
array $products = [],
?CouponAvailabilityCalendar $availabilityCalendar = null,
?GS1 $gtin = null,
?\DateTimeInterface $updatedAt = null,
): Coupon {
return new self(
id: CouponId::generate(),
code: $code,
name: $name,
status: $status,
productName: $productName,
amount: $amount,
picture: $picture,
product: $product,
diffusion: $diffusion,
availability: $availability,
validity: $validity,
legalInformation: $legalInformation,
code: $code,
couponValidityInformation: $couponValidityInformation,
couponLegalInformation: $couponLegalInformation,
processingCode: $processingCode,
brandCode: $brandCode,
createdBy: $createdBy,
products: $products,
availabilityCalendar: $availabilityCalendar,
gtin: $gtin,
updatedAt: $updatedAt,
);
}
/**
* @param array<int, CouponLegalInformation> $legalInformation
* @param array<CouponValidityInformation> $couponValidityInformation
* @param array<CouponLegalInformation> $couponLegalInformation
* @param array<IncoCode> $products
*/
public static function hydrate(
CouponId $id,
CouponCode $code,
CouponName $name,
bool $status,
CouponProductName $productName,
MoneyAmount $amount,
CouponPicture $picture,
CouponProduct $product,
CouponDiffusion $diffusion,
CouponAvailabilityCalendar $availability,
CouponValidity $validity,
array $legalInformation,
ProcessingCode $code,
array $couponValidityInformation,
array $couponLegalInformation,
ProcessingCode $processingCode,
BrandCode $brandCode,
ActorId $createdBy,
array $products = [],
?CouponAvailabilityCalendar $availabilityCalendar = null,
?GS1 $gtin = null,
?\DateTimeInterface $updatedAt = null,
): self {
return new self(
id: $id,
code: $code,
name: $name,
status: $status,
productName: $productName,
amount: $amount,
picture: $picture,
product: $product,
diffusion: $diffusion,
availability: $availability,
validity: $validity,
legalInformation: $legalInformation,
code: $code,
couponValidityInformation: $couponValidityInformation,
couponLegalInformation: $couponLegalInformation,
processingCode: $processingCode,
brandCode: $brandCode,
createdBy: $createdBy,
products: $products,
availabilityCalendar: $availabilityCalendar,
gtin: $gtin,
updatedAt: $updatedAt,
);

View file

@ -4,17 +4,19 @@ declare(strict_types=1);
namespace Domain\Models\ValueObjects\Campaign;
use Domain\Models\ValueObjects\CountryAuthorized;
use Domain\Models\ValueObjects\Coupon\CouponQuantity;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\Stock\StockId;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
final readonly class CampaignCoupon
{
public function __construct(
public ProcessingCode $code,
public StockId $qty,
public WarningThresholdQuantity $warningThresholdQty,
public ProcessingCode $processingCode,
public CouponQuantity $quantity,
public CountryAuthorized $availableIn,
public ?WarningThresholdQuantity $warningThresholdQuantity = null,
public ?PrintRules $printRules = null,
) {
}

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects\Coupon;
final readonly class CouponCode
{
private const string REGEX = '/^[A-Z]{3}\d{2}$/';
public function __construct(
public string $value,
) {
if (! preg_match(self::REGEX, $value)) {
throw new \DomainException(
"Invalid code '{$value}' does not match pattern " . self::REGEX,
);
}
if ($value[3] === $value[4]) {
throw new \DomainException(
"Invalid code '{$value}' has the same digit repeated",
);
}
}
}

View file

@ -4,10 +4,9 @@ declare(strict_types=1);
namespace Domain\Models\ValueObjects\Coupon;
use Shared\DDD\Models\Equatable;
use Shared\Enums\Country;
final readonly class CouponLegalInformation implements Equatable
final readonly class CouponLegalInformation
{
public function __construct(
public string $legalText,
@ -17,10 +16,4 @@ final readonly class CouponLegalInformation implements Equatable
throw new \DomainException('Legal text cannot be empty.');
}
}
#[\Override]
public function equals(mixed $other): bool
{
return $other instanceof self && $this->legalText === $other->legalText && $this->forLocale === $other->forLocale;
}
}

View file

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects\Coupon;
use Domain\Models\ValueObjects\IncoCode;
final readonly class CouponProduct
{
public function __construct(
public string $name,
/** @var IncoCode[] */
public array $incoCodes,
/** @var CouponProductInfo[] */
public array $labels,
) {
if (mb_trim($this->name) === '') {
throw new \DomainException('Coupon Product name cannot be empty.');
}
}
}

View file

@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects\Coupon;
use Shared\DDD\Models\Equatable;
use Shared\Enums\Country;
final readonly class CouponProductInfo implements Equatable
{
public function __construct(
public string $name,
public string $range,
public string $validityText,
public Country $forLocale
) {
$trimmedName = mb_trim($this->name);
if ($trimmedName === '') {
throw new \DomainException('Product Info name cannot be empty.');
}
$trimmedRange = mb_trim($this->range);
if ($trimmedRange === '') {
throw new \DomainException('Product Info range cannot be empty.');
}
$trimmedValidityText = mb_trim($this->validityText);
if ($trimmedValidityText === '') {
throw new \DomainException('Product Info validity text cannot be empty.');
}
}
#[\Override]
public function equals(mixed $other): bool
{
return $other instanceof self
&& $this->name === $other->name
&& $this->range === $other->range
&& $this->validityText === $other->validityText
&& $this->forLocale === $other->forLocale;
}
}

View file

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects\Coupon;
final readonly class CouponQuantity
{
public function __construct(
public int $quantity,
) {
if ($this->quantity <= 0) {
throw new \DomainException('CouponQuantity quantity must be positive.');
}
}
}

View file

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects\Coupon;
use Domain\Models\ValueObjects\CountryAuthorized;
final readonly class CouponValidityInformation
{
public function __construct(
public string $validityText,
public CountryAuthorized $forLocale
) {
if (mb_trim($this->validityText) === '') {
throw new \DomainException('Validity text cannot be empty.');
}
}
}

View file

@ -4,19 +4,12 @@ declare(strict_types=1);
namespace Domain\Models\ValueObjects;
use Shared\DDD\Models\Equatable;
final readonly class CouponPicture implements Equatable
final readonly class CouponPicture
{
public function __construct(
public PicturePath $product,
public PicturePath $thumbnail
public PicturePath $decoration,
public PicturePath $thumbnail,
public PicturePath $inactiveThumbnail
) {
}
#[\Override]
public function equals(mixed $other): bool
{
return $other instanceof self && $this->product->equals($other->product) && $this->thumbnail->equals($other->thumbnail);
}
}

View file

@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects;
final readonly class CouponProductName
{
public function __construct(
public string $value,
) {
if (mb_trim($this->value) === '') {
throw new \DomainException('Coupon product name cannot be empty.');
}
}
}

View file

@ -5,28 +5,41 @@ declare(strict_types=1);
namespace Domain\Models\ValueObjects\RequestedCoupon;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
final readonly class RequestedCoupon
{
/**
* @param array<CouponValidityInformation> $couponValidityInformation
* @param array<IncoCode> $products
*/
public function __construct(
public CouponName $name,
public CouponProductName $productName,
public RequestedCouponQuantity $quantity,
public WarningThresholdQuantity $warningThresholdQuantity,
public Period $period,
public Period $diffusion,
public MoneyAmount $moneyAmount,
public ProcessingCode $processingCode,
public CouponPicture $couponPicture,
public ?GS1 $gs1,
public CouponValidity $couponValidity
public CouponValidity $couponValidity,
public array $couponValidityInformation,
public array $products = [],
public ?PrintRules $printRules = null,
public ?GS1 $gs1 = null,
public ?WarningThresholdQuantity $warningThresholdQuantity = null,
) {
foreach ($this->couponValidityInformation as $validityInformation) {
if (!$validityInformation instanceof CouponValidityInformation) {
throw new \DomainException('Coupon validity information must be CouponValidityInformation instances.');
}
}
}
}

View file

@ -9,36 +9,39 @@ use Application\UseCases\CampaignRequest\Find\Input;
use Application\UseCases\CampaignRequest\Find\Output\CampaignRequestFound;
use Application\UseCases\CampaignRequest\Find\Output\CampaignRequestNotFound;
use Application\UseCases\CampaignRequest\Find\Output\Output;
use DateTime;
use Domain\Enums\BarcodeFormat;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Enums\Country;
use Domain\Exceptions\BrandNotFoundException;
use Domain\Models\Entities\Brand;
use Domain\Models\Entities\CampaignRequest;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Brand\BrandId;
use Domain\Models\ValueObjects\Brand\BrandName;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Domain\Ports\Repositories\BrandRepository;
use Domain\Ports\Repositories\CampaignRequestRepository;
use Shared\DDD\UseCases\Input as BaseInput;
use Shared\Enums\Currency;
use DateTime;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Shared\ValueObjects\Email;
use Shared\ValueObjects\Money;
use Shared\ValueObjects\Rcs;
@ -49,15 +52,21 @@ describe('find campaign request use case', function (): void {
beforeEach(function (): void {
$this->requestedCoupon = new RequestedCoupon(
new CouponName('Coupon A'),
new CouponProductName('Product A'),
new RequestedCouponQuantity(100),
new WarningThresholdQuantity(10),
new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')),
new MoneyAmount(new Money(Currency::EUR, 300)),
new ProcessingCode('PC1'),
new CouponPicture(new PicturePath('coupons/product.jpg'), new PicturePath('coupons/thumbnail.jpg')),
new CouponPicture(
new PicturePath('coupons/product.jpg'),
new PicturePath('coupons/thumbnail.jpg'),
new PicturePath('coupons/inactive_thumbnail.jpg')
),
new CouponValidity(30, new DateTime('2026-02-01')),
[new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', new CountryAuthorized(Country::FRANCE))],
[new IncoCode(35784), new IncoCode(15522)],
new PrintRules(maxPerCustomer: 3),
new GS1('01234567890128', BarcodeFormat::EAN13),
new CouponValidity(30, new DateTime()),
new WarningThresholdQuantity(10)
);
});
@ -68,9 +77,9 @@ describe('find campaign request use case', function (): void {
$campaignRequest = CampaignRequest::hydrate(
$id,
$brandCode,
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
);
@ -97,9 +106,9 @@ describe('find campaign request use case', function (): void {
// THEN
expect($result)->toBeInstanceOf(CampaignRequestFound::class)
->and($result->brand)->toBe($brand)
->and($result->name)->toBe($campaignRequest->name)
->and($result->applicant)->toBe($campaignRequest->applicant)
->and($result->campaignType)->toBe($campaignRequest->campaignType)
->and($result->printRules)->toBe($campaignRequest->printRules)
->and($result->requestedCoupon)->toBe($campaignRequest->requestedCoupon)
->and($result->status)->toBe($campaignRequest->status)
->and($result->rejectionReason)->toBeNull();
@ -113,9 +122,9 @@ describe('find campaign request use case', function (): void {
$campaignRequest = CampaignRequest::hydrate(
$id,
$code,
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::REJECTED,
$rejectionReason,
@ -173,9 +182,9 @@ describe('find campaign request use case', function (): void {
$campaignRequest = CampaignRequest::hydrate(
$id,
$brandCode,
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
);

View file

@ -4,11 +4,13 @@ declare(strict_types=1);
namespace Application\UseCases\CampaignRequest\List;
use DateTime;
use Domain\Enums\BarcodeFormat;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Models\Entities\CampaignRequest;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestFilters;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId;
@ -16,21 +18,21 @@ use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestSortClause;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestSortCriteria;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestSortField;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Domain\Ports\Repositories\CampaignRequestRepository;
use Shared\Enums\Currency;
use DateTime;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Shared\ValueObjects\Email;
use Shared\ValueObjects\Money;
use Shared\ValueObjects\Pagination\Paginated;
@ -42,28 +44,40 @@ describe('list campaigns request use case', function (): void {
beforeEach(function (): void {
$this->couponA = new RequestedCoupon(
new CouponName('Coupon A'),
new CouponProductName('Product A'),
new RequestedCouponQuantity(100),
new WarningThresholdQuantity(10),
new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')),
new MoneyAmount(new Money(Currency::EUR, 300)),
new ProcessingCode('PC1'),
new CouponPicture(new PicturePath('coupons/product.jpg'), new PicturePath('coupons/thumbnail.jpg')),
new CouponPicture(
new PicturePath('coupons/product.jpg'),
new PicturePath('coupons/thumbnail.jpg'),
new PicturePath('coupons/inactive_thumbnail.jpg')
),
new CouponValidity(30, new DateTime('2026-02-01')),
[new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', new CountryAuthorized(Country::FRANCE))],
[new IncoCode(35784), new IncoCode(15522)],
new PrintRules(maxPerCustomer: 3),
new GS1('01234567890128', BarcodeFormat::EAN13),
new CouponValidity(30, new DateTime()),
new WarningThresholdQuantity(10)
);
$this->couponB = new RequestedCoupon(
new CouponName('Coupon B'),
new CouponProductName('Product B'),
new RequestedCouponQuantity(100),
new WarningThresholdQuantity(10),
new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')),
new MoneyAmount(new Money(Currency::EUR, 300)),
new ProcessingCode('PC2'),
new CouponPicture(new PicturePath('coupons/product.jpg'), new PicturePath('coupons/thumbnail.jpg')),
new CouponPicture(
new PicturePath('coupons/product.jpg'),
new PicturePath('coupons/thumbnail.jpg'),
new PicturePath('coupons/inactive_thumbnail.jpg')
),
new CouponValidity(30, new DateTime('2026-02-01')),
[new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', new CountryAuthorized(Country::FRANCE))],
[new IncoCode(24514), new IncoCode(46259)],
new PrintRules(maxPerCustomer: 3),
new GS1('01234567890128', BarcodeFormat::EAN13),
new CouponValidity(30, new DateTime()),
new WarningThresholdQuantity(10)
);
});
@ -73,18 +87,18 @@ describe('list campaigns request use case', function (): void {
$campaignRequest1 = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->couponA,
CampaignRequestStatus::CREATED,
);
$campaignRequest2 = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA02'),
new CampaignName('Campaign B'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->couponB,
CampaignRequestStatus::CREATED,
);
@ -117,6 +131,7 @@ describe('list campaigns request use case', function (): void {
expect($items)->toHaveCount(2)
->and($items[0])->toBeInstanceOf(CampaignRequestListItem::class)
->and($items[0]->brandCode)->toBe($campaignRequest1->brandCode)
->and($items[0]->name)->toBe($campaignRequest1->name)
->and($items[0]->applicant)->toBe($campaignRequest1->applicant)
->and($items[0]->campaignType)->toBe($campaignRequest1->campaignType)
->and($items[0]->status)->toBe($campaignRequest1->status)
@ -130,9 +145,9 @@ describe('list campaigns request use case', function (): void {
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->couponA,
CampaignRequestStatus::CREATED,
);
@ -157,6 +172,7 @@ describe('list campaigns request use case', function (): void {
expect($items)->toHaveCount(1)
->and($items[0])->toBeInstanceOf(CampaignRequestListItem::class)
->and($items[0]->brandCode)->toBe($campaignRequest->brandCode)
->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);
@ -168,18 +184,18 @@ describe('list campaigns request use case', function (): void {
$campaignRequest1 = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->couponA,
CampaignRequestStatus::CREATED,
);
$campaignRequest2 = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA02'),
new CampaignName('Campaign B'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->couponB,
CampaignRequestStatus::CREATED,
);

View file

@ -10,55 +10,64 @@ use Application\UseCases\CampaignRequest\Reject\Output\CampaignRequestRejected;
use Application\UseCases\CampaignRequest\Reject\Output\Output;
use Application\UseCases\CampaignRequest\Reject\RejectCampaignRequest;
use Carbon\CarbonImmutable;
use DateTime;
use Domain\Enums\BarcodeFormat;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Enums\Country;
use Domain\Models\Entities\CampaignRequest;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Domain\Ports\Repositories\CampaignRequestRepository;
use Shared\Enums\Currency;
use DateTime;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Shared\ValueObjects\Email;
use Shared\ValueObjects\Money;
covers(RejectCampaignRequest::class, Input::class, CampaignRequestRejected::class, CampaignRequestNotFound::class);
describe('reject campaign request use case', function (): void {
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
beforeEach(function (): void {
$this->requestedCoupon = new RequestedCoupon(
new CouponName('Coupon A'),
new CouponProductName('Product A'),
new RequestedCouponQuantity(100),
new WarningThresholdQuantity(10),
new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')),
new MoneyAmount(new Money(Currency::EUR, 300)),
new ProcessingCode('PC1'),
new CouponPicture(new PicturePath('coupons/product.jpg'), new PicturePath('coupons/thumbnail.jpg')),
new CouponPicture(
new PicturePath('coupons/product.jpg'),
new PicturePath('coupons/thumbnail.jpg'),
new PicturePath('coupons/inactive_thumbnail.jpg')
),
new CouponValidity(30, new DateTime('2026-02-01')),
[new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', new CountryAuthorized(Country::FRANCE))],
[new IncoCode(35784), new IncoCode(15522)],
new PrintRules(maxPerCustomer: 3),
new GS1('01234567890128', BarcodeFormat::EAN13),
new CouponValidity(30, new DateTime()),
new WarningThresholdQuantity(10)
);
});
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
it('rejects a campaign request and persists it', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15 12:00:00');
@ -66,9 +75,9 @@ describe('reject campaign request use case', function (): void {
$campaignRequest = CampaignRequest::hydrate(
$id,
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
);
@ -118,9 +127,9 @@ describe('reject campaign request use case', function (): void {
$campaignRequest = CampaignRequest::hydrate(
$id,
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::TRANSFORMED,
);

View file

@ -4,33 +4,37 @@ declare(strict_types=1);
namespace Tests\Unit\Domain\Models\Entities;
use Carbon\CarbonImmutable;
use DateTime;
use Domain\Enums\BarcodeFormat;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Enums\Country;
use Domain\Models\Entities\CampaignRequest;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestStatus;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestType;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
use Domain\Models\ValueObjects\CountryAuthorized;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Ramsey\Uuid\Uuid;
use Shared\Enums\Currency;
use DateTime;
use Shared\ValueObjects\Email;
use Shared\ValueObjects\Money;
use Carbon\CarbonImmutable;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Ramsey\Uuid\Uuid;
use Shared\Enums\Currency;
use Shared\ValueObjects\Email;
use Shared\ValueObjects\Money;
covers(CampaignRequest::class);
@ -42,15 +46,21 @@ describe('campaign request', function (): void {
beforeEach(function (): void {
$this->requestedCoupon = new RequestedCoupon(
new CouponName('Coupon A'),
new CouponProductName('Product A'),
new RequestedCouponQuantity(100),
new WarningThresholdQuantity(10),
new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01')),
new MoneyAmount(new Money(Currency::EUR, 300)),
new ProcessingCode('PC1'),
new CouponPicture(new PicturePath('coupons/product.jpg'), new PicturePath('coupons/thumbnail.jpg')),
new CouponPicture(
new PicturePath('coupons/product.jpg'),
new PicturePath('coupons/thumbnail.jpg'),
new PicturePath('coupons/inactive_thumbnail.jpg')
),
new CouponValidity(30, new DateTime('2026-02-01')),
[new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', new CountryAuthorized(Country::FRANCE))],
[new IncoCode(35784), new IncoCode(15522)],
new PrintRules(maxPerCustomer: 3),
new GS1('01234567890128', BarcodeFormat::EAN13),
new CouponValidity(30, new DateTime()),
new WarningThresholdQuantity(10)
);
});
@ -58,9 +68,9 @@ describe('campaign request', function (): void {
// GIVEN
$id = CampaignRequestId::generate();
$code = new BrandCode('AAA01');
$name = new CampaignName('Campaign A');
$applicant = new Applicant('John', 'Doe', new Email('john.doe@example.com'));
$campaignType = CampaignRequestType::CLUB;
$printRules = new PrintRules(maxPerCustomer: 3);
$requestedCoupon = $this->requestedCoupon;
$status = CampaignRequestStatus::CREATED;
@ -68,9 +78,9 @@ describe('campaign request', function (): void {
$campaignRequest = CampaignRequest::hydrate(
$id,
$code,
$name,
$applicant,
$campaignType,
$printRules,
$requestedCoupon,
$status,
);
@ -78,31 +88,15 @@ describe('campaign request', function (): void {
// THEN
expect($campaignRequest->id())->toBe($id)
->and($campaignRequest->brandCode)->toBe($code)
->and($campaignRequest->name)->toBe($name)
->and($campaignRequest->applicant)->toBe($applicant)
->and($campaignRequest->campaignType)->toBe($campaignType)
->and($campaignRequest->printRules)->toBe($printRules)
->and($campaignRequest->requestedCoupon)->toBe($requestedCoupon)
->and($campaignRequest->status)->toBe($status)
->and($campaignRequest->updatedAt)->toBeNull()
->and($campaignRequest->updatedAt())->toBeNull();
});
it('hydrates a campaign request with a null printRules', function (): void {
// WHEN
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
null,
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
);
// THEN
expect($campaignRequest->printRules)->toBeNull();
});
it('hydrates a campaign request with an explicit updatedAt date', function (): void {
// GIVEN
$updatedAt = new \DateTimeImmutable('2026-06-15 12:00:00', new \DateTimeZone('UTC'));
@ -111,9 +105,9 @@ describe('campaign request', function (): void {
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
null,
@ -133,9 +127,9 @@ describe('campaign request', function (): void {
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::REJECTED,
$rejectionReason,
@ -151,9 +145,9 @@ describe('campaign request', function (): void {
$action = fn (): CampaignRequest => CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::REJECTED,
);
@ -170,9 +164,9 @@ describe('campaign request', function (): void {
$action = fn (): CampaignRequest => CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
new RejectionReason('Missing legal documents.'),
@ -191,9 +185,9 @@ describe('campaign request', function (): void {
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
);
@ -207,9 +201,9 @@ describe('campaign request', function (): void {
->and($rejected->rejectionReason)->toBe($reason)
->and($rejected->id())->toBe($campaignRequest->id())
->and($rejected->brandCode)->toBe($campaignRequest->brandCode)
->and($rejected->name)->toBe($campaignRequest->name)
->and($rejected->applicant)->toBe($campaignRequest->applicant)
->and($rejected->campaignType)->toBe($campaignRequest->campaignType)
->and($rejected->printRules)->toBe($campaignRequest->printRules)
->and($rejected->requestedCoupon)->toBe($campaignRequest->requestedCoupon)
->and($rejected->updatedAt)->not->toBeNull()
->and($rejected->updatedAt()?->format('Y-m-d H:i:s'))->toBe('2026-06-15 12:00:00');
@ -220,9 +214,9 @@ describe('campaign request', function (): void {
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::TRANSFORMED,
);
@ -239,9 +233,9 @@ describe('campaign request', function (): void {
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::REJECTED,
new RejectionReason('First reason.'),
@ -261,9 +255,9 @@ describe('campaign request', function (): void {
$campaignRequest = CampaignRequest::hydrate(
$id,
new BrandCode('AAA01'),
new CampaignName('Campaign A'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
CampaignRequestType::CLUB,
new PrintRules(maxPerCustomer: 3),
$this->requestedCoupon,
CampaignRequestStatus::CREATED,
);

View file

@ -7,30 +7,21 @@ namespace Tests\Unit\Domain\Models\Entities;
use Carbon\CarbonImmutable;
use Shared\Enums\Country;
use Domain\Models\Entities\Campaign;
use Domain\Models\Entities\Tag;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Campaign\CampaignCode;
use Domain\Models\ValueObjects\Campaign\CampaignCoupon;
use Domain\Models\ValueObjects\Campaign\CampaignId;
use Domain\Models\ValueObjects\Campaign\CampaignName;
use Domain\Models\ValueObjects\CountryAuthorized;
use Domain\Models\ValueObjects\Coupon\CouponQuantity;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\Stock\StockId;
use Domain\Models\ValueObjects\Tag\TagId;
use Domain\Models\ValueObjects\Tag\TagName;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Ramsey\Uuid\Uuid;
covers(Campaign::class);
describe('campaign', function (): void {
beforeEach(function (): void {
$this->tag = Tag::hydrate(
TagId::generate(),
new TagName('Tag A'),
);
});
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
@ -42,40 +33,36 @@ describe('campaign', function (): void {
$name = new CampaignName('Name');
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = Country::FRANCE;
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new CouponQuantity(100),
new CountryAuthorized(Country::FRANCE),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
$brandCode = new BrandCode('AAA01');
// WHEN
$campaign = Campaign::hydrate(
$id,
$code,
$this->tag,
$name,
$publication,
$printAllAtOnce,
$availableIn,
$coupons,
$brand,
$brandCode,
);
// THEN
expect($campaign->id())->toBe($id)
->and($campaign->code)->toBe($code)
->and($campaign->tag)->toBe($this->tag)
->and($campaign->name)->toBe($name)
->and($campaign->publication)->toBe($publication)
->and($campaign->printAllAtOnce)->toBe($printAllAtOnce)
->and($campaign->availableIn)->toBe($availableIn)
->and($campaign->coupons)->toBe($coupons)
->and($campaign->brand)->toBe($brand)
->and($campaign->brandCode)->toBe($brandCode)
->and($campaign->updatedAt)->toBeNull()
->and($campaign->updatedAt())->toBeNull();
});
@ -87,21 +74,18 @@ describe('campaign', function (): void {
$name = new CampaignName('Name');
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = Country::FRANCE;
$coupons = [];
$brand = new BrandCode('AAA01');
$brandCode = new BrandCode('AAA01');
// WHEN
$campaign = Campaign::hydrate(
$id,
$code,
$this->tag,
$name,
$publication,
$printAllAtOnce,
$availableIn,
$coupons,
$brand,
$brandCode,
);
// THEN
@ -115,29 +99,27 @@ describe('campaign', function (): void {
$name = new CampaignName('Name');
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = Country::FRANCE;
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new CouponQuantity(100),
new CountryAuthorized(Country::FRANCE),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
$brandCode = new BrandCode('AAA01');
$updatedAt = new \DateTimeImmutable('2026-09-22 10:00:00', new \DateTimeZone('UTC'));
// WHEN
$campaign = Campaign::hydrate(
$id,
$code,
$this->tag,
$name,
$publication,
$printAllAtOnce,
$availableIn,
$coupons,
$brand,
$brandCode,
$updatedAt
);
@ -152,39 +134,35 @@ describe('campaign', function (): void {
$name = new CampaignName('Name');
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = Country::FRANCE;
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new CouponQuantity(100),
new CountryAuthorized(Country::FRANCE),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
$brandCode = new BrandCode('AAA01');
// WHEN
$campaign = Campaign::create(
$code,
$this->tag,
$name,
$publication,
$printAllAtOnce,
$availableIn,
$coupons,
$brand
$brandCode
);
// THEN
expect($campaign->id())->toBeInstanceOf(CampaignId::class)
->and($campaign->code)->toBe($code)
->and($campaign->tag)->toBe($this->tag)
->and($campaign->name)->toBe($name)
->and($campaign->publication)->toBe($publication)
->and($campaign->printAllAtOnce)->toBe($printAllAtOnce)
->and($campaign->availableIn)->toBe($availableIn)
->and($campaign->coupons)->toBe($coupons)
->and($campaign->brand)->toBe($brand)
->and($campaign->brandCode)->toBe($brandCode)
->and($campaign->updatedAt)->toBeNull()
->and($campaign->updatedAt())->toBeNull();
});
@ -195,37 +173,33 @@ describe('campaign', function (): void {
$name = new CampaignName('Name');
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = Country::FRANCE;
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new CouponQuantity(100),
new CountryAuthorized(Country::FRANCE),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
$brandCode = new BrandCode('AAA01');
// WHEN
$firstCampaign = Campaign::create(
$code,
$this->tag,
$name,
$publication,
$printAllAtOnce,
$availableIn,
$coupons,
$brand
$brandCode
);
$secondCampaign = Campaign::create(
$code,
$this->tag,
$name,
$publication,
$printAllAtOnce,
$availableIn,
$coupons,
$brand
$brandCode
);
// THEN
@ -239,15 +213,14 @@ describe('campaign', function (): void {
$campaign = Campaign::hydrate(
$id,
new CampaignCode('Camp100'),
$this->tag,
new CampaignName('Name'),
\boolval(0),
\boolval(1),
Country::FRANCE,
[
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new CouponQuantity(100),
new CountryAuthorized(Country::FRANCE),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
@ -269,21 +242,18 @@ describe('campaign', function (): void {
$name = new CampaignName('Name');
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = Country::FRANCE;
$coupons = ['PC1'];
$brand = new BrandCode('AAA01');
$brandCode = new BrandCode('AAA01');
// WHEN / THEN
expect(fn (): Campaign => Campaign::hydrate(
$id,
$code,
$this->tag,
$name,
$publication,
$printAllAtOnce,
$availableIn,
$coupons,
$brand
$brandCode
))->toThrow(\DomainException::class, 'Campaign coupons must be CampaignCoupon instances.');
});
});

View file

@ -5,199 +5,323 @@ declare(strict_types=1);
namespace Tests\Unit\Domain\Models\Entities;
use Carbon\CarbonImmutable;
use DateTime;
use Domain\Enums\BarcodeFormat;
use Shared\Enums\Country;
use Domain\Models\Entities\Coupon;
use Domain\Models\ValueObjects\Actor\ActorId;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\Coupon\CouponAvailabilityCalendar;
use Domain\Models\ValueObjects\Coupon\CouponCode;
use Domain\Models\ValueObjects\Coupon\CouponDiffusion;
use Domain\Models\ValueObjects\Coupon\CouponId;
use Domain\Models\ValueObjects\Coupon\CouponLegalInformation;
use Domain\Models\ValueObjects\Coupon\CouponProduct;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\ProcessingCode;
use Ramsey\Uuid\Uuid;
use Shared\Enums\Currency;
use DateTime;
use Shared\ValueObjects\Money;
covers(Coupon::class);
describe('coupon', function (): void {
beforeEach(function (): void {
$this->code = new CouponCode('COU01');
$this->name = new CouponName('Name');
$this->amount = new MoneyAmount(new Money(Currency::EUR, 1));
$this->picture = new CouponPicture(
new PicturePath('files/product.jpg'),
new PicturePath('files/thumbnail.jpg'),
new PicturePath('files/inactive_thumbnail.jpg')
);
$this->diffusion = new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]);
$this->validity = new CouponValidity(30, new DateTime('2026-02-01'));
$this->couponValidityInformation = [
new CouponValidityInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
new CountryAuthorized(Country::FRANCE)
)
];
$this->couponLegalInformation = [
new CouponLegalInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
new CountryAuthorized(Country::FRANCE)
)
];
$this->processingCode = new ProcessingCode('5425003944130');
$this->brandCode = new BrandCode('AAA01');
$this->createdBy = ActorId::generate();
$this->products = [new IncoCode(35784), new IncoCode(15522)];
$this->availabilityCalendar = new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]);
$this->gtin = new GS1('110101010', BarcodeFormat::EAN13);
});
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
it('creates a coupon', function (): void {
// GIVEN
$id = CouponId::generate();
$name = new CouponName('Name');
$status = \boolval(0);
$productName = new CouponProductName('Lorem ipsum');
$amount = new MoneyAmount(new Money(Currency::EUR, 1));
$picture = new CouponPicture(new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'));
$product = new CouponProduct('Lorem ipsum', [], []);
$diffusion = new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]);
$availability = new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]);
$validity = new CouponValidity(30, new DateTime('2026-11-30'));
$legalInformation = [
new CouponLegalInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
Country::FRANCE
)
];
$code = new ProcessingCode('5425003944130');
$brandCode = new BrandCode('AAA01');
$createdBy = ActorId::generate();
$gtin = new GS1('110101010', BarcodeFormat::EAN13);
$id = CouponId::generate();
// WHEN
$coupon = Coupon::hydrate(
$id,
$name,
$status,
$productName,
$amount,
$picture,
$product,
$diffusion,
$availability,
$validity,
$legalInformation,
$code,
$brandCode,
$createdBy,
$gtin,
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
$this->couponValidityInformation,
$this->couponLegalInformation,
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
);
// THEN
expect($coupon->id())->toBe($id)
->and($coupon->name)->toBe($name)
->and($coupon->status)->toBe($status)
->and($coupon->productName)->toBe($productName)
->and($coupon->amount)->toBe($amount)
->and($coupon->picture)->toBe($picture)
->and($coupon->product)->toBe($product)
->and($coupon->diffusion)->toBe($diffusion)
->and($coupon->availability)->toBe($availability)
->and($coupon->validity)->toBe($validity)
->and($coupon->legalInformation)->toBe($legalInformation)
->and($coupon->code)->toBe($code)
->and($coupon->brandCode)->toBe($brandCode)
->and($coupon->createdBy)->toBe($createdBy)
->and($coupon->gtin)->toBe($gtin)
->and($coupon->code)->toBe($this->code)
->and($coupon->name)->toBe($this->name)
->and($coupon->amount)->toBe($this->amount)
->and($coupon->picture)->toBe($this->picture)
->and($coupon->diffusion)->toBe($this->diffusion)
->and($coupon->validity)->toBe($this->validity)
->and($coupon->couponValidityInformation)->toBe($this->couponValidityInformation)
->and($coupon->couponLegalInformation)->toBe($this->couponLegalInformation)
->and($coupon->processingCode)->toBe($this->processingCode)
->and($coupon->brandCode)->toBe($this->brandCode)
->and($coupon->createdBy)->toBe($this->createdBy)
->and($coupon->products)->toBe($this->products)
->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar)
->and($coupon->gtin)->toBe($this->gtin)
->and($coupon->updatedAt)->toBeNull()
->and($coupon->updatedAt())->toBeNull();
});
it('creates a coupon without gs1', function (): void {
it('hydrates a coupon without gs1', function (): void {
// GIVEN
$id = CouponId::generate();
$name = new CouponName('Name');
$status = \boolval(0);
$productName = new CouponProductName('Lorem ipsum');
$amount = new MoneyAmount(new Money(Currency::EUR, 1));
$picture = new CouponPicture(new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'));
$product = new CouponProduct('Lorem ipsum', [], []);
$diffusion = new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]);
$availability = new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]);
$validity = new CouponValidity(30, new DateTime('2026-11-30'));
$legalInformation = [
new CouponLegalInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
Country::FRANCE
)
];
$code = new ProcessingCode('5425003944130');
$brandCode = new BrandCode('AAA01');
$createdBy = ActorId::generate();
$id = CouponId::generate();
// WHEN
$coupon = Coupon::hydrate(
$id,
$name,
$status,
$productName,
$amount,
$picture,
$product,
$diffusion,
$availability,
$validity,
$legalInformation,
$code,
$brandCode,
$createdBy,
id: $id,
code: $this->code,
name: $this->name,
amount: $this->amount,
picture: $this->picture,
diffusion: $this->diffusion,
validity: $this->validity,
couponValidityInformation: $this->couponValidityInformation,
couponLegalInformation: $this->couponLegalInformation,
processingCode: $this->processingCode,
brandCode: $this->brandCode,
createdBy: $this->createdBy,
products: $this->products,
availabilityCalendar: $this->availabilityCalendar
);
// THEN
expect($coupon->products)->toBe($this->products)
->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar)
->and($coupon->gtin)->toBeNull();
});
it('hydrates a coupon without availability calendar', function (): void {
// GIVEN
$id = CouponId::generate();
// WHEN
$coupon = Coupon::hydrate(
id: $id,
code: $this->code,
name: $this->name,
amount: $this->amount,
picture: $this->picture,
diffusion: $this->diffusion,
validity: $this->validity,
couponValidityInformation: $this->couponValidityInformation,
couponLegalInformation: $this->couponLegalInformation,
processingCode: $this->processingCode,
brandCode: $this->brandCode,
createdBy: $this->createdBy,
products: $this->products,
gtin: $this->gtin
);
// THEN
expect($coupon->availabilityCalendar)->toBeNull()
->and($coupon->products)->toBe($this->products)
->and($coupon->gtin)->toBe($this->gtin);
});
it('hydrates a coupon without products', function (): void {
// GIVEN
$id = CouponId::generate();
// WHEN
$coupon = Coupon::hydrate(
id: $id,
code: $this->code,
name: $this->name,
amount: $this->amount,
picture: $this->picture,
diffusion: $this->diffusion,
validity: $this->validity,
couponValidityInformation: $this->couponValidityInformation,
couponLegalInformation: $this->couponLegalInformation,
processingCode: $this->processingCode,
brandCode: $this->brandCode,
createdBy: $this->createdBy,
availabilityCalendar: $this->availabilityCalendar,
gtin: $this->gtin
);
// THEN
expect($coupon->products)->toBe([])
->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar)
->and($coupon->gtin)->toBe($this->gtin);
});
it('hydrates a coupon with only required fields', function (): void {
// GIVEN
$id = CouponId::generate();
// WHEN
$coupon = Coupon::hydrate(
id: $id,
code: $this->code,
name: $this->name,
amount: $this->amount,
picture: $this->picture,
diffusion: $this->diffusion,
validity: $this->validity,
couponValidityInformation: $this->couponValidityInformation,
couponLegalInformation: $this->couponLegalInformation,
processingCode: $this->processingCode,
brandCode: $this->brandCode,
createdBy: $this->createdBy,
);
// THEN
expect($coupon->id())->toBe($id)
->and($coupon->name)->toBe($name)
->and($coupon->status)->toBe($status)
->and($coupon->productName)->toBe($productName)
->and($coupon->amount)->toBe($amount)
->and($coupon->picture)->toBe($picture)
->and($coupon->product)->toBe($product)
->and($coupon->diffusion)->toBe($diffusion)
->and($coupon->availability)->toBe($availability)
->and($coupon->validity)->toBe($validity)
->and($coupon->legalInformation)->toBe($legalInformation)
->and($coupon->code)->toBe($code)
->and($coupon->brandCode)->toBe($brandCode)
->and($coupon->createdBy)->toBe($createdBy)
->and($coupon->products)->toBe([])
->and($coupon->availabilityCalendar)->toBeNull()
->and($coupon->gtin)->toBeNull()
->and($coupon->updatedAt)->toBeNull()
->and($coupon->updatedAt())->toBeNull();
});
it('creates a coupon with an explicit updatedAt date', function (): void {
it('accepts empty validity and legal information lists', function (): void {
// GIVEN
$id = CouponId::generate();
$name = new CouponName('Name');
$status = \boolval(0);
$productName = new CouponProductName('Lorem ipsum');
$amount = new MoneyAmount(new Money(Currency::EUR, 1));
$picture = new CouponPicture(new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'));
$product = new CouponProduct('Lorem ipsum', [], []);
$diffusion = new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]);
$availability = new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]);
$validity = new CouponValidity(30, new DateTime('2026-11-30'));
$legalInformation = [
new CouponLegalInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
Country::FRANCE
)
];
$code = new ProcessingCode('5425003944130');
$brandCode = new BrandCode('AAA01');
$gtin = new GS1('110101010', BarcodeFormat::EAN13);
$createdBy = ActorId::generate();
$updatedAt = new \DateTimeImmutable('2026-09-22 10:00:00', new \DateTimeZone('UTC'));
$id = CouponId::generate();
// WHEN
$coupon = Coupon::hydrate(
$id,
$name,
$status,
$productName,
$amount,
$picture,
$product,
$diffusion,
$availability,
$validity,
$legalInformation,
$code,
$brandCode,
$createdBy,
$gtin,
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
[],
[],
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
);
// THEN
expect($coupon->couponValidityInformation)->toBe([])
->and($coupon->couponLegalInformation)->toBe([]);
});
it('throws when validity information contains an invalid item', function (): void {
// GIVEN
$id = CouponId::generate();
// WHEN / THEN
expect(fn () => Coupon::hydrate(
$id,
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
['Not a CouponValidityInformation'],
$this->couponLegalInformation,
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
))->toThrow(\DomainException::class, 'Coupon validity information must be CouponValidityInformation instances.');
});
it('throws when legal information contains an invalid item', function (): void {
// GIVEN
$id = CouponId::generate();
// WHEN / THEN
expect(fn () => Coupon::hydrate(
$id,
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
$this->couponValidityInformation,
['Not a CouponLegalInformation'],
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
))->toThrow(\DomainException::class, 'Coupon legal information must be CouponLegalInformation instances.');
});
it('hydrates a coupon with an explicit updatedAt date', function (): void {
// GIVEN
$id = CouponId::generate();
$updatedAt = new \DateTimeImmutable('2026-09-22 10:00:00', new \DateTimeZone('UTC'));
// WHEN
$coupon = Coupon::hydrate(
$id,
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
$this->couponValidityInformation,
$this->couponLegalInformation,
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
$updatedAt
);
@ -207,119 +331,77 @@ describe('coupon', function (): void {
});
it('creates a new coupon with a freshly generated id', function (): void {
// GIVEN
$name = new CouponName('Name');
$status = \boolval(0);
$productName = new CouponProductName('Lorem ipsum');
$amount = new MoneyAmount(new Money(Currency::EUR, 1));
$picture = new CouponPicture(new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'));
$product = new CouponProduct('Lorem ipsum', [], []);
$diffusion = new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]);
$availability = new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]);
$validity = new CouponValidity(30, new DateTime('2026-11-30'));
$legalInformation = [
new CouponLegalInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
Country::FRANCE
)
];
$code = new ProcessingCode('5425003944130');
$brandCode = new BrandCode('AAA01');
$createdBy = ActorId::generate();
$gtin = new GS1('110101010', BarcodeFormat::EAN13);
// WHEN
// GIVEN / WHEN
$coupon = Coupon::create(
$name,
$status,
$productName,
$amount,
$picture,
$product,
$diffusion,
$availability,
$validity,
$legalInformation,
$code,
$brandCode,
$createdBy,
$gtin,
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
$this->couponValidityInformation,
$this->couponLegalInformation,
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
);
// THEN
expect($coupon->id())->toBeInstanceOf(CouponId::class)
->and($coupon->name)->toBe($name)
->and($coupon->status)->toBe($status)
->and($coupon->productName)->toBe($productName)
->and($coupon->amount)->toBe($amount)
->and($coupon->picture)->toBe($picture)
->and($coupon->product)->toBe($product)
->and($coupon->diffusion)->toBe($diffusion)
->and($coupon->availability)->toBe($availability)
->and($coupon->validity)->toBe($validity)
->and($coupon->legalInformation)->toBe($legalInformation)
->and($coupon->code)->toBe($code)
->and($coupon->brandCode)->toBe($brandCode)
->and($coupon->createdBy)->toBe($createdBy)
->and($coupon->gtin)->toBe($gtin)
->and($coupon->code)->toBe($this->code)
->and($coupon->name)->toBe($this->name)
->and($coupon->amount)->toBe($this->amount)
->and($coupon->picture)->toBe($this->picture)
->and($coupon->diffusion)->toBe($this->diffusion)
->and($coupon->validity)->toBe($this->validity)
->and($coupon->couponValidityInformation)->toBe($this->couponValidityInformation)
->and($coupon->couponLegalInformation)->toBe($this->couponLegalInformation)
->and($coupon->processingCode)->toBe($this->processingCode)
->and($coupon->brandCode)->toBe($this->brandCode)
->and($coupon->createdBy)->toBe($this->createdBy)
->and($coupon->products)->toBe($this->products)
->and($coupon->availabilityCalendar)->toBe($this->availabilityCalendar)
->and($coupon->gtin)->toBe($this->gtin)
->and($coupon->updatedAt)->toBeNull()
->and($coupon->updatedAt())->toBeNull();
});
it('generates a different id for each created coupon', function (): void {
// GIVEN
$name = new CouponName('Name');
$status = \boolval(0);
$productName = new CouponProductName('Lorem ipsum');
$amount = new MoneyAmount(new Money(Currency::EUR, 1));
$picture = new CouponPicture(new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg'));
$product = new CouponProduct('Lorem ipsum', [], []);
$diffusion = new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]);
$availability = new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]);
$validity = new CouponValidity(30, new DateTime('2026-11-30'));
$legalInformation = [
new CouponLegalInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
Country::FRANCE
)
];
$code = new ProcessingCode('5425003944130');
$brandCode = new BrandCode('AAA01');
$createdBy = ActorId::generate();
$gtin = new GS1('110101010', BarcodeFormat::EAN13);
// WHEN
// GIVEN / WHEN
$firstCoupon = Coupon::create(
$name,
$status,
$productName,
$amount,
$picture,
$product,
$diffusion,
$availability,
$validity,
$legalInformation,
$code,
$brandCode,
$createdBy,
$gtin
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
$this->couponValidityInformation,
$this->couponLegalInformation,
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
);
$secondCoupon = Coupon::create(
$name,
$status,
$productName,
$amount,
$picture,
$product,
$diffusion,
$availability,
$validity,
$legalInformation,
$code,
$brandCode,
$createdBy,
$gtin
$this->code,
$this->name,
$this->amount,
$this->picture,
$this->diffusion,
$this->validity,
$this->couponValidityInformation,
$this->couponLegalInformation,
$this->processingCode,
$this->brandCode,
$this->createdBy,
$this->products,
$this->availabilityCalendar,
$this->gtin,
);
// THEN
@ -332,19 +414,23 @@ describe('coupon', function (): void {
$id = new CouponId(Uuid::uuid7($expected));
$coupon = Coupon::hydrate(
$id,
new CouponCode('COU01'),
new CouponName('Name'),
\boolval(0),
new CouponProductName('Lorem ipsum'),
new MoneyAmount(new Money(Currency::EUR, 1)),
new CouponPicture(new PicturePath('files/product.jpg'), new PicturePath('files/thumbnail.jpg')),
new CouponProduct('Lorem ipsum', [], []),
new CouponPicture(
new PicturePath('files/product.jpg'),
new PicturePath('files/thumbnail.jpg'),
new PicturePath('files/inactive_thumbnail.jpg')
),
new CouponDiffusion([new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'))]),
new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]),
new CouponValidity(30, new DateTime('2026-11-30')),
[new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', Country::FRANCE)],
new CouponValidity(30, new DateTime('2026-02-01')),
[new CouponValidityInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', new CountryAuthorized(Country::FRANCE))],
[new CouponLegalInformation('Lorem ipsum dolor sit amet consectetur adipiscing elit.', new CountryAuthorized(Country::FRANCE))],
new ProcessingCode('5425003944130'),
new BrandCode('AAA01'),
ActorId::generate(),
[new IncoCode(35784), new IncoCode(15522)],
new CouponAvailabilityCalendar([new Period(new DateTime('2026-08-25'), new DateTime('2026-10-31'))]),
new GS1('110101010', BarcodeFormat::EAN13),
);

View file

@ -4,10 +4,12 @@ declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects\Campaign;
use Domain\Enums\Country;
use Domain\Models\ValueObjects\Campaign\CampaignCoupon;
use Domain\Models\ValueObjects\CountryAuthorized;
use Domain\Models\ValueObjects\Coupon\CouponQuantity;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\Stock\StockId;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
covers(CampaignCoupon::class);
@ -15,33 +17,79 @@ covers(CampaignCoupon::class);
describe('campaign coupon', function (): void {
it('creates a valid campaign coupon', function (): void {
// GIVEN
$code = new ProcessingCode('PC1');
$qty = StockId::generate();
$printRules = new PrintRules(maxPerCustomer: 3);
$warningThresholdQty = new WarningThresholdQuantity(100);
$processingCode = new ProcessingCode('PC1');
$quantity = new CouponQuantity(10);
$availableIn = new CountryAuthorized(Country::FRANCE);
$warningThresholdQuantity = new WarningThresholdQuantity(100);
$printRules = new PrintRules(maxPerCustomer: 3);
// WHEN
$campaignCoupon = new CampaignCoupon($code, $qty, $warningThresholdQty, $printRules);
$campaignCoupon = new CampaignCoupon($processingCode, $quantity, $availableIn, $warningThresholdQuantity, $printRules);
// THEN
expect($campaignCoupon->code)->toBe($code)
->and($campaignCoupon->qty)->toBe($qty)
->and($campaignCoupon->printRules)->toBe($printRules)
->and($campaignCoupon->warningThresholdQty)->toBe($warningThresholdQty);
expect($campaignCoupon->processingCode)->toBe($processingCode)
->and($campaignCoupon->quantity)->toBe($quantity)
->and($campaignCoupon->availableIn)->toBe($availableIn)
->and($campaignCoupon->warningThresholdQuantity)->toBe($warningThresholdQuantity)
->and($campaignCoupon->printRules)->toBe($printRules);
});
it('creates a valid campaign coupon whitout warning threshold quantity', function (): void {
// GIVEN
$processingCode = new ProcessingCode('PC1');
$quantity = new CouponQuantity(10);
$availableIn = new CountryAuthorized(Country::FRANCE);
$printRules = new PrintRules(maxPerCustomer: 3);
// WHEN
$campaignCoupon = new CampaignCoupon(
processingCode: $processingCode,
quantity: $quantity,
availableIn: $availableIn,
printRules: $printRules
);
// THEN
expect($campaignCoupon->processingCode)->toBe($processingCode)
->and($campaignCoupon->quantity)->toBe($quantity)
->and($campaignCoupon->availableIn)->toBe($availableIn)
->and($campaignCoupon->printRules)->toBe($printRules);
});
it('creates a valid campaign coupon whitout print rules', function (): void {
// GIVEN
$code = new ProcessingCode('PC1');
$qty = StockId::generate();
$warningThresholdQty = new WarningThresholdQuantity(100);
$processingCode = new ProcessingCode('PC1');
$quantity = new CouponQuantity(10);
$availableIn = new CountryAuthorized(Country::FRANCE);
$warningThresholdQuantity = new WarningThresholdQuantity(100);
// WHEN
$campaignCoupon = new CampaignCoupon($code, $qty, $warningThresholdQty);
$campaignCoupon = new CampaignCoupon(
processingCode: $processingCode,
quantity: $quantity,
availableIn: $availableIn,
warningThresholdQuantity: $warningThresholdQuantity
);
// THEN
expect($campaignCoupon->code)->toBe($code)
->and($campaignCoupon->qty)->toBe($qty)
->and($campaignCoupon->warningThresholdQty)->toBe($warningThresholdQty);
expect($campaignCoupon->processingCode)->toBe($processingCode)
->and($campaignCoupon->quantity)->toBe($quantity)
->and($campaignCoupon->availableIn)->toBe($availableIn)
->and($campaignCoupon->warningThresholdQuantity)->toBe($warningThresholdQuantity);
});
it('creates a valid campaign coupon whitout warning threshold quantity and print rules', function (): void {
// GIVEN
$processingCode = new ProcessingCode('PC1');
$quantity = new CouponQuantity(10);
$availableIn = new CountryAuthorized(Country::FRANCE);
// WHEN
$campaignCoupon = new CampaignCoupon(processingCode: $processingCode, quantity: $quantity, availableIn: $availableIn);
// THEN
expect($campaignCoupon->processingCode)->toBe($processingCode)
->and($campaignCoupon->quantity)->toBe($quantity)
->and($campaignCoupon->availableIn)->toBe($availableIn);
});
});

View file

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects\Coupon;
use Domain\Models\ValueObjects\Coupon\CouponCode;
covers(CouponCode::class);
describe('coupon code', function (): void {
it('creates a valid coupon code', function (): void {
// GIVEN
$value = 'AAA01';
// WHEN
$code = new CouponCode($value);
// THEN
expect($code)->toBeInstanceOf(CouponCode::class)
->and($code->value)->toBe($value);
});
});
describe('coupon code validation rules', function (): void {
it('rejects invalid coupon codes', function (string $invalid): void {
// WHEN / THEN
expect(fn (): CouponCode => new CouponCode($invalid))->toThrow(\DomainException::class);
// GIVEN
})->with([
// rejects empty strings
'',
' ',
"\t",
"\n",
// rejects numbers only
'12345',
'67890',
// rejects letters only
'ABCDE',
'FGHIJ',
// rejects codes with special characters
'AB@12',
'CD#34',
'EF$56',
// rejects codes that have the same digit repeated
'ABC11',
'DEF22',
'GHI33',
// rejects codes with lowercase letters
'AbC12',
'dEf34',
'GhI56',
]);
it('rejects codes with more/less than 3 letters or more/less than 2 digits', function (string $invalid): void {
// WHEN / THEN
expect(fn (): CouponCode => new CouponCode($invalid))->toThrow(\DomainException::class);
// GIVEN
})->with([
'ABCD12',
'ABC123',
'ABCDE123',
'AB12',
'A1',
'AB1',
'A12',
'AB123',
'A123',
'ABC1',
'AB12C',
]);
});

View file

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects;
namespace Tests\Unit\Domain\Models\ValueObjects\Coupon;
use Shared\Enums\Country;
use Domain\Models\ValueObjects\Coupon\CouponLegalInformation;

View file

@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects;
use Shared\Enums\Country;
use Domain\Models\ValueObjects\Coupon\CouponProductInfo;
covers(CouponProductInfo::class);
describe('coupon product info', function (): void {
it('creates a valid coupon product info', function (): void {
// GIVEN
$name = 'Lorem ipsum';
$range = 'Lorem';
$validityText = 'Lorem ipsum dolor sit amet consectetur adipiscing elit.';
$forLocale = Country::FRANCE;
// WHEN
$couponProductInfo = new CouponProductInfo($name, $range, $validityText, $forLocale);
// THEN
expect($couponProductInfo->name)->toBe($name)
->and($couponProductInfo->range)->toBe($range)
->and($couponProductInfo->validityText)->toBe($validityText)
->and($couponProductInfo->forLocale)->toBe($forLocale);
});
it('rejects invalid coupon product info', function (): void {
// GIVEN
$name = '';
$range = '';
$validityText = '';
$forLocale = Country::FRANCE;
// GIVEN / THEN
expect(fn (): CouponProductInfo => new CouponProductInfo($name, $range, $validityText, $forLocale))->toThrow(\DomainException::class);
});
});

View file

@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects\Coupon;
use Domain\Models\ValueObjects\Coupon\CouponProduct;
covers(CouponProduct::class);
describe('coupon product', function (): void {
it('creates a valid coupon product', function (): void {
// GIVEN
$name = 'Lorem';
$incoCodes = [];
$labels = [];
// WHEN
$couponProduct = new CouponProduct($name, $incoCodes, $labels);
// THEN
expect($couponProduct->name)->toBe($name)
->and($couponProduct->incoCodes)->toBe($incoCodes)
->and($couponProduct->labels)->toBe($labels);
});
it('rejects invalid coupon product', function (): void {
// GIVEN
$name = '';
$incoCodes = [];
$labels = [];
// GIVEN / THEN
expect(fn (): CouponProduct => new CouponProduct($name, $incoCodes, $labels))->toThrow(\DomainException::class);
});
});

View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects\Coupon;
use Domain\Models\ValueObjects\Coupon\CouponQuantity;
covers(CouponQuantity::class);
describe('coupon quantity', function (): void {
it('creates a valid coupon quantity', function (): void {
// GIVEN
$quantity = 10;
// WHEN
$couponQuantity = new CouponQuantity($quantity);
// THEN
expect($couponQuantity)->toBeInstanceOf(CouponQuantity::class)
->and($couponQuantity->quantity)->toBe($quantity);
});
it('rejects a quantity of zero', function (): void {
// WHEN
$action = fn (): CouponQuantity => new CouponQuantity(0);
// THEN
expect($action)->toThrow(\DomainException::class, 'CouponQuantity quantity must be positive.');
});
it('rejects a negative quantity', function (): void {
// WHEN
$action = fn (): CouponQuantity => new CouponQuantity(-1);
// THEN
expect($action)->toThrow(\DomainException::class, 'CouponQuantity quantity must be positive.');
});
});

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects\Coupon;
use Domain\Enums\Country;
use Domain\Models\ValueObjects\CountryAuthorized;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
covers(CouponValidityInformation::class);
describe('coupon validity information', function (): void {
it('creates a valid coupon validity information', function (): void {
// GIVEN
$validityText = 'Lorem ipsum dolor sit amet consectetur adipiscing elit.';
$forLocale = new CountryAuthorized(Country::FRANCE);
// WHEN
$couponValidityInformation = new CouponValidityInformation($validityText, $forLocale);
// THEN
expect($couponValidityInformation->validityText)->toBe($validityText)
->and($couponValidityInformation->forLocale)->toBe($forLocale);
});
it('rejects invalid coupon validity information', function (): void {
// GIVEN
$validityText = '';
$forLocale = new CountryAuthorized(Country::FRANCE);
// GIVEN / THEN
expect(fn (): CouponValidityInformation => new CouponValidityInformation($validityText, $forLocale))->toThrow(\DomainException::class);
});
});

View file

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects;
use Domain\Models\ValueObjects\CouponProductName;
covers(CouponProductName::class);
describe('coupon product name', function (): void {
it('creates a valid coupon product name', function (): void {
// GIVEN
$value = 'Lorem';
// WHEN
$couponProductName = new CouponProductName($value);
// THEN
expect($couponProductName->value)->toBe($value);
});
it('rejects invalid coupon product name', function (): void {
// GIVEN
$value = '';
// GIVEN / THEN
expect(fn (): CouponProductName => new CouponProductName($value))->toThrow(\DomainException::class);
});
});

View file

@ -5,67 +5,239 @@ declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects\RequestedCoupon;
use Carbon\CarbonImmutable;
use DateTime;
use Domain\Enums\BarcodeFormat;
use Domain\Enums\Country;
use Domain\Models\ValueObjects\CountryAuthorized;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
use Domain\Models\ValueObjects\CouponProductName;
use Domain\Models\ValueObjects\GS1;
use Domain\Models\ValueObjects\IncoCode;
use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Shared\Enums\Currency;
use DateTime;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCoupon;
use Shared\ValueObjects\Money;
covers(RequestedCoupon::class);
describe('requested coupon', function (): void {
beforeEach(function (): void {
$this->name = new CouponName('Coupon A');
$this->quantity = new RequestedCouponQuantity(100);
$this->diffusion = new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'));
$this->moneyAmount = new MoneyAmount(new Money(Currency::EUR, 300));
$this->processingCode = new ProcessingCode('PC1');
$this->couponPicture = new CouponPicture(
new PicturePath('coupons/product.jpg'),
new PicturePath('coupons/thumbnail.jpg'),
new PicturePath('coupons/inactive_thumbnail.jpg')
);
$this->couponValidity = new CouponValidity(30, new DateTime('2026-02-01'));
$this->couponValidityInformation = [
new CouponValidityInformation(
'Lorem ipsum dolor sit amet consectetur adipiscing elit.',
new CountryAuthorized(Country::FRANCE)
),
];
$this->products = [new IncoCode(35784), new IncoCode(15522)];
$this->printRules = new PrintRules(maxPerCustomer: 3);
$this->gs1 = new GS1('01234567890128', BarcodeFormat::EAN13);
$this->warningThresholdQuantity = new WarningThresholdQuantity(10);
});
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
it('hydrates a requested coupon with all its fields', function (): void {
// GIVEN
$name = new CouponName('Coupon A');
$productName = new CouponProductName('Product A');
$quantity = new RequestedCouponQuantity(100);
$warningThresholdQuantity = new WarningThresholdQuantity(10);
$period = new Period(new DateTime('2026-01-01'), new DateTime('2026-02-01'));
$moneyAmount = new MoneyAmount(new Money(Currency::EUR, 300));
$processingCode = new ProcessingCode('PC1');
$couponPicture = new CouponPicture(new PicturePath('coupons/product.jpg'), new PicturePath('coupons/thumbnail.jpg'));
$gs1 = new GS1('01234567890128', BarcodeFormat::EAN13);
$couponValidity = new CouponValidity(30, new DateTime());
// WHEN
// GIVEN / WHEN
$coupon = new RequestedCoupon(
$name,
$productName,
$quantity,
$warningThresholdQuantity,
$period,
$moneyAmount,
$processingCode,
$couponPicture,
$gs1,
$couponValidity,
$this->name,
$this->quantity,
$this->diffusion,
$this->moneyAmount,
$this->processingCode,
$this->couponPicture,
$this->couponValidity,
$this->couponValidityInformation,
$this->products,
$this->printRules,
$this->gs1,
$this->warningThresholdQuantity
);
// THEN
expect($coupon->name)->toBe($name)
->and($coupon->productName)->toBe($productName)
->and($coupon->quantity)->toBe($quantity)
->and($coupon->warningThresholdQuantity)->toBe($warningThresholdQuantity)
->and($coupon->period)->toBe($period)
->and($coupon->moneyAmount)->toBe($moneyAmount)
->and($coupon->processingCode)->toBe($processingCode)
->and($coupon->couponPicture)->toBe($couponPicture)
->and($coupon->gs1)->toBe($gs1)
->and($coupon->couponValidity)->toBe($couponValidity);
expect($coupon->name)->toBe($this->name)
->and($coupon->quantity)->toBe($this->quantity)
->and($coupon->diffusion)->toBe($this->diffusion)
->and($coupon->moneyAmount)->toBe($this->moneyAmount)
->and($coupon->processingCode)->toBe($this->processingCode)
->and($coupon->couponPicture)->toBe($this->couponPicture)
->and($coupon->couponValidity)->toBe($this->couponValidity)
->and($coupon->couponValidityInformation)->toBe($this->couponValidityInformation)
->and($coupon->products)->toBe($this->products)
->and($coupon->printRules)->toBe($this->printRules)
->and($coupon->gs1)->toBe($this->gs1)
->and($coupon->warningThresholdQuantity)->toBe($this->warningThresholdQuantity);
});
it('hydrates a requested coupon without products', function (): void {
// GIVEN / WHEN
$coupon = new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: $this->moneyAmount,
processingCode: $this->processingCode,
couponPicture:$this->couponPicture,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
printRules: $this->printRules,
gs1: $this->gs1,
warningThresholdQuantity: $this->warningThresholdQuantity
);
// THEN
expect($coupon->products)->toBe([])
->and($coupon->printRules)->toBe($this->printRules)
->and($coupon->gs1)->toBe($this->gs1)
->and($coupon->warningThresholdQuantity)->toBe($this->warningThresholdQuantity);
});
it('hydrates a requested coupon without print rules', function (): void {
// GIVEN / WHEN
$coupon = new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: $this->moneyAmount,
processingCode: $this->processingCode,
couponPicture:$this->couponPicture,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
products: $this->products,
gs1: $this->gs1,
warningThresholdQuantity: $this->warningThresholdQuantity
);
// THEN
expect($coupon->printRules)->toBeNull()
->and($coupon->products)->toBe($this->products)
->and($coupon->gs1)->toBe($this->gs1)
->and($coupon->warningThresholdQuantity)->toBe($this->warningThresholdQuantity);
});
it('hydrates a requested coupon without gs1', function (): void {
// GIVEN / WHEN
$coupon = new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: $this->moneyAmount,
processingCode: $this->processingCode,
couponPicture:$this->couponPicture,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
products: $this->products,
printRules: $this->printRules,
warningThresholdQuantity: $this->warningThresholdQuantity
);
// THEN
expect($coupon->gs1)->toBeNull()
->and($coupon->products)->toBe($this->products)
->and($coupon->printRules)->toBe($this->printRules)
->and($coupon->warningThresholdQuantity)->toBe($this->warningThresholdQuantity);
});
it('hydrates a requested coupon without warning threshold quantity', function (): void {
// GIVEN / WHEN
$coupon = new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: $this->moneyAmount,
processingCode: $this->processingCode,
couponPicture:$this->couponPicture,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
products: $this->products,
printRules: $this->printRules,
gs1: $this->gs1,
);
// THEN
expect($coupon->warningThresholdQuantity)->toBeNull()
->and($coupon->products)->toBe($this->products)
->and($coupon->printRules)->toBe($this->printRules)
->and($coupon->gs1)->toBe($this->gs1);
});
it('hydrates a requested coupon with only required fields', function (): void {
// GIVEN / WHEN
$coupon = new RequestedCoupon(
name: $this->name,
quantity: $this->quantity,
diffusion: $this->diffusion,
moneyAmount: $this->moneyAmount,
processingCode: $this->processingCode,
couponPicture:$this->couponPicture,
couponValidity: $this->couponValidity,
couponValidityInformation: $this->couponValidityInformation,
);
// THEN
expect($coupon->products)->toBe([])
->and($coupon->printRules)->toBeNull()
->and($coupon->gs1)->toBeNull()
->and($coupon->warningThresholdQuantity)->toBeNull();
});
it('accepts an empty validity information list', function (): void {
// GIVEN / WHEN
$coupon = new RequestedCoupon(
$this->name,
$this->quantity,
$this->diffusion,
$this->moneyAmount,
$this->processingCode,
$this->couponPicture,
$this->couponValidity,
[],
$this->products,
$this->printRules,
$this->gs1,
$this->warningThresholdQuantity
);
// THEN
expect($coupon->couponValidityInformation)->toBe([]);
});
it('throws when validity information contains an invalid item', function (): void {
// GIVEN / WHEN / THEN
expect(fn () => new RequestedCoupon(
$this->name,
$this->quantity,
$this->diffusion,
$this->moneyAmount,
$this->processingCode,
$this->couponPicture,
$this->couponValidity,
['Not a CouponValidityInformation'],
$this->products,
$this->printRules,
$this->gs1,
$this->warningThresholdQuantity
))->toThrow(\DomainException::class, 'Coupon validity information must be CouponValidityInformation instances.');
});
it('hydrates a requested coupon without a GS1', function (): void {