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 981b7ae + 9280eda commit d49d1ae
Show file tree
Hide file tree
Showing 95 changed files with 1,445 additions and 356 deletions.
4 changes: 0 additions & 4 deletions app/config/graphql/types/ModelType/Product/Product.types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ Product:
type:
type: "String"
defaultValue: null
price:
type: "ProductPrice!"
description: "Product price"
resolve: '@=query("priceByProductQuery", value)'
accessories:
type: "[Product!]!"
parameters:
Expand Down
8 changes: 8 additions & 0 deletions app/config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ security:
- ROLE_COMPLAINT_STATUS_FULL
- ROLE_INQUIRY_VIEW
- ROLE_WATCHDOG_FULL
- ROLE_PRICE_LIST_FULL
ROLE_ALL_VIEW:
- ROLE_ORDER_VIEW
- ROLE_CUSTOMER_VIEW
Expand Down Expand Up @@ -117,6 +118,7 @@ security:
- ROLE_COMPLAINT_STATUS_VIEW
- ROLE_INQUIRY_VIEW
- ROLE_WATCHDOG_VIEW
- ROLE_PRICE_LIST_FULL
ROLE_ORDER_FULL: [ROLE_ORDER_VIEW]
ROLE_CUSTOMER_FULL: [ROLE_CUSTOMER_VIEW]
ROLE_NEWSLETTER_FULL: [ROLE_NEWSLETTER_VIEW]
Expand Down Expand Up @@ -168,6 +170,7 @@ security:
ROLE_COMPLAINT_FULL: [ROLE_COMPLAINT_VIEW]
ROLE_COMPLAINT_STATUS_FULL: [ROLE_COMPLAINT_STATUS_VIEW]
ROLE_WATCHDOG_FULL: [ROLE_WATCHDOG_VIEW]
ROLE_PRICE_LIST_FULL: [ROLE_PRICE_LIST_VIEW]
# FE API ROLES
ROLE_API_ALL:
- ROLE_API_CUSTOMER_SELF_MANAGE
Expand Down Expand Up @@ -308,6 +311,11 @@ security:
- { path: ^/%admin_url%/pricing/group/delete, roles: ROLE_PRICING_GROUP_FULL }
- { path: ^/%admin_url%/pricing/group/list, roles: ROLE_PRICING_GROUP_FULL, methods: [POST] }
- { path: ^/%admin_url%/pricing/group/, roles: ROLE_PRICING_GROUP_VIEW }
# price lists
- { path: ^/%admin_url%/pricing/price-list/new, roles: ROLE_PRICE_LIST_FULL }
- { path: ^/%admin_url%/pricing/price-list/edit, roles: ROLE_PRICE_LIST_FULL, methods: [ POST ] }
- { path: ^/%admin_url%/pricing/price-list/delete, roles: ROLE_PRICE_LIST_FULL }
- { path: ^/%admin_url%/pricing/price-list/, roles: ROLE_PRICE_LIST_VIEW }
# vats
- { path: ^/%admin_url%/vat/delete, roles: ROLE_VAT_FULL }
- { path: ^/%admin_url%/vat/list, roles: ROLE_VAT_FULL, methods: [POST] }
Expand Down
3 changes: 2 additions & 1 deletion app/config/packages/twig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ twig:
debug: "%kernel.debug%"
exception_controller: null
form_themes:
- '@ShopsysFramework/Admin/Form/Product/Price/priceListOverviewType.html.twig'
- '@ShopsysFramework/Admin/Form/Product/Price/pricesWithCalculatedSellingPricesType.html.twig'
- '@ShopsysFramework/Admin/Form/Product/Price/productPricesWithVatSelectType.html.twig'
- '@ShopsysFramework/Admin/Form/abstractFileuploadFields.html.twig'
Expand Down Expand Up @@ -35,6 +36,7 @@ twig:
- '@ShopsysFramework/Admin/Form/parameterValueConversionList.html.twig'
- '@ShopsysFramework/Admin/Form/paymentTransactionsFormTheme.html.twig'
- '@ShopsysFramework/Admin/Form/priceandvattableByDomainsFields.html.twig'
- '@ShopsysFramework/Admin/Form/priceListProductsPickerType.html.twig'
- '@ShopsysFramework/Admin/Form/productParameterValue.html.twig'
- '@ShopsysFramework/Admin/Form/promoCodeFlagFormTheme.html.twig'
- '@ShopsysFramework/Admin/Form/promoCodeLimitFormTheme.html.twig'
Expand All @@ -44,7 +46,6 @@ twig:
- 'Admin/Form/languageConstantFormTheme.html.twig'
- 'Admin/Form/productDetailFormTheme.html.twig'
- 'Admin/Form/productVideoValue.html.twig'
- 'Admin/Form/productsFormTheme.html.twig'
globals:
globalMultipleFormSetting: '@Shopsys\FrameworkBundle\Component\Form\MultipleFormSetting'
usersnapApiKey: '%env(USERSNAP_PROJECT_API_KEY)%'
Expand Down
2 changes: 1 addition & 1 deletion app/docker/elasticsearch/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
FROM docker.elastic.co/elasticsearch/elasticsearch:7.16.1

# install ICU Analysis plugin to sort by language properly
RUN bin/elasticsearch-plugin install analysis-icu
6 changes: 6 additions & 0 deletions app/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1926,8 +1926,14 @@ enum ProductOrderingModeEnum {

"Represents the price of the product"
type ProductPrice {
"Basic price of the product. If product has no discounts, it's the same as the selling price"
basicPrice: Price!
"Determines whether it's a final price or starting price"
isPriceFrom: Boolean!
"Date of the next price change, null if no price change is planned"
nextPriceChange: DateTime
"Percentage discount on the selling price compared to the base price, null if no discount is applied"
percentageDiscount: Float
"Price without VAT"
priceWithoutVat: Money!
"Price with VAT"
Expand Down
140 changes: 140 additions & 0 deletions app/src/DataFixtures/Demo/PriceListDataFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

declare(strict_types=1);

namespace App\DataFixtures\Demo;

use DateTimeImmutable;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use Shopsys\FrameworkBundle\Component\DataFixture\AbstractReferenceFixture;
use Shopsys\FrameworkBundle\Component\Money\Money;
use Shopsys\FrameworkBundle\Component\Translation\Translator;
use Shopsys\FrameworkBundle\Model\PriceList\PriceListDataFactory;
use Shopsys\FrameworkBundle\Model\PriceList\PriceListFacade;
use Shopsys\FrameworkBundle\Model\PriceList\PriceListProductPriceData;
use Shopsys\FrameworkBundle\Model\PriceList\PriceListProductPriceDataFactory;
use Shopsys\FrameworkBundle\Model\Pricing\Currency\Currency;
use Shopsys\FrameworkBundle\Model\Pricing\PriceConverter;
use Shopsys\FrameworkBundle\Model\Pricing\Vat\Vat;
use Shopsys\FrameworkBundle\Model\Product\Product;

class PriceListDataFixture extends AbstractReferenceFixture implements DependentFixtureInterface
{
public const string ACTIVE_SPECIAL_OFFERS_REFERENCE = 'special_offers';
public const string EXPIRED_BLUE_WEDNESDAY_REFERENCE = 'blue_wednesday';
public const string ACTIVE_ITEMS_ON_SALE_REFERENCE = 'items_on_sale';
public const string FUTURE_PROMOTED_PRODUCTS_REFERENCE = 'promoted_products';

/**
* @param \Shopsys\FrameworkBundle\Model\PriceList\PriceListFacade $priceListFacade
* @param \Shopsys\FrameworkBundle\Model\PriceList\PriceListDataFactory $priceListDataFactory
* @param \Shopsys\FrameworkBundle\Model\PriceList\PriceListProductPriceDataFactory $priceListProductPriceDataFactory
* @param \Shopsys\FrameworkBundle\Model\Pricing\PriceConverter $priceConverter
*/
public function __construct(
private readonly PriceListFacade $priceListFacade,
private readonly PriceListDataFactory $priceListDataFactory,
private readonly PriceListProductPriceDataFactory $priceListProductPriceDataFactory,
private readonly PriceConverter $priceConverter,
) {
}

/**
* @param \Doctrine\Persistence\ObjectManager $manager
*/
public function load(ObjectManager $manager): void
{
$currencyCzk = $this->getReference(CurrencyDataFixture::CURRENCY_CZK, Currency::class);

foreach ($this->domainsForDataFixtureProvider->getAllowedDemoDataDomains() as $domainConfig) {
$vat = $this->getReferenceForDomain(VatDataFixture::VAT_HIGH, $domainConfig->getId(), Vat::class);

$priceListData = $this->priceListDataFactory->create();
$priceListData->name = t('Special offers', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainConfig->getLocale());
$priceListData->domainId = $domainConfig->getId();
$priceListData->validFrom = new DateTimeImmutable('2023-01-10 08:30:00');
$priceListData->validTo = new DateTimeImmutable('2084-01-10 08:30:00');
$priceListData->priceListProductPricesData = [
$this->createPriceListProductPriceData('27', '42', $domainConfig->getId(), $currencyCzk, $vat),
$this->createPriceListProductPriceData('28', '50', $domainConfig->getId(), $currencyCzk, $vat),
];
$priceList = $this->priceListFacade->create($priceListData);
$this->addReferenceForDomain(self::ACTIVE_SPECIAL_OFFERS_REFERENCE, $priceList, $domainConfig->getId());

$priceListData = $this->priceListDataFactory->create();
$priceListData->name = t('Blue wednesday', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainConfig->getLocale());
$priceListData->domainId = $domainConfig->getId();
$priceListData->validFrom = new DateTimeImmutable('2023-11-10 00:00:00');
$priceListData->validTo = new DateTimeImmutable('2023-11-10 23:59:59');
$priceListData->priceListProductPricesData = [
$this->createPriceListProductPriceData('1', '2800', $domainConfig->getId(), $currencyCzk, $vat),
$this->createPriceListProductPriceData('72', '90', $domainConfig->getId(), $currencyCzk, $vat),
];
$priceList = $this->priceListFacade->create($priceListData);
$this->addReferenceForDomain(self::EXPIRED_BLUE_WEDNESDAY_REFERENCE, $priceList, $domainConfig->getId());

$priceListData = $this->priceListDataFactory->create();
$priceListData->name = t('Items on sale', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainConfig->getLocale());
$priceListData->domainId = $domainConfig->getId();
$priceListData->validFrom = new DateTimeImmutable('2023-02-12 06:20:00');
$priceListData->validTo = new DateTimeImmutable('2084-05-10 08:30:00');
$priceListData->priceListProductPricesData = [
$this->createPriceListProductPriceData('117', '290', $domainConfig->getId(), $currencyCzk, $vat),
$this->createPriceListProductPriceData('19', '170', $domainConfig->getId(), $currencyCzk, $vat),
];
$priceList = $this->priceListFacade->create($priceListData);
$this->addReferenceForDomain(self::ACTIVE_ITEMS_ON_SALE_REFERENCE, $priceList, $domainConfig->getId());

$priceListData = $this->priceListDataFactory->create();
$priceListData->name = t('Promoted products', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainConfig->getLocale());
$priceListData->domainId = $domainConfig->getId();
$priceListData->validFrom = new DateTimeImmutable('2083-10-15 00:20:00');
$priceListData->validTo = new DateTimeImmutable('2084-10-15 06:30:00');
$priceListData->priceListProductPricesData = [
$this->createPriceListProductPriceData('145', '800', $domainConfig->getId(), $currencyCzk, $vat),
$this->createPriceListProductPriceData('120', '160', $domainConfig->getId(), $currencyCzk, $vat),
];
$priceList = $this->priceListFacade->create($priceListData);
$this->addReferenceForDomain(self::FUTURE_PROMOTED_PRODUCTS_REFERENCE, $priceList, $domainConfig->getId());
}
}

/**
* @param string $productId
* @param string $priceValue
* @param int $domainId
* @param \Shopsys\FrameworkBundle\Model\Pricing\Currency\Currency $currency
* @param \Shopsys\FrameworkBundle\Model\Pricing\Vat\Vat $vat
* @return \Shopsys\FrameworkBundle\Model\PriceList\PriceListProductPriceData
*/
private function createPriceListProductPriceData(
string $productId,
string $priceValue,
int $domainId,
Currency $currency,
Vat $vat,
): PriceListProductPriceData {
$product = $this->getReference(ProductDataFixture::PRODUCT_PREFIX . $productId, Product::class);
$priceAmount = $this->priceConverter->convertPriceToInputPriceWithoutVatInDomainDefaultCurrency(
Money::create($priceValue),
$currency,
$vat->getPercent(),
$domainId,
);

return $this->priceListProductPriceDataFactory->create($product, $priceAmount, $domainId);
}

/**
* @return string[]
*/
public function getDependencies(): array
{
return [
CurrencyDataFixture::class,
ProductDataFixture::class,
VatDataFixture::class,
];
}
}
Loading

0 comments on commit d49d1ae

Please sign in to comment.