mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
[ApiBundle] Add generate promotion coupons action
This commit is contained in:
parent
09e576fbba
commit
8e8d5a69e6
21 changed files with 755 additions and 13 deletions
|
|
@ -14,7 +14,7 @@ Feature: Being unable to generate too many coupons with prefix and suffix
|
|||
And it is coupon based promotion
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Being unable to generate too many coupons with prefix
|
||||
Given I have generated 8 coupons for this promotion with code length 1 and prefix "CHRISTMAS_"
|
||||
When I want to generate new coupons for this promotion
|
||||
|
|
@ -25,7 +25,7 @@ Feature: Being unable to generate too many coupons with prefix and suffix
|
|||
Then I should be notified that generating 2 coupons with code length equal to 1 is not possible
|
||||
And there should still be 8 coupons related to this promotion
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Being unable to generate too many coupons with suffix
|
||||
Given I have generated 8 coupons for this promotion with code length 1 and suffix "_CHRISTMAS"
|
||||
When I want to generate new coupons for this promotion
|
||||
|
|
@ -36,7 +36,7 @@ Feature: Being unable to generate too many coupons with prefix and suffix
|
|||
Then I should be notified that generating 2 coupons with code length equal to 1 is not possible
|
||||
And there should still be 8 coupons related to this promotion
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Being unable to generate too many coupons with prefix and suffix
|
||||
Given I have generated 8 coupons for this promotion with code length 1, prefix "CHRISTMAS_" and suffix "_SALE"
|
||||
When I want to generate new coupons for this promotion
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Feature: Coupon generate instruction validation
|
|||
And it is coupon based promotion
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Trying to generate new coupons without specifying their amount
|
||||
When I want to generate new coupons for this promotion
|
||||
And I do not specify its amount
|
||||
|
|
@ -21,7 +21,7 @@ Feature: Coupon generate instruction validation
|
|||
Then I should be notified that generate amount is required
|
||||
And there should be 0 coupon related to this promotion
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Trying to generate new coupons without specifying their code length
|
||||
When I want to generate new coupons for this promotion
|
||||
And I do not specify their code length
|
||||
|
|
@ -32,7 +32,7 @@ Feature: Coupon generate instruction validation
|
|||
Then I should be notified that generate code length is required
|
||||
And there should be 0 coupon related to this promotion
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Trying to generate new coupons with code length impossible to generate
|
||||
When I want to generate new coupons for this promotion
|
||||
And I specify their code length as 50
|
||||
|
|
@ -43,7 +43,7 @@ Feature: Coupon generate instruction validation
|
|||
Then I should be notified that generate code length is out of range
|
||||
And there should be 0 coupon related to this promotion
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Trying to generate new coupons with amount and code length impossible to generate
|
||||
When I want to generate new coupons for this promotion
|
||||
And I specify their code length as 1
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Feature: Generating new coupons
|
|||
And it is coupon based promotion
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Generating a new coupons
|
||||
When I want to generate new coupons for this promotion
|
||||
And I choose the amount of 5 coupons to be generated
|
||||
|
|
@ -21,7 +21,7 @@ Feature: Generating new coupons
|
|||
Then I should be notified that they have been successfully generated
|
||||
And there should be 5 coupons related to this promotion
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Generating new coupons without expiration date
|
||||
When I want to generate new coupons for this promotion
|
||||
And I choose the amount of 5 coupons to be generated
|
||||
|
|
@ -31,7 +31,7 @@ Feature: Generating new coupons
|
|||
Then I should be notified that they have been successfully generated
|
||||
And there should be 5 coupons related to this promotion
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Generating new coupons with a large long code length value
|
||||
When I want to generate new coupons for this promotion
|
||||
And I choose the amount of 10 coupons to be generated
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Feature: Generating new coupons with prefix and suffix
|
|||
And it is coupon based promotion
|
||||
And I am logged in as an administrator
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Generating new coupons with prefix
|
||||
When I want to generate new coupons for this promotion
|
||||
And I choose the amount of 5 coupons to be generated
|
||||
|
|
@ -21,7 +21,7 @@ Feature: Generating new coupons with prefix and suffix
|
|||
And there should be 5 coupons related to this promotion
|
||||
And all of the coupon codes should be prefixed with "CHRISTMAS_"
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Generating new coupons with suffix
|
||||
When I want to generate new coupons for this promotion
|
||||
And I choose the amount of 5 coupons to be generated
|
||||
|
|
@ -32,7 +32,7 @@ Feature: Generating new coupons with prefix and suffix
|
|||
And there should be 5 coupons related to this promotion
|
||||
And all of the coupon codes should be suffixed with "_CHRISTMAS"
|
||||
|
||||
@ui
|
||||
@api @ui
|
||||
Scenario: Generating new coupons with prefix and suffix
|
||||
When I want to generate new coupons for this promotion
|
||||
And I choose the amount of 5 coupons to be generated
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ namespace Sylius\Behat\Context\Api\Admin;
|
|||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\RequestFactoryInterface;
|
||||
use Sylius\Behat\Client\RequestInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\Converter\SectionAwareIriConverterInterface;
|
||||
|
|
@ -24,8 +26,11 @@ use Webmozart\Assert\Assert;
|
|||
|
||||
final class ManagingPromotionCouponsContext implements Context
|
||||
{
|
||||
private ?RequestInterface $request = null;
|
||||
|
||||
public function __construct(
|
||||
private ApiClientInterface $client,
|
||||
private RequestFactoryInterface $requestFactory,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private SectionAwareIriConverterInterface $sectionAwareIriConverter,
|
||||
) {
|
||||
|
|
@ -73,6 +78,15 @@ final class ManagingPromotionCouponsContext implements Context
|
|||
$this->client->buildUpdateRequest(Resources::PROMOTION_COUPONS, $coupon->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I want to generate new coupons for (this promotion)$/
|
||||
*/
|
||||
public function iWantToGenerateNewCouponsForThisPromotion(PromotionInterface $promotion): void
|
||||
{
|
||||
$this->request = $this->requestFactory->create('admin', 'promotion-coupons/generate', 'Bearer');
|
||||
$this->request->updateContent(['promotionCode' => $promotion->getCode()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I (try to) delete :coupon coupon related to this promotion
|
||||
*/
|
||||
|
|
@ -124,6 +138,55 @@ final class ManagingPromotionCouponsContext implements Context
|
|||
$this->client->addRequestData('reusableFromCancelledOrders', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I choose the amount of :amount coupons to be generated
|
||||
*/
|
||||
public function iSpecifyItsAmountAs(int $amount): void
|
||||
{
|
||||
$this->request->updateContent(['instruction' => ['amount' => $amount]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify their prefix as :prefix
|
||||
*/
|
||||
public function specifyPrefixAs(string $prefix): void
|
||||
{
|
||||
$this->request->updateContent(['instruction' => ['prefix' => $prefix]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I specify their suffix as :suffix
|
||||
*/
|
||||
public function specifySuffixAs(string $suffix): void
|
||||
{
|
||||
$this->request->updateContent(['instruction' => ['suffix' => $suffix]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I specify their code length as (\d+)$/
|
||||
* @When I do not specify their code length
|
||||
*/
|
||||
public function iSpecifyTheirCodeLengthAs(?int $codeLength = null): void
|
||||
{
|
||||
$this->request->updateContent(['instruction' => ['codeLength' => $codeLength]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I limit generated coupons usage to (\d+) times?$/
|
||||
*/
|
||||
public function iSetGeneratedCouponsUsageLimitTo(int $limit):void
|
||||
{
|
||||
$this->request->updateContent(['instruction' => ['usageLimit' => $limit]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I make generated coupons valid until :date
|
||||
*/
|
||||
public function iMakeGeneratedCouponsValidUntil(\DateTimeInterface $date): void
|
||||
{
|
||||
$this->request->updateContent(['instruction' => ['expiresAt' => $date->format('Y-m-d')]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I do not specify its :field
|
||||
*/
|
||||
|
|
@ -140,6 +203,15 @@ final class ManagingPromotionCouponsContext implements Context
|
|||
$this->client->create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I (try to) generate it
|
||||
* @When I (try to) generate these coupons
|
||||
*/
|
||||
public function iResetIt(): void
|
||||
{
|
||||
$this->client->executeCustomRequest($this->request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I sort coupons by (ascending|descending) number of uses$/
|
||||
*/
|
||||
|
|
@ -331,6 +403,26 @@ final class ManagingPromotionCouponsContext implements Context
|
|||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then all of the coupon codes should be prefixed with :prefix
|
||||
*/
|
||||
public function allOfTheCouponCodesShouldBePrefixedWith(string $prefix): void
|
||||
{
|
||||
foreach ($this->responseChecker->getCollection($this->client->getLastResponse()) as $promotionCoupon) {
|
||||
Assert::startsWith($promotionCoupon['code'], $prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then all of the coupon codes should be suffixed with :suffix
|
||||
*/
|
||||
public function allOfTheCouponCodesShouldBeSuffixedWith(string $suffix): void
|
||||
{
|
||||
foreach ($this->responseChecker->getCollection($this->client->getLastResponse()) as $promotionCoupon) {
|
||||
Assert::endsWith($promotionCoupon['code'], $suffix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^there should still be only one coupon with code "([^"]+)" related to (this promotion)$/
|
||||
*/
|
||||
|
|
@ -433,6 +525,65 @@ final class ManagingPromotionCouponsContext implements Context
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that generating :amount coupons with code length equal to :codeLength is not possible
|
||||
*/
|
||||
public function iShouldBeNotifiedThatGeneratingCouponsWithCodeLengthIsNotPossible(int $amount, int $codeLength): void
|
||||
{
|
||||
Assert::contains(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
sprintf(
|
||||
'Invalid coupon code length or coupons amount. It is not possible to generate %d unique coupons with %d code length',
|
||||
$amount,
|
||||
$codeLength,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that generate amount is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatGenerateAmountIsRequired(): void
|
||||
{
|
||||
Assert::same(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'instruction.amount: Please enter amount of coupons to generate.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that generate code length is required
|
||||
*/
|
||||
public function iShouldBeNotifiedThatCodeLengthIsRequired(): void
|
||||
{
|
||||
Assert::same(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'instruction.codeLength: Please enter coupon code length.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that generate code length is out of range
|
||||
*/
|
||||
public function iShouldBeNotifiedThatCodeLengthIsOutOfRange(): void
|
||||
{
|
||||
Assert::same(
|
||||
$this->responseChecker->getError($this->client->getLastResponse()),
|
||||
'instruction.codeLength: Coupon code length must be between 1 and 40.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that they have been successfully generated
|
||||
*/
|
||||
public function iShouldBeNotifiedThatTheyHaveBeenSuccessfullyGenerated(): void
|
||||
{
|
||||
Assert::true(
|
||||
$this->responseChecker->isCreationSuccessful($this->client->getLastResponse()),
|
||||
'Promotion coupon could not be generated',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that coupon with this code already exists
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -293,6 +293,7 @@
|
|||
|
||||
<service id="sylius.behat.context.api.admin.managing_promotion_coupons" class="Sylius\Behat\Context\Api\Admin\ManagingPromotionCouponsContext">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="sylius.behat.request_factory" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.section_iri_converter" />
|
||||
</service>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Command\Promotion;
|
||||
|
||||
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface;
|
||||
|
||||
/** @experimental */
|
||||
class GeneratePromotionCoupon
|
||||
{
|
||||
public function __construct(
|
||||
private string $promotionCode,
|
||||
private PromotionCouponGeneratorInstructionInterface $instruction,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getPromotionCode(): string
|
||||
{
|
||||
return $this->promotionCode;
|
||||
}
|
||||
|
||||
public function getPromotionCouponGeneratorInstruction(): PromotionCouponGeneratorInstructionInterface
|
||||
{
|
||||
return $this->instruction;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Command\Promotion;
|
||||
|
||||
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface;
|
||||
|
||||
/** @experimental */
|
||||
class PromotionCouponGeneratorInstruction implements PromotionCouponGeneratorInstructionInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ?string $prefix = null,
|
||||
private ?int $codeLength = null,
|
||||
private ?string $suffix = null,
|
||||
private ?int $amount = null,
|
||||
private ?\DateTimeInterface $expiresAt = null,
|
||||
private ?int $usageLimit = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getAmount(): int
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function setAmount(?int $amount): void
|
||||
{
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
public function getCodeLength(): int
|
||||
{
|
||||
return $this->codeLength;
|
||||
}
|
||||
|
||||
public function setCodeLength(?int $codeLength): void
|
||||
{
|
||||
$this->codeLength = $codeLength;
|
||||
}
|
||||
|
||||
public function getPrefix(): ?string
|
||||
{
|
||||
return $this->prefix;
|
||||
}
|
||||
|
||||
public function setPrefix(?string $prefix): void
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
public function getSuffix(): ?string
|
||||
{
|
||||
return $this->suffix;
|
||||
}
|
||||
|
||||
public function setSuffix(?string $suffix): void
|
||||
{
|
||||
$this->suffix = $suffix;
|
||||
}
|
||||
|
||||
public function getExpiresAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->expiresAt;
|
||||
}
|
||||
|
||||
public function setExpiresAt(?\DateTimeInterface $expiresAt): void
|
||||
{
|
||||
$this->expiresAt = $expiresAt;
|
||||
}
|
||||
|
||||
public function getUsageLimit(): ?int
|
||||
{
|
||||
return $this->usageLimit;
|
||||
}
|
||||
|
||||
public function setUsageLimit(?int $usageLimit): void
|
||||
{
|
||||
$this->usageLimit = $usageLimit;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\CommandHandler\Promotion;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PromotionNotFoundException;
|
||||
use Sylius\Component\Core\Model\PromotionInterface;
|
||||
use Sylius\Component\Core\Repository\PromotionRepositoryInterface;
|
||||
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInterface;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class GeneratePromotionCouponHandler implements MessageHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @param PromotionRepositoryInterface<PromotionInterface> $promotionRepository
|
||||
*/
|
||||
public function __construct(
|
||||
private PromotionRepositoryInterface $promotionRepository,
|
||||
private PromotionCouponGeneratorInterface $promotionCouponGenerator,
|
||||
) {
|
||||
}
|
||||
|
||||
public function __invoke(GeneratePromotionCoupon $generatePromotionCoupon): Collection
|
||||
{
|
||||
/** @var PromotionInterface|null $promotion */
|
||||
$promotion = $this->promotionRepository->findOneBy(['code' => $generatePromotionCoupon->getPromotionCode()]);
|
||||
if ($promotion === null) {
|
||||
throw new PromotionNotFoundException();
|
||||
}
|
||||
|
||||
$promotionCoupons = $this->promotionCouponGenerator->generate(
|
||||
$promotion,
|
||||
$generatePromotionCoupon->getPromotionCouponGeneratorInstruction()
|
||||
);
|
||||
|
||||
return new ArrayCollection($promotionCoupons);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Exception;
|
||||
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/** @experimental */
|
||||
final class PromotionNotFoundException extends NotFoundHttpException
|
||||
{
|
||||
/** @param array<array-key, mixed> $headers */
|
||||
public function __construct(
|
||||
string $message = 'Promotion not found.',
|
||||
\Throwable $previous = null,
|
||||
int $code = 0,
|
||||
array $headers = [],
|
||||
) {
|
||||
parent::__construct($message, $previous, $code, $headers);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,22 @@
|
|||
<attribute name="groups">admin:promotion_coupon:create</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
|
||||
<collectionOperation name="admin_generate">
|
||||
<attribute name="method">POST</attribute>
|
||||
<attribute name="messenger">input</attribute>
|
||||
<attribute name="path">/promotion-coupons/generate</attribute>
|
||||
<attribute name="input">Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon</attribute>
|
||||
<attribute name="denormalization_context">
|
||||
<attribute name="groups">admin:promotion_coupon:update</attribute>
|
||||
</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:promotion_coupon:read</attribute>
|
||||
</attribute>
|
||||
<attribute name="openapi_context">
|
||||
<attribute name="summary">Generate promotion coupons</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
</collectionOperations>
|
||||
|
||||
<itemOperations>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Sylius Sp. z o.o.
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
|
||||
>
|
||||
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon">
|
||||
<attribute name="promotionCode">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
<attribute name="instruction">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
</class>
|
||||
</serializer>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Sylius Sp. z o.o.
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping https://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
|
||||
>
|
||||
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\PromotionCouponGeneratorInstruction">
|
||||
<attribute name="prefix">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
<attribute name="codeLength">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
<attribute name="suffix">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
<attribute name="amount">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
<attribute name="expiresAt">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
<attribute name="usageLimit">
|
||||
<group>admin:promotion_coupon:update</group>
|
||||
</attribute>
|
||||
</class>
|
||||
</serializer>
|
||||
|
|
@ -224,5 +224,12 @@
|
|||
<tag name="messenger.message_handler" bus="sylius.command_bus" />
|
||||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\ApiBundle\CommandHandler\Promotion\GeneratePromotionCouponHandler">
|
||||
<argument type="service" id="sylius.repository.promotion" />
|
||||
<argument type="service" id="sylius.promotion_coupon_generator" />
|
||||
<tag name="messenger.message_handler" bus="sylius.command_bus" />
|
||||
<tag name="messenger.message_handler" bus="sylius_default.bus" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -138,5 +138,15 @@
|
|||
<service id="Sylius\Bundle\ApiBundle\Serializer\TranslatableLocaleKeyDenormalizer">
|
||||
<tag name="serializer.normalizer" priority="96" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
id="sylius.api.serializer.denormalizer.promotion_coupon_generator_instruction"
|
||||
class="Sylius\Bundle\ApiBundle\Serializer\ObjectDenormalizer"
|
||||
>
|
||||
<argument type="service" id="serializer.normalizer.object" />
|
||||
<argument type="string">Sylius\Bundle\ApiBundle\Command\Promotion\PromotionCouponGeneratorInstruction</argument>
|
||||
<argument type="string">Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface</argument>
|
||||
<tag name="serializer.normalizer" priority="64" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Sylius Sp. z o.o.
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
|
||||
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon">
|
||||
<property name="promotionCode">
|
||||
<constraint name="NotBlank">
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="instruction">
|
||||
<constraint name="Valid" />
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Sylius Sp. z o.o.
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
|
||||
<class name="Sylius\Bundle\ApiBundle\Command\Promotion\PromotionCouponGeneratorInstruction">
|
||||
<property name="amount">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.promotion_coupon_generator_instruction.amount.not_blank</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Range">
|
||||
<option name="min">1</option>
|
||||
<option name="minMessage">sylius.promotion_coupon_generator_instruction.amount.min</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="codeLength">
|
||||
<constraint name="NotBlank">
|
||||
<option name="message">sylius.promotion_coupon_generator_instruction.code_length.not_blank</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
<constraint name="Range">
|
||||
<option name="min">1</option>
|
||||
<option name="max">40</option>
|
||||
<option name="notInRangeMessage">sylius.promotion_coupon_generator_instruction.code_length.not_in_range</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
<property name="usageLimit">
|
||||
<constraint name="Range">
|
||||
<option name="min">1</option>
|
||||
<option name="minMessage">sylius.promotion_coupon_generator_instruction.usage_limit.min</option>
|
||||
<option name="groups">
|
||||
<value>sylius</value>
|
||||
</option>
|
||||
</constraint>
|
||||
</property>
|
||||
</class>
|
||||
</constraint-mapping>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Sylius\Bundle\ApiBundle\Serializer;
|
||||
|
||||
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
|
||||
/** @experimental */
|
||||
final class ObjectDenormalizer implements ContextAwareDenormalizerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private DenormalizerInterface $objectNormalizer,
|
||||
private string $classType,
|
||||
private string $interfaceType,
|
||||
) {
|
||||
}
|
||||
|
||||
public function denormalize($data, $type, $format = null, array $context = [])
|
||||
{
|
||||
return $this->objectNormalizer->denormalize(
|
||||
$data,
|
||||
$this->classType,
|
||||
$format,
|
||||
$context,
|
||||
);
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
|
||||
{
|
||||
return $type === $this->interfaceType;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Sylius Sp. z o.o.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace spec\Sylius\Bundle\ApiBundle\CommandHandler\Promotion;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\ApiBundle\Command\Promotion\GeneratePromotionCoupon;
|
||||
use Sylius\Bundle\ApiBundle\Exception\PromotionNotFoundException;
|
||||
use Sylius\Component\Core\Model\PromotionCouponInterface;
|
||||
use Sylius\Component\Core\Model\PromotionInterface;
|
||||
use Sylius\Component\Core\Repository\PromotionRepositoryInterface;
|
||||
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInstructionInterface;
|
||||
use Sylius\Component\Promotion\Generator\PromotionCouponGeneratorInterface;
|
||||
|
||||
final class GeneratePromotionCouponHandlerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
PromotionRepositoryInterface $promotionRepository,
|
||||
PromotionCouponGeneratorInterface $promotionCouponGenerator,
|
||||
) {
|
||||
$this->beConstructedWith(
|
||||
$promotionRepository,
|
||||
$promotionCouponGenerator,
|
||||
);
|
||||
}
|
||||
|
||||
function it_throws_exception_if_promotion_is_not_found(
|
||||
PromotionRepositoryInterface $promotionRepository,
|
||||
PromotionCouponGeneratorInstructionInterface $promotionCouponGeneratorInstruction
|
||||
): void {
|
||||
$promotionRepository->findOneBy(['code' => 'promotion_code'])->willReturn(null);
|
||||
|
||||
$generatePromotionCoupon = new GeneratePromotionCoupon('promotion_code', $promotionCouponGeneratorInstruction->getWrappedObject());
|
||||
|
||||
$this->shouldThrow(PromotionNotFoundException::class)
|
||||
->during('__invoke', [$generatePromotionCoupon])
|
||||
;
|
||||
}
|
||||
|
||||
function it_generates_promotion_coupons(
|
||||
PromotionRepositoryInterface $promotionRepository,
|
||||
PromotionCouponGeneratorInterface $promotionCouponGenerator,
|
||||
PromotionInterface $promotion,
|
||||
PromotionCouponGeneratorInstructionInterface $promotionCouponGeneratorInstruction,
|
||||
PromotionCouponInterface $promotionCouponOne,
|
||||
PromotionCouponInterface $promotionCouponTwo
|
||||
): void {
|
||||
$promotionRepository->findOneBy(['code' => 'promotion_code'])->willReturn($promotion);
|
||||
|
||||
$generatePromotionCoupon = new GeneratePromotionCoupon('promotion_code', $promotionCouponGeneratorInstruction->getWrappedObject());
|
||||
|
||||
$promotionCouponGenerator->generate($promotion, $promotionCouponGeneratorInstruction)->willReturn([$promotionCouponOne, $promotionCouponTwo]);
|
||||
|
||||
$this($generatePromotionCoupon)->shouldIterateAs([$promotionCouponOne, $promotionCouponTwo]);
|
||||
}
|
||||
}
|
||||
|
|
@ -117,6 +117,38 @@ final class PromotionCouponsTest extends JsonApiTestCase
|
|||
Response::HTTP_OK,
|
||||
);
|
||||
}
|
||||
/** @test */
|
||||
public function it_generates_a_promotion_coupons(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'channel.yaml', 'promotion/promotion.yaml']);
|
||||
$header = array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
|
||||
/** @var PromotionInterface $promotion */
|
||||
$promotion = $fixtures['promotion_50_off'];
|
||||
|
||||
$this->client->request(
|
||||
method: 'POST',
|
||||
uri: '/api/v2/admin/promotion-coupons/generate' ,
|
||||
server: $header,
|
||||
content: json_encode([
|
||||
'promotionCode' => $promotion->getCode(),
|
||||
'instruction' => [
|
||||
'amount' => 4,
|
||||
'prefix' => 'ABC',
|
||||
'codeLength' => 6,
|
||||
'suffix' => 'XYZ',
|
||||
'usageLimit' => 10,
|
||||
'expiresAt' => '2020-01-01 12:00:00',
|
||||
],
|
||||
], JSON_THROW_ON_ERROR)
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/promotion_coupon/generate_promotion_coupons_response',
|
||||
Response::HTTP_CREATED,
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_removes_a_promotion_coupon(): void
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/PromotionCoupon",
|
||||
"@id": "\/api\/v2\/admin\/promotion-coupons",
|
||||
"@type": "hydra:Collection",
|
||||
"hydra:member": [
|
||||
{
|
||||
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
|
||||
"@type": "PromotionCoupon",
|
||||
"perCustomerUsageLimit": null,
|
||||
"reusableFromCancelledOrders": true,
|
||||
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
|
||||
"usageLimit": 10,
|
||||
"used": 0,
|
||||
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
|
||||
"expiresAt": "2020-01-01 12:00:00",
|
||||
"createdAt": @date@,
|
||||
"updatedAt": @date@
|
||||
},
|
||||
{
|
||||
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
|
||||
"@type": "PromotionCoupon",
|
||||
"perCustomerUsageLimit": null,
|
||||
"reusableFromCancelledOrders": true,
|
||||
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
|
||||
"usageLimit": 10,
|
||||
"used": 0,
|
||||
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
|
||||
"expiresAt": "2020-01-01 12:00:00",
|
||||
"createdAt": @date@,
|
||||
"updatedAt": @date@
|
||||
},
|
||||
{
|
||||
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
|
||||
"@type": "PromotionCoupon",
|
||||
"perCustomerUsageLimit": null,
|
||||
"reusableFromCancelledOrders": true,
|
||||
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
|
||||
"usageLimit": 10,
|
||||
"used": 0,
|
||||
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
|
||||
"expiresAt": "2020-01-01 12:00:00",
|
||||
"createdAt": @date@,
|
||||
"updatedAt": @date@
|
||||
},
|
||||
{
|
||||
"@id": "@string@.startsWith('\/api\/v2\/admin\/promotion-coupons\/ABC').endsWith('XYZ')",
|
||||
"@type": "PromotionCoupon",
|
||||
"perCustomerUsageLimit": null,
|
||||
"reusableFromCancelledOrders": true,
|
||||
"code": "@string@.startsWith('ABC').endsWith('XYZ')",
|
||||
"usageLimit": 10,
|
||||
"used": 0,
|
||||
"promotion": "\/api\/v2\/admin\/promotions\/50_off",
|
||||
"expiresAt": "2020-01-01 12:00:00",
|
||||
"createdAt": @date@,
|
||||
"updatedAt": @date@
|
||||
}
|
||||
],
|
||||
"hydra:totalItems": 4
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue