mirror of
https://github.com/Sylius/Sylius.git
synced 2026-07-14 17:10:53 +00:00
feature #14854 [PriceHistory][API] ChannelPricingLogEntry implementation (Rafikooo)
This PR was merged into the 1.13 branch. Discussion ---------- | Q | A | |-----------------|--------------------------------------------------------------| | Branch? | 1.13 <!-- see the comment below --> | | Bug fix? | no | | New feature? | yes | | BC breaks? | yes | | Deprecations? | yes <!-- don't forget to update the UPGRADE-*.md file --> | | Related tickets | https://github.com/Sylius/Sylius/pull/14852 | | License | MIT | <!-- - Bug fixes must be submitted against the 1.12 branch - Features and deprecations must be submitted against the 1.13 branch - Make sure that the correct base branch is set To be sure you are not breaking any Backward Compatibilities, check the documentation: https://docs.sylius.com/en/latest/book/organization/backward-compatibility-promise.html -->  Commits -------9044e9f491[PriceHistory] Add ChannelPricingLogEntry entity68d613978e[PriceHistory] Add ChannelPricingLogEntry repository27987feea7[PriceHistory] Add ChannelPricingLogEntry factory3fa4ed6c38[PriceHistory] Add ChannelPricingLogEntry migrationc30530f77a[PriceHistory] Add ChannelPricingLogEntry resource configurationbac7a9d2b3[PriceHistory] Tag scenarios with seeing price history enabledf774da1af8[PriceHistory] Add missing product and catalog promotion setup23d616c3c2[PriceHistory][API] Add behat context87f4dc81b5[PriceHistory][API][Admin] Add ChannelPricingLogEnties endpointea5c2e6148[PriceHistory][Domain] Add fields to Channel and ChannelPricing entites0a98a1eb80[PriceHistory][Migration] Add migration for new Channel and ChannelPricing entity fieldsbbbebe52a5[PriceHistory] Add a core implementation of calculating the lowest price before discount0cdf3cfa5f[PriceHistory][Test] Add contract tests for ChannelPricingLogEntry entityaf8844eb75[Channel] Add sylius validation group parameter75b24af569[PriceHistory] Reflect changes from the plugin
This commit is contained in:
commit
f74f7f3c48
64 changed files with 2456 additions and 18 deletions
|
|
@ -1,4 +1,4 @@
|
|||
@managing_product_variants
|
||||
@viewing_price_history
|
||||
Feature: Seeing the correct catalog price history after creating a product variant
|
||||
In order to be aware of historical product prices
|
||||
As an Administrator
|
||||
|
|
@ -8,7 +8,7 @@ Feature: Seeing the correct catalog price history after creating a product varia
|
|||
Given the store operates on a single channel in "United States"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing historical product variant prices after the product variant has been created without any promotion applied
|
||||
Given the store has a "Wyborowa Vodka" configurable product
|
||||
When I want to create a new variant of this product
|
||||
|
|
@ -20,7 +20,7 @@ Feature: Seeing the correct catalog price history after creating a product varia
|
|||
Then I should see a single log entry in the catalog price history
|
||||
And there should be a log entry with the "$20.00" selling price, "$25.00" original price and datetime of the price change
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing historical product variant prices after the product variant has been created without original price and any promotion applied
|
||||
Given the store has a "Wyborowa Vodka" configurable product
|
||||
When I want to create a new variant of this product
|
||||
|
|
@ -31,7 +31,7 @@ Feature: Seeing the correct catalog price history after creating a product varia
|
|||
Then I should see a single log entry in the catalog price history
|
||||
And there should be a log entry with the "$20.00" selling price, no original price and datetime of the price change
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing historical product variant prices after the product variant has been created with catalog promotions applied
|
||||
Given the store has a "Wyborowa Vodka" configurable product
|
||||
And there is a catalog promotion "Christmas sale" that reduces price by "50%" and applies on "Wyborowa Vodka" product
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@managing_product_variants
|
||||
@viewing_price_history
|
||||
Feature: Seeing the correct catalog price history after editing a product variant
|
||||
In order to be aware of historical product prices
|
||||
As an Administrator
|
||||
|
|
@ -9,7 +9,7 @@ Feature: Seeing the correct catalog price history after editing a product varian
|
|||
And the store has a product "Wyborowa Vodka" priced at "$40.00" in "United States" channel
|
||||
And I am logged in as an administrator
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing historical product variant prices after the product variant has been edited
|
||||
When I want to modify the "Wyborowa Vodka" product variant
|
||||
And I change its price to "$42.00" for "United States" channel
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@managing_catalog_promotions
|
||||
@viewing_price_history_after_catalog_promotions
|
||||
Feature: Seeing the price history of a product variant after changes to catalog promotions
|
||||
In order to be aware of historical variant prices
|
||||
As an Administrator
|
||||
|
|
@ -14,14 +14,14 @@ Feature: Seeing the price history of a product variant after changes to catalog
|
|||
And there is an exclusive catalog promotion "Extra sale" with priority 10 that reduces price by "10%" and applies on "Wyborowa Vodka Lemon" variant
|
||||
And I am logged in as an administrator
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing the catalog price history of a variant with many catalog promotions
|
||||
And I go to the price history of a variant with code "WYBOROWA_VODKA_EXQUISITE"
|
||||
Then I should see 2 log entries in the catalog price history
|
||||
And there should be a log entry on the 1st position with the "$5.00" selling price, "$15.00" original price and datetime of the price change
|
||||
And there should be a log entry on the 2nd position with the "$40.00" selling price, "$15.00" original price and datetime of the price change
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing the catalog price history of a variant with one catalog promotion
|
||||
And I go to the price history of a variant with code "WYBOROWA_VODKA_LEMON"
|
||||
Then I should see 2 log entries in the catalog price history
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@managing_catalog_promotions
|
||||
@viewing_price_history_after_catalog_promotions
|
||||
Feature: Seeing the price history of a simple product after changes to catalog promotions
|
||||
In order to be aware of historical simple product prices
|
||||
As an Administrator
|
||||
|
|
@ -13,7 +13,7 @@ Feature: Seeing the price history of a simple product after changes to catalog p
|
|||
And it reduces price by "90%"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing the catalog price history of a simple product
|
||||
Given the "Winter sale" catalog promotion is enabled
|
||||
When I disable "Winter sale" catalog promotion
|
||||
|
|
@ -23,7 +23,7 @@ Feature: Seeing the price history of a simple product after changes to catalog p
|
|||
And there should be a log entry on the 2nd position with the "$10.00" selling price, "$100.00" original price and datetime of the price change
|
||||
And there should be a log entry on the 3rd position with the "$100.00" selling price, no original price and datetime of the price change
|
||||
|
||||
@todo
|
||||
@api
|
||||
Scenario: Seeing the catalog price history of a simple product with a price different than an original price
|
||||
Given "Wyborowa Vodka" variant is originally priced at "$120.00" in "United States" channel
|
||||
And the "Winter sale" catalog promotion is enabled
|
||||
|
|
@ -228,6 +228,12 @@
|
|||
<!-- level 8 issues - some fatal errors in PHP -->
|
||||
|
||||
<MethodSignatureMismatch errorLevel="info" />
|
||||
|
||||
<InvalidStringClass>
|
||||
<errorLevel type="info">
|
||||
<file name="src/Sylius/Component/Core/Factory/ChannelPricingLogEntryFactory.php" />
|
||||
</errorLevel>
|
||||
</InvalidStringClass>
|
||||
</issueHandlers>
|
||||
|
||||
<stubs>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Behat\Context\Api\Admin;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Sylius\Behat\Client\ApiClientInterface;
|
||||
use Sylius\Behat\Client\ResponseCheckerInterface;
|
||||
use Sylius\Behat\Context\Api\Resources;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Model\ProductVariantInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ChannelPricingLogEntryContext implements Context
|
||||
{
|
||||
public function __construct(
|
||||
private ApiClientInterface $client,
|
||||
private ResponseCheckerInterface $responseChecker,
|
||||
private SharedStorageInterface $sharedStorage,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I go to the price history of a (variant with code "[^"]+")$/
|
||||
*/
|
||||
public function iGoToThePriceHistoryOfAVariant(ProductVariantInterface $productVariant): void
|
||||
{
|
||||
$channel = $this->sharedStorage->get('channel');
|
||||
Assert::notNull($channel);
|
||||
|
||||
$this->sharedStorage->set('variant', $productVariant);
|
||||
|
||||
$this->client->index(Resources::CHANNEL_PRICING_LOG_ENTRY);
|
||||
$this->client->addFilter('channelPricing.channelCode', $channel->getCode());
|
||||
$this->client->addFilter('channelPricing.productVariant.code', $productVariant->getCode());
|
||||
$this->client->filter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should see :count log entries in the catalog price history
|
||||
* @Then I should see a single log entry in the catalog price history
|
||||
*/
|
||||
public function iShouldSeeLogEntriesInTheCatalogPriceHistoryForTheVariant(int $count = 1): void
|
||||
{
|
||||
Assert::same($this->responseChecker->countCollectionItems($this->client->getLastResponse()), $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^there should be a log entry on the (\d+)(?:|st|nd|rd|th) position with the ("[^"]+") selling price, (no|"[^"]+") original price and datetime of the price change$/
|
||||
*/
|
||||
public function thereShouldBeALogEntryOnThePositionWithTheSellingPriceOriginalPriceAndDatetimeOfThePriceChange(
|
||||
int $position,
|
||||
int $price,
|
||||
int|string $originalPrice,
|
||||
): void {
|
||||
if ('no' === $originalPrice) {
|
||||
$originalPrice = null;
|
||||
}
|
||||
|
||||
$logEntry = $this->responseChecker->getCollection($this->client->getLastResponse())[$position - 1];
|
||||
|
||||
Assert::same($logEntry['price'], $price);
|
||||
Assert::same($logEntry['originalPrice'], $originalPrice);
|
||||
Assert::keyExists($logEntry, 'loggedAt');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^there should be a log entry with the ("[^"]+") selling price, (no|"[^"]+") original price and datetime of the price change$/
|
||||
*/
|
||||
public function thereShouldBeALogEntryWithTheSellingPriceOriginalPriceAndDatetimeOfThePriceChange(
|
||||
int $price,
|
||||
int|string $originalPrice,
|
||||
): void {
|
||||
$this->thereShouldBeALogEntryOnThePositionWithTheSellingPriceOriginalPriceAndDatetimeOfThePriceChange(
|
||||
1,
|
||||
$price,
|
||||
$originalPrice,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -63,6 +63,19 @@ final class ManagingProductVariantsContext implements Context
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I set its original price to ("[^"]+") for ("[^"]+" channel)$/
|
||||
*/
|
||||
public function iSetItsOriginalPriceToForChannel(int $originalPrice, ChannelInterface $channel): void
|
||||
{
|
||||
$this->client->addRequestData('channelPricings', [
|
||||
$channel->getCode() => [
|
||||
'originalPrice' => $originalPrice,
|
||||
'channelCode' => $channel->getCode(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I set its minimum price to ("[^"]+") for ("[^"]+" channel)$/
|
||||
*/
|
||||
|
|
@ -126,6 +139,27 @@ final class ManagingProductVariantsContext implements Context
|
|||
$this->createNewVariantWithPrice($name, $price, $product, $channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I want to modify the :variant product variant
|
||||
*/
|
||||
public function iWantToModifyProductVariant(ProductVariantInterface $variant): void
|
||||
{
|
||||
$this->client->buildUpdateRequest(Resources::PRODUCT_VARIANTS, $variant->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^I change its price to ("[^"]+") for ("[^"]+" channel)$/
|
||||
*/
|
||||
public function iChangeItsPriceToForChannel(int $originalPrice, ChannelInterface $channel): void
|
||||
{
|
||||
$this->client->addRequestData('channelPricings', [
|
||||
$channel->getCode() => [
|
||||
'price' => $originalPrice,
|
||||
'channelCode' => $channel->getCode(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I should be notified that it has been successfully created
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ final class Resources
|
|||
|
||||
public const CATALOG_PROMOTIONS = 'catalog-promotions';
|
||||
|
||||
public const CHANNEL_PRICING_LOG_ENTRY = 'channel-pricing-log-entries';
|
||||
|
||||
public const CHANNELS = 'channels';
|
||||
|
||||
public const CONTACT_REQUESTS = 'contact-requests';
|
||||
|
|
|
|||
|
|
@ -773,6 +773,84 @@ final class CatalogPromotionContext implements Context
|
|||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the :catalogPromotion catalog promotion is enabled
|
||||
*/
|
||||
public function theCatalogPromotionIsEnabled(CatalogPromotionInterface $catalogPromotion): void
|
||||
{
|
||||
$catalogPromotion->setEnabled(true);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->eventBus->dispatch(new CatalogPromotionUpdated($catalogPromotion->getCode()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given there is disabled catalog promotion named :name
|
||||
*/
|
||||
public function thereIsCatalogPromotionsNamed(string $name): void
|
||||
{
|
||||
$this->createCatalogPromotion(name: $name, enabled: false);
|
||||
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^there is a catalog promotion "([^"]+)" with priority ([^"]+) that reduces price by ("[^"]+") and applies on ("[^"]+" product)$/
|
||||
*/
|
||||
public function thereIsACatalogPromotionWithPriorityThatReducesPriceByAndAppliesOnProduct(
|
||||
string $name,
|
||||
int $priority,
|
||||
float $discount,
|
||||
ProductInterface $product,
|
||||
): void {
|
||||
$catalogPromotion = $this->createCatalogPromotion(
|
||||
name: $name,
|
||||
scopes: [[
|
||||
'type' => InForProductScopeVariantChecker::TYPE,
|
||||
'configuration' => ['products' => [$product->getCode()]],
|
||||
]],
|
||||
actions: [[
|
||||
'type' => PercentageDiscountPriceCalculator::TYPE,
|
||||
'configuration' => ['amount' => $discount],
|
||||
]],
|
||||
priority: $priority,
|
||||
);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->eventBus->dispatch(new CatalogPromotionUpdated($catalogPromotion->getCode()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^there is disabled catalog promotion "([^"]+)" with priority ([^"]+) that reduces price by fixed ("[^"]+") in the ("[^"]+" channel) and applies on ("[^"]+" product)$/
|
||||
*/
|
||||
public function thereIsDisabledCatalogPromotionWithPriorityThatReducesPriceByFixedInTheChannelAndAppliesOnProduct(
|
||||
string $name,
|
||||
int $priority,
|
||||
int $discount,
|
||||
ChannelInterface $channel,
|
||||
ProductInterface $product,
|
||||
): void {
|
||||
$catalogPromotion = $this->createCatalogPromotion(
|
||||
name: $name,
|
||||
channels: [$channel],
|
||||
scopes: [[
|
||||
'type' => InForProductScopeVariantChecker::TYPE,
|
||||
'configuration' => ['products' => [$product->getCode()]],
|
||||
]],
|
||||
actions: [[
|
||||
'type' => FixedDiscountPriceCalculator::TYPE,
|
||||
'configuration' => [$channel->getCode() => ['amount' => $discount / 100]],
|
||||
]],
|
||||
priority: $priority,
|
||||
enabled: false,
|
||||
);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->eventBus->dispatch(new CatalogPromotionUpdated($catalogPromotion->getCode()));
|
||||
}
|
||||
|
||||
private function createCatalogPromotion(
|
||||
string $name,
|
||||
?string $code = null,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use Behat\Gherkin\Node\TableNode;
|
|||
use Behat\Mink\Element\NodeElement;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Sylius\Behat\Service\SharedStorageInterface;
|
||||
use Sylius\Component\Core\Event\ProductUpdated;
|
||||
use Sylius\Component\Core\Formatter\StringInflector;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
|
|
@ -39,6 +40,7 @@ use Sylius\Component\Resource\Factory\FactoryInterface;
|
|||
use Sylius\Component\Shipping\Model\ShippingCategoryInterface;
|
||||
use Sylius\Component\Taxation\Model\TaxCategoryInterface;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ProductContext implements Context
|
||||
|
|
@ -60,6 +62,7 @@ final class ProductContext implements Context
|
|||
private ImageUploaderInterface $imageUploader,
|
||||
private SlugGeneratorInterface $slugGenerator,
|
||||
private \ArrayAccess $minkParameters,
|
||||
private MessageBusInterface $eventBus,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -1076,6 +1079,162 @@ final class ProductContext implements Context
|
|||
$this->saveProduct($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^(this product) has all possible variants priced at ("[^"]+") with indexed names$/
|
||||
*/
|
||||
public function thisProductHasAllPossibleVariantsPricedAtWithIndexedNames(
|
||||
ProductInterface $product,
|
||||
int $price,
|
||||
): void {
|
||||
try {
|
||||
foreach ($product->getVariants() as $productVariant) {
|
||||
$product->removeVariant($productVariant);
|
||||
}
|
||||
|
||||
$this->productVariantGenerator->generate($product);
|
||||
} catch (\InvalidArgumentException) {
|
||||
/** @var ProductVariantInterface $productVariant */
|
||||
$productVariant = $this->productVariantFactory->createNew();
|
||||
|
||||
$product->addVariant($productVariant);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
/** @var ProductVariantInterface $productVariant */
|
||||
foreach ($product->getVariants() as $productVariant) {
|
||||
$productVariant->setCode(sprintf('%s-variant-%d', $product->getCode(), $i));
|
||||
$productVariant->setName(sprintf('%s variant %d', $product->getName(), $i));
|
||||
|
||||
foreach ($product->getChannels() as $channel) {
|
||||
$productVariant->addChannelPricing($this->createChannelPricingForChannel($price, $channel));
|
||||
}
|
||||
|
||||
++$i;
|
||||
}
|
||||
|
||||
$this->objectManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^the ("[^"]+" product) is now priced at ("[^"]+") and originally priced at ("[^"]+")$/
|
||||
*/
|
||||
public function theProductIsPricedAtAndOriginallyPricedAt(
|
||||
ProductInterface $product,
|
||||
int $price,
|
||||
int $originalPrice,
|
||||
): void {
|
||||
$channelPricing = $this->getChannelPricingFromProduct($product);
|
||||
|
||||
$channelPricing->setPrice($price);
|
||||
$channelPricing->setOriginalPrice($originalPrice);
|
||||
|
||||
$this->saveProduct($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^the (product "[^"]+") has a "([^"]+)" variant priced at ("[^"]+") and originally priced at ("[^"]+")$/
|
||||
*/
|
||||
public function theProductHasVariantPricedAtAndOriginallyPricedAt(
|
||||
ProductInterface $product,
|
||||
string $productVariantName,
|
||||
int $price,
|
||||
int $originalPrice,
|
||||
): void {
|
||||
/** @var ChannelPricingInterface $channelPricing */
|
||||
$channelPricing = $this->channelPricingFactory->createNew();
|
||||
$channelPricing->setPrice($price);
|
||||
$channelPricing->setOriginalPrice($originalPrice);
|
||||
$channelPricing->setChannelCode($this->sharedStorage->get('channel')->getCode());
|
||||
|
||||
/** @var ProductVariantInterface $variant */
|
||||
$variant = $this->productVariantFactory->createNew();
|
||||
$variant->setName($productVariantName);
|
||||
$variant->setCode(StringInflector::nameToUppercaseCode($productVariantName));
|
||||
$variant->setProduct($product);
|
||||
$variant->setOnHand(0);
|
||||
$variant->addChannelPricing($channelPricing);
|
||||
$variant->setShippingRequired(true);
|
||||
|
||||
$product->setVariantSelectionMethod(ProductInterface::VARIANT_SELECTION_CHOICE);
|
||||
$product->addVariant($variant);
|
||||
|
||||
$this->saveProduct($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^(this product)'s price changed to ("[^"]+")$/
|
||||
*/
|
||||
public function thisProductsPriceChangedTo(ProductInterface $product, int $price): void
|
||||
{
|
||||
$channelPricing = $this->getChannelPricingFromProduct($product);
|
||||
$channelPricing->setPrice($price);
|
||||
|
||||
$this->saveProduct($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^(this product)'s price changed to ("[^"]+") and original price changed to ("[^"]+")$/
|
||||
*/
|
||||
public function thisProductsPriceChangedToAndOriginalPriceChangedTo(
|
||||
ProductInterface $product,
|
||||
int $price,
|
||||
int $originalPrice,
|
||||
): void {
|
||||
$channelPricing = $this->getChannelPricingFromProduct($product);
|
||||
|
||||
$channelPricing->setPrice($price);
|
||||
$channelPricing->setOriginalPrice($originalPrice);
|
||||
|
||||
$this->saveProduct($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^(this variant)'s price changed to ("[^"]+") and original price changed to ("[^"]+")$/
|
||||
*/
|
||||
public function thisVariantsPriceChangedToAndOriginalPriceChangedTo(
|
||||
ProductVariantInterface $productVariant,
|
||||
int $price,
|
||||
int $originalPrice,
|
||||
): void {
|
||||
$channelPricing = $this->getChannelPricingFromVariant($productVariant);
|
||||
|
||||
$channelPricing->setPrice($price);
|
||||
$channelPricing->setOriginalPrice($originalPrice);
|
||||
|
||||
/** @var ProductInterface $product */
|
||||
$product = $productVariant->getProduct();
|
||||
|
||||
$this->saveProduct($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^(this product)'s price changed to ("[^"]+") and original price was removed$/
|
||||
*/
|
||||
public function thisProductsPriceChangedToAndOriginalPriceWasRemoved(ProductInterface $product, $price): void
|
||||
{
|
||||
$channelPricing = $this->getChannelPricingFromProduct($product);
|
||||
$channelPricing->setPrice($price);
|
||||
$channelPricing->setOriginalPrice(null);
|
||||
|
||||
$this->saveProduct($product);
|
||||
}
|
||||
|
||||
private function getChannelPricingFromProduct(ProductInterface $product): ChannelPricingInterface
|
||||
{
|
||||
$variant = $this->defaultVariantResolver->getVariant($product);
|
||||
Assert::notNull($variant);
|
||||
|
||||
return $this->getChannelPricingFromVariant($variant);
|
||||
}
|
||||
|
||||
private function getChannelPricingFromVariant(ProductVariantInterface $productVariant): ChannelPricingInterface
|
||||
{
|
||||
$channelPricing = $productVariant->getChannelPricings()->first();
|
||||
Assert::isInstanceOf($channelPricing, ChannelPricingInterface::class);
|
||||
|
||||
return $channelPricing;
|
||||
}
|
||||
|
||||
private function getPriceFromString(string $price): int
|
||||
{
|
||||
return (int) round((float) str_replace(['€', '£', '$'], '', $price) * 100, 2);
|
||||
|
|
@ -1147,6 +1306,8 @@ final class ProductContext implements Context
|
|||
private function saveProduct(ProductInterface $product)
|
||||
{
|
||||
$this->productRepository->add($product);
|
||||
$this->eventBus->dispatch(new ProductUpdated($product->getCode()));
|
||||
$this->sharedStorage->set('variant', $product->getVariants()->first());
|
||||
$this->sharedStorage->set('product', $product);
|
||||
}
|
||||
|
||||
|
|
@ -1238,10 +1399,7 @@ final class ProductContext implements Context
|
|||
$productVariant->addTranslation($translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ChannelPricingInterface
|
||||
*/
|
||||
private function createChannelPricingForChannel(int $price, ChannelInterface $channel = null)
|
||||
private function createChannelPricingForChannel(int $price, ChannelInterface $channel = null): ChannelPricingInterface
|
||||
{
|
||||
/** @var ChannelPricingInterface $channelPricing */
|
||||
$channelPricing = $this->channelPricingFactory->createNew();
|
||||
|
|
|
|||
|
|
@ -194,5 +194,11 @@
|
|||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument>%sylius.security.new_api_route%</argument>
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Behat\Context\Api\Admin\ChannelPricingLogEntryContext" public="true">
|
||||
<argument type="service" id="sylius.behat.api_platform_client.admin" />
|
||||
<argument type="service" id="Sylius\Behat\Client\ResponseCheckerInterface" />
|
||||
<argument type="service" id="sylius.behat.shared_storage" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@
|
|||
<argument type="service" id="sylius.image_uploader" />
|
||||
<argument type="service" id="sylius.generator.slug" />
|
||||
<argument type="service" id="behat.mink.parameters" />
|
||||
<argument type="service" id="sylius.event_bus" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.behat.context.setup.product_association" class="Sylius\Behat\Context\Setup\ProductAssociationContext">
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@ imports:
|
|||
- suites/api/product/managing_product_variants.yml
|
||||
- suites/api/product/managing_product_variants_ajax.yml
|
||||
- suites/api/product/managing_products.yml
|
||||
- suites/api/product/viewing_products.yml
|
||||
- suites/api/product/viewing_price_history.yml
|
||||
- suites/api/product/viewing_price_history_after_catalog_promotions.yml
|
||||
- suites/api/product/viewing_product_reviews.yml
|
||||
- suites/api/product/viewing_product_variants.yml
|
||||
- suites/api/product/viewing_products.yml
|
||||
- suites/api/promotion/applying_catalog_promotions.yml
|
||||
- suites/api/promotion/applying_promotion_coupon.yml
|
||||
- suites/api/promotion/applying_promotion_rules.yml
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ default:
|
|||
- sylius.behat.context.setup.product
|
||||
- sylius.behat.context.setup.admin_api_security
|
||||
- sylius.behat.context.setup.taxonomy
|
||||
- Sylius\Behat\Context\Setup\CatalogPromotionContext
|
||||
|
||||
- sylius.behat.context.api.admin.managing_product_variants
|
||||
- Sylius\Behat\Context\Api\Admin\ChannelPricingLogEntryContext
|
||||
filters:
|
||||
tags: "@managing_product_variants&&@api"
|
||||
javascript: false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Paweł Jędrzejewski
|
||||
|
||||
default:
|
||||
suites:
|
||||
api_viewing_price_history:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.product_variant
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
|
||||
- sylius.behat.context.setup.admin_api_security
|
||||
- sylius.behat.context.setup.admin_user
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.product
|
||||
- Sylius\Behat\Context\Setup\CatalogPromotionContext
|
||||
|
||||
- sylius.behat.context.api.admin.managing_product_variants
|
||||
- sylius.behat.context.api.admin.save
|
||||
- Sylius\Behat\Context\Api\Admin\ChannelPricingLogEntryContext
|
||||
filters:
|
||||
tags: "@viewing_price_history&&@api"
|
||||
javascript: false
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# This file is part of the Sylius package.
|
||||
# (c) Paweł Jędrzejewski
|
||||
|
||||
default:
|
||||
suites:
|
||||
api_viewing_price_history_after_catalog_promotions:
|
||||
contexts:
|
||||
- sylius.behat.context.hook.doctrine_orm
|
||||
|
||||
- sylius.behat.context.transform.channel
|
||||
- sylius.behat.context.transform.lexical
|
||||
- sylius.behat.context.transform.product
|
||||
- sylius.behat.context.transform.product_variant
|
||||
- sylius.behat.context.transform.shared_storage
|
||||
- Sylius\Behat\Context\Transform\CatalogPromotionContext
|
||||
|
||||
- sylius.behat.context.setup.admin_api_security
|
||||
- sylius.behat.context.setup.admin_user
|
||||
- sylius.behat.context.setup.channel
|
||||
- sylius.behat.context.setup.product
|
||||
- Sylius\Behat\Context\Setup\CatalogPromotionContext
|
||||
|
||||
- sylius.behat.context.api.admin.managing_catalog_promotions
|
||||
- Sylius\Behat\Context\Api\Admin\ChannelPricingLogEntryContext
|
||||
|
||||
filters:
|
||||
tags: "@viewing_price_history_after_catalog_promotions&&@api"
|
||||
javascript: false
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<resources xmlns="https://api-platform.com/schema/metadata"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://api-platform.com/schema/metadata https://api-platform.com/schema/metadata/metadata-2.0.xsd"
|
||||
>
|
||||
<resource class="%sylius.model.channel_pricing_log_entry.class%" shortName="ChannelPricingLogEntry">
|
||||
<collectionOperations>
|
||||
<collectionOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/channel-pricing-log-entries</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:channel_pricing_log_entry:read</attribute>
|
||||
</attribute>
|
||||
<attribute name="filters">
|
||||
<attribute>sylius.api.channel_pricing_channel_filter</attribute>
|
||||
<attribute>sylius.api.channel_pricing_product_variant_filter</attribute>
|
||||
</attribute>
|
||||
<attribute name="order">
|
||||
<attribute name="id">DESC</attribute>
|
||||
</attribute>
|
||||
</collectionOperation>
|
||||
</collectionOperations>
|
||||
|
||||
<itemOperations>
|
||||
<itemOperation name="admin_get">
|
||||
<attribute name="method">GET</attribute>
|
||||
<attribute name="path">/admin/channel-pricing-log-entries/{id}</attribute>
|
||||
<attribute name="normalization_context">
|
||||
<attribute name="groups">admin:channel_pricing_log_entry:read</attribute>
|
||||
</attribute>
|
||||
</itemOperation>
|
||||
</itemOperations>
|
||||
|
||||
<property name="id" identifier="true" writable="false" />
|
||||
<property name="channelPricing" readable="true" writable="false" />
|
||||
<property name="price" readable="true" writable="false" />
|
||||
<property name="originalPrice" readable="true" writable="false" />
|
||||
<property name="loggedAt" readable="true" writable="false" />
|
||||
</resource>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" ?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
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\Component\Core\Model\ChannelPricingLogEntry">
|
||||
<attribute name="channelPricing">
|
||||
<group>admin:channel_pricing_log_entry:read</group>
|
||||
</attribute>
|
||||
<attribute name="price">
|
||||
<group>admin:channel_pricing_log_entry:read</group>
|
||||
</attribute>
|
||||
<attribute name="originalPrice">
|
||||
<group>admin:channel_pricing_log_entry:read</group>
|
||||
</attribute>
|
||||
<attribute name="loggedAt">
|
||||
<group>admin:channel_pricing_log_entry:read</group>
|
||||
</attribute>
|
||||
</class>
|
||||
</serializer>
|
||||
|
|
@ -196,5 +196,19 @@
|
|||
</argument>
|
||||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.api.channel_pricing_channel_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="channelPricing.channelCode">exact</argument>
|
||||
</argument>
|
||||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.api.channel_pricing_product_variant_filter" parent="api_platform.doctrine.orm.search_filter" public="true">
|
||||
<argument type="collection">
|
||||
<argument key="channelPricing.productVariant.code">exact</argument>
|
||||
</argument>
|
||||
<tag name="api_platform.filter" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
|
||||
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
<parameters>
|
||||
<parameter key="sylius.channel.validation_groups" type="collection">
|
||||
<parameter>sylius</parameter>
|
||||
</parameter>
|
||||
|
||||
<parameter key="sylius.form.type.channel.validation_groups" type="collection">
|
||||
<parameter>sylius</parameter>
|
||||
</parameter>
|
||||
|
|
|
|||
|
|
@ -14,14 +14,19 @@ declare(strict_types=1);
|
|||
namespace Sylius\Bundle\CoreBundle\DependencyInjection;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\Controller\ProductTaxonController;
|
||||
use Sylius\Bundle\CoreBundle\Doctrine\ORM\ChannelPricingLogEntryRepository;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\Product\ChannelPricingType;
|
||||
use Sylius\Bundle\CoreBundle\Form\Type\ShopBillingDataType;
|
||||
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
|
||||
use Sylius\Bundle\ResourceBundle\Form\Type\DefaultResourceType;
|
||||
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
|
||||
use Sylius\Component\Core\Factory\ChannelPricingLogEntryFactory;
|
||||
use Sylius\Component\Core\Model\AvatarImage;
|
||||
use Sylius\Component\Core\Model\AvatarImageInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricing;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntry;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
use Sylius\Component\Core\Model\ProductImage;
|
||||
use Sylius\Component\Core\Model\ProductImageInterface;
|
||||
use Sylius\Component\Core\Model\ProductTaxon;
|
||||
|
|
@ -175,6 +180,23 @@ final class Configuration implements ConfigurationInterface
|
|||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('channel_pricing_log_entry')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->variableNode('options')->end()
|
||||
->arrayNode('classes')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
->scalarNode('model')->defaultValue(ChannelPricingLogEntry::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('interface')->defaultValue(ChannelPricingLogEntryInterface::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('controller')->defaultValue(ResourceController::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('repository')->defaultValue(ChannelPricingLogEntryRepository::class)->cannotBeEmpty()->end()
|
||||
->scalarNode('factory')->defaultValue(ChannelPricingLogEntryFactory::class)->end()
|
||||
->scalarNode('form')->defaultValue(DefaultResourceType::class)->cannotBeEmpty()->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('shop_billing_data')
|
||||
->addDefaultsIfNotSet()
|
||||
->children()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\Doctrine\ORM;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Sylius\Bundle\ResourceBundle\Doctrine\ORM\EntityRepository;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
use Sylius\Component\Core\Repository\ChannelPricingLogEntryRepositoryInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class ChannelPricingLogEntryRepository extends EntityRepository implements ChannelPricingLogEntryRepositoryInterface
|
||||
{
|
||||
public function createByChannelPricingIdListQueryBuilder(mixed $channelPricingId): QueryBuilder
|
||||
{
|
||||
return $this->createQueryBuilder('o')
|
||||
->innerJoin('o.channelPricing', 'channelPricing')
|
||||
->andWhere('channelPricing = :channelPricingId')
|
||||
->orderBy('o.id', 'DESC')
|
||||
->setParameter('channelPricingId', $channelPricingId)
|
||||
;
|
||||
}
|
||||
|
||||
public function findOlderThan(\DateTimeInterface $date, ?int $limit = null): array
|
||||
{
|
||||
$queryBuilder = $this->createQueryBuilder('o')
|
||||
->andWhere('o.loggedAt < :date')
|
||||
->setParameter('date', $date)
|
||||
;
|
||||
|
||||
if (null !== $limit) {
|
||||
Assert::positiveInteger($limit);
|
||||
$queryBuilder->setMaxResults($limit);
|
||||
}
|
||||
|
||||
return $queryBuilder->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findLatestOneByChannelPricing(ChannelPricingInterface $channelPricing): ?ChannelPricingLogEntryInterface
|
||||
{
|
||||
/** @var ChannelPricingLogEntryInterface|null $channelPricingLogEntry */
|
||||
$channelPricingLogEntry = $this->createQueryBuilder('o')
|
||||
->andWhere('o.channelPricing = :channelPricing')
|
||||
->setParameter('channelPricing', $channelPricing)
|
||||
->orderBy('o.id', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
|
||||
return $channelPricingLogEntry;
|
||||
}
|
||||
|
||||
public function findLowestPriceInPeriod(
|
||||
int $latestChannelPricingLogEntryId,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
\DateTimeInterface $startDate,
|
||||
): ?int {
|
||||
$lowestPriceSetInPeriod = $this->findLowestPriceSetInPeriod($latestChannelPricingLogEntryId, $channelPricing, $startDate);
|
||||
$latestPriceSetBeyondPeriod = $this->findLatestPriceSetBeyondPeriod($channelPricing, $startDate);
|
||||
|
||||
if (null === $lowestPriceSetInPeriod) {
|
||||
return $latestPriceSetBeyondPeriod;
|
||||
}
|
||||
|
||||
if (null === $latestPriceSetBeyondPeriod) {
|
||||
return $lowestPriceSetInPeriod;
|
||||
}
|
||||
|
||||
if ($latestPriceSetBeyondPeriod < $lowestPriceSetInPeriod) {
|
||||
return $latestPriceSetBeyondPeriod;
|
||||
}
|
||||
|
||||
return $lowestPriceSetInPeriod;
|
||||
}
|
||||
|
||||
private function findLowestPriceSetInPeriod(
|
||||
int $latestChannelPricingLogEntryId,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
\DateTimeInterface $startDate,
|
||||
): ?int {
|
||||
/** @var ChannelPricingLogEntryInterface|null $channelPricingLogEntry */
|
||||
$channelPricingLogEntry = $this->createQueryBuilder('o')
|
||||
->andWhere('o.loggedAt >= :startDate')
|
||||
->andWhere('o.id != :channelPricingLogEntryId')
|
||||
->andWhere('o.channelPricing = :channelPricing')
|
||||
->setParameter('startDate', $startDate)
|
||||
->setParameter('channelPricing', $channelPricing)
|
||||
->setParameter('channelPricingLogEntryId', $latestChannelPricingLogEntryId)
|
||||
->orderBy('o.price', 'ASC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
|
||||
return $channelPricingLogEntry?->getPrice();
|
||||
}
|
||||
|
||||
private function findLatestPriceSetBeyondPeriod(
|
||||
ChannelPricingInterface $channelPricing,
|
||||
\DateTimeInterface $startDate,
|
||||
): ?int {
|
||||
/** @var ChannelPricingLogEntryInterface|null $channelPricingLogEntry */
|
||||
$channelPricingLogEntry = $this->createQueryBuilder('o')
|
||||
->andWhere('o.loggedAt < :startDate')
|
||||
->andWhere('o.channelPricing = :channelPricing')
|
||||
->setParameter('startDate', $startDate)
|
||||
->setParameter('channelPricing', $channelPricing)
|
||||
->orderBy('o.id', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
|
||||
return $channelPricingLogEntry?->getPrice();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20230310160512 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add ChannelPricingLogEntry';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE sylius_channel_pricing_log_entry (id INT AUTO_INCREMENT NOT NULL, channel_pricing_id INT NOT NULL, price INT NOT NULL, original_price INT DEFAULT NULL, logged_at DATETIME NOT NULL, INDEX IDX_77181A53EADFFE5 (channel_pricing_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE sylius_channel_pricing_log_entry ADD CONSTRAINT FK_77181A53EADFFE5 FOREIGN KEY (channel_pricing_id) REFERENCES sylius_channel_pricing (id) ON DELETE CASCADE');
|
||||
|
||||
/** Create an initial log state based on the price of products at the time of migration processing */
|
||||
$this->addSql('INSERT INTO `sylius_channel_pricing_log_entry` (`channel_pricing_id`, `price`, `original_price`, `logged_at`) SELECT `id`, `price`, `original_price`, NOW() FROM `sylius_channel_pricing`');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_channel_pricing_log_entry DROP FOREIGN KEY FK_77181A53EADFFE5');
|
||||
$this->addSql('DROP TABLE sylius_channel_pricing_log_entry');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20230314073130 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add lowest_price_before_discount column to sylius_channel_pricing table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_channel_pricing ADD lowest_price_before_discount INT DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_channel_pricing DROP lowest_price_before_discount');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20230314073215 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add lowest_price_for_discounted_products_checking_period column to sylius_channel table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_channel ADD lowest_price_for_discounted_products_checking_period TINYINT(1) DEFAULT 30 NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE sylius_channel DROP lowest_price_for_discounted_products_checking_period');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\EntityObserver;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLoggerInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class CreateLogEntryOnPriceChangeObserver implements EntityObserverInterface
|
||||
{
|
||||
public function __construct(private PriceChangeLoggerInterface $priceChangeLogger)
|
||||
{
|
||||
}
|
||||
|
||||
public function onChange(object $entity): void
|
||||
{
|
||||
Assert::isInstanceOf($entity, ChannelPricingInterface::class);
|
||||
|
||||
$this->priceChangeLogger->log($entity);
|
||||
}
|
||||
|
||||
public function supports(object $entity): bool
|
||||
{
|
||||
return $entity instanceof ChannelPricingInterface && null !== $entity->getPrice();
|
||||
}
|
||||
|
||||
public function observedFields(): array
|
||||
{
|
||||
return ['price', 'originalPrice'];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\EntityObserver;
|
||||
|
||||
interface EntityObserverInterface
|
||||
{
|
||||
public function onChange(object $entity): void;
|
||||
|
||||
public function supports(object $entity): bool;
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function observedFields(): array;
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\EntityObserver;
|
||||
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\Processor\ProductLowestPriceBeforeDiscountProcessorInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ProcessLowestPriceOnCheckingPeriodChangeObserver implements EntityObserverInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ProductLowestPriceBeforeDiscountProcessorInterface $productLowestPriceBeforeDiscountProcessor,
|
||||
private RepositoryInterface $channelPricingRepository,
|
||||
private int $batchSize,
|
||||
) {
|
||||
}
|
||||
|
||||
public function onChange(object $entity): void
|
||||
{
|
||||
Assert::isInstanceOf($entity, ChannelInterface::class);
|
||||
|
||||
$limit = $this->batchSize;
|
||||
$offset = 0;
|
||||
|
||||
do {
|
||||
/** @var ChannelPricingInterface[] $channelPricings */
|
||||
$channelPricings = $this->channelPricingRepository->findBy(
|
||||
['channelCode' => $entity->getCode()],
|
||||
['id' => 'ASC'],
|
||||
$limit,
|
||||
$offset,
|
||||
);
|
||||
|
||||
foreach ($channelPricings as $channelPricing) {
|
||||
$this->productLowestPriceBeforeDiscountProcessor->process($channelPricing);
|
||||
}
|
||||
|
||||
$offset += $limit;
|
||||
} while ([] !== $channelPricings);
|
||||
}
|
||||
|
||||
public function supports(object $entity): bool
|
||||
{
|
||||
return $entity instanceof ChannelInterface;
|
||||
}
|
||||
|
||||
public function observedFields(): array
|
||||
{
|
||||
return ['lowestPriceForDiscountedProductsCheckingPeriod'];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\EventListener;
|
||||
|
||||
use ApiPlatform\Symfony\Validator\Exception\ValidationException;
|
||||
use Sylius\Component\Channel\Factory\ChannelFactoryInterface;
|
||||
use Sylius\Component\Channel\Model\ChannelInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
final class BeforeDenormalizationChannelValidationListener
|
||||
{
|
||||
public function __construct(
|
||||
private ValidatorInterface $validator,
|
||||
private ChannelFactoryInterface $channelFactory,
|
||||
private array $validationGroups = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function onKernelRequest(RequestEvent $event): void
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
$parameters = $request->attributes->all();
|
||||
|
||||
if (
|
||||
!is_a($parameters['_api_resource_class'] ?? null, ChannelInterface::class, true) ||
|
||||
$request->isMethodSafe() ||
|
||||
$request->isMethod(Request::METHOD_DELETE)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var string $requestContent */
|
||||
$requestContent = $request->getContent(false);
|
||||
$content = (array) json_decode($requestContent, true, 512, \JSON_THROW_ON_ERROR);
|
||||
$channelModel = $this->channelFactory->createNew();
|
||||
|
||||
$violations = $this->validator->startContext()->getViolations();
|
||||
foreach ($content as $key => $value) {
|
||||
$propertyViolations = $this->validator->validatePropertyValue(
|
||||
$channelModel,
|
||||
$key,
|
||||
$value,
|
||||
$this->validationGroups,
|
||||
);
|
||||
|
||||
if ($propertyViolations->count() > 0) {
|
||||
$violations->addAll($propertyViolations);
|
||||
}
|
||||
}
|
||||
|
||||
if ($violations->count() > 0) {
|
||||
throw new ValidationException($violations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\EventListener;
|
||||
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\EntityObserver\EntityObserverInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class OnFlushEntityObserverListener
|
||||
{
|
||||
public function __construct(private iterable $entityObservers)
|
||||
{
|
||||
Assert::allImplementsInterface($this->entityObservers, EntityObserverInterface::class);
|
||||
}
|
||||
|
||||
public function onFlush(OnFlushEventArgs $eventArgs): void
|
||||
{
|
||||
$entityManager = $eventArgs->getObjectManager();
|
||||
$unitOfWork = $entityManager->getUnitOfWork();
|
||||
|
||||
$scheduledEntities = array_merge(
|
||||
$unitOfWork->getScheduledEntityInsertions(),
|
||||
$unitOfWork->getScheduledEntityUpdates(),
|
||||
);
|
||||
|
||||
$atLeastOneEntityChanged = false;
|
||||
|
||||
foreach ($scheduledEntities as $entity) {
|
||||
/** @var EntityObserverInterface $entityObserver */
|
||||
foreach ($this->entityObservers as $entityObserver) {
|
||||
if (
|
||||
!$entityObserver->supports($entity) ||
|
||||
!$this->isEntityChanged($unitOfWork, $entity, $entityObserver->observedFields())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$atLeastOneEntityChanged = true;
|
||||
$entityObserver->onChange($entity);
|
||||
}
|
||||
}
|
||||
|
||||
if ($atLeastOneEntityChanged) {
|
||||
$unitOfWork->computeChangeSets();
|
||||
}
|
||||
}
|
||||
|
||||
private function isEntityChanged(UnitOfWork $unitOfWork, object $entity, array $supportedFields): bool
|
||||
{
|
||||
$changedFields = array_keys($unitOfWork->getEntityChangeSet($entity));
|
||||
|
||||
return [] !== array_intersect($changedFields, $supportedFields);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\Logger;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use Sylius\Calendar\Provider\DateTimeProviderInterface;
|
||||
use Sylius\Component\Core\Factory\ChannelPricingLogEntryFactoryInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class PriceChangeLogger implements PriceChangeLoggerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ChannelPricingLogEntryFactoryInterface $logEntryFactory,
|
||||
private ObjectManager $logEntryManager,
|
||||
private DateTimeProviderInterface $dateTimeProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function log(ChannelPricingInterface $channelPricing): void
|
||||
{
|
||||
Assert::notNull($channelPricing->getPrice());
|
||||
|
||||
/** @psalm-suppress PossiblyNullArgument */
|
||||
$logEntry = $this->logEntryFactory->create(
|
||||
$channelPricing,
|
||||
$this->dateTimeProvider->now(),
|
||||
$channelPricing->getPrice(),
|
||||
$channelPricing->getOriginalPrice(),
|
||||
);
|
||||
|
||||
$this->logEntryManager->persist($logEntry);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\Logger;
|
||||
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
|
||||
interface PriceChangeLoggerInterface
|
||||
{
|
||||
public function log(ChannelPricingInterface $channelPricing): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\Processor;
|
||||
|
||||
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
use Sylius\Component\Core\Repository\ChannelPricingLogEntryRepositoryInterface;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class ProductLowestPriceBeforeDiscountProcessor implements ProductLowestPriceBeforeDiscountProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ChannelPricingLogEntryRepositoryInterface $channelPricingLogEntryRepository,
|
||||
private ChannelRepositoryInterface $channelRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(ChannelPricingInterface $channelPricing): void
|
||||
{
|
||||
if (!$this->isPromotionApplied($channelPricing)) {
|
||||
$channelPricing->setLowestPriceBeforeDiscount(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$latestLogEntry = $this->channelPricingLogEntryRepository->findLatestOneByChannelPricing($channelPricing);
|
||||
|
||||
if ($latestLogEntry === null) {
|
||||
$channelPricing->setLowestPriceBeforeDiscount(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$channelCode = $channelPricing->getChannelCode();
|
||||
Assert::string($channelCode);
|
||||
|
||||
/** @var ChannelInterface $channel */
|
||||
$channel = $this->channelRepository->findOneByCode($channelCode);
|
||||
|
||||
if (!$channel instanceof ChannelInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lowestPriceInPeriod = $this->findLowestPriceInPeriod(
|
||||
$latestLogEntry,
|
||||
$channel->getLowestPriceForDiscountedProductsCheckingPeriod(),
|
||||
);
|
||||
|
||||
$channelPricing->setLowestPriceBeforeDiscount($lowestPriceInPeriod);
|
||||
}
|
||||
|
||||
private function isPromotionApplied(ChannelPricingInterface $channelPricing): bool
|
||||
{
|
||||
return
|
||||
$channelPricing->getOriginalPrice() !== null &&
|
||||
$channelPricing->getPrice() < $channelPricing->getOriginalPrice()
|
||||
;
|
||||
}
|
||||
|
||||
private function findLowestPriceInPeriod(
|
||||
ChannelPricingLogEntryInterface $latestLogEntry,
|
||||
int $lowestPriceForDiscountedProductsCheckingPeriod,
|
||||
): ?int {
|
||||
$loggedAt = new \DateTimeImmutable($latestLogEntry->getLoggedAt()->format('Y-m-d H:i:s'));
|
||||
|
||||
/** @var \DateTimeInterface $startDate */
|
||||
$startDate = $loggedAt->sub(new \DateInterval(sprintf('P%sD', $lowestPriceForDiscountedProductsCheckingPeriod)));
|
||||
|
||||
return $this
|
||||
->channelPricingLogEntryRepository
|
||||
->findLowestPriceInPeriod($latestLogEntry->getId(), $latestLogEntry->getChannelPricing(), $startDate)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\Processor;
|
||||
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
|
||||
interface ProductLowestPriceBeforeDiscountProcessorInterface
|
||||
{
|
||||
public function process(ChannelPricingInterface $channelPricing): void;
|
||||
}
|
||||
|
|
@ -29,6 +29,15 @@
|
|||
<option name="default">0</option>
|
||||
</options>
|
||||
</field>
|
||||
<field
|
||||
name="lowestPriceForDiscountedProductsCheckingPeriod"
|
||||
column="lowest_price_for_discounted_products_checking_period"
|
||||
type="boolean"
|
||||
>
|
||||
<options>
|
||||
<option name="default">30</option>
|
||||
</options>
|
||||
</field>
|
||||
|
||||
<many-to-one field="defaultLocale" target-entity="Sylius\Component\Locale\Model\LocaleInterface" fetch="EAGER">
|
||||
<join-column name="default_locale_id" referenced-column-name="id" nullable="false" />
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
<option name="default">0</option>
|
||||
</options>
|
||||
</field>
|
||||
<field name="lowestPriceBeforeDiscount" column="lowest_price_before_discount" type="integer" nullable="true" />
|
||||
<field name="channelCode" column="channel_code" type="string" />
|
||||
<many-to-many field="appliedPromotions" target-entity="Sylius\Component\Promotion\Model\CatalogPromotionInterface">
|
||||
<order-by>
|
||||
|
|
@ -50,6 +51,9 @@
|
|||
|
||||
<many-to-one field="productVariant" target-entity="Sylius\Component\Product\Model\ProductVariantInterface" inversed-by="channelPricings">
|
||||
<join-column name="product_variant_id" referenced-column-name="id" nullable="false" on-delete="CASCADE" />
|
||||
<cascade>
|
||||
<cascade-persist />
|
||||
</cascade>
|
||||
</many-to-one>
|
||||
</mapped-superclass>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd"
|
||||
>
|
||||
|
||||
<mapped-superclass name="Sylius\Component\Core\Model\ChannelPricingLogEntry" table="sylius_channel_pricing_log_entry">
|
||||
<id name="id" column="id" type="integer">
|
||||
<generator />
|
||||
</id>
|
||||
|
||||
<field name="price" column="price" type="integer" />
|
||||
<field name="originalPrice" column="original_price" type="integer" nullable="true" />
|
||||
<field name="loggedAt" column="logged_at" type="datetime" />
|
||||
|
||||
<many-to-one field="channelPricing" target-entity="Sylius\Component\Core\Model\ChannelPricingInterface">
|
||||
<join-column name="channel_pricing_id" nullable="false" on-delete="CASCADE" />
|
||||
<cascade>
|
||||
<cascade-persist />
|
||||
</cascade>
|
||||
</many-to-one>
|
||||
</mapped-superclass>
|
||||
</doctrine-mapping>
|
||||
|
|
@ -23,12 +23,13 @@
|
|||
<import resource="services/emails.xml" />
|
||||
<import resource="services/factories.xml" />
|
||||
<import resource="services/fixtures.xml" />
|
||||
<import resource="services/fixtures_listeners.xml" />
|
||||
<import resource="services/fixtures_factories.xml" />
|
||||
<import resource="services/fixtures_listeners.xml" />
|
||||
<import resource="services/form.xml" />
|
||||
<import resource="services/installer.xml" />
|
||||
<import resource="services/installer_requirements.xml" />
|
||||
<import resource="services/listeners.xml" />
|
||||
<import resource="services/logger.xml" />
|
||||
<import resource="services/message_handlers.xml" />
|
||||
<import resource="services/order_processing.xml" />
|
||||
<import resource="services/payment.xml" />
|
||||
|
|
|
|||
|
|
@ -95,5 +95,22 @@
|
|||
<tag name="kernel.event_listener" event="sylius.taxon.post_delete" method="removeTaxonFromPromotionRules" />
|
||||
<tag name="kernel.event_listener" event="sylius.taxon.pre_delete" method="handleRemovingRootTaxonAtPositionZero" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\CoreBundle\PriceHistory\EntityObserver\CreateLogEntryOnPriceChangeObserver">
|
||||
<argument type="service" id="Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLoggerInterface" />
|
||||
<tag name="sylius.entity_observer" />
|
||||
</service>
|
||||
|
||||
<service id="sylius.listener.channel_pricing_change" class="Sylius\Bundle\CoreBundle\PriceHistory\EventListener\OnFlushEntityObserverListener">
|
||||
<argument type="tagged_iterator" tag="sylius.entity_observer" />
|
||||
<tag name="doctrine.event_listener" event="onFlush" lazy="true" />
|
||||
</service>
|
||||
|
||||
<service id="Sylius\Bundle\CoreBundle\PriceHistory\EventListener\BeforeDenormalizationChannelValidationListener">
|
||||
<argument type="service" id="validator" />
|
||||
<argument type="service" id="sylius.custom_factory.channel" />
|
||||
<argument>%sylius.channel.validation_groups%</argument>
|
||||
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="3" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
|
||||
This file is part of the Sylius package.
|
||||
|
||||
(c) Paweł Jędrzejewski
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
|
||||
-->
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
<services>
|
||||
<defaults public="true" />
|
||||
|
||||
<service
|
||||
id="Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLoggerInterface"
|
||||
class="Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLogger"
|
||||
>
|
||||
<argument type="service" id="sylius.factory.channel_pricing_log_entry" />
|
||||
<argument type="service" id="sylius.manager.channel_pricing_log_entry" />
|
||||
<argument type="service" id="Sylius\Calendar\Provider\DateTimeProviderInterface" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\EntityObserver;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\EntityObserver\EntityObserverInterface;
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLoggerInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
|
||||
final class CreateLogEntryOnPriceChangeObserverSpec extends ObjectBehavior
|
||||
{
|
||||
function let(PriceChangeLoggerInterface $priceChangeLogger): void
|
||||
{
|
||||
$this->beConstructedWith($priceChangeLogger);
|
||||
}
|
||||
|
||||
function it_implements_on_entity_change_interface(): void
|
||||
{
|
||||
$this->shouldImplement(EntityObserverInterface::class);
|
||||
}
|
||||
|
||||
function it_supports_channel_pricing_with_price_specified_only(
|
||||
ChannelPricingInterface $channelPricingWithPrice,
|
||||
ChannelPricingInterface $channelPricingWithoutPrice,
|
||||
OrderInterface $order,
|
||||
): void {
|
||||
$channelPricingWithPrice->getPrice()->willReturn(1000);
|
||||
|
||||
$this->supports($channelPricingWithPrice)->shouldReturn(true);
|
||||
$this->supports($channelPricingWithoutPrice)->shouldReturn(false);
|
||||
$this->supports($order)->shouldReturn(false);
|
||||
}
|
||||
|
||||
function it_supports_price_and_original_price_fields(): void
|
||||
{
|
||||
$this->observedFields()->shouldReturn(['price', 'originalPrice']);
|
||||
}
|
||||
|
||||
function it_logs_price_change(
|
||||
PriceChangeLoggerInterface $priceChangeLogger,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
): void {
|
||||
$priceChangeLogger->log($channelPricing)->shouldBeCalled();
|
||||
|
||||
$this->onChange($channelPricing);
|
||||
}
|
||||
|
||||
function it_throws_an_error_if_entity_is_not_channel_pricing(
|
||||
PriceChangeLoggerInterface $priceChangeLogger,
|
||||
ChannelInterface $channel,
|
||||
): void {
|
||||
$priceChangeLogger->log($channel)->shouldNotBeCalled();
|
||||
|
||||
$this->shouldThrow(\InvalidArgumentException::class)->during('onChange', [$channel]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\EntityObserver;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\EntityObserver\EntityObserverInterface;
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\Processor\ProductLowestPriceBeforeDiscountProcessorInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\OrderInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
|
||||
final class ProcessLowestPriceOnCheckingPeriodChangeObserverSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
ProductLowestPriceBeforeDiscountProcessorInterface $productLowestPriceBeforeDiscountProcessor,
|
||||
RepositoryInterface $channelPricingRepository,
|
||||
): void {
|
||||
$this->beConstructedWith($productLowestPriceBeforeDiscountProcessor, $channelPricingRepository, 2);
|
||||
}
|
||||
|
||||
function it_implements_on_entity_change_interface(): void
|
||||
{
|
||||
$this->shouldImplement(EntityObserverInterface::class);
|
||||
}
|
||||
|
||||
function it_supports_channel_pricing_interface_only(
|
||||
ChannelInterface $channel,
|
||||
OrderInterface $order,
|
||||
): void {
|
||||
$this->supports($channel)->shouldReturn(true);
|
||||
$this->supports($order)->shouldReturn(false);
|
||||
}
|
||||
|
||||
function it_supports_lowest_price_for_discounted_products_checking_period_field(): void
|
||||
{
|
||||
$this->observedFields()->shouldReturn(['lowestPriceForDiscountedProductsCheckingPeriod']);
|
||||
}
|
||||
|
||||
function it_processes_product_lowest_price_for_each_channel_pricing_within_channel(
|
||||
ProductLowestPriceBeforeDiscountProcessorInterface $productLowestPriceBeforeDiscountProcessor,
|
||||
RepositoryInterface $channelPricingRepository,
|
||||
ChannelInterface $channel,
|
||||
ChannelPricingInterface $firstChannelPricing,
|
||||
ChannelPricingInterface $secondChannelPricing,
|
||||
ChannelPricingInterface $thirdChannelPricing,
|
||||
ChannelPricingInterface $fourthChannelPricing,
|
||||
ChannelPricingInterface $fifthChannelPricing,
|
||||
): void {
|
||||
$channel->getCode()->willReturn('WEB');
|
||||
|
||||
$batches = [
|
||||
[
|
||||
$firstChannelPricing->getWrappedObject(),
|
||||
$secondChannelPricing->getWrappedObject(),
|
||||
],
|
||||
[
|
||||
$thirdChannelPricing->getWrappedObject(),
|
||||
$fourthChannelPricing->getWrappedObject(),
|
||||
],
|
||||
[
|
||||
$fifthChannelPricing->getWrappedObject(),
|
||||
],
|
||||
[],
|
||||
];
|
||||
|
||||
$batchSize = 2;
|
||||
|
||||
foreach ($batches as $key => $batch) {
|
||||
$channelPricingRepository
|
||||
->findBy(['channelCode' => 'WEB'], ['id' => 'ASC'], 2, $key * $batchSize)
|
||||
->willReturn($batch)
|
||||
->shouldBeCalled()
|
||||
;
|
||||
}
|
||||
|
||||
$productLowestPriceBeforeDiscountProcessor->process($firstChannelPricing)->shouldBeCalled();
|
||||
$productLowestPriceBeforeDiscountProcessor->process($secondChannelPricing)->shouldBeCalled();
|
||||
$productLowestPriceBeforeDiscountProcessor->process($thirdChannelPricing)->shouldBeCalled();
|
||||
$productLowestPriceBeforeDiscountProcessor->process($fourthChannelPricing)->shouldBeCalled();
|
||||
$productLowestPriceBeforeDiscountProcessor->process($fifthChannelPricing)->shouldBeCalled();
|
||||
|
||||
$this->onChange($channel);
|
||||
}
|
||||
|
||||
function it_throws_an_exception_if_entity_is_not_channel(
|
||||
ProductLowestPriceBeforeDiscountProcessorInterface $productLowestPriceBeforeDiscountProcessor,
|
||||
RepositoryInterface $channelPricingRepository,
|
||||
OrderInterface $order,
|
||||
): void {
|
||||
$channelPricingRepository->findBy(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$productLowestPriceBeforeDiscountProcessor->process(Argument::any())->shouldNotBeCalled();
|
||||
$productLowestPriceBeforeDiscountProcessor->process(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->shouldThrow(\InvalidArgumentException::class)->during('onChange', [$order]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\Logger;
|
||||
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\Logger\PriceChangeLoggerInterface;
|
||||
use Sylius\Calendar\Provider\DateTimeProviderInterface;
|
||||
use Sylius\Component\Core\Factory\ChannelPricingLogEntryFactoryInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
|
||||
final class PriceChangeLoggerSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
ChannelPricingLogEntryFactoryInterface $logEntryFactory,
|
||||
ObjectManager $logEntryManager,
|
||||
DateTimeProviderInterface $dateTimeProvider,
|
||||
): void {
|
||||
$this->beConstructedWith($logEntryFactory, $logEntryManager, $dateTimeProvider);
|
||||
}
|
||||
|
||||
function it_implements_price_change_logger_interface(): void
|
||||
{
|
||||
$this->shouldImplement(PriceChangeLoggerInterface::class);
|
||||
}
|
||||
|
||||
function it_throws_exception_when_there_is_no_price(
|
||||
ChannelPricingLogEntryFactoryInterface $logEntryFactory,
|
||||
ObjectManager $logEntryManager,
|
||||
DateTimeProviderInterface $dateTimeProvider,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
): void {
|
||||
$channelPricing->getPrice()->willReturn(null);
|
||||
|
||||
$dateTimeProvider->now()->shouldNotBeCalled();
|
||||
$logEntryFactory->create(Argument::cetera())->shouldNotBeCalled();
|
||||
$logEntryManager->persist(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$this->shouldThrow(\InvalidArgumentException::class)->during('log', [$channelPricing]);
|
||||
}
|
||||
|
||||
function it_logs_price_change(
|
||||
ChannelPricingLogEntryFactoryInterface $logEntryFactory,
|
||||
ObjectManager $logEntryManager,
|
||||
DateTimeProviderInterface $dateTimeProvider,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
ChannelPricingLogEntryInterface $logEntry,
|
||||
): void {
|
||||
$date = new \DateTimeImmutable();
|
||||
$price = 1000;
|
||||
$originalPrice = 1200;
|
||||
|
||||
$channelPricing->getPrice()->willReturn($price);
|
||||
$channelPricing->getOriginalPrice()->willReturn($originalPrice);
|
||||
|
||||
$dateTimeProvider->now()->willReturn($date);
|
||||
|
||||
$logEntryFactory
|
||||
->create($channelPricing, $date, $price, $originalPrice)
|
||||
->shouldBeCalled()
|
||||
->willReturn($logEntry)
|
||||
;
|
||||
|
||||
$logEntryManager->persist($logEntry)->shouldBeCalled();
|
||||
|
||||
$this->log($channelPricing);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\CoreBundle\PriceHistory\Processor;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Prophecy\Argument;
|
||||
use Sylius\Bundle\CoreBundle\PriceHistory\Processor\ProductLowestPriceBeforeDiscountProcessorInterface;
|
||||
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
|
||||
use Sylius\Component\Core\Model\ChannelInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntry;
|
||||
use Sylius\Component\Core\Repository\ChannelPricingLogEntryRepositoryInterface;
|
||||
|
||||
final class ProductLowestPriceBeforeDiscountProcessorSpec extends ObjectBehavior
|
||||
{
|
||||
function let(
|
||||
ChannelPricingLogEntryRepositoryInterface $channelPricingLogEntryRepository,
|
||||
ChannelRepositoryInterface $channelRepository,
|
||||
): void {
|
||||
$this->beConstructedWith($channelPricingLogEntryRepository, $channelRepository);
|
||||
}
|
||||
|
||||
function it_implements_product_lowest_price_processor_interface(): void
|
||||
{
|
||||
$this->shouldImplement(ProductLowestPriceBeforeDiscountProcessorInterface::class);
|
||||
}
|
||||
|
||||
function it_sets_lowest_price_before_discount_to_null_if_original_price_is_null(
|
||||
ChannelPricingLogEntryRepositoryInterface $channelPricingLogEntryRepository,
|
||||
ChannelRepositoryInterface $channelRepository,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
): void {
|
||||
$channelPricing->getOriginalPrice()->willReturn(null);
|
||||
$channelPricing->getPrice()->willReturn(2100);
|
||||
|
||||
$channelRepository->findOneByCode(Argument::any())->shouldNotBeCalled();
|
||||
$channelPricingLogEntryRepository->findLatestOneByChannelPricing($channelPricing)->shouldNotBeCalled();
|
||||
$channelPricingLogEntryRepository->findLowestPriceInPeriod(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$channelPricing->getChannelCode()->shouldNotBeCalled();
|
||||
$channelPricing->setLowestPriceBeforeDiscount(null)->shouldBeCalled();
|
||||
|
||||
$this->process($channelPricing);
|
||||
}
|
||||
|
||||
function it_sets_lowest_price_before_discount_to_null_if_price_is_equal_original_price(
|
||||
ChannelPricingLogEntryRepositoryInterface $channelPricingLogEntryRepository,
|
||||
ChannelRepositoryInterface $channelRepository,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
): void {
|
||||
$channelPricing->getOriginalPrice()->willReturn(2100);
|
||||
$channelPricing->getPrice()->willReturn(2100);
|
||||
|
||||
$channelRepository->findOneByCode(Argument::any())->shouldNotBeCalled();
|
||||
$channelPricingLogEntryRepository->findLatestOneByChannelPricing($channelPricing)->shouldNotBeCalled();
|
||||
$channelPricingLogEntryRepository->findLowestPriceInPeriod(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$channelPricing->getChannelCode()->shouldNotBeCalled();
|
||||
$channelPricing->setLowestPriceBeforeDiscount(null)->shouldBeCalled();
|
||||
|
||||
$this->process($channelPricing);
|
||||
}
|
||||
|
||||
function it_sets_lowest_price_before_discount_to_null_if_price_is_greater_than_original_price(
|
||||
ChannelPricingLogEntryRepositoryInterface $channelPricingLogEntryRepository,
|
||||
ChannelRepositoryInterface $channelRepository,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
): void {
|
||||
$channelPricing->getOriginalPrice()->willReturn(2100);
|
||||
$channelPricing->getPrice()->willReturn(3700);
|
||||
|
||||
$channelRepository->findOneByCode(Argument::any())->shouldNotBeCalled();
|
||||
$channelPricingLogEntryRepository->findLatestOneByChannelPricing($channelPricing)->shouldNotBeCalled();
|
||||
$channelPricingLogEntryRepository->findLowestPriceInPeriod(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$channelPricing->getChannelCode()->shouldNotBeCalled();
|
||||
$channelPricing->setLowestPriceBeforeDiscount(null)->shouldBeCalled();
|
||||
|
||||
$this->process($channelPricing);
|
||||
}
|
||||
|
||||
function it_sets_lowest_price_before_discount_to_null_if_there_is_no_log_entries(
|
||||
ChannelPricingLogEntryRepositoryInterface $channelPricingLogEntryRepository,
|
||||
ChannelRepositoryInterface $channelRepository,
|
||||
ChannelInterface $channel,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
): void {
|
||||
$channelPricing->getOriginalPrice()->willReturn(3700);
|
||||
$channelPricing->getPrice()->willReturn(2100);
|
||||
$channelPricing->getChannelCode()->willReturn('WEB');
|
||||
$channel->getLowestPriceForDiscountedProductsCheckingPeriod()->shouldNotBeCalled();
|
||||
|
||||
$channelRepository->findOneByCode('WEB')->willReturn($channel);
|
||||
$channelPricingLogEntryRepository->findLatestOneByChannelPricing($channelPricing)->willReturn(null);
|
||||
$channelPricingLogEntryRepository->findLowestPriceInPeriod(Argument::cetera())->shouldNotBeCalled();
|
||||
|
||||
$channelPricing->setLowestPriceBeforeDiscount(null)->shouldBeCalled();
|
||||
|
||||
$this->process($channelPricing);
|
||||
}
|
||||
|
||||
function it_sets_lowest_price_before_discount_to_lowest_price_found_in_the_given_period_if_price_is_less_than_original_price(
|
||||
ChannelPricingLogEntryRepositoryInterface $channelPricingLogEntryRepository,
|
||||
ChannelRepositoryInterface $channelRepository,
|
||||
ChannelInterface $channel,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
ChannelPricingLogEntry $latestLogEntry,
|
||||
): void {
|
||||
$channelPricing->getOriginalPrice()->willReturn(3700);
|
||||
$channelPricing->getPrice()->willReturn(2100);
|
||||
$channelPricing->getChannelCode()->willReturn('WEB');
|
||||
|
||||
$channelRepository->findOneByCode('WEB')->willReturn($channel);
|
||||
$channel->getLowestPriceForDiscountedProductsCheckingPeriod()->willReturn(30);
|
||||
|
||||
$unformattedDate = new \DateTimeImmutable();
|
||||
$latestLogEntry->getLoggedAt()->willReturn($unformattedDate);
|
||||
$loggedAt = new \DateTimeImmutable($unformattedDate->format('Y-m-d H:i:s'));
|
||||
$startDate = $loggedAt->sub(new \DateInterval(sprintf('P%dD', 30)));
|
||||
|
||||
$latestLogEntry->getChannelPricing()->willReturn($channelPricing);
|
||||
$latestLogEntry->getLoggedAt()->willReturn($loggedAt);
|
||||
$latestLogEntry->getId()->willReturn(1234);
|
||||
|
||||
$channelPricingLogEntryRepository->findLatestOneByChannelPricing($channelPricing)->willReturn($latestLogEntry);
|
||||
$channelPricingLogEntryRepository
|
||||
->findLowestPriceInPeriod(1234, $channelPricing, $startDate)
|
||||
->willReturn(6900)
|
||||
;
|
||||
|
||||
$channelPricing->setLowestPriceBeforeDiscount(6900)->shouldBeCalled();
|
||||
|
||||
$this->process($channelPricing);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Component\Core\Factory;
|
||||
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
use Sylius\Component\Resource\Exception\UnsupportedMethodException;
|
||||
|
||||
final class ChannelPricingLogEntryFactory implements ChannelPricingLogEntryFactoryInterface
|
||||
{
|
||||
public function __construct(private string $className)
|
||||
{
|
||||
if (!is_a($className, ChannelPricingLogEntryInterface::class, true)) {
|
||||
throw new \DomainException(sprintf(
|
||||
'This factory requires %s or its descend to be used as resource',
|
||||
ChannelPricingLogEntryInterface::class,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UnsupportedMethodException
|
||||
*/
|
||||
public function createNew(): object
|
||||
{
|
||||
throw new UnsupportedMethodException('createNew');
|
||||
}
|
||||
|
||||
public function create(
|
||||
ChannelPricingInterface $channelPricing,
|
||||
\DateTimeInterface $loggedAt,
|
||||
int $price,
|
||||
?int $originalPrice = null,
|
||||
): ChannelPricingLogEntryInterface {
|
||||
/** @var ChannelPricingLogEntryInterface $channelPricingLogEntry */
|
||||
$channelPricingLogEntry = new $this->className(
|
||||
$channelPricing,
|
||||
$loggedAt,
|
||||
$price,
|
||||
$originalPrice,
|
||||
);
|
||||
|
||||
return $channelPricingLogEntry;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Component\Core\Factory;
|
||||
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
use Sylius\Component\Resource\Factory\FactoryInterface;
|
||||
|
||||
interface ChannelPricingLogEntryFactoryInterface extends FactoryInterface
|
||||
{
|
||||
public function create(
|
||||
ChannelPricingInterface $channelPricing,
|
||||
\DateTimeInterface $loggedAt,
|
||||
int $price,
|
||||
?int $originalPrice = null,
|
||||
): ChannelPricingLogEntryInterface;
|
||||
}
|
||||
|
|
@ -82,6 +82,8 @@ class Channel extends BaseChannel implements ChannelInterface
|
|||
/** @var TaxonInterface|null */
|
||||
protected $menuTaxon;
|
||||
|
||||
protected int $lowestPriceForDiscountedProductsCheckingPeriod = 30;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
|
@ -295,4 +297,14 @@ class Channel extends BaseChannel implements ChannelInterface
|
|||
{
|
||||
$this->menuTaxon = $menuTaxon;
|
||||
}
|
||||
|
||||
public function getLowestPriceForDiscountedProductsCheckingPeriod(): int
|
||||
{
|
||||
return $this->lowestPriceForDiscountedProductsCheckingPeriod;
|
||||
}
|
||||
|
||||
public function setLowestPriceForDiscountedProductsCheckingPeriod(int $periodInDays): void
|
||||
{
|
||||
$this->lowestPriceForDiscountedProductsCheckingPeriod = $periodInDays;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,4 +91,8 @@ interface ChannelInterface extends
|
|||
public function removeCountry(CountryInterface $country): void;
|
||||
|
||||
public function hasCountry(CountryInterface $country): bool;
|
||||
|
||||
public function getLowestPriceForDiscountedProductsCheckingPeriod(): int;
|
||||
|
||||
public function setLowestPriceForDiscountedProductsCheckingPeriod(int $periodInDays): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class ChannelPricing implements ChannelPricingInterface, \Stringable
|
|||
/** @var int */
|
||||
protected $minimumPrice = 0;
|
||||
|
||||
/** @var int|null */
|
||||
protected $lowestPriceBeforeDiscount;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection
|
||||
*
|
||||
|
|
@ -98,6 +101,16 @@ class ChannelPricing implements ChannelPricingInterface, \Stringable
|
|||
$this->originalPrice = $originalPrice;
|
||||
}
|
||||
|
||||
public function getLowestPriceBeforeDiscount(): ?int
|
||||
{
|
||||
return $this->lowestPriceBeforeDiscount;
|
||||
}
|
||||
|
||||
public function setLowestPriceBeforeDiscount(?int $lowestPriceBeforeDiscount): void
|
||||
{
|
||||
$this->lowestPriceBeforeDiscount = $lowestPriceBeforeDiscount;
|
||||
}
|
||||
|
||||
public function isPriceReduced(): bool
|
||||
{
|
||||
return $this->originalPrice > $this->price;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ interface ChannelPricingInterface extends ResourceInterface
|
|||
|
||||
public function setOriginalPrice(?int $originalPrice): void;
|
||||
|
||||
public function getLowestPriceBeforeDiscount(): ?int;
|
||||
|
||||
public function setLowestPriceBeforeDiscount(?int $lowestPriceBeforeDiscount): void;
|
||||
|
||||
public function getMinimumPrice(): int;
|
||||
|
||||
public function setMinimumPrice(int $minimumPrice): void;
|
||||
|
|
|
|||
56
src/Sylius/Component/Core/Model/ChannelPricingLogEntry.php
Normal file
56
src/Sylius/Component/Core/Model/ChannelPricingLogEntry.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Component\Core\Model;
|
||||
|
||||
class ChannelPricingLogEntry implements ChannelPricingLogEntryInterface
|
||||
{
|
||||
/** @var mixed|null */
|
||||
protected $id;
|
||||
|
||||
public function __construct(
|
||||
protected ChannelPricingInterface $channelPricing,
|
||||
protected \DateTimeInterface $loggedAt,
|
||||
protected int $price,
|
||||
protected ?int $originalPrice,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress MissingReturnType
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getChannelPricing(): ChannelPricingInterface
|
||||
{
|
||||
return $this->channelPricing;
|
||||
}
|
||||
|
||||
public function getPrice(): int
|
||||
{
|
||||
return $this->price;
|
||||
}
|
||||
|
||||
public function getOriginalPrice(): ?int
|
||||
{
|
||||
return $this->originalPrice;
|
||||
}
|
||||
|
||||
public function getLoggedAt(): \DateTimeInterface
|
||||
{
|
||||
return $this->loggedAt;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Component\Core\Model;
|
||||
|
||||
use Sylius\Component\Resource\Model\ResourceInterface;
|
||||
|
||||
interface ChannelPricingLogEntryInterface extends ResourceInterface
|
||||
{
|
||||
public function getChannelPricing(): ChannelPricingInterface;
|
||||
|
||||
public function getPrice(): int;
|
||||
|
||||
public function getOriginalPrice(): ?int;
|
||||
|
||||
public function getLoggedAt(): \DateTimeInterface;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Component\Core\Repository;
|
||||
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
|
||||
interface ChannelPricingLogEntryRepositoryInterface extends RepositoryInterface
|
||||
{
|
||||
public function createByChannelPricingIdListQueryBuilder(mixed $channelPricingId): QueryBuilder;
|
||||
|
||||
public function findLatestOneByChannelPricing(ChannelPricingInterface $channelPricing): ?ChannelPricingLogEntryInterface;
|
||||
|
||||
public function findLowestPriceInPeriod(
|
||||
int $latestChannelPricingLogEntryId,
|
||||
ChannelPricingInterface $channelPricing,
|
||||
\DateTimeInterface $startDate,
|
||||
): ?int;
|
||||
|
||||
/**
|
||||
* @return array|ChannelPricingLogEntryInterface[]
|
||||
*/
|
||||
public function findOlderThan(\DateTimeInterface $date, ?int $limit = null): array;
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Component\Core\Factory;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntry;
|
||||
use Sylius\Component\Resource\Exception\UnsupportedMethodException;
|
||||
use Sylius\Component\Resource\Model\ResourceInterface;
|
||||
|
||||
final class ChannelPricingLogEntryFactorySpec extends ObjectBehavior
|
||||
{
|
||||
function let(): void
|
||||
{
|
||||
$this->beConstructedWith(ChannelPricingLogEntry::class);
|
||||
}
|
||||
|
||||
function it_throws_an_exception_when_invalid_class_name_is_passed(): void
|
||||
{
|
||||
$this->beConstructedWith(ResourceInterface::class);
|
||||
|
||||
$this->shouldThrow(\DomainException::class)->duringInstantiation();
|
||||
}
|
||||
|
||||
function it_throws_an_exception_when_create_new_is_called(): void
|
||||
{
|
||||
$this->shouldThrow(UnsupportedMethodException::class)->during('createNew');
|
||||
}
|
||||
|
||||
function it_creates_a_channel_pricing_log_entry(ChannelPricingInterface $channelPricing): void
|
||||
{
|
||||
$date = new \DateTimeImmutable();
|
||||
$price = 1000;
|
||||
$originalPrice = 2000;
|
||||
|
||||
$this
|
||||
->create($channelPricing, $date, $price, $originalPrice)
|
||||
->shouldBeLike(new ChannelPricingLogEntry(
|
||||
$channelPricing->getWrappedObject(),
|
||||
$date,
|
||||
$price,
|
||||
$originalPrice,
|
||||
))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Component\Core\Model;
|
||||
|
||||
use PhpSpec\ObjectBehavior;
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
|
||||
final class ChannelPricingLogEntrySpec extends ObjectBehavior
|
||||
{
|
||||
function let(ChannelPricingInterface $channelPricing): void
|
||||
{
|
||||
$this->beConstructedWith($channelPricing, new \DateTime(), 1000, 2000);
|
||||
}
|
||||
|
||||
function it_implements_channel_pricing_log_entry_interface(): void
|
||||
{
|
||||
$this->shouldImplement(ChannelPricingLogEntryInterface::class);
|
||||
}
|
||||
|
||||
function it_initialize_with_no_original_price(ChannelPricingInterface $channelPricing): void
|
||||
{
|
||||
$this->beConstructedWith($channelPricing, new \DateTime(), 1000, null);
|
||||
$this->getOriginalPrice()->shouldReturn(null);
|
||||
}
|
||||
|
||||
function it_gets_a_channel_pricing(): void
|
||||
{
|
||||
$this->getChannelPricing()->shouldReturnAnInstanceOf(ChannelPricingInterface::class);
|
||||
}
|
||||
|
||||
function it_gets_a_price(): void
|
||||
{
|
||||
$this->getPrice()->shouldReturn(1000);
|
||||
}
|
||||
|
||||
function it_gets_an_original_price(): void
|
||||
{
|
||||
$this->getOriginalPrice()->shouldReturn(2000);
|
||||
}
|
||||
|
||||
function it_gets_a_logged_at(): void
|
||||
{
|
||||
$this->getLoggedAt()->shouldReturnAnInstanceOf(\DateTimeInterface::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,12 @@ final class ChannelPricingSpec extends ObjectBehavior
|
|||
$this->getOriginalPrice()->shouldReturn(2000);
|
||||
}
|
||||
|
||||
function its_lowest_price_before_discount_is_mutable(): void
|
||||
{
|
||||
$this->setLowestPriceBeforeDiscount(2000);
|
||||
$this->getLowestPriceBeforeDiscount()->shouldReturn(2000);
|
||||
}
|
||||
|
||||
function its_price_can_be_reduced(): void
|
||||
{
|
||||
$this->setPrice(1000);
|
||||
|
|
|
|||
104
tests/Api/Admin/ChannelPricingLogEntryTest.php
Normal file
104
tests/Api/Admin/ChannelPricingLogEntryTest.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Sylius package.
|
||||
*
|
||||
* (c) Paweł Jędrzejewski
|
||||
*
|
||||
* 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\Tests\Api\Admin;
|
||||
|
||||
use Sylius\Component\Core\Model\ChannelPricingInterface;
|
||||
use Sylius\Component\Core\Model\ChannelPricingLogEntryInterface;
|
||||
use Sylius\Component\Resource\Repository\RepositoryInterface;
|
||||
use Sylius\Tests\Api\JsonApiTestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class ChannelPricingLogEntryTest extends JsonApiTestCase
|
||||
{
|
||||
/** @test */
|
||||
public function it_denies_access_to_a_channel_pricing_log_entries_for_not_authenticated_user(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['product/product_variant_with_lowest_price.yaml']);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: '/api/v2/admin/channel-pricing-log-entries',
|
||||
server: $this->getUnloggedHeader(),
|
||||
);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertSame(Response::HTTP_UNAUTHORIZED, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_single_channel_pricing_log_entry(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'product/product_variant_with_lowest_price.yaml']);
|
||||
|
||||
/** @var ChannelPricingInterface $channelPricing */
|
||||
$channelPricing = $fixtures['channel_pricing_product_variant_mug_blue_home'];
|
||||
|
||||
/** @var RepositoryInterface $channelPricingLogEntryRepository */
|
||||
$channelPricingLogEntryRepository = $this->getContainer()->get('sylius.repository.channel_pricing_log_entry');
|
||||
/** @var ChannelPricingLogEntryInterface $firstLogEntry */
|
||||
$channelPricingLogEntry = $channelPricingLogEntryRepository->findOneBy(['channelPricing' => $channelPricing]);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: sprintf('/api/v2/admin/channel-pricing-log-entries/%d', $channelPricingLogEntry->getId()),
|
||||
server: $this->getLoggedHeader(),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/get_channel_pricing_log_entry_response',
|
||||
Response::HTTP_OK
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_all_channel_pricing_log_entries(): void
|
||||
{
|
||||
$this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'product/product_variant_with_lowest_price.yaml']);
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: '/api/v2/admin/channel-pricing-log-entries',
|
||||
server: $this->getLoggedHeader(),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/get_channel_pricing_log_entries_response',
|
||||
Response::HTTP_OK
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_filtered_channel_pricing_log_entries(): void
|
||||
{
|
||||
$fixtures = $this->loadFixturesFromFiles(['authentication/api_administrator.yaml', 'product/product_variant_with_lowest_price.yaml']);
|
||||
|
||||
$uri = '/api/v2/admin/channel-pricing-log-entries';
|
||||
$uri .= '?channelPricing.channelCode=' . $fixtures['channel_home']->getCode();
|
||||
$uri .= '&channelPricing.productVariant.code=' . $fixtures['product_variant_mug_blue']->getCode();
|
||||
|
||||
$this->client->request(
|
||||
method: 'GET',
|
||||
uri: $uri,
|
||||
server: $this->getLoggedHeader(),
|
||||
);
|
||||
|
||||
$this->assertResponse(
|
||||
$this->client->getResponse(),
|
||||
'admin/get_filtered_channel_pricing_log_entries_response',
|
||||
Response::HTTP_OK
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
Sylius\Component\Core\Model\Channel:
|
||||
channel_home:
|
||||
code: 'HOME'
|
||||
name: 'Home Channel'
|
||||
hostname: 'home.localhost'
|
||||
description: 'Lorem ipsum'
|
||||
baseCurrency: '@currency_usd'
|
||||
defaultLocale: '@locale_en'
|
||||
locales: ['@locale_en', '@locale_pl']
|
||||
color: 'black'
|
||||
enabled: true
|
||||
taxCalculationStrategy: 'order_items_based'
|
||||
channel_fashion:
|
||||
code: 'FASHION'
|
||||
name: 'Fashion Channel'
|
||||
hostname: 'fashion.localhost'
|
||||
description: 'Lorem ipsum'
|
||||
baseCurrency: '@currency_usd'
|
||||
defaultLocale: '@locale_en'
|
||||
locales: ['@locale_en', '@locale_pl']
|
||||
color: 'black'
|
||||
enabled: true
|
||||
taxCalculationStrategy: 'order_items_based'
|
||||
|
||||
Sylius\Component\Currency\Model\Currency:
|
||||
currency_usd:
|
||||
code: 'USD'
|
||||
|
||||
Sylius\Component\Locale\Model\Locale:
|
||||
locale_en:
|
||||
code: 'en_US'
|
||||
locale_pl:
|
||||
code: 'pl_PL'
|
||||
locale_de:
|
||||
code: 'de_DE'
|
||||
|
||||
Sylius\Component\Core\Model\Product:
|
||||
product_mug:
|
||||
code: 'MUG'
|
||||
channels: ['@channel_home', '@channel_fashion']
|
||||
currentLocale: 'en_US'
|
||||
translations:
|
||||
en_US: '@product_translation_mug'
|
||||
|
||||
Sylius\Component\Core\Model\ProductTranslation:
|
||||
product_translation_mug:
|
||||
slug: 'mug'
|
||||
locale: 'en_US'
|
||||
name: 'Mug'
|
||||
description: '<paragraph(2)>'
|
||||
translatable: '@product_mug'
|
||||
|
||||
Sylius\Component\Core\Model\ProductVariant:
|
||||
product_variant_mug_blue:
|
||||
code: 'MUG_BLUE'
|
||||
product: '@product_mug'
|
||||
currentLocale: 'en_US'
|
||||
translations:
|
||||
en_US: '@product_variant_translation_mug_blue'
|
||||
channelPricings:
|
||||
HOME: '@channel_pricing_product_variant_mug_blue_home'
|
||||
FASHION: '@channel_pricing_product_variant_mug_blue_fashion'
|
||||
|
||||
product_variant_mug_red:
|
||||
code: 'MUG_RED'
|
||||
product: '@product_mug'
|
||||
currentLocale: 'en_US'
|
||||
translations:
|
||||
en_US: '@product_variant_translation_mug_red'
|
||||
channelPricings:
|
||||
HOME: '@channel_pricing_product_variant_mug_red_home'
|
||||
FASHION: '@channel_pricing_product_variant_mug_red_fashion'
|
||||
|
||||
Sylius\Component\Product\Model\ProductVariantTranslation:
|
||||
product_variant_translation_mug_blue:
|
||||
locale: 'en_US'
|
||||
name: 'Blue Mug'
|
||||
translatable: '@product_variant_mug_blue'
|
||||
product_variant_translation_mug_red:
|
||||
locale: 'en_US'
|
||||
name: 'Red Mug'
|
||||
translatable: '@product_variant_mug_red'
|
||||
|
||||
Sylius\Component\Core\Model\ChannelPricing:
|
||||
channel_pricing_product_variant_mug_blue_home:
|
||||
channelCode: 'HOME'
|
||||
price: 1000
|
||||
originalPrice: 2000
|
||||
lowestPriceBeforeDiscount: 2100
|
||||
channel_pricing_product_variant_mug_blue_fashion:
|
||||
channelCode: 'FASHION'
|
||||
price: 2000
|
||||
channel_pricing_product_variant_mug_red_home:
|
||||
channelCode: 'HOME'
|
||||
price: 3000
|
||||
channel_pricing_product_variant_mug_red_fashion:
|
||||
channelCode: 'FASHION'
|
||||
price: 3000
|
||||
|
|
@ -14,9 +14,12 @@ declare(strict_types=1);
|
|||
namespace Sylius\Tests\Api;
|
||||
|
||||
use ApiTestCase\JsonApiTestCase as BaseJsonApiTestCase;
|
||||
use Sylius\Tests\Api\Utils\AdminUserLoginTrait;
|
||||
|
||||
abstract class JsonApiTestCase extends BaseJsonApiTestCase
|
||||
{
|
||||
use AdminUserLoginTrait;
|
||||
|
||||
public const CONTENT_TYPE_HEADER = ['CONTENT_TYPE' => 'application/ld+json', 'HTTP_ACCEPT' => 'application/ld+json'];
|
||||
|
||||
public const PATCH_CONTENT_TYPE_HEADER = ['CONTENT_TYPE' => 'application/merge-patch+json', 'HTTP_ACCEPT' => 'application/ld+json'];
|
||||
|
|
@ -37,4 +40,14 @@ abstract class JsonApiTestCase extends BaseJsonApiTestCase
|
|||
|
||||
return parent::get($id);
|
||||
}
|
||||
|
||||
protected function getUnloggedHeader(): array
|
||||
{
|
||||
return self::CONTENT_TYPE_HEADER;
|
||||
}
|
||||
|
||||
protected function getLoggedHeader(): array
|
||||
{
|
||||
return array_merge($this->logInAdminUser('api@example.com'), self::CONTENT_TYPE_HEADER);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ChannelPricingLogEntry",
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries",
|
||||
"@type": "hydra:Collection",
|
||||
"hydra:member": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries\/@integer@",
|
||||
"@type": "ChannelPricingLogEntry",
|
||||
"channelPricing": "\/api\/v2\/admin\/channel-pricings\/@integer@",
|
||||
"price": 3000,
|
||||
"originalPrice": null,
|
||||
"loggedAt": "@date@"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries\/@integer@",
|
||||
"@type": "ChannelPricingLogEntry",
|
||||
"channelPricing": "\/api\/v2\/admin\/channel-pricings\/@integer@",
|
||||
"price": 1000,
|
||||
"originalPrice": 2000,
|
||||
"loggedAt": "@date@"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries\/@integer@",
|
||||
"@type": "ChannelPricingLogEntry",
|
||||
"channelPricing": "\/api\/v2\/admin\/channel-pricings\/@integer@",
|
||||
"price": 3000,
|
||||
"originalPrice": null,
|
||||
"loggedAt": "@date@"
|
||||
},
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries\/@integer@",
|
||||
"@type": "ChannelPricingLogEntry",
|
||||
"channelPricing": "\/api\/v2\/admin\/channel-pricings\/@integer@",
|
||||
"price": 2000,
|
||||
"originalPrice": null,
|
||||
"loggedAt": "@date@"
|
||||
}
|
||||
],
|
||||
"hydra:totalItems": 4,
|
||||
"hydra:search": {
|
||||
"@type": "hydra:IriTemplate",
|
||||
"hydra:template": "\/api\/v2\/admin\/channel-pricing-log-entries{?channelPricing.channelCode,channelPricing.channelCode[],channelPricing.productVariant.code,channelPricing.productVariant.code[]}",
|
||||
"hydra:variableRepresentation": "BasicRepresentation",
|
||||
"hydra:mapping": [
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.channelCode",
|
||||
"property": "channelPricing.channelCode",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.channelCode[]",
|
||||
"property": "channelPricing.channelCode",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.productVariant.code",
|
||||
"property": "channelPricing.productVariant.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.productVariant.code[]",
|
||||
"property": "channelPricing.productVariant.code",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ChannelPricingLogEntry",
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries/@integer@",
|
||||
"@type": "ChannelPricingLogEntry",
|
||||
"channelPricing": "\/api\/v2\/admin\/channel-pricings\/@integer@",
|
||||
"price": 1000,
|
||||
"originalPrice": 2000,
|
||||
"loggedAt": "@date@"
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"@context": "\/api\/v2\/contexts\/ChannelPricingLogEntry",
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries",
|
||||
"@type": "hydra:Collection",
|
||||
"hydra:member": [
|
||||
{
|
||||
"@id": "\/api\/v2\/admin\/channel-pricing-log-entries\/@integer@",
|
||||
"@type": "ChannelPricingLogEntry",
|
||||
"channelPricing": "\/api\/v2\/admin\/channel-pricings\/@integer@",
|
||||
"price": 1000,
|
||||
"originalPrice": 2000,
|
||||
"loggedAt": "@date@"
|
||||
}
|
||||
],
|
||||
"hydra:totalItems": 1,
|
||||
"hydra:view": {
|
||||
"@id": @string@,
|
||||
"@type": "hydra:PartialCollectionView"
|
||||
},
|
||||
"hydra:search": {
|
||||
"@type": "hydra:IriTemplate",
|
||||
"hydra:template": "\/api\/v2\/admin\/channel-pricing-log-entries{?channelPricing.channelCode,channelPricing.channelCode[],channelPricing.productVariant.code,channelPricing.productVariant.code[]}",
|
||||
"hydra:variableRepresentation": "BasicRepresentation",
|
||||
"hydra:mapping": [
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.channelCode",
|
||||
"property": "channelPricing.channelCode",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.channelCode[]",
|
||||
"property": "channelPricing.channelCode",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.productVariant.code",
|
||||
"property": "channelPricing.productVariant.code",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"@type": "IriTemplateMapping",
|
||||
"variable": "channelPricing.productVariant.code[]",
|
||||
"property": "channelPricing.productVariant.code",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue