refactor: ♻️ update coupon Entity and related VO
This commit is contained in:
parent
4d3c778d17
commit
fdbde84728
37 changed files with 366 additions and 324 deletions
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Application\UseCases\Brand\Create;
|
||||
|
||||
use Domain\Exceptions\BrandCodeAlreadyExistsException;
|
||||
use Domain\Models\Entities\Brand;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
use Domain\Ports\Services\PictureStorage;
|
||||
|
|
@ -28,7 +27,7 @@ final readonly class CreateBrand implements UseCase, Transactional
|
|||
Assert::isInstanceOf($input, Input::class);
|
||||
|
||||
if ($this->repository->existsByCode($input->code)) {
|
||||
throw new BrandCodeAlreadyExistsException($input->code);
|
||||
throw new \DomainException(sprintf('Brand code "%s" already exists.', $input->code->value));
|
||||
}
|
||||
|
||||
$logoPath = $this->pictureStorage->store($input->logo);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Application\UseCases\CampaignRequest\Create;
|
||||
|
||||
use Domain\Exceptions\BrandNotFoundException;
|
||||
use Domain\Models\Entities\CampaignRequest;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
use Domain\Ports\Repositories\CampaignRequestRepository;
|
||||
|
|
@ -27,8 +26,8 @@ final readonly class CreateCampaignRequest implements UseCase, Transactional
|
|||
{
|
||||
Assert::isInstanceOf($input, Input::class);
|
||||
|
||||
if (!$this->brandRepository->existsByCode($input->brandCode)) {
|
||||
throw new BrandNotFoundException($input->brandCode);
|
||||
if (! $this->brandRepository->existsByCode($input->brandCode)) {
|
||||
throw new \DomainException(sprintf('Brand with code "%s" was not found.', $input->brandCode->value));
|
||||
}
|
||||
|
||||
$campaignRequest = CampaignRequest::create(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ namespace Application\UseCases\CampaignRequest\Find;
|
|||
use Application\UseCases\CampaignRequest\Find\Output\CampaignRequestFound;
|
||||
use Application\UseCases\CampaignRequest\Find\Output\CampaignRequestNotFound;
|
||||
use Application\UseCases\CampaignRequest\Find\Output\Output;
|
||||
use Domain\Exceptions\BrandNotFoundException;
|
||||
use Domain\Models\Entities\Brand;
|
||||
use Domain\Models\Entities\CampaignRequest;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
|
|
@ -39,7 +38,7 @@ final readonly class FindCampaignRequest implements UseCase
|
|||
$brand = $this->brandRepository->findByCode($campaignRequest->brandCode);
|
||||
|
||||
if (! $brand instanceof Brand) {
|
||||
throw new BrandNotFoundException($campaignRequest->brandCode);
|
||||
throw new \DomainException(sprintf('Brand with code "%s" was not found.', $campaignRequest->brandCode->value));
|
||||
}
|
||||
|
||||
return new CampaignRequestFound(
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Enums;
|
||||
|
||||
enum BarcodeFormat: string
|
||||
{
|
||||
case EAN13 = 'ean';
|
||||
case CODE128 = 'code128';
|
||||
case GS1_128 = 'gs1';
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Exceptions;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
|
||||
/**
|
||||
* There cannot be two brands with the same code.
|
||||
*/
|
||||
final class BrandCodeAlreadyExistsException extends \DomainException
|
||||
{
|
||||
public function __construct(BrandCode $code)
|
||||
{
|
||||
parent::__construct(sprintf('Brand code "%s" already exists.', $code->value));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Exceptions;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Shared\DDD\Exceptions\NotFoundException;
|
||||
|
||||
/**
|
||||
* The brand referenced by a code could not be found.
|
||||
*/
|
||||
final class BrandNotFoundException extends NotFoundException
|
||||
{
|
||||
public function __construct(BrandCode $brandCode)
|
||||
{
|
||||
parent::__construct(sprintf('Brand with code "%s" was not found.', $brandCode->value));
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ namespace Domain\Models\Entities;
|
|||
use Domain\Models\ValueObjects\Actor\ActorId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Campaign\CampaignCode;
|
||||
use Domain\Models\ValueObjects\Campaign\CampaignCoupon;
|
||||
use Domain\Models\ValueObjects\Campaign\CampaignCouponArray;
|
||||
use Domain\Models\ValueObjects\Campaign\CampaignId;
|
||||
use Domain\Models\ValueObjects\Campaign\CampaignName;
|
||||
use Shared\DDD\Models\AggregateRoot;
|
||||
|
|
@ -18,25 +18,17 @@ use Shared\DDD\Models\HasTimestampIdentifier;
|
|||
*/
|
||||
final readonly class Campaign implements AggregateRoot, HasTimestampIdentifier
|
||||
{
|
||||
/**
|
||||
* @param array<CampaignCoupon> $coupons
|
||||
*/
|
||||
private function __construct(
|
||||
public CampaignId $id,
|
||||
public CampaignCode $code,
|
||||
public CampaignName $name,
|
||||
public bool $publication,
|
||||
public bool $printAllAtOnce,
|
||||
public array $coupons,
|
||||
public CampaignCouponArray $coupons,
|
||||
public BrandCode $brandCode,
|
||||
public ActorId $actorId,
|
||||
public ?\DateTimeInterface $updatedAt = null,
|
||||
) {
|
||||
foreach ($this->coupons as $coupon) {
|
||||
if (!$coupon instanceof CampaignCoupon) {
|
||||
throw new \DomainException('Campaign coupons must be CampaignCoupon instances.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function id(): CampaignId
|
||||
|
|
@ -54,15 +46,12 @@ final readonly class Campaign implements AggregateRoot, HasTimestampIdentifier
|
|||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<CampaignCoupon> $coupons
|
||||
*/
|
||||
public static function create(
|
||||
CampaignCode $code,
|
||||
CampaignName $name,
|
||||
bool $publication,
|
||||
bool $printAllAtOnce,
|
||||
array $coupons,
|
||||
CampaignCouponArray $coupons,
|
||||
BrandCode $brandCode,
|
||||
ActorId $actorId,
|
||||
?\DateTimeInterface $updatedAt = null,
|
||||
|
|
@ -80,16 +69,13 @@ final readonly class Campaign implements AggregateRoot, HasTimestampIdentifier
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<CampaignCoupon> $coupons
|
||||
*/
|
||||
public static function hydrate(
|
||||
CampaignId $id,
|
||||
CampaignCode $code,
|
||||
CampaignName $name,
|
||||
bool $publication,
|
||||
bool $printAllAtOnce,
|
||||
array $coupons,
|
||||
CampaignCouponArray $coupons,
|
||||
BrandCode $brandCode,
|
||||
ActorId $actorId,
|
||||
?\DateTimeInterface $updatedAt = null,
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
|
|||
campaignType: $campaignType,
|
||||
requestedCoupon: $requestedCoupon,
|
||||
status: CampaignRequestStatus::CREATED,
|
||||
rejectionReason: null,
|
||||
updatedAt: $updatedAt,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ 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\CouponValidity;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
|
||||
use Domain\Models\ValueObjects\CouponName;
|
||||
use Domain\Models\ValueObjects\CouponPicture;
|
||||
use Domain\Models\ValueObjects\GS1;
|
||||
use Domain\Models\ValueObjects\IncoCode;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponMovementArray;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponName;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponPicture;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponProcessingCodeArray;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponProductArray;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponQuantity;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponValidityInformationArray;
|
||||
use Domain\Models\ValueObjects\MoneyAmount;
|
||||
use Domain\Models\ValueObjects\ProcessingCode;
|
||||
use Shared\DDD\Models\AggregateRoot;
|
||||
use Shared\DDD\Models\HasTimestampIdentifier;
|
||||
|
||||
|
|
@ -27,11 +27,6 @@ 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,
|
||||
|
|
@ -39,28 +34,17 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier
|
|||
public MoneyAmount $amount,
|
||||
public CouponPicture $picture,
|
||||
public CouponDiffusion $diffusion,
|
||||
public CouponValidity $validity,
|
||||
public array $couponValidityInformation,
|
||||
public array $couponLegalInformation,
|
||||
public ProcessingCode $processingCode,
|
||||
public CouponValidityInformationArray $validityInformations,
|
||||
public CouponLegalInformationArray $legalInformations,
|
||||
public CouponProcessingCodeArray $processingCode,
|
||||
public BrandCode $brandCode,
|
||||
public ActorId $createdBy,
|
||||
public array $products = [],
|
||||
public CouponProductArray $products,
|
||||
public CouponMovementArray $movements,
|
||||
public CouponQuantity $quantity,
|
||||
public ?CouponAvailabilityCalendar $availabilityCalendar = null,
|
||||
public ?GS1 $gs1 = null,
|
||||
public ?\DateTimeInterface $updatedAt = null,
|
||||
) {
|
||||
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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function id(): CouponId
|
||||
|
|
@ -78,26 +62,21 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier
|
|||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<CouponValidityInformation> $couponValidityInformation
|
||||
* @param array<CouponLegalInformation> $couponLegalInformation
|
||||
* @param array<IncoCode> $products
|
||||
*/
|
||||
public static function create(
|
||||
CouponCode $code,
|
||||
CouponName $name,
|
||||
MoneyAmount $amount,
|
||||
CouponPicture $picture,
|
||||
CouponDiffusion $diffusion,
|
||||
CouponValidity $validity,
|
||||
array $couponValidityInformation,
|
||||
array $couponLegalInformation,
|
||||
ProcessingCode $processingCode,
|
||||
CouponValidityInformationArray $validityInformations,
|
||||
CouponLegalInformationArray $legalInformations,
|
||||
CouponProcessingCodeArray $processingCode,
|
||||
BrandCode $brandCode,
|
||||
ActorId $createdBy,
|
||||
array $products = [],
|
||||
CouponProductArray $products,
|
||||
CouponMovementArray $movements,
|
||||
CouponQuantity $quantity,
|
||||
?CouponAvailabilityCalendar $availabilityCalendar = null,
|
||||
?GS1 $gs1 = null,
|
||||
?\DateTimeInterface $updatedAt = null,
|
||||
): Coupon {
|
||||
return new self(
|
||||
|
|
@ -107,24 +86,19 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier
|
|||
amount: $amount,
|
||||
picture: $picture,
|
||||
diffusion: $diffusion,
|
||||
validity: $validity,
|
||||
couponValidityInformation: $couponValidityInformation,
|
||||
couponLegalInformation: $couponLegalInformation,
|
||||
validityInformations: $validityInformations,
|
||||
legalInformations: $legalInformations,
|
||||
processingCode: $processingCode,
|
||||
brandCode: $brandCode,
|
||||
createdBy: $createdBy,
|
||||
products: $products,
|
||||
movements: $movements,
|
||||
quantity: $quantity,
|
||||
availabilityCalendar: $availabilityCalendar,
|
||||
gs1: $gs1,
|
||||
updatedAt: $updatedAt,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<CouponValidityInformation> $couponValidityInformation
|
||||
* @param array<CouponLegalInformation> $couponLegalInformation
|
||||
* @param array<IncoCode> $products
|
||||
*/
|
||||
public static function hydrate(
|
||||
CouponId $id,
|
||||
CouponCode $code,
|
||||
|
|
@ -132,15 +106,15 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier
|
|||
MoneyAmount $amount,
|
||||
CouponPicture $picture,
|
||||
CouponDiffusion $diffusion,
|
||||
CouponValidity $validity,
|
||||
array $couponValidityInformation,
|
||||
array $couponLegalInformation,
|
||||
ProcessingCode $processingCode,
|
||||
CouponValidityInformationArray $validityInformations,
|
||||
CouponLegalInformationArray $legalInformations,
|
||||
CouponProcessingCodeArray $processingCode,
|
||||
BrandCode $brandCode,
|
||||
ActorId $createdBy,
|
||||
array $products = [],
|
||||
CouponProductArray $products,
|
||||
CouponMovementArray $movements,
|
||||
CouponQuantity $quantity,
|
||||
?CouponAvailabilityCalendar $availabilityCalendar = null,
|
||||
?GS1 $gs1 = null,
|
||||
?\DateTimeInterface $updatedAt = null,
|
||||
): self {
|
||||
return new self(
|
||||
|
|
@ -150,15 +124,15 @@ final readonly class Coupon implements AggregateRoot, HasTimestampIdentifier
|
|||
amount: $amount,
|
||||
picture: $picture,
|
||||
diffusion: $diffusion,
|
||||
validity: $validity,
|
||||
couponValidityInformation: $couponValidityInformation,
|
||||
couponLegalInformation: $couponLegalInformation,
|
||||
validityInformations: $validityInformations,
|
||||
legalInformations: $legalInformations,
|
||||
processingCode: $processingCode,
|
||||
brandCode: $brandCode,
|
||||
createdBy: $createdBy,
|
||||
products: $products,
|
||||
movements: $movements,
|
||||
quantity: $quantity,
|
||||
availabilityCalendar: $availabilityCalendar,
|
||||
gs1: $gs1,
|
||||
updatedAt: $updatedAt,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||
namespace Domain\Models\Entities;
|
||||
|
||||
use Domain\Models\ValueObjects\Campaign\CampaignCode;
|
||||
use Domain\Models\ValueObjects\ProcessingCode;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponCode;
|
||||
use Domain\Models\ValueObjects\Stock\StockAdjustment;
|
||||
use Domain\Models\ValueObjects\Stock\StockAmount;
|
||||
use Domain\Models\ValueObjects\Stock\StockId;
|
||||
|
|
@ -21,7 +21,7 @@ final readonly class Stock implements AggregateRoot, HasTimestampIdentifier
|
|||
public StockId $id,
|
||||
public StockAmount $stockAmount,
|
||||
public CampaignCode $campaignCode,
|
||||
public ProcessingCode $processingCode,
|
||||
public CouponCode $couponCode,
|
||||
/** @var array<int, StockAdjustment> $stockAdjustment */
|
||||
public array $stockAdjustment,
|
||||
public ?\DateTimeInterface $updatedAt = null,
|
||||
|
|
@ -55,7 +55,7 @@ final readonly class Stock implements AggregateRoot, HasTimestampIdentifier
|
|||
StockId $id,
|
||||
StockAmount $stockAmount,
|
||||
CampaignCode $campaignCode,
|
||||
ProcessingCode $processingCode,
|
||||
CouponCode $couponCode,
|
||||
array $stockAdjustment = [],
|
||||
?\DateTimeInterface $updatedAt = null,
|
||||
): self {
|
||||
|
|
@ -63,7 +63,7 @@ final readonly class Stock implements AggregateRoot, HasTimestampIdentifier
|
|||
id: $id,
|
||||
stockAmount: $stockAmount,
|
||||
campaignCode: $campaignCode,
|
||||
processingCode: $processingCode,
|
||||
couponCode: $couponCode,
|
||||
stockAdjustment: $stockAdjustment,
|
||||
updatedAt: $updatedAt,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,17 +4,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace Domain\Models\ValueObjects\Campaign;
|
||||
|
||||
use Domain\Models\ValueObjects\Coupon\CouponQuantity;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponCode;
|
||||
use Domain\Models\ValueObjects\PrintRules;
|
||||
use Domain\Models\ValueObjects\ProcessingCode;
|
||||
use Shared\Enums\Country;
|
||||
|
||||
final readonly class CampaignCoupon
|
||||
{
|
||||
public function __construct(
|
||||
public ProcessingCode $processingCode,
|
||||
public CouponQuantity $quantity,
|
||||
public Country $availableIn,
|
||||
public CouponCode $couponCode,
|
||||
public ?PrintRules $printRules = null,
|
||||
) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Campaign;
|
||||
|
||||
final readonly class CampaignCouponArray
|
||||
{
|
||||
public function __construct(
|
||||
/** @var CampaignCoupon[] */
|
||||
public array $value,
|
||||
) {
|
||||
if ($this->value === []) {
|
||||
throw new \DomainException('Coupons requires at least one CampaignCoupon.');
|
||||
}
|
||||
|
||||
foreach ($this->value as $coupon) {
|
||||
if (! $coupon instanceof CampaignCoupon) {
|
||||
throw new \DomainException('Coupons must be CampaignCoupon instances.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ final readonly class CampaignRequestFilters
|
|||
public ?BrandCode $brandCode = null,
|
||||
public ?CampaignRequestStatus $status = null,
|
||||
) {
|
||||
if (null === $this->brandCode && null === $this->status) {
|
||||
if (!$this->brandCode instanceof BrandCode && !$this->status instanceof CampaignRequestStatus) {
|
||||
throw new \InvalidArgumentException('CampaignRequestFilters require at least a brand code or a status.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ final readonly class CouponAvailabilityCalendar implements Equatable
|
|||
}
|
||||
|
||||
foreach ($this->periods as $period) {
|
||||
if (!$period instanceof Period) {
|
||||
if (! $period instanceof Period) {
|
||||
throw new \DomainException('CouponAvailabilityCalendar periods must be Period instances.');
|
||||
}
|
||||
}
|
||||
|
|
@ -27,23 +27,17 @@ final readonly class CouponAvailabilityCalendar implements Equatable
|
|||
|
||||
public function isActiveAt(DateTime $in): bool
|
||||
{
|
||||
foreach ($this->periods as $period) {
|
||||
if ($in >= $period->start && $in <= $period->end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return array_any($this->periods, fn ($period): bool => $in >= $period->start && $in <= $period->end);
|
||||
}
|
||||
|
||||
public function beginsAt(): DateTime
|
||||
{
|
||||
return min(array_map(fn (Period $period) => $period->start, $this->periods));
|
||||
return min(array_map(fn (Period $period): DateTime => $period->start, $this->periods));
|
||||
}
|
||||
|
||||
public function endsAt(): DateTime
|
||||
{
|
||||
return max(array_map(fn (Period $period) => $period->end, $this->periods));
|
||||
return max(array_map(fn (Period $period): DateTime => $period->end, $this->periods));
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects;
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
|
||||
final readonly class IncoCode implements Equatable
|
||||
final readonly class CouponIncoCode
|
||||
{
|
||||
/**
|
||||
* @throws \DomainException
|
||||
|
|
@ -18,10 +16,4 @@ final readonly class IncoCode implements Equatable
|
|||
throw new \DomainException('Inco Code must be a positive integer.');
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->value === $other->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ final readonly class CouponLegalInformation
|
|||
{
|
||||
public function __construct(
|
||||
public string $legalText,
|
||||
public Country $forLocale
|
||||
public Country $forLocale,
|
||||
) {
|
||||
if (mb_trim($this->legalText) === '') {
|
||||
throw new \DomainException('Legal text cannot be empty.');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
use Shared\Enums\Country;
|
||||
|
||||
final readonly class CouponLegalInformationArray
|
||||
{
|
||||
public function __construct(
|
||||
/** @var CouponLegalInformation[] */
|
||||
public array $value,
|
||||
) {
|
||||
if ($this->value === []) {
|
||||
throw new \DomainException('CouponLegalInformations requires at least one CouponLegalInformation.');
|
||||
}
|
||||
|
||||
$hasFrance = false;
|
||||
|
||||
foreach ($this->value as $couponLegalInformation) {
|
||||
if (! $couponLegalInformation instanceof CouponLegalInformation) {
|
||||
throw new \DomainException('Value of CouponLegalInformationArray must be CouponLegalInformation instances.');
|
||||
}
|
||||
|
||||
if ($couponLegalInformation->forLocale === Country::FRANCE) {
|
||||
$hasFrance = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $hasFrance) {
|
||||
throw new \DomainException('CouponLegalInformations requires a CouponLegalInformation for France.');
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Domain/Models/ValueObjects/Coupon/CouponMovement.php
Normal file
17
src/Domain/Models/ValueObjects/Coupon/CouponMovement.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
final readonly class CouponMovement
|
||||
{
|
||||
public \DateTimeInterface $createdAt;
|
||||
|
||||
public function __construct(
|
||||
public int $quantity,
|
||||
?\DateTimeInterface $createdAt = null,
|
||||
) {
|
||||
$this->createdAt = $createdAt ?? new \DateTimeImmutable();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
final readonly class CouponMovementArray
|
||||
{
|
||||
public function __construct(
|
||||
/** @var CouponMovement[] */
|
||||
public array $couponMovements,
|
||||
) {
|
||||
if ($this->couponMovements === []) {
|
||||
throw new \DomainException('CouponMovementArray requires at least one CouponMovement.');
|
||||
}
|
||||
|
||||
foreach ($this->couponMovements as $couponMovement) {
|
||||
if (! $couponMovement instanceof CouponMovement) {
|
||||
throw new \DomainException('Value of CouponMovementArray must be CouponMovement instances.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Domain/Models/ValueObjects/Coupon/CouponName.php
Normal file
22
src/Domain/Models/ValueObjects/Coupon/CouponName.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
final readonly class CouponName
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
) {
|
||||
$trimmed = mb_trim($value);
|
||||
|
||||
if ($trimmed === '') {
|
||||
throw new \DomainException('Coupon name cannot be empty.');
|
||||
}
|
||||
|
||||
if (mb_strlen($trimmed) > 200) {
|
||||
throw new \DomainException('Coupon name exceeds the maximum allowed length.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,14 +2,16 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects;
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
use Domain\Models\ValueObjects\PicturePath;
|
||||
|
||||
final readonly class CouponPicture
|
||||
{
|
||||
public function __construct(
|
||||
public PicturePath $decoration,
|
||||
public PicturePath $thumbnail,
|
||||
public PicturePath $inactiveThumbnail
|
||||
public PicturePath $mobileThumbnail,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
use Shared\Enums\Country;
|
||||
|
||||
final readonly class CouponProcessingCode implements \Stringable, Equatable
|
||||
{
|
||||
private const string PATTERN = '/^[A-Za-z0-9]{13}\z/';
|
||||
|
||||
public function __construct(
|
||||
public string $value,
|
||||
public Country $availableIn,
|
||||
) {
|
||||
if (preg_match(self::PATTERN, $this->value) !== 1) {
|
||||
throw new \DomainException(
|
||||
'Processing Code must be exactly 13 alphanumeric characters, e.g. ABC1234567890.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->value === $other->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
use Shared\Enums\Country;
|
||||
|
||||
final readonly class CouponProcessingCodeArray
|
||||
{
|
||||
public function __construct(
|
||||
/** @var CouponProcessingCode[] */
|
||||
public array $value,
|
||||
) {
|
||||
if ($this->value === []) {
|
||||
throw new \DomainException('ProcessingCodes requires at least one CouponProcessingCode.');
|
||||
}
|
||||
|
||||
$hasFrance = false;
|
||||
|
||||
foreach ($this->value as $processingCode) {
|
||||
if (! $processingCode instanceof CouponProcessingCode) {
|
||||
throw new \DomainException('Value of CouponProcessingCodeArray must be CouponProcessingCode instances.');
|
||||
}
|
||||
|
||||
if ($processingCode->availableIn === Country::FRANCE) {
|
||||
$hasFrance = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $hasFrance) {
|
||||
throw new \DomainException('CouponProcessingCodes requires a CouponProcessingCode for France.');
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/Domain/Models/ValueObjects/Coupon/CouponProductArray.php
Normal file
19
src/Domain/Models/ValueObjects/Coupon/CouponProductArray.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
final readonly class CouponProductArray
|
||||
{
|
||||
public function __construct(
|
||||
/** @var CouponIncoCode[] */
|
||||
public array $value = [],
|
||||
) {
|
||||
foreach ($this->value as $product) {
|
||||
if (! $product instanceof CouponIncoCode) {
|
||||
throw new \DomainException('Value of CouponProductArray must be CouponIncoCode instances.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,9 +7,9 @@ namespace Domain\Models\ValueObjects\Coupon;
|
|||
final readonly class CouponQuantity
|
||||
{
|
||||
public function __construct(
|
||||
public int $quantity,
|
||||
public int $value,
|
||||
) {
|
||||
if ($this->quantity <= 0) {
|
||||
if ($this->value <= 0) {
|
||||
throw new \DomainException('CouponQuantity quantity must be positive.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
use DateTime;
|
||||
|
||||
final readonly class CouponValidity implements Equatable
|
||||
{
|
||||
public function __construct(
|
||||
public int $days,
|
||||
public DateTime $until
|
||||
) {
|
||||
if ($this->days <= 0) {
|
||||
throw new \DomainException('Coupon Validity days must be positive.');
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->days === $other->days && $this->until == $other->until;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects\Coupon;
|
||||
|
||||
use Shared\Enums\Country;
|
||||
|
||||
final readonly class CouponValidityInformationArray
|
||||
{
|
||||
public function __construct(
|
||||
/** @var CouponValidityInformation[] */
|
||||
public array $value,
|
||||
) {
|
||||
if ($this->value === []) {
|
||||
throw new \DomainException('CouponValidityInformations requires at least one CouponValidityInformation.');
|
||||
}
|
||||
|
||||
$hasFrance = false;
|
||||
|
||||
foreach ($this->value as $couponLegalInformation) {
|
||||
if (! $couponLegalInformation instanceof CouponValidityInformation) {
|
||||
throw new \DomainException('Value of CouponValidityInformationArray must be CouponValidityInformation instances.');
|
||||
}
|
||||
|
||||
if ($couponLegalInformation->forLocale === Country::FRANCE) {
|
||||
$hasFrance = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $hasFrance) {
|
||||
throw new \DomainException('CouponValidityInformations requires a CouponValidityInformation for France.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects;
|
||||
|
||||
final readonly class CouponName
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
) {
|
||||
if (mb_trim($this->value) === '') {
|
||||
throw new \DomainException('Coupon name cannot be empty.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects;
|
||||
|
||||
use Domain\Enums\BarcodeFormat;
|
||||
use Shared\DDD\Models\Equatable;
|
||||
|
||||
final readonly class GS1 implements \Stringable, Equatable
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
public BarcodeFormat $format
|
||||
) {
|
||||
if (mb_trim($this->value) === '') {
|
||||
throw new \DomainException('GS1 value cannot be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->value === $other->value && $this->format === $other->format;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Domain\Models\ValueObjects;
|
||||
|
||||
use Shared\DDD\Models\Equatable;
|
||||
|
||||
final readonly class ProcessingCode implements \Stringable, Equatable
|
||||
{
|
||||
public function __construct(
|
||||
public string $value,
|
||||
) {
|
||||
if (mb_trim($this->value) === '') {
|
||||
throw new \DomainException('Processing Code value cannot be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function equals(mixed $other): bool
|
||||
{
|
||||
return $other instanceof self && $this->value === $other->value;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,58 +4,67 @@ declare(strict_types=1);
|
|||
|
||||
namespace Domain\Models\ValueObjects\RequestedCoupon;
|
||||
|
||||
use Domain\Models\ValueObjects\Coupon\CouponValidity;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponLegalInformation;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponLegalInformationArray;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponName;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponPicture;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponProcessingCode;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponProcessingCodeArray;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponProductArray;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponValidityInformation;
|
||||
use Domain\Models\ValueObjects\CouponName;
|
||||
use Domain\Models\ValueObjects\GS1;
|
||||
use Domain\Models\ValueObjects\IncoCode;
|
||||
use Domain\Models\ValueObjects\Coupon\CouponValidityInformationArray;
|
||||
use Domain\Models\ValueObjects\MoneyAmount;
|
||||
use Domain\Models\ValueObjects\Period;
|
||||
use Domain\Models\ValueObjects\PrintRules;
|
||||
use Domain\Models\ValueObjects\ProcessingCode;
|
||||
use Shared\Enums\Country;
|
||||
|
||||
final readonly class RequestedCoupon
|
||||
{
|
||||
/**
|
||||
* @param array<CouponValidityInformation> $couponValidityInformation
|
||||
* @param array<IncoCode> $products
|
||||
*/
|
||||
public function __construct(
|
||||
public CouponName $name,
|
||||
public CouponName $couponName,
|
||||
public RequestedCouponQuantity $quantity,
|
||||
public Period $diffusion,
|
||||
public MoneyAmount $moneyAmount,
|
||||
public ProcessingCode $processingCode,
|
||||
public CouponValidity $couponValidity,
|
||||
public array $couponValidityInformation,
|
||||
public array $products = [],
|
||||
public CouponPicture $couponPicture,
|
||||
public CouponProcessingCodeArray $couponProcessingCodes,
|
||||
public CouponValidityInformationArray $couponValidityInformations,
|
||||
public CouponLegalInformationArray $couponLegalInformations,
|
||||
public CouponProductArray $couponProducts,
|
||||
public ?PrintRules $printRules = null,
|
||||
public ?GS1 $gs1 = null,
|
||||
) {
|
||||
foreach ($this->couponValidityInformation as $validityInformation) {
|
||||
if (!$validityInformation instanceof CouponValidityInformation) {
|
||||
throw new \DomainException('Coupon validity information must be CouponValidityInformation instances.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->hasValidityInformationFor(Country::FRANCE)) {
|
||||
throw new \DomainException('A coupon requires validity information for France.');
|
||||
}
|
||||
|
||||
if ($this->gs1 instanceof GS1 && !$this->hasValidityInformationFor(Country::BENELUX)) {
|
||||
throw new \DomainException('A GS1 coupon requires validity information for Benelux.');
|
||||
if ($this->hasProcessingCodeFor(Country::BENELUX)) {
|
||||
$this->assertCountryIsFullyCovered(Country::BENELUX);
|
||||
}
|
||||
}
|
||||
|
||||
private function hasValidityInformationFor(Country $country): bool
|
||||
private function assertCountryIsFullyCovered(Country $country): void
|
||||
{
|
||||
foreach ($this->couponValidityInformation as $validityInformation) {
|
||||
if ($validityInformation->forLocale === $country) {
|
||||
return true;
|
||||
}
|
||||
if (! $this->hasProcessingCodeFor($country)) {
|
||||
throw new \DomainException(sprintf('A coupon requires a processing code for %s.', $country->name));
|
||||
}
|
||||
|
||||
return false;
|
||||
if (! $this->hasInformationFor($country, $this->couponValidityInformations)) {
|
||||
throw new \DomainException(sprintf('A coupon requires validity information for %s.', $country->name));
|
||||
}
|
||||
|
||||
if (! $this->hasInformationFor($country, $this->couponLegalInformations)) {
|
||||
throw new \DomainException(sprintf('A coupon requires legal information for %s.', $country->name));
|
||||
}
|
||||
}
|
||||
|
||||
private function hasProcessingCodeFor(Country $country): bool
|
||||
{
|
||||
return array_any(
|
||||
$this->couponProcessingCodes->value,
|
||||
fn (CouponProcessingCode $processingCode): bool => $processingCode->availableIn === $country,
|
||||
);
|
||||
}
|
||||
|
||||
private function hasInformationFor(Country $country, CouponValidityInformationArray | CouponLegalInformationArray $informations): bool
|
||||
{
|
||||
return array_any(
|
||||
$informations->value,
|
||||
fn (CouponValidityInformation | CouponLegalInformation $information): bool => $information->forLocale === $country,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ final class DatesHelper
|
|||
$now = self::now();
|
||||
|
||||
return match (true) {
|
||||
$from !== null && $to !== null => $now->between($from, $to),
|
||||
$from !== null => $now->greaterThanOrEqualTo($from),
|
||||
$to !== null => $now->lessThanOrEqualTo($to),
|
||||
default => false,
|
||||
$from instanceof CarbonInterface && $to instanceof CarbonInterface => $now->between($from, $to),
|
||||
$from instanceof CarbonInterface => $now->greaterThanOrEqualTo($from),
|
||||
$to instanceof CarbonInterface => $now->lessThanOrEqualTo($to),
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ final class DatesHelper
|
|||
break;
|
||||
default:
|
||||
try {
|
||||
$output = (new \DateTimeImmutable($datetime, new \DateTimeZone('UTC')))->setTimezone(new \DateTimeZone('UTC'));
|
||||
$output = new \DateTimeImmutable($datetime, new \DateTimeZone('UTC'))->setTimezone(new \DateTimeZone('UTC'));
|
||||
} catch (\Exception) {
|
||||
throw InvalidDateFormatException::forDateString($datetime);
|
||||
}
|
||||
|
|
@ -96,21 +96,21 @@ final class DatesHelper
|
|||
return $output;
|
||||
}
|
||||
|
||||
protected static function parseFrench(string $datetime): \DateTimeInterface
|
||||
private static function parseFrench(string $datetime): \DateTimeInterface
|
||||
{
|
||||
$dt = \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $datetime . ' 00:00:00', new \DateTimeZone('UTC'));
|
||||
|
||||
return $dt ?: self::parseFallback($datetime);
|
||||
}
|
||||
|
||||
protected static function parseFrenchInverted(string $datetime): \DateTimeInterface
|
||||
private static function parseFrenchInverted(string $datetime): \DateTimeInterface
|
||||
{
|
||||
$dt = \DateTimeImmutable::createFromFormat('d-m-Y H:i:s', $datetime . ' 00:00:00', new \DateTimeZone('UTC'));
|
||||
|
||||
return $dt ?: self::parseFallback($datetime);
|
||||
}
|
||||
|
||||
protected static function parseRfc3339(string $datetime): \DateTimeInterface
|
||||
private static function parseRfc3339(string $datetime): \DateTimeInterface
|
||||
{
|
||||
$dt = \DateTimeImmutable::createFromFormat(\DateTimeInterface::RFC3339, $datetime);
|
||||
if ($dt !== false) {
|
||||
|
|
@ -122,17 +122,17 @@ final class DatesHelper
|
|||
return $dt ? $dt->setTimezone(new \DateTimeZone('UTC')) : self::parseFallback($datetime);
|
||||
}
|
||||
|
||||
protected static function parseIso8601(string $datetime): \DateTimeInterface
|
||||
private static function parseIso8601(string $datetime): \DateTimeInterface
|
||||
{
|
||||
$dt = \DateTimeImmutable::createFromFormat(\DateTimeInterface::ATOM, $datetime);
|
||||
|
||||
return $dt ? $dt->setTimezone(new \DateTimeZone('UTC')) : self::parseFallback($datetime);
|
||||
}
|
||||
|
||||
protected static function parseFallback(string $datetime): \DateTimeInterface
|
||||
private static function parseFallback(string $datetime): \DateTimeInterface
|
||||
{
|
||||
try {
|
||||
return (new \DateTimeImmutable($datetime, new \DateTimeZone('UTC')))->setTimezone(new \DateTimeZone('UTC'));
|
||||
return new \DateTimeImmutable($datetime, new \DateTimeZone('UTC'))->setTimezone(new \DateTimeZone('UTC'));
|
||||
} catch (\Throwable) {
|
||||
throw InvalidDateFormatException::forDateString($datetime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ final readonly class Paginated
|
|||
{
|
||||
/** @param T[] $items */
|
||||
public function __construct(
|
||||
private readonly Pagination $pagination,
|
||||
private Pagination $pagination,
|
||||
private array $items,
|
||||
private int $total
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use Shared\DDD\Models\Equatable;
|
|||
/**
|
||||
* Immutable value object representing a pagination range.
|
||||
*/
|
||||
final readonly class Pagination implements Equatable
|
||||
final readonly class Pagination implements Equatable, \Stringable
|
||||
{
|
||||
/** ERR_TOO_MANY_RECORDS fixed to 10k */
|
||||
public const int MAX_PAGE_SIZE = PageSize::MAX_PAGE_SIZE;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ describe('reject campaign request use case', function (): void {
|
|||
|
||||
it('rejects a campaign request and persists it', function (): void {
|
||||
// GIVEN
|
||||
CarbonImmutable::setTestNow('2026-06-15 12:00:00');
|
||||
CarbonImmutable::setTestNow('2026-06-15 12:00:00 UTC');
|
||||
$id = CampaignRequestId::generate();
|
||||
$campaignRequest = CampaignRequest::hydrate(
|
||||
$id,
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ describe('brand', function (): void {
|
|||
|
||||
it('updates a brand logo and sets updatedAt timestamp', function (): void {
|
||||
// GIVEN
|
||||
CarbonImmutable::setTestNow('2026-06-15 12:00:00');
|
||||
CarbonImmutable::setTestNow('2026-06-15 12:00:00 UTC');
|
||||
$id = BrandId::generate();
|
||||
$logoOld = new PicturePath('files/picture-old.jpg');
|
||||
$logoNew = new PicturePath('files/picture-new.jpg');
|
||||
|
|
@ -148,7 +148,7 @@ describe('brand', function (): void {
|
|||
|
||||
it('updates a brand rcs and sets updatedAt timestamp', function (): void {
|
||||
// GIVEN
|
||||
CarbonImmutable::setTestNow('2026-06-15 14:30:00');
|
||||
CarbonImmutable::setTestNow('2026-06-15 14:30:00 UTC');
|
||||
$id = BrandId::generate();
|
||||
$rcsOld = new Rcs('RCS PARIS 111 222 333');
|
||||
$rcsNew = new Rcs('RCS NANTES 444 555 666');
|
||||
|
|
@ -177,7 +177,7 @@ describe('brand', function (): void {
|
|||
|
||||
it('updates a brand contact and sets updatedAt timestamp', function (): void {
|
||||
// GIVEN
|
||||
CarbonImmutable::setTestNow('2026-06-15 16:45:00');
|
||||
CarbonImmutable::setTestNow('2026-06-15 16:45:00 UTC');
|
||||
$id = BrandId::generate();
|
||||
$contactOld = new Email('contact-old@example.com');
|
||||
$contactNew = new Email('contact-new@example.com');
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ describe('campaign request', function (): void {
|
|||
|
||||
it('rejects a campaign request and sets the rejection reason and updatedAt', function (): void {
|
||||
// GIVEN
|
||||
CarbonImmutable::setTestNow('2026-06-15 12:00:00');
|
||||
CarbonImmutable::setTestNow('2026-06-15 12:00:00 UTC');
|
||||
$campaignRequest = CampaignRequest::hydrate(
|
||||
CampaignRequestId::generate(),
|
||||
$this->code,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue