Skip to content

Commit

Permalink
added time-limited price lists with product special prices (#3628)
Browse files Browse the repository at this point in the history
  • Loading branch information
grossmannmartin authored Jan 17, 2025
2 parents 07aa08d + f23f4c9 commit 53be8f7
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 70 deletions.
36 changes: 36 additions & 0 deletions src/Model/Price/PriceInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Price;

use DateTimeInterface;
use Shopsys\FrameworkBundle\Component\Money\Money;
use Shopsys\FrameworkBundle\Model\Pricing\Price;

class PriceInfo
{
public Money $priceWithoutVat;

public Money $priceWithVat;

public Money $vatAmount;

public ?DateTimeInterface $nextPriceChange = null;

public ?float $percentageDiscount = null;

public Price $basicPrice;

public bool $isPriceFrom;

/**
* @param \Shopsys\FrameworkBundle\Model\Pricing\Price $price
*/
public function setSellingPrice(Price $price): void
{
$this->priceWithoutVat = $price->getPriceWithoutVat();
$this->priceWithVat = $price->getPriceWithVat();
$this->vatAmount = $price->getVatAmount();
}
}
99 changes: 99 additions & 0 deletions src/Model/Price/PriceInfoFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Price;

use DateTimeImmutable;
use DateTimeInterface;
use LogicException;
use Shopsys\FrameworkBundle\Component\Money\Money;
use Shopsys\FrameworkBundle\Model\Pricing\SpecialPrice\SpecialPrice;
use Shopsys\FrameworkBundle\Model\Product\Pricing\ProductPrice;

class PriceInfoFactory
{
/**
* @param \Shopsys\FrameworkBundle\Model\Product\Pricing\ProductPrice $basicProductPrice
* @param \Shopsys\FrameworkBundle\Model\Pricing\SpecialPrice\SpecialPrice|null $specialPrice
* @return \Shopsys\FrontendApiBundle\Model\Price\PriceInfo
*/
public function create(
ProductPrice $basicProductPrice,
?SpecialPrice $specialPrice,
): PriceInfo {
$priceInfo = new PriceInfo();
$priceInfo->basicPrice = $basicProductPrice;
$priceInfo->isPriceFrom = $basicProductPrice->isPriceFrom();

if ($specialPrice === null) {
$priceInfo->setSellingPrice($basicProductPrice);

return $priceInfo;
}

$priceInfo->nextPriceChange = $this->determineNextPriceChange($specialPrice);

if (!$specialPrice->isFuturePrice()) {
$priceInfo->setSellingPrice($specialPrice->price);
$priceInfo->percentageDiscount = $this->calculatePercentageDiscount($basicProductPrice->getPriceWithVat(), $specialPrice->price->getPriceWithVat());
} else {
$priceInfo->setSellingPrice($basicProductPrice);
}

return $priceInfo;
}

/**
* @return \Shopsys\FrontendApiBundle\Model\Price\PriceInfo
*/
public function createHiddenPriceInfo(): PriceInfo
{
return $this->create(
ProductPrice::createHiddenProductPrice(),
null,
);
}

/**
* @param \Shopsys\FrameworkBundle\Model\Pricing\SpecialPrice\SpecialPrice $specialPrice
* @return \DateTimeInterface
*/
protected function determineNextPriceChange(SpecialPrice $specialPrice): DateTimeInterface
{
$now = new DateTimeImmutable();

$futureDates = [];

if ($specialPrice->validFrom > $now) {
$futureDates[] = $specialPrice->validFrom;
}

if ($specialPrice->validTo > $now) {
$futureDates[] = $specialPrice->validTo;
}

if (count($futureDates) === 0) {
throw new LogicException('Special price was selected, but the validity is in the past. Check the implementation of the special price selection algorithm.');
}

return min($futureDates);
}

/**
* @param \Shopsys\FrameworkBundle\Component\Money\Money $basicPriceWithVat
* @param \Shopsys\FrameworkBundle\Component\Money\Money $specialPriceWithVat
* @return float
*/
protected function calculatePercentageDiscount(
Money $basicPriceWithVat,
Money $specialPriceWithVat,
): float {
$floatDiscount = $basicPriceWithVat
->subtract($specialPriceWithVat)
->divide($basicPriceWithVat->getAmount(), 6)
->multiply(100);

return max(1, floor((float)$floatDiscount->getAmount()));
}
}
44 changes: 0 additions & 44 deletions src/Model/Resolver/Price/PriceQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,32 @@
use Shopsys\FrameworkBundle\Model\Payment\PaymentPriceProvider;
use Shopsys\FrameworkBundle\Model\Pricing\Currency\CurrencyFacade;
use Shopsys\FrameworkBundle\Model\Pricing\Price;
use Shopsys\FrameworkBundle\Model\Product\Pricing\ProductPrice;
use Shopsys\FrameworkBundle\Model\Product\Product;
use Shopsys\FrameworkBundle\Model\Product\ProductCachedAttributesFacade;
use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum;
use Shopsys\FrameworkBundle\Model\Transport\Transport;
use Shopsys\FrameworkBundle\Model\Transport\TransportPriceCalculation;
use Shopsys\FrameworkBundle\Model\Transport\TransportPriceProvider;
use Shopsys\FrontendApiBundle\Component\GqlContext\GqlContextHelper;
use Shopsys\FrontendApiBundle\Model\Cart\CartApiFacade;
use Shopsys\FrontendApiBundle\Model\Order\OrderApiFacade;
use Shopsys\FrontendApiBundle\Model\Price\PriceFacade;
use Shopsys\FrontendApiBundle\Model\Resolver\AbstractQuery;
use Shopsys\FrontendApiBundle\Model\Resolver\Price\Exception\ProductPriceMissingUserError;

class PriceQuery extends AbstractQuery
{
/**
* @param \Shopsys\FrameworkBundle\Model\Product\ProductCachedAttributesFacade $productCachedAttributesFacade
* @param \Shopsys\FrameworkBundle\Model\Payment\PaymentPriceCalculation $paymentPriceCalculation
* @param \Shopsys\FrameworkBundle\Component\Domain\Domain $domain
* @param \Shopsys\FrameworkBundle\Model\Pricing\Currency\CurrencyFacade $currencyFacade
* @param \Shopsys\FrameworkBundle\Model\Transport\TransportPriceCalculation $transportPriceCalculation
* @param \Shopsys\FrontendApiBundle\Model\Price\PriceFacade $priceFacade
* @param \Shopsys\FrameworkBundle\Model\Customer\User\CurrentCustomerUser $currentCustomerUser
* @param \Shopsys\FrontendApiBundle\Model\Cart\CartApiFacade $cartApiFacade
* @param \Shopsys\FrontendApiBundle\Model\Order\OrderApiFacade $orderApiFacade
* @param \Shopsys\FrameworkBundle\Model\Transport\TransportPriceProvider $transportPriceProvider
* @param \Shopsys\FrameworkBundle\Model\Payment\PaymentPriceProvider $paymentPriceProvider
*/
public function __construct(
protected readonly ProductCachedAttributesFacade $productCachedAttributesFacade,
protected readonly PaymentPriceCalculation $paymentPriceCalculation,
protected readonly Domain $domain,
protected readonly CurrencyFacade $currencyFacade,
protected readonly TransportPriceCalculation $transportPriceCalculation,
protected readonly PriceFacade $priceFacade,
protected readonly CurrentCustomerUser $currentCustomerUser,
protected readonly CartApiFacade $cartApiFacade,
protected readonly OrderApiFacade $orderApiFacade,
Expand All @@ -56,40 +46,6 @@ public function __construct(
) {
}

/**
* @param \Shopsys\FrameworkBundle\Model\Product\Product|array $data
* @return \Shopsys\FrameworkBundle\Model\Product\Pricing\ProductPrice
*/
public function priceByProductQuery(Product|array $data): ProductPrice
{
if ($this->isProductUponInquiry($data)) {
return ProductPrice::createHiddenProductPrice();
}

if ($data instanceof Product) {
$productPrice = $this->productCachedAttributesFacade->getProductSellingPrice($data);
} else {
$productPrice = $this->priceFacade->createProductPriceFromArrayForCurrentCustomer($data['prices']);
}

if ($productPrice === null) {
throw new ProductPriceMissingUserError('The product price is not set.');
}

return $productPrice;
}

/**
* @param \Shopsys\FrameworkBundle\Model\Product\Product|array $data
* @return bool
*/
protected function isProductUponInquiry(Product|array $data): bool
{
$productType = $data instanceof Product ? $data->getProductType() : $data['product_type'];

return $productType === ProductTypeEnum::TYPE_INQUIRY;
}

/**
* @param \Shopsys\FrameworkBundle\Model\Payment\Payment $payment
* @param string|null $cartUuid
Expand Down
77 changes: 77 additions & 0 deletions src/Model/Resolver/Price/ProductPriceQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Shopsys\FrontendApiBundle\Model\Resolver\Price;

use Shopsys\FrameworkBundle\Component\Domain\Domain;
use Shopsys\FrameworkBundle\Model\Pricing\SpecialPrice\SpecialPriceFacade;
use Shopsys\FrameworkBundle\Model\Product\Product;
use Shopsys\FrameworkBundle\Model\Product\ProductCachedAttributesFacade;
use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum;
use Shopsys\FrontendApiBundle\Model\Price\PriceFacade;
use Shopsys\FrontendApiBundle\Model\Price\PriceInfo;
use Shopsys\FrontendApiBundle\Model\Price\PriceInfoFactory;
use Shopsys\FrontendApiBundle\Model\Resolver\AbstractQuery;
use Shopsys\FrontendApiBundle\Model\Resolver\Price\Exception\ProductPriceMissingUserError;

class ProductPriceQuery extends AbstractQuery
{
/**
* @param \Shopsys\FrameworkBundle\Model\Pricing\SpecialPrice\SpecialPriceFacade $specialPriceFacade
* @param \Shopsys\FrontendApiBundle\Model\Resolver\Price\SpecialPriceApiFactory $specialPriceApiFactory
* @param \Shopsys\FrameworkBundle\Component\Domain\Domain $domain
* @param \Shopsys\FrontendApiBundle\Model\Price\PriceFacade $priceFacade
* @param \Shopsys\FrameworkBundle\Model\Product\ProductCachedAttributesFacade $productCachedAttributesFacade
* @param \Shopsys\FrontendApiBundle\Model\Price\PriceInfoFactory $priceInfoFactory
*/
public function __construct(
protected readonly SpecialPriceFacade $specialPriceFacade,
protected readonly SpecialPriceApiFactory $specialPriceApiFactory,
protected readonly Domain $domain,
protected readonly PriceFacade $priceFacade,
protected readonly ProductCachedAttributesFacade $productCachedAttributesFacade,
protected readonly PriceInfoFactory $priceInfoFactory,
) {
}

/**
* @param array|\Shopsys\FrameworkBundle\Model\Product\Product $data
* @return \Shopsys\FrontendApiBundle\Model\Price\PriceInfo
*/
public function priceByProductQuery(Product|array $data): PriceInfo
{
if ($this->isProductUponInquiry($data)) {
return $this->priceInfoFactory->createHiddenPriceInfo();
}

if ($data instanceof Product) {
$basicProductPrice = $this->productCachedAttributesFacade->getProductBasicPrice($data);

if ($basicProductPrice === null) {
throw new ProductPriceMissingUserError('The product price is not set.');
}

$specialPrice = $this->specialPriceFacade->findRelevantSpecialPrice($data, $this->domain->getId(), $basicProductPrice);
} else {
$basicProductPrice = $this->priceFacade->createProductPriceFromArrayForCurrentCustomer($data['prices']);
$specialPrice = $this->specialPriceApiFactory->createSpecialPriceFromArray($data, $basicProductPrice);
}

return $this->priceInfoFactory->create(
$basicProductPrice,
$specialPrice,
);
}

/**
* @param \Shopsys\FrameworkBundle\Model\Product\Product|array $data
* @return bool
*/
protected function isProductUponInquiry(Product|array $data): bool
{
$productType = $data instanceof Product ? $data->getProductType() : $data['product_type'];

return $productType === ProductTypeEnum::TYPE_INQUIRY;
}
}
25 changes: 0 additions & 25 deletions src/Model/Resolver/Price/ProductPriceResolverMap.php

This file was deleted.

Loading

0 comments on commit 53be8f7

Please sign in to comment.