fix: 🐛 add Quantity and WarningQuantity as VO

This commit is contained in:
K Muller 2026-09-25 11:54:47 +02:00
parent be382a53c0
commit 80da7ec333
13 changed files with 188 additions and 52 deletions

View file

@ -13,8 +13,10 @@ use Domain\Models\ValueObjects\MoneyAmount;
use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponId;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Shared\DDD\Models\AggregateRoot;
use Shared\DDD\Models\HasTimestampIdentifier;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
/**
* @implements \Shared\DDD\Models\AggregateRoot<\Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponId>
@ -25,8 +27,8 @@ final readonly class RequestedCoupon implements AggregateRoot, HasTimestampIdent
public RequestedCouponId $id,
public CouponName $name,
public CouponProductName $productName,
public int $quantity,
public int $warningThresholdQuantity,
public RequestedCouponQuantity $quantity,
public WarningThresholdQuantity $warningThresholdQuantity,
public Period $period,
public MoneyAmount $moneyAmount,
public ProcessingCode $processingCode,
@ -56,8 +58,8 @@ final readonly class RequestedCoupon implements AggregateRoot, HasTimestampIdent
RequestedCouponId $id,
CouponName $name,
CouponProductName $productName,
int $quantity,
int $warningThresholdQuantity,
RequestedCouponQuantity $quantity,
WarningThresholdQuantity $warningThresholdQuantity,
Period $period,
MoneyAmount $moneyAmount,
ProcessingCode $processingCode,

View file

@ -7,17 +7,15 @@ namespace Domain\Models\ValueObjects\Campaign;
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 int $warningThresholdQty,
public WarningThresholdQuantity $warningThresholdQty,
public ?PrintRules $printRules = null,
) {
if ($this->warningThresholdQty <= 0) {
throw new \DomainException('Campaign Coupon warningThresholdQty must be positive.');
}
}
}

View file

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects\RequestedCoupon;
final readonly class RequestedCouponQuantity
{
public function __construct(
public int $value,
) {
if ($this->value <= 0) {
throw new \DomainException('Quantity should be more than 0.');
}
}
}

View file

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

View file

@ -35,6 +35,8 @@ use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponId;
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;
@ -52,8 +54,8 @@ describe('find campaign request use case', function (): void {
RequestedCouponId::generate(),
new CouponName('Coupon A'),
new CouponProductName('Product A'),
100,
10,
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'),

View file

@ -29,6 +29,8 @@ use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponId;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Domain\Ports\Repositories\CampaignRequestRepository;
use Shared\DDD\UseCases\Input as BaseInput;
use Shared\Enums\Currency;
@ -46,8 +48,8 @@ describe('list campaigns request use case', function (): void {
RequestedCouponId::generate(),
new CouponName('Coupon A'),
new CouponProductName('Product A'),
100,
10,
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'),
@ -60,8 +62,8 @@ describe('list campaigns request use case', function (): void {
RequestedCouponId::generate(),
new CouponName('Coupon B'),
new CouponProductName('Product B'),
100,
10,
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'),

View file

@ -32,6 +32,8 @@ use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponId;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Domain\Ports\Repositories\CampaignRequestRepository;
use Shared\DDD\UseCases\Input as BaseInput;
use Shared\Enums\Currency;
@ -51,8 +53,8 @@ describe('reject campaign request use case', function (): void {
RequestedCouponId::generate(),
new CouponName('Coupon A'),
new CouponProductName('Product A'),
100,
10,
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'),

View file

@ -32,6 +32,8 @@ use Shared\ValueObjects\Email;
use Shared\ValueObjects\Money;
use Carbon\CarbonImmutable;
use Domain\Models\ValueObjects\CountryAuthorized;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
covers(CampaignRequest::class);
@ -45,8 +47,8 @@ describe('campaign request', function (): void {
RequestedCouponId::generate(),
new CouponName('Coupon A'),
new CouponProductName('Product A'),
100,
10,
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'),

View file

@ -19,6 +19,7 @@ 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);
@ -43,7 +44,14 @@ describe('campaign', function (): void {
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = new CountryAuthorized(Country::FRANCE);
$coupons = [new CampaignCoupon(new ProcessingCode('PC1'), StockId::generate(), 100, new PrintRules(maxPerCustomer: 3))];
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
// WHEN
@ -109,7 +117,14 @@ describe('campaign', function (): void {
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = new CountryAuthorized(Country::FRANCE);
$coupons = [new CampaignCoupon(new ProcessingCode('PC1'), StockId::generate(), 100, new PrintRules(maxPerCustomer: 3))];
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
$updatedAt = new \DateTimeImmutable('2026-09-22 10:00:00', new \DateTimeZone('UTC'));
@ -139,7 +154,14 @@ describe('campaign', function (): void {
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = new CountryAuthorized(Country::FRANCE);
$coupons = [new CampaignCoupon(new ProcessingCode('PC1'), StockId::generate(), 100, new PrintRules(maxPerCustomer: 3))];
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
// WHEN
@ -175,7 +197,14 @@ describe('campaign', function (): void {
$publication = \boolval(0);
$printAllAtOnce = \boolval(1);
$availableIn = new CountryAuthorized(Country::FRANCE);
$coupons = [new CampaignCoupon(new ProcessingCode('PC1'), StockId::generate(), 100, new PrintRules(maxPerCustomer: 3))];
$coupons = [
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
];
$brand = new BrandCode('AAA01');
// WHEN
@ -216,7 +245,14 @@ describe('campaign', function (): void {
\boolval(0),
\boolval(1),
new CountryAuthorized(Country::FRANCE),
[new CampaignCoupon(new ProcessingCode('PC1'), StockId::generate(), 100, new PrintRules(maxPerCustomer: 3))],
[
new CampaignCoupon(
new ProcessingCode('PC1'),
StockId::generate(),
new WarningThresholdQuantity(100),
new PrintRules(maxPerCustomer: 3)
)
],
new BrandCode('AAA01')
);

View file

@ -17,6 +17,8 @@ use Domain\Models\ValueObjects\Period;
use Domain\Models\ValueObjects\PicturePath;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponId;
use Domain\Models\ValueObjects\RequestedCoupon\RequestedCouponQuantity;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
use Ramsey\Uuid\Uuid;
use Shared\Enums\Currency;
use Shared\ValueObjects\DateTime;
@ -34,8 +36,8 @@ describe('requested coupon', function (): void {
$id = RequestedCouponId::generate();
$name = new CouponName('Coupon A');
$productName = new CouponProductName('Product A');
$quantity = 100;
$warningThresholdQuantity = 10;
$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');
@ -83,8 +85,8 @@ describe('requested coupon', function (): void {
RequestedCouponId::generate(),
new CouponName('Coupon A'),
new CouponProductName('Product A'),
100,
10,
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'),
@ -107,8 +109,8 @@ describe('requested coupon', function (): void {
$id,
new CouponName('Coupon A'),
new CouponProductName('Product A'),
100,
10,
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'),

View file

@ -8,6 +8,7 @@ use Domain\Models\ValueObjects\Campaign\CampaignCoupon;
use Domain\Models\ValueObjects\PrintRules;
use Domain\Models\ValueObjects\ProcessingCode;
use Domain\Models\ValueObjects\Stock\StockId;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
covers(CampaignCoupon::class);
@ -17,7 +18,7 @@ describe('campaign coupon', function (): void {
$code = new ProcessingCode('PC1');
$qty = StockId::generate();
$printRules = new PrintRules(maxPerCustomer: 3);
$warningThresholdQty = 100;
$warningThresholdQty = new WarningThresholdQuantity(100);
// WHEN
$campaignCoupon = new CampaignCoupon($code, $qty, $warningThresholdQty, $printRules);
@ -33,7 +34,7 @@ describe('campaign coupon', function (): void {
// GIVEN
$code = new ProcessingCode('PC1');
$qty = StockId::generate();
$warningThresholdQty = 100;
$warningThresholdQty = new WarningThresholdQuantity(100);
// WHEN
$campaignCoupon = new CampaignCoupon($code, $qty, $warningThresholdQty);
@ -43,25 +44,4 @@ describe('campaign coupon', function (): void {
->and($campaignCoupon->qty)->toBe($qty)
->and($campaignCoupon->warningThresholdQty)->toBe($warningThresholdQty);
});
it('rejects invalid campaign coupon', function (): void {
// GIVEN
$code = new ProcessingCode('PC1');
$qty = StockId::generate();
$printRules = new PrintRules(maxPerCustomer: 3);
$warningThresholdQty = -100;
// GIVEN / THEN
expect(fn (): CampaignCoupon => new CampaignCoupon($code, $qty, $warningThresholdQty, $printRules))->toThrow(\DomainException::class);
});
it('rejects invalid campaign coupon whitout print rules', function (): void {
// GIVEN
$code = new ProcessingCode('PC1');
$qty = StockId::generate();
$warningThresholdQty = -100;
// GIVEN / THEN
expect(fn (): CampaignCoupon => new CampaignCoupon($code, $qty, $warningThresholdQty))->toThrow(\DomainException::class);
});
});

View file

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

View file

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects;
use Domain\Models\ValueObjects\WarningThresholdQuantity;
covers(WarningThresholdQuantity::class);
describe('warning threshold quantity', function (): void {
it('creates a valid warning threshold quantity', function (): void {
// GIVEN
$value = 10;
// WHEN
$threshold = new WarningThresholdQuantity($value);
// THEN
expect($threshold)->toBeInstanceOf(WarningThresholdQuantity::class)
->and($threshold->value)->toBe($value);
});
it('accepts a zero value, meaning no warning', function (): void {
// WHEN
$threshold = new WarningThresholdQuantity(0);
// THEN
expect($threshold->value)->toBe(0);
});
it('rejects a negative value', function (): void {
// WHEN
$action = fn (): WarningThresholdQuantity => new WarningThresholdQuantity(-1);
// THEN
expect($action)->toThrow(\DomainException::class, 'Warning threshold quantity cannot be negative.');
});
});