fix: 🐛 add missing rejection field in campaignRequest

This commit is contained in:
K Muller 2026-09-22 16:29:34 +02:00 • committed by Corentin Huet
parent 00a33f1b7e
commit 269ff25f0f
4 changed files with 130 additions and 6 deletions

View file

@ -10,6 +10,7 @@ use Domain\Enums\Country;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
use Domain\Models\ValueObjects\PrintRules;
use Shared\DDD\Models\AggregateRoot;
use Shared\DDD\Models\HasTimestampIdentifier;
@ -28,8 +29,17 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
public ?PrintRules $printRules,
public RequestedCoupon $requestedCoupon,
public CampaignStatus $status,
public ?RejectionReason $rejectionReason = null,
public ?\DateTimeInterface $updatedAt = null,
) {}
) {
if ($this->status === CampaignStatus::REJECTED && ! $this->rejectionReason instanceof RejectionReason) {
throw new \DomainException('A rejection reason is required when the campaign request is rejected.');
}
if ($this->status !== CampaignStatus::REJECTED && $this->rejectionReason instanceof RejectionReason) {
throw new \DomainException('A rejection reason can only be set when the campaign request is rejected.');
}
}
public function id(): CampaignRequestId
{
@ -56,6 +66,7 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
?PrintRules $printRules,
RequestedCoupon $requestedCoupon,
CampaignStatus $status,
?RejectionReason $rejectionReason = null,
?\DateTimeInterface $updatedAt = null,
): self {
return new self(
@ -67,6 +78,7 @@ final readonly class CampaignRequest implements AggregateRoot, HasTimestampIdent
printRules: $printRules,
requestedCoupon: $requestedCoupon,
status: $status,
rejectionReason: $rejectionReason,
updatedAt: $updatedAt,
);
}

View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Domain\Models\ValueObjects\CampaignRequest;
final readonly class RejectionReason
{
public function __construct(
public string $value,
) {
$trimmed = trim($value);
if ($trimmed === '') {
throw new \DomainException('Rejection reason cannot be empty.');
}
}
}

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Tests\Unit\Domain\Models\Entities;
use Carbon\CarbonImmutable;
use Domain\Enums\BarcodeFormat;
use Domain\Enums\Campaign\CampaignStatus;
use Domain\Enums\Campaign\CampaignType;
@ -14,6 +13,7 @@ use Domain\Models\Entities\RequestedCoupon;
use Domain\Models\ValueObjects\Brand\BrandCode;
use Domain\Models\ValueObjects\CampaignRequest\Applicant;
use Domain\Models\ValueObjects\CampaignRequest\CampaignRequestId;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
use Domain\Models\ValueObjects\Coupon\CouponValidity;
use Domain\Models\ValueObjects\CouponName;
use Domain\Models\ValueObjects\CouponPicture;
@ -51,10 +51,6 @@ function aRequestedCoupon(): RequestedCoupon
}
describe('campaign request', function (): void {
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
it('hydrates a campaign request with all its fields', function (): void {
// GIVEN
$id = CampaignRequestId::generate();
@ -122,6 +118,7 @@ describe('campaign request', function (): void {
new PrintRules(maxPerCustomer: 3),
aRequestedCoupon(),
CampaignStatus::CREATED,
null,
$updatedAt,
);
@ -130,6 +127,69 @@ describe('campaign request', function (): void {
->and($campaignRequest->updatedAt())->toBe($updatedAt);
});
it('hydrates a rejected campaign request with a rejection reason', function (): void {
// GIVEN
$rejectionReason = new RejectionReason('Missing legal documents.');
// WHEN
$campaignRequest = CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
Country::FR,
CampaignType::CLUB,
new PrintRules(maxPerCustomer: 3),
aRequestedCoupon(),
CampaignStatus::REJECTED,
$rejectionReason,
);
// THEN
expect($campaignRequest->status)->toBe(CampaignStatus::REJECTED)
->and($campaignRequest->rejectionReason)->toBe($rejectionReason);
});
it('rejects a rejected campaign request without a rejection reason', function (): void {
// WHEN
$action = fn (): CampaignRequest => CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
Country::FR,
CampaignType::CLUB,
new PrintRules(maxPerCustomer: 3),
aRequestedCoupon(),
CampaignStatus::REJECTED,
);
// THEN
expect($action)->toThrow(
\DomainException::class,
'A rejection reason is required when the campaign request is rejected.'
);
});
it('rejects a non-rejected campaign request with a rejection reason', function (): void {
// WHEN
$action = fn (): CampaignRequest => CampaignRequest::hydrate(
CampaignRequestId::generate(),
new BrandCode('AAA01'),
new Applicant('John', 'Doe', new Email('john.doe@example.com')),
Country::FR,
CampaignType::CLUB,
new PrintRules(maxPerCustomer: 3),
aRequestedCoupon(),
CampaignStatus::CREATED,
new RejectionReason('Missing legal documents.'),
);
// THEN
expect($action)->toThrow(
\DomainException::class,
'A rejection reason can only be set when the campaign request is rejected.'
);
});
it('returns the creation date extracted from the id', function (): void {
// GIVEN
$expected = new \DateTimeImmutable('2024-06-15 12:34:56.789000', new \DateTimeZone('UTC'));

View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\Models\ValueObjects\CampaignRequest;
use Domain\Models\ValueObjects\CampaignRequest\RejectionReason;
covers(RejectionReason::class);
describe('rejection reason', function (): void {
it('creates a valid rejection reason', function (): void {
// GIVEN
$value = 'Missing legal documents.';
// WHEN
$reason = new RejectionReason($value);
// THEN
expect($reason)->toBeInstanceOf(RejectionReason::class)
->and($reason->value)->toBe($value);
});
it('rejects an empty or whitespace-only reason', function (string $value): void {
// WHEN
$action = fn (): RejectionReason => new RejectionReason($value);
// THEN
expect($action)->toThrow(\DomainException::class, 'Rejection reason cannot be empty.');
})->with([
'empty' => '',
'spaces' => ' ',
]);
});