From 14e6538695c80f461a478ab8ef43c7238b2afb25 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Tue, 24 Sep 2024 11:24:48 +0200 Subject: [PATCH 01/24] added product type --- src/Migrations/Version20240924082209.php | 28 ++++++++++++++++++++++++ src/Model/Product/Product.php | 15 +++++++++++++ src/Model/Product/ProductData.php | 6 +++++ src/Model/Product/ProductDataFactory.php | 1 + src/Model/Product/ProductTypeEnum.php | 14 ++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 src/Migrations/Version20240924082209.php create mode 100644 src/Model/Product/ProductTypeEnum.php diff --git a/src/Migrations/Version20240924082209.php b/src/Migrations/Version20240924082209.php new file mode 100644 index 0000000000..4106f16ec9 --- /dev/null +++ b/src/Migrations/Version20240924082209.php @@ -0,0 +1,28 @@ +sql('ALTER TABLE products ADD product_type VARCHAR(32) DEFAULT NULL'); + $this->sql('UPDATE products SET product_type = \'basic\''); + $this->sql('ALTER TABLE products ALTER product_type SET NOT NULL'); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + */ + public function down(Schema $schema): void + { + } +} diff --git a/src/Model/Product/Product.php b/src/Model/Product/Product.php index 7ea7acbd7c..8334aac13d 100644 --- a/src/Model/Product/Product.php +++ b/src/Model/Product/Product.php @@ -171,6 +171,12 @@ class Product extends AbstractTranslatableEntity */ protected $excludedTransports; + /** + * @var string + * @ORM\Column(type="string", length=32, nullable=false) + */ + protected $productType; + /** * @param \Shopsys\FrameworkBundle\Model\Product\ProductData $productData * @param \Shopsys\FrameworkBundle\Model\Product\Product[]|null $variants @@ -234,6 +240,7 @@ protected function setData(ProductData $productData): void $this->brand = $productData->brand; $this->unit = $productData->unit; $this->weight = $productData->weight; + $this->productType = $productData->productType; $this->setTranslations($productData); $this->setExcludedTransports($productData->excludedTransports); } @@ -749,6 +756,14 @@ public function getSaleExclusion(int $domainId) return $this->getProductDomain($domainId)->getSaleExclusion(); } + /** + * @return string + */ + public function getProductType() + { + return $this->productType; + } + /** * @return \Shopsys\FrameworkBundle\Model\Product\ProductTranslation */ diff --git a/src/Model/Product/ProductData.php b/src/Model/Product/ProductData.php index f30a4dc657..16f182439e 100644 --- a/src/Model/Product/ProductData.php +++ b/src/Model/Product/ProductData.php @@ -198,6 +198,11 @@ class ProductData */ public $excludedTransports; + /** + * @var string|null + */ + public $productType; + public function __construct() { $this->name = []; @@ -227,5 +232,6 @@ public function __construct() $this->saleExclusion = []; $this->domainHidden = []; $this->excludedTransports = []; + $this->productType = ProductTypeEnum::TYPE_BASIC; } } diff --git a/src/Model/Product/ProductDataFactory.php b/src/Model/Product/ProductDataFactory.php index 2de27aaf37..8aaa10980a 100644 --- a/src/Model/Product/ProductDataFactory.php +++ b/src/Model/Product/ProductDataFactory.php @@ -184,6 +184,7 @@ protected function fillFromProduct(ProductData $productData, Product $product): $productData->weight = $product->getWeight(); $productData->files = $this->uploadedFileDataFactory->createByEntity($product); $productData->excludedTransports = $product->getExcludedTransports(); + $productData->productType = $product->getProductType(); $this->fillProductStockByProduct($productData, $product); } diff --git a/src/Model/Product/ProductTypeEnum.php b/src/Model/Product/ProductTypeEnum.php new file mode 100644 index 0000000000..cc31a59b4b --- /dev/null +++ b/src/Model/Product/ProductTypeEnum.php @@ -0,0 +1,14 @@ + Date: Tue, 24 Sep 2024 13:36:11 +0200 Subject: [PATCH 02/24] product type can now be set in admin --- src/Form/Admin/Product/ProductFormType.php | 13 ++++++++++++- src/Model/Product/ProductTypeEnum.php | 11 +++++++++++ src/Resources/translations/messages.cs.po | 6 ++++++ src/Resources/translations/messages.en.po | 6 ++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/Form/Admin/Product/ProductFormType.php b/src/Form/Admin/Product/ProductFormType.php index 92270b4ca4..134e33261c 100644 --- a/src/Form/Admin/Product/ProductFormType.php +++ b/src/Form/Admin/Product/ProductFormType.php @@ -36,6 +36,7 @@ use Shopsys\FrameworkBundle\Model\Product\Product; use Shopsys\FrameworkBundle\Model\Product\ProductData; use Shopsys\FrameworkBundle\Model\Product\ProductFacade; +use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum; use Shopsys\FrameworkBundle\Model\Product\Unit\UnitFacade; use Shopsys\FrameworkBundle\Model\Seo\SeoSettingFacade; use Shopsys\FrameworkBundle\Model\Transport\TransportFacade; @@ -69,6 +70,7 @@ class ProductFormType extends AbstractType * @param \Shopsys\FrameworkBundle\Model\Product\ProductFacade $productFacade * @param \Shopsys\FrameworkBundle\Model\Transport\TransportFacade $transportFacade * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $urlGenerator + * @param \Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum $productTypeEnum */ public function __construct( private readonly BrandFacade $brandFacade, @@ -83,6 +85,7 @@ public function __construct( private readonly ProductFacade $productFacade, private readonly TransportFacade $transportFacade, private readonly UrlGeneratorInterface $urlGenerator, + private readonly ProductTypeEnum $productTypeEnum, ) { } @@ -167,6 +170,14 @@ private function createBasicInformationGroup( 'label' => t('Basic information'), ]); + if (!$this->isProductMainVariant($product)) { + $builderBasicInformationGroup->add('productType', ChoiceType::class, [ + 'required' => true, + 'choices' => $this->productTypeEnum->getAllIndexedByTranslations(), + 'label' => t('Product type'), + ]); + } + $builderBasicInformationGroup->add('catnum', TextType::class, [ 'required' => true, 'constraints' => [ @@ -747,7 +758,7 @@ private function createParametersGroup(FormBuilderInterface $builder): FormBuild 'error_bubbling' => false, 'render_form_row' => false, ]) - ->addModelTransformer($this->productParameterValueToProductParameterValuesLocalizedTransformer)); + ->addModelTransformer($this->productParameterValueToProductParameterValuesLocalizedTransformer)); return $builderParametersGroup; } diff --git a/src/Model/Product/ProductTypeEnum.php b/src/Model/Product/ProductTypeEnum.php index cc31a59b4b..7822563b13 100644 --- a/src/Model/Product/ProductTypeEnum.php +++ b/src/Model/Product/ProductTypeEnum.php @@ -11,4 +11,15 @@ class ProductTypeEnum extends AbstractEnum public const string TYPE_BASIC = 'basic'; public const string TYPE_INQUIRY = 'inquiry'; + + /** + * @return array + */ + public function getAllIndexedByTranslations(): array + { + return [ + t('Basic') => self::TYPE_BASIC, + t('Upon inquiry') => self::TYPE_INQUIRY, + ]; + } } diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index eb9a73e068..331469b994 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -3130,6 +3130,9 @@ msgstr "Zboží bylo uloženo do objednávky" msgid "Product settings on the main page successfully changed" msgstr "Nastavení zboží na titulce bylo úspěšně změněno." +msgid "Product type" +msgstr "Typ produktu" + msgid "Products" msgstr "Zboží" @@ -4042,6 +4045,9 @@ msgstr "Soubor {{ name }} byl upraven msgid "Uploading..." msgstr "Nahrávám..." +msgid "Upon inquiry" +msgstr "Na poptávku" + msgid "Url address" msgstr "Url adresa" diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index 74f724598c..845125c86b 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -3130,6 +3130,9 @@ msgstr "" msgid "Product settings on the main page successfully changed" msgstr "" +msgid "Product type" +msgstr "" + msgid "Products" msgstr "" @@ -4042,6 +4045,9 @@ msgstr "" msgid "Uploading..." msgstr "" +msgid "Upon inquiry" +msgstr "" + msgid "Url address" msgstr "" From 7bebf74aa72ce8e77c504bc341baceed8148268a Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Tue, 24 Sep 2024 15:31:48 +0200 Subject: [PATCH 03/24] fixed creating new urls on product rename --- src/Model/Product/ProductFacade.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Model/Product/ProductFacade.php b/src/Model/Product/ProductFacade.php index 44abf64864..bbc85082c3 100644 --- a/src/Model/Product/ProductFacade.php +++ b/src/Model/Product/ProductFacade.php @@ -149,6 +149,7 @@ public function edit( string $priority = ProductRecalculationPriorityEnum::REGULAR, ): Product { $product = $this->productRepository->getById($productId); + $originalNames = $product->getFullnames(); $productCategoryDomains = $this->productCategoryDomainFactory->createMultiple( $product, @@ -174,7 +175,7 @@ public function edit( $this->uploadedFileFacade->manageFiles($product, $productData->files); $this->friendlyUrlFacade->saveUrlListFormData('front_product_detail', $product->getId(), $productData->urls); - $this->createFriendlyUrlsWhenRenamed($product, $product->getFullnames()); + $this->createFriendlyUrlsWhenRenamed($product, $originalNames); $this->pluginCrudExtensionFacade->saveAllData('product', $product->getId(), $productData->pluginData); From df4ca523c5296d8bdf4ea974b7cb07493a0a4d11 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 25 Sep 2024 09:01:41 +0200 Subject: [PATCH 04/24] inquiry product type is now listed in catalog regardless the price and stock --- src/Model/Product/Elasticsearch/ProductExportRepository.php | 1 + .../Elasticsearch/Scope/ProductExportFieldProvider.php | 1 + src/Model/Product/ProductSellingDeniedRecalculator.php | 4 ++++ src/Model/Product/ProductVisibilityRepository.php | 4 ++++ src/Model/Product/Search/ProductElasticsearchConverter.php | 3 +++ .../Product/Search/ProductElasticsearchConverterTest.php | 2 ++ 6 files changed, 15 insertions(+) diff --git a/src/Model/Product/Elasticsearch/ProductExportRepository.php b/src/Model/Product/Elasticsearch/ProductExportRepository.php index d3da67583e..ca7770c58f 100644 --- a/src/Model/Product/Elasticsearch/ProductExportRepository.php +++ b/src/Model/Product/Elasticsearch/ProductExportRepository.php @@ -185,6 +185,7 @@ protected function getExportedFieldValue(int $domainId, Product $product, string ProductExportFieldProvider::SEO_META_DESCRIPTION => $product->getSeoMetaDescription($domainId), ProductExportFieldProvider::ACCESSORIES => $this->extractAccessoriesIds($product), ProductExportFieldProvider::HREFLANG_LINKS => $this->hreflangLinksFacade->getForProduct($product, $domainId), + ProductExportFieldProvider::PRODUCT_TYPE => $product->getProductType(), default => throw new InvalidArgumentException(sprintf('There is no definition for exporting "%s" field to Elasticsearch', $field)), }; } diff --git a/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php b/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php index 95cf3ca246..e75f992756 100644 --- a/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php +++ b/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php @@ -43,6 +43,7 @@ class ProductExportFieldProvider public const string SEO_META_DESCRIPTION = 'seo_meta_description'; public const string ACCESSORIES = 'accessories'; public const string HREFLANG_LINKS = 'hreflang_links'; + public const string PRODUCT_TYPE = 'product_type'; /** * @return string[] diff --git a/src/Model/Product/ProductSellingDeniedRecalculator.php b/src/Model/Product/ProductSellingDeniedRecalculator.php index 3e069afa3d..ff8e4766ab 100644 --- a/src/Model/Product/ProductSellingDeniedRecalculator.php +++ b/src/Model/Product/ProductSellingDeniedRecalculator.php @@ -75,6 +75,8 @@ protected function calculatePerDomain(array $productIds): void pd.domain_hidden = TRUE OR ( p.variant_type != :variantTypeMain + AND + p.product_type != :inquiryProductType AND NOT EXISTS( SELECT 1 @@ -102,6 +104,7 @@ protected function calculatePerDomain(array $productIds): void $params = []; $params['productIds'] = $productIds; $params['variantTypeMain'] = Product::VARIANT_TYPE_MAIN; + $params['inquiryProductType'] = ProductTypeEnum::TYPE_INQUIRY; foreach ($this->domain->getAll() as $domain) { $params['domainId'] = $domain->getId(); @@ -112,6 +115,7 @@ protected function calculatePerDomain(array $productIds): void [ 'productIds' => ArrayParameterType::INTEGER, 'variantTypeMain' => Types::STRING, + 'inquiryProductType' => Types::STRING, 'domainId' => Types::INTEGER, ], ); diff --git a/src/Model/Product/ProductVisibilityRepository.php b/src/Model/Product/ProductVisibilityRepository.php index e57d47865d..8c73622a48 100644 --- a/src/Model/Product/ProductVisibilityRepository.php +++ b/src/Model/Product/ProductVisibilityRepository.php @@ -139,6 +139,7 @@ protected function calculateIndependentVisibility(?array $productIds) 'domainId' => null, 'pricingGroupId' => null, 'variantTypeMain' => Product::VARIANT_TYPE_MAIN, + 'inquiryProductType' => ProductTypeEnum::TYPE_INQUIRY, ]; $variableTypes = [ @@ -147,6 +148,7 @@ protected function calculateIndependentVisibility(?array $productIds) 'domainId' => Types::INTEGER, 'pricingGroupId' => Types::INTEGER, 'variantTypeMain' => Types::STRING, + 'inquiryProductType' => Types::STRING, ]; if ($productIds !== null) { @@ -169,6 +171,8 @@ protected function calculateIndependentVisibility(?array $productIds) ( p.variant_type = :variantTypeMain OR + p.product_type = :inquiryProductType + OR EXISTS ( SELECT 1 FROM product_manual_input_prices as pmip diff --git a/src/Model/Product/Search/ProductElasticsearchConverter.php b/src/Model/Product/Search/ProductElasticsearchConverter.php index d3b5afd53c..5747819c53 100644 --- a/src/Model/Product/Search/ProductElasticsearchConverter.php +++ b/src/Model/Product/Search/ProductElasticsearchConverter.php @@ -4,6 +4,8 @@ namespace Shopsys\FrameworkBundle\Model\Product\Search; +use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum; + class ProductElasticsearchConverter { /** @@ -52,6 +54,7 @@ public function fillEmptyFields(array $product): array $result['seo_title'] = $product['seo_title'] ?? null; $result['seo_meta_description'] = $product['seo_meta_description'] ?? null; $result['hreflang_links'] = $product['hreflang_links'] ?? []; + $result['product_type'] = $product['product_type'] ?? ProductTypeEnum::TYPE_BASIC; return $result; } diff --git a/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php b/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php index 3e75db429a..50532c3db3 100644 --- a/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php +++ b/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php @@ -53,6 +53,7 @@ public function testFillEmptyFields(): void 'seo_title' => null, 'seo_meta_description' => null, 'hreflang_links' => [], + 'product_type' => 'basic', ]; $converter = new ProductElasticsearchConverter(); @@ -118,6 +119,7 @@ public function testFillEmptyParameterFields(): void 'seo_title' => null, 'seo_meta_description' => null, 'hreflang_links' => [], + 'product_type' => 'basic', ]; $converter = new ProductElasticsearchConverter(); From 04c2d7fe3c13729ca2afddc41c6fbdad09192a80 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Thu, 26 Sep 2024 17:32:12 +0200 Subject: [PATCH 05/24] product upon inquiry now hides its price in graphql --- src/Component/Money/HiddenMoney.php | 158 +++++++++++++++++++++ src/Model/Pricing/Price.php | 12 ++ src/Model/Product/Pricing/ProductPrice.php | 11 ++ src/Twig/HiddenPriceExtension.php | 5 +- 4 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/Component/Money/HiddenMoney.php diff --git a/src/Component/Money/HiddenMoney.php b/src/Component/Money/HiddenMoney.php new file mode 100644 index 0000000000..2db55d0ac5 --- /dev/null +++ b/src/Component/Money/HiddenMoney.php @@ -0,0 +1,158 @@ +priceWithoutVat->isZero() && $this->priceWithVat->isZero(); } + + /** + * @return self + */ + public static function createHiddenPrice(): self + { + return new self( + new HiddenMoney(), + new HiddenMoney(), + ); + } } diff --git a/src/Model/Product/Pricing/ProductPrice.php b/src/Model/Product/Pricing/ProductPrice.php index cfcbc73826..d407d2ec1a 100644 --- a/src/Model/Product/Pricing/ProductPrice.php +++ b/src/Model/Product/Pricing/ProductPrice.php @@ -28,4 +28,15 @@ public function isPriceFrom() { return $this->priceFrom; } + + /** + * @return self + */ + public static function createHiddenProductPrice(): self + { + return new self( + self::createHiddenPrice(), + false, + ); + } } diff --git a/src/Twig/HiddenPriceExtension.php b/src/Twig/HiddenPriceExtension.php index 2eb0c8b2f5..1a68535616 100644 --- a/src/Twig/HiddenPriceExtension.php +++ b/src/Twig/HiddenPriceExtension.php @@ -4,6 +4,7 @@ namespace Shopsys\FrameworkBundle\Twig; +use Shopsys\FrameworkBundle\Component\Money\HiddenMoney; use Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser; use Shopsys\FrameworkBundle\Model\Customer\User\Role\CustomerUserRoleResolver; use Twig\Extension\AbstractExtension; @@ -11,8 +12,6 @@ class HiddenPriceExtension extends AbstractExtension { - protected const string HIDDEN_FORMAT = '***'; - /** * @param \Shopsys\FrameworkBundle\Model\Customer\User\Role\CustomerUserRoleResolver $customerUserRoleResolver */ @@ -42,7 +41,7 @@ public function getFilters(): array public function hidePriceFilter(string $price, ?CustomerUser $customerUser): string { if (!$this->customerUserRoleResolver->canCustomerUserSeePrices($customerUser)) { - return static::HIDDEN_FORMAT; + return HiddenMoney::HIDDEN_FORMAT; } return $price; From 3e21fcf9a3a837718f235a22d9588d94cca6bbb3 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Fri, 27 Sep 2024 12:29:13 +0200 Subject: [PATCH 06/24] added mutation to create inquiry --- src/Migrations/Version20240926162253.php | 48 ++++++++++ src/Model/Inquiry/Inquiry.php | 108 +++++++++++++++++++++++ src/Model/Inquiry/InquiryData.php | 53 +++++++++++ src/Model/Inquiry/InquiryDataFactory.php | 24 +++++ src/Model/Inquiry/InquiryFacade.php | 36 ++++++++ src/Model/Inquiry/InquiryFactory.php | 29 ++++++ src/Model/Inquiry/InquiryRepository.php | 27 ++++++ 7 files changed, 325 insertions(+) create mode 100644 src/Migrations/Version20240926162253.php create mode 100644 src/Model/Inquiry/Inquiry.php create mode 100644 src/Model/Inquiry/InquiryData.php create mode 100644 src/Model/Inquiry/InquiryDataFactory.php create mode 100644 src/Model/Inquiry/InquiryFacade.php create mode 100644 src/Model/Inquiry/InquiryFactory.php create mode 100644 src/Model/Inquiry/InquiryRepository.php diff --git a/src/Migrations/Version20240926162253.php b/src/Migrations/Version20240926162253.php new file mode 100644 index 0000000000..4e1c16247e --- /dev/null +++ b/src/Migrations/Version20240926162253.php @@ -0,0 +1,48 @@ +sql(' + CREATE TABLE inquiries ( + id SERIAL NOT NULL, + product_id INT DEFAULT NULL, + product_catnum VARCHAR(100) NOT NULL, + first_name VARCHAR(100) NOT NULL, + last_name VARCHAR(100) NOT NULL, + email VARCHAR(255) NOT NULL, + telephone VARCHAR(30) NOT NULL, + company_name VARCHAR(100) DEFAULT NULL, + company_number VARCHAR(50) DEFAULT NULL, + company_tax_number VARCHAR(50) DEFAULT NULL, + note TEXT DEFAULT NULL, + PRIMARY KEY(id) + )'); + $this->sql('CREATE INDEX IDX_1CCE4D54584665A ON inquiries (product_id)'); + $this->sql(' + ALTER TABLE + inquiries + ADD + CONSTRAINT FK_1CCE4D54584665A FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE + SET + NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + */ + public function down(Schema $schema): void + { + } +} diff --git a/src/Model/Inquiry/Inquiry.php b/src/Model/Inquiry/Inquiry.php new file mode 100644 index 0000000000..5071fb208d --- /dev/null +++ b/src/Model/Inquiry/Inquiry.php @@ -0,0 +1,108 @@ +setData($inquiryData); + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Inquiry\InquiryData $inquiryData + */ + protected function setData(InquiryData $inquiryData): void + { + $this->firstName = $inquiryData->firstName; + $this->lastName = $inquiryData->lastName; + $this->email = $inquiryData->email; + $this->telephone = $inquiryData->telephone; + $this->companyName = $inquiryData->companyName; + $this->companyNumber = $inquiryData->companyNumber; + $this->companyTaxNumber = $inquiryData->companyTaxNumber; + $this->note = $inquiryData->note; + $this->product = $inquiryData->product; + $this->productCatnum = $inquiryData->product->getCatnum(); + } +} diff --git a/src/Model/Inquiry/InquiryData.php b/src/Model/Inquiry/InquiryData.php new file mode 100644 index 0000000000..7148045faf --- /dev/null +++ b/src/Model/Inquiry/InquiryData.php @@ -0,0 +1,53 @@ +createInstance(); + } +} diff --git a/src/Model/Inquiry/InquiryFacade.php b/src/Model/Inquiry/InquiryFacade.php new file mode 100644 index 0000000000..d759804dce --- /dev/null +++ b/src/Model/Inquiry/InquiryFacade.php @@ -0,0 +1,36 @@ +inquiryFactory->create($inquiryData); + + $this->em->persist($inquiry); + $this->em->flush(); + + return $inquiry; + } +} diff --git a/src/Model/Inquiry/InquiryFactory.php b/src/Model/Inquiry/InquiryFactory.php new file mode 100644 index 0000000000..eaa48df7a3 --- /dev/null +++ b/src/Model/Inquiry/InquiryFactory.php @@ -0,0 +1,29 @@ +entityNameResolver->resolve(Inquiry::class); + + return new $entityClassName($inquiryData); + } +} diff --git a/src/Model/Inquiry/InquiryRepository.php b/src/Model/Inquiry/InquiryRepository.php new file mode 100644 index 0000000000..7d044cf031 --- /dev/null +++ b/src/Model/Inquiry/InquiryRepository.php @@ -0,0 +1,27 @@ +em->getRepository(Inquiry::class); + } +} From cd03dc62656be336f2093d45c1b0c9f3f80d73cd Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Fri, 27 Sep 2024 16:39:45 +0200 Subject: [PATCH 07/24] added inquiry overview in admin --- src/Controller/Admin/InquiryController.php | 50 ++++++++++++++++ src/Migrations/Version20240926162253.php | 3 + .../AdminNavigation/ConfigureMenuEvent.php | 1 + src/Model/AdminNavigation/SideMenuBuilder.php | 17 ++++++ src/Model/Inquiry/Inquiry.php | 9 +++ src/Model/Inquiry/InquiryData.php | 5 ++ src/Model/Inquiry/InquiryFacade.php | 32 +++++++++++ src/Model/Inquiry/InquiryGridFactory.php | 57 +++++++++++++++++++ src/Model/Inquiry/InquiryRepository.php | 20 +++++++ .../Security/MenuItemsGrantedRolesSetting.php | 4 ++ src/Model/Security/Roles.php | 5 ++ src/Resources/translations/messages.cs.po | 21 +++++++ src/Resources/translations/messages.en.po | 21 +++++++ .../Admin/Content/Inquiry/list.html.twig | 24 ++++++++ .../Admin/Content/Inquiry/listGrid.html.twig | 32 +++++++++++ .../Inquiry/quickSearchFormContent.html.twig | 12 ++++ 16 files changed, 313 insertions(+) create mode 100644 src/Controller/Admin/InquiryController.php create mode 100644 src/Model/Inquiry/InquiryGridFactory.php create mode 100644 src/Resources/views/Admin/Content/Inquiry/list.html.twig create mode 100644 src/Resources/views/Admin/Content/Inquiry/listGrid.html.twig create mode 100644 src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig diff --git a/src/Controller/Admin/InquiryController.php b/src/Controller/Admin/InquiryController.php new file mode 100644 index 0000000000..4c85002b95 --- /dev/null +++ b/src/Controller/Admin/InquiryController.php @@ -0,0 +1,50 @@ +createForm(QuickSearchFormType::class, new QuickSearchFormData()); + $quickSearchForm->handleRequest($request); + + $queryBuilder = $this->inquiryFacade->getInquiryListQueryBuilderByQuickSearchData( + $quickSearchForm->getData(), + $this->localization->getAdminLocale(), + ); + + return $this->render('@ShopsysFramework/Admin/Content/Inquiry/list.html.twig', [ + 'gridView' => $this->inquiryGridFactory->createView($queryBuilder, $this->getCurrentAdministrator()), + 'quickSearchForm' => $quickSearchForm->createView(), + ]); + } +} diff --git a/src/Migrations/Version20240926162253.php b/src/Migrations/Version20240926162253.php index 4e1c16247e..15771b18ba 100644 --- a/src/Migrations/Version20240926162253.php +++ b/src/Migrations/Version20240926162253.php @@ -27,6 +27,7 @@ public function up(Schema $schema): void company_number VARCHAR(50) DEFAULT NULL, company_tax_number VARCHAR(50) DEFAULT NULL, note TEXT DEFAULT NULL, + created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id) )'); $this->sql('CREATE INDEX IDX_1CCE4D54584665A ON inquiries (product_id)'); @@ -37,6 +38,8 @@ public function up(Schema $schema): void CONSTRAINT FK_1CCE4D54584665A FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + + $this->sql('COMMENT ON COLUMN inquiries.created_at IS \'(DC2Type:datetime_immutable)\''); } /** diff --git a/src/Model/AdminNavigation/ConfigureMenuEvent.php b/src/Model/AdminNavigation/ConfigureMenuEvent.php index 9a4e153816..450e450a9d 100644 --- a/src/Model/AdminNavigation/ConfigureMenuEvent.php +++ b/src/Model/AdminNavigation/ConfigureMenuEvent.php @@ -13,6 +13,7 @@ class ConfigureMenuEvent extends Event public const SIDE_MENU_ROOT = 'shopsys.admin_side_menu.configure_root'; public const SIDE_MENU_DASHBOARD = 'shopsys.admin_side_menu.configure_dashboard'; public const SIDE_MENU_ORDERS = 'shopsys.admin_side_menu.configure_orders'; + public const SIDE_MENU_INQUIRIES = 'shopsys.admin_side_menu.configure_inquiries'; public const SIDE_MENU_CUSTOMERS = 'shopsys.admin_side_menu.configure_customers'; public const SIDE_MENU_PRODUCTS = 'shopsys.admin_side_menu.configure_products'; public const SIDE_MENU_PRICING = 'shopsys.admin_side_menu.configure_pricing'; diff --git a/src/Model/AdminNavigation/SideMenuBuilder.php b/src/Model/AdminNavigation/SideMenuBuilder.php index 8ac8b87be0..21d950c468 100644 --- a/src/Model/AdminNavigation/SideMenuBuilder.php +++ b/src/Model/AdminNavigation/SideMenuBuilder.php @@ -36,6 +36,7 @@ public function createMenu(): ItemInterface $menu->addChild($this->createDashboardMenu()); $menu->addChild($this->createOrdersMenu()); + $menu->addChild($this->createInquiriesMenu()); $menu->addChild($this->createCustomersMenu()); $menu->addChild($this->createProductsMenu()); $menu->addChild($this->createPricingMenu()); @@ -101,6 +102,22 @@ protected function createOrdersMenu(): ItemInterface return $menu; } + /** + * @return \Knp\Menu\ItemInterface + */ + protected function createInquiriesMenu(): ItemInterface + { + $menu = $this->menuFactory->createItem('inquiries', [ + 'route' => 'admin_inquiry_list', + 'label' => t('Inquiries'), + ]); + $menu->setExtra('icon', 'letter'); + + $this->dispatchConfigureMenuEvent(ConfigureMenuEvent::SIDE_MENU_INQUIRIES, $menu); + + return $menu; + } + /** * @return \Knp\Menu\ItemInterface */ diff --git a/src/Model/Inquiry/Inquiry.php b/src/Model/Inquiry/Inquiry.php index 5071fb208d..bfa6a4abe0 100644 --- a/src/Model/Inquiry/Inquiry.php +++ b/src/Model/Inquiry/Inquiry.php @@ -4,6 +4,7 @@ namespace Shopsys\FrameworkBundle\Model\Inquiry; +use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; /** @@ -62,6 +63,12 @@ class Inquiry */ protected $companyTaxNumber; + /** + * @var \DateTimeImmutable + * @ORM\Column(type="datetime_immutable") + */ + protected $createdAt; + /** * @var string * @ORM\Column(type="text", nullable=true) @@ -86,6 +93,8 @@ class Inquiry */ public function __construct(InquiryData $inquiryData) { + $this->createdAt = $inquiryData->createdAt ?? new DateTimeImmutable(); + $this->setData($inquiryData); } diff --git a/src/Model/Inquiry/InquiryData.php b/src/Model/Inquiry/InquiryData.php index 7148045faf..e08506c741 100644 --- a/src/Model/Inquiry/InquiryData.php +++ b/src/Model/Inquiry/InquiryData.php @@ -50,4 +50,9 @@ class InquiryData * @var \Shopsys\FrameworkBundle\Model\Product\Product|null */ public $product; + + /** + * @var \DateTimeImmutable|null + */ + public $createdAt; } diff --git a/src/Model/Inquiry/InquiryFacade.php b/src/Model/Inquiry/InquiryFacade.php index d759804dce..b6fcaf1bd5 100644 --- a/src/Model/Inquiry/InquiryFacade.php +++ b/src/Model/Inquiry/InquiryFacade.php @@ -5,6 +5,9 @@ namespace Shopsys\FrameworkBundle\Model\Inquiry; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; +use Shopsys\FrameworkBundle\Component\String\DatabaseSearching; +use Shopsys\FrameworkBundle\Form\Admin\QuickSearch\QuickSearchFormData; class InquiryFacade { @@ -33,4 +36,33 @@ public function create(InquiryData $inquiryData): Inquiry return $inquiry; } + + /** + * @param \Shopsys\FrameworkBundle\Form\Admin\QuickSearch\QuickSearchFormData $quickSearchData + * @param string $locale + * @return \Doctrine\ORM\QueryBuilder + */ + public function getInquiryListQueryBuilderByQuickSearchData( + QuickSearchFormData $quickSearchData, + string $locale, + ): QueryBuilder { + $queryBuilder = $this->inquiryRepository->getInquiriesQueryBuilder($locale); + + if ($quickSearchData->text !== null && $quickSearchData->text !== '') { + $queryBuilder + ->andWhere('( + i.companyNumber LIKE :text + OR + NORMALIZED(i.lastName) LIKE NORMALIZED(:text) + OR + NORMALIZED(i.companyName) LIKE NORMALIZED(:text) + OR + NORMALIZED(i.email) LIKE NORMALIZED(:text) + )'); + $querySearchText = DatabaseSearching::getFullTextLikeSearchString($quickSearchData->text); + $queryBuilder->setParameter('text', $querySearchText); + } + + return $queryBuilder; + } } diff --git a/src/Model/Inquiry/InquiryGridFactory.php b/src/Model/Inquiry/InquiryGridFactory.php new file mode 100644 index 0000000000..e779f08762 --- /dev/null +++ b/src/Model/Inquiry/InquiryGridFactory.php @@ -0,0 +1,57 @@ +gridFactory->create('inquiryList', $dataSource); + + $grid->enablePaging(); + $grid->setDefaultOrder('createdAt', DataSourceInterface::ORDER_DESC); + + $grid->addColumn('productName', 'productName', t('Product name'), true); + $grid->addColumn('fullName', 'fullName', t('Full name'), true); + $grid->addColumn('email', 'i.email', t('Email'), true); + $grid->addColumn('telephone', 'i.telephone', t('Phone')); + $grid->addColumn('company', 'company', t('Company (Company number)'), true); + $grid->addColumn('createdAt', 'i.createdAt', t('Created'), true); + + + $grid->setTheme('@ShopsysFramework/Admin/Content/Inquiry/listGrid.html.twig'); + + $this->administratorGridFacade->restoreAndRememberGridLimit($administrator, $grid); + + return $grid->createView(); + } +} diff --git a/src/Model/Inquiry/InquiryRepository.php b/src/Model/Inquiry/InquiryRepository.php index 7d044cf031..2e432869df 100644 --- a/src/Model/Inquiry/InquiryRepository.php +++ b/src/Model/Inquiry/InquiryRepository.php @@ -6,6 +6,8 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\Query\Expr\Join; +use Doctrine\ORM\QueryBuilder; class InquiryRepository { @@ -24,4 +26,22 @@ protected function getInquiryRepository(): EntityRepository { return $this->em->getRepository(Inquiry::class); } + + /** + * @param string $locale + * @return \Doctrine\ORM\QueryBuilder + */ + public function getInquiriesQueryBuilder(string $locale): QueryBuilder + { + return $this->getInquiryRepository() + ->createQueryBuilder('i') + ->addSelect('IDENTITY(i.product) as productId') + ->addSelect('pt.name as productName') + ->addSelect('CONCAT(i.lastName, \' \', i.firstName) as fullName') + ->addSelect('CONCAT(i.companyName, \' (\', i.companyNumber, \')\') as company') + ->leftJoin('i.product', 'p') + ->leftJoin('p.translations', 'pt', Join::WITH, 'pt.locale = :locale') + ->setParameter('locale', $locale) + ->orderBy('i.createdAt', 'DESC'); + } } diff --git a/src/Model/Security/MenuItemsGrantedRolesSetting.php b/src/Model/Security/MenuItemsGrantedRolesSetting.php index 016df61f55..5f83ae37ac 100644 --- a/src/Model/Security/MenuItemsGrantedRolesSetting.php +++ b/src/Model/Security/MenuItemsGrantedRolesSetting.php @@ -28,6 +28,9 @@ protected function getPagesGrantedRoles(): array 'orders' => [ Roles::ROLE_ORDER_VIEW, ], + 'inquiries' => [ + Roles::ROLE_INQUIRY_VIEW, + ], 'customers' . static::MENU_ITEM_PATH_SEPARATOR . 'customers_overview' => [ Roles::ROLE_CUSTOMER_VIEW, ], @@ -185,6 +188,7 @@ protected function getPagesGrantedRoles(): array protected function getSectionsGrantedRoles(array $pagesGrantedRoles): array { $sectionsGrantedRoles = [ + 'inquiries' => [], 'customers' => [], 'products' => [], 'pricing' => [], diff --git a/src/Model/Security/Roles.php b/src/Model/Security/Roles.php index 568ec5908e..821bbe0887 100644 --- a/src/Model/Security/Roles.php +++ b/src/Model/Security/Roles.php @@ -166,6 +166,8 @@ class Roles public const string ROLE_COMPLAINT_STATUS_FULL = 'ROLE_COMPLAINT_STATUS_FULL'; public const string ROLE_COMPLAINT_STATUS_VIEW = 'ROLE_COMPLAINT_STATUS_VIEW'; + public const string ROLE_INQUIRY_VIEW = 'ROLE_INQUIRY_VIEW'; + /** * @return array */ @@ -390,6 +392,9 @@ public function getAvailableAdministratorRolesGrid(): array static::ROLE_COMPLAINT_STATUS_FULL => t('Complaint statuses - full'), static::ROLE_COMPLAINT_STATUS_VIEW => t('Complaint statuses - view'), ], + [ + static::ROLE_INQUIRY_VIEW => t('Inquiries - view'), + ], ]; } diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index 331469b994..3354d34ff2 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -2128,6 +2128,15 @@ msgstr "Vstupní cena s DPH" msgid "Input price without VAT" msgstr "Vstupní cena bez DPH" +msgid "Inquiries" +msgstr "Poptávky" + +msgid "Inquiries - view" +msgstr "Poptávky - zobrazení" + +msgid "Inquiries overview" +msgstr "Přehled poptávek" + msgid "Instagram URL" msgstr "Instagram URL" @@ -2569,6 +2578,9 @@ msgstr "Žádné soubory nebyly nalezeny." msgid "No flags found. You have to create some first." msgstr "Nebyly nalezeny žádné příznaky. Nejprve musíte nějaké vytvořit." +msgid "No inquiries found." +msgstr "Nebyly nalezeny žádné poptávky." + msgid "No logs created so far." msgstr "Žádné protokoly nejsou zatím vytvořeny." @@ -3406,6 +3418,9 @@ msgstr "Vyhledávání ve nahraných souborech podle názvu souboru, přípony n msgid "Searching in all currencies" msgstr "Vyhledává se ve všech měnách" +msgid "Searching searches in company number, company name, customer last name, and customer email. It is possible to use wildcards * and ?." +msgstr "Vyhledávání hledá v IČ, názvu firmy, příjmení zákazníka, a e-mailu zákazníka. Je možné používat operátory * a ?." + msgid "Searching searches in complaint number, order number, delivery last name, delivery company name, last name from order, email from order, complaint author last name, complaint author company name. It is possible to use wildcards * and ?." msgstr "Vyhledávání hledá v čísle reklamace, čísle objednávky, příjmení pro doručení, názvu firmy pro doručení, příjmení z objednávky, e-mailu objednávky, příjmení zadavatele reklamace a názvu firmy zadavatele reklamace. Je možné používat operátory * a ?." @@ -3772,6 +3787,9 @@ msgstr "Čím vyšší priorita, tím bude stát zobrazen výše v seznamech. St msgid "The optimal size of the icon is 46x26 px. Only PNG format is allowed." msgstr "Optimální velikost ikony je 46x26 px. Je povolen pouze PNG formát." +msgid "The product is no longer in the catalog. This was the catalog number at the time of the inquiry." +msgstr "Produkt již není v katalogu. Toto bylo katalogové číslo v době vytvoření poptávky." + msgid "There are no parameter values of type color yet." msgstr "Žádná hodnota parametru typu barva zatím nebyla vytvořena." @@ -4348,6 +4366,9 @@ msgstr "v kategorii (mezi prvním a druhým řádkem produktů)" msgid "include" msgstr "obsahuje" +msgid "inquiries" +msgstr "poptávky" + msgid "is" msgstr "je" diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index 845125c86b..ab7a4271b5 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -2128,6 +2128,15 @@ msgstr "" msgid "Input price without VAT" msgstr "" +msgid "Inquiries" +msgstr "" + +msgid "Inquiries - view" +msgstr "" + +msgid "Inquiries overview" +msgstr "" + msgid "Instagram URL" msgstr "" @@ -2569,6 +2578,9 @@ msgstr "" msgid "No flags found. You have to create some first." msgstr "" +msgid "No inquiries found." +msgstr "" + msgid "No logs created so far." msgstr "" @@ -3406,6 +3418,9 @@ msgstr "" msgid "Searching in all currencies" msgstr "" +msgid "Searching searches in company number, company name, customer last name, and customer email. It is possible to use wildcards * and ?." +msgstr "" + msgid "Searching searches in complaint number, order number, delivery last name, delivery company name, last name from order, email from order, complaint author last name, complaint author company name. It is possible to use wildcards * and ?." msgstr "" @@ -3772,6 +3787,9 @@ msgstr "" msgid "The optimal size of the icon is 46x26 px. Only PNG format is allowed." msgstr "" +msgid "The product is no longer in the catalog. This was the catalog number at the time of the inquiry." +msgstr "" + msgid "There are no parameter values of type color yet." msgstr "" @@ -4348,6 +4366,9 @@ msgstr "" msgid "include" msgstr "" +msgid "inquiries" +msgstr "" + msgid "is" msgstr "" diff --git a/src/Resources/views/Admin/Content/Inquiry/list.html.twig b/src/Resources/views/Admin/Content/Inquiry/list.html.twig new file mode 100644 index 0000000000..4779a9be6c --- /dev/null +++ b/src/Resources/views/Admin/Content/Inquiry/list.html.twig @@ -0,0 +1,24 @@ +{% extends '@ShopsysFramework/Admin/Layout/layoutWithPanel.html.twig' %} + +{% block title %}- {{ 'Inquiries overview'|trans }}{% endblock %} +{% block h1 %}{{ 'Inquiries overview'|trans }}{% endblock %} + +{% block main_content %} +
+ +
+
+ {% include '@ShopsysFramework/Admin/Content/Inquiry/quickSearchFormContent.html.twig' with {quickSearchForm: quickSearchForm} %} +
+
+
+ + {{ gridView.render() }} +{% endblock %} diff --git a/src/Resources/views/Admin/Content/Inquiry/listGrid.html.twig b/src/Resources/views/Admin/Content/Inquiry/listGrid.html.twig new file mode 100644 index 0000000000..7d66af1d73 --- /dev/null +++ b/src/Resources/views/Admin/Content/Inquiry/listGrid.html.twig @@ -0,0 +1,32 @@ +{% extends '@ShopsysFramework/Admin/Grid/Grid.html.twig' %} + +{% block grid_value_cell_id_productName %} + {% if value %} + {{ value }} + {% else %} + {{ row.i.productCatnum }} + + {{ icon('info') }} + + {% endif %} + {% if row.productId %} + {{ icon('forward-page') }} + {% endif %} +{% endblock %} + +{% block grid_value_cell_id_createdAt %} + {{ value|formatDateTime }} +{% endblock %} + +{% block grid_no_data %} + {{ 'No inquiries found.'|trans }} +{% endblock %} + +{% block grid_pager_totalcount %} + {% set entityName = 'inquiries'|trans %} + {{ parent() }} +{% endblock %} diff --git a/src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig b/src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig new file mode 100644 index 0000000000..c9fae1ef87 --- /dev/null +++ b/src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig @@ -0,0 +1,12 @@ +{{ form_start(quickSearchForm) }} + +{{ form_end(quickSearchForm) }} From ee521295277eb8db65ef8fe41319f5c64221ac97 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Mon, 30 Sep 2024 17:31:51 +0200 Subject: [PATCH 08/24] added inquiry detail in admin --- src/Controller/Admin/InquiryController.php | 14 +++ src/Model/AdminNavigation/SideMenuBuilder.php | 6 ++ .../Exception/InquiryNotFoundException.php | 11 +++ src/Model/Inquiry/Inquiry.php | 96 +++++++++++++++++++ src/Model/Inquiry/InquiryFacade.php | 9 ++ src/Model/Inquiry/InquiryGridFactory.php | 1 + src/Model/Inquiry/InquiryRepository.php | 16 ++++ src/Resources/translations/messages.cs.po | 19 +++- src/Resources/translations/messages.en.po | 19 +++- .../Admin/Content/Inquiry/detail.html.twig | 86 +++++++++++++++++ 10 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 src/Model/Inquiry/Exception/InquiryNotFoundException.php create mode 100644 src/Resources/views/Admin/Content/Inquiry/detail.html.twig diff --git a/src/Controller/Admin/InquiryController.php b/src/Controller/Admin/InquiryController.php index 4c85002b95..3a4d81c00c 100644 --- a/src/Controller/Admin/InquiryController.php +++ b/src/Controller/Admin/InquiryController.php @@ -47,4 +47,18 @@ public function listAction(Request $request): Response 'quickSearchForm' => $quickSearchForm->createView(), ]); } + + /** + * @param int $id + * @return \Symfony\Component\HttpFoundation\Response + */ + #[Route(path: '/inquiry/detail/{id}', requirements: ['id' => '\d+'])] + public function detailAction(int $id): Response + { + $inquiry = $this->inquiryFacade->getById($id); + + return $this->render('@ShopsysFramework/Admin/Content/Inquiry/detail.html.twig', [ + 'inquiry' => $inquiry, + ]); + } } diff --git a/src/Model/AdminNavigation/SideMenuBuilder.php b/src/Model/AdminNavigation/SideMenuBuilder.php index 21d950c468..32f5e3acf9 100644 --- a/src/Model/AdminNavigation/SideMenuBuilder.php +++ b/src/Model/AdminNavigation/SideMenuBuilder.php @@ -113,6 +113,12 @@ protected function createInquiriesMenu(): ItemInterface ]); $menu->setExtra('icon', 'letter'); + $menu->addChild('detail', [ + 'route' => 'admin_inquiry_detail', + 'label' => t('Inquiry detail'), + 'display' => false, + ]); + $this->dispatchConfigureMenuEvent(ConfigureMenuEvent::SIDE_MENU_INQUIRIES, $menu); return $menu; diff --git a/src/Model/Inquiry/Exception/InquiryNotFoundException.php b/src/Model/Inquiry/Exception/InquiryNotFoundException.php new file mode 100644 index 0000000000..a4a1d0c12c --- /dev/null +++ b/src/Model/Inquiry/Exception/InquiryNotFoundException.php @@ -0,0 +1,11 @@ +product = $inquiryData->product; $this->productCatnum = $inquiryData->product->getCatnum(); } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return string + */ + public function getFirstName() + { + return $this->firstName; + } + + /** + * @return string + */ + public function getLastName() + { + return $this->lastName; + } + + /** + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * @return string + */ + public function getTelephone() + { + return $this->telephone; + } + + /** + * @return string|null + */ + public function getCompanyName() + { + return $this->companyName; + } + + /** + * @return string|null + */ + public function getCompanyNumber() + { + return $this->companyNumber; + } + + /** + * @return string|null + */ + public function getCompanyTaxNumber() + { + return $this->companyTaxNumber; + } + + /** + * @return \DateTimeImmutable + */ + public function getCreatedAt() + { + return $this->createdAt; + } + + /** + * @return string|null + */ + public function getNote() + { + return $this->note; + } + + /** + * @return \Shopsys\FrameworkBundle\Model\Product\Product|null + */ + public function getProduct() + { + return $this->product; + } + + /** + * @return string + */ + public function getProductCatnum() + { + return $this->productCatnum; + } } diff --git a/src/Model/Inquiry/InquiryFacade.php b/src/Model/Inquiry/InquiryFacade.php index b6fcaf1bd5..ec04d9ce9e 100644 --- a/src/Model/Inquiry/InquiryFacade.php +++ b/src/Model/Inquiry/InquiryFacade.php @@ -23,6 +23,15 @@ public function __construct( ) { } + /** + * @param int $id + * @return \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry + */ + public function getById(int $id): Inquiry + { + return $this->inquiryRepository->getById($id); + } + /** * @param \Shopsys\FrameworkBundle\Model\Inquiry\InquiryData $inquiryData * @return \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry diff --git a/src/Model/Inquiry/InquiryGridFactory.php b/src/Model/Inquiry/InquiryGridFactory.php index e779f08762..40599f9d97 100644 --- a/src/Model/Inquiry/InquiryGridFactory.php +++ b/src/Model/Inquiry/InquiryGridFactory.php @@ -47,6 +47,7 @@ public function createView( $grid->addColumn('company', 'company', t('Company (Company number)'), true); $grid->addColumn('createdAt', 'i.createdAt', t('Created'), true); + $grid->addActionColumn('file-all', t('Show detail'), 'admin_inquiry_detail', ['id' => 'i.id']); $grid->setTheme('@ShopsysFramework/Admin/Content/Inquiry/listGrid.html.twig'); diff --git a/src/Model/Inquiry/InquiryRepository.php b/src/Model/Inquiry/InquiryRepository.php index 2e432869df..41a558629a 100644 --- a/src/Model/Inquiry/InquiryRepository.php +++ b/src/Model/Inquiry/InquiryRepository.php @@ -8,6 +8,7 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; +use Shopsys\FrameworkBundle\Model\Inquiry\Exception\InquiryNotFoundException; class InquiryRepository { @@ -27,6 +28,21 @@ protected function getInquiryRepository(): EntityRepository return $this->em->getRepository(Inquiry::class); } + /** + * @param int $id + * @return \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry + */ + public function getById(int $id): Inquiry + { + $inquiry = $this->getInquiryRepository()->find($id); + + if ($inquiry === null) { + throw new InquiryNotFoundException(); + } + + return $inquiry; + } + /** * @param string $locale * @return \Doctrine\ORM\QueryBuilder diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index 3354d34ff2..9c34693ba6 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -691,6 +691,9 @@ msgstr "Komunikace se zákazníkem" msgid "Company" msgstr "Firma" +msgid "Company (Company number)" +msgstr "Firma (IČ)" + msgid "Company data" msgstr "Firemní údaje" @@ -2128,6 +2131,9 @@ msgstr "Vstupní cena s DPH" msgid "Input price without VAT" msgstr "Vstupní cena bez DPH" +msgid "Inquired product" +msgstr "Poptávaný produkt" + msgid "Inquiries" msgstr "Poptávky" @@ -2137,6 +2143,12 @@ msgstr "Poptávky - zobrazení" msgid "Inquiries overview" msgstr "Přehled poptávek" +msgid "Inquiry by" +msgstr "Poptávka od" + +msgid "Inquiry detail" +msgstr "Detail poptávky" + msgid "Instagram URL" msgstr "Instagram URL" @@ -3112,6 +3124,9 @@ msgstr "Bylo upraveno zboží {{ product|productDi msgid "Product {{ product|productDisplayName }} deleted" msgstr "Produkt {{ product|productDisplayName }} byl smazán" +msgid "Product associated with this inquiry is no longer available in the catalog." +msgstr "Produkt v této poptávce není již dostupný v katalogu." + msgid "Product excluded from sale" msgstr "Zboží je vyřazeno z prodeje" @@ -3628,6 +3643,9 @@ msgstr "Krátký popis se nastavuje u hlavní varianty." msgid "Short description of category" msgstr "Krátký popis kategorie" +msgid "Show detail" +msgstr "Zobrazit detail" + msgid "Show combinations" msgstr "Zobrazit kombinace" @@ -4464,4 +4482,3 @@ msgstr "{1} Byl vytvořen %count% slevový kupón|[2,4] Byly vy msgid "{1}Sales representative \"%label%\" is assigned to %count% customer and will be removed if you proceed.

Customer:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[2,10]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[11,Inf]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%
+%extraCount% more

Do you really want to remove sales representative \"%label%\" permanently?" msgstr "{1}Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkovi a bude odstraněn, pokud budete pokračovat.

Zákazník:
%customersEnumeration%

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?|[2,10]Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkům a bude odstraněn, pokud budete pokračovat.

Zákazníci:
%customersEnumeration%

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?|[11,14]Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkům a bude odstraněn, pokud budete pokračovat.

Zákazníci:
%customersEnumeration%
+%extraCount% další

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?|[15,Inf]Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkům a bude odstraněn, pokud budete pokračovat.

Zákazníci:
%customersEnumeration%
+%extraCount% dalších

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?" - diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index ab7a4271b5..2194cca920 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -691,6 +691,9 @@ msgstr "" msgid "Company" msgstr "" +msgid "Company (Company number)" +msgstr "" + msgid "Company data" msgstr "" @@ -2128,6 +2131,9 @@ msgstr "" msgid "Input price without VAT" msgstr "" +msgid "Inquired product" +msgstr "" + msgid "Inquiries" msgstr "" @@ -2137,6 +2143,12 @@ msgstr "" msgid "Inquiries overview" msgstr "" +msgid "Inquiry by" +msgstr "" + +msgid "Inquiry detail" +msgstr "" + msgid "Instagram URL" msgstr "" @@ -3112,6 +3124,9 @@ msgstr "" msgid "Product {{ product|productDisplayName }} deleted" msgstr "" +msgid "Product associated with this inquiry is no longer available in the catalog." +msgstr "" + msgid "Product excluded from sale" msgstr "" @@ -3628,6 +3643,9 @@ msgstr "" msgid "Short description of category" msgstr "" +msgid "Show detail" +msgstr "" + msgid "Show combinations" msgstr "" @@ -4464,4 +4482,3 @@ msgstr "" msgid "{1}Sales representative \"%label%\" is assigned to %count% customer and will be removed if you proceed.

Customer:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[2,10]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[11,Inf]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%
+%extraCount% more

Do you really want to remove sales representative \"%label%\" permanently?" msgstr "" - diff --git a/src/Resources/views/Admin/Content/Inquiry/detail.html.twig b/src/Resources/views/Admin/Content/Inquiry/detail.html.twig new file mode 100644 index 0000000000..268e849573 --- /dev/null +++ b/src/Resources/views/Admin/Content/Inquiry/detail.html.twig @@ -0,0 +1,86 @@ +{% extends '@ShopsysFramework/Admin/Layout/layoutWithPanel.html.twig' %} + +{% block title %}- {{ 'Inquiry by'|trans }} {{ inquiry.email }}{% endblock %} +{% block h1 %}{{ 'Inquiry by'|trans }} {{ inquiry.email }}{% endblock %} + +{% block main_content %} +
+
{{ 'Customer detail'|trans }}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'First name'|trans }}{{ inquiry.firstName }}
{{ 'Last name'|trans }}{{ inquiry.lastName }}
{{ 'Email'|trans }}{{ inquiry.email }}
{{ 'Telephone'|trans }}{{ inquiry.telephone }}
{{ 'Company name'|trans }}{{ inquiry.companyName ?: '-' }}
{{ 'Company number'|trans }}{{ inquiry.companyNumber ?: '-' }}
{{ 'Company Tax number'|trans }}{{ inquiry.companyTaxNumber ?: '-' }}
{{ 'Created'|trans }}{{ inquiry.createdAt|formatDateTime }}
{{ 'Note'|trans }}
{{ inquiry.note }}
+
+
+ +
+
{{ 'Inquired product'|trans }}
+
+ + + {% if inquiry.product is not null %} + + + + {% else %} + + + {% endif %} + +
+ {{ image(inquiry.product, { height: 100, type: null }) }} + + {{ inquiry.product.name }} + + {{ 'Catalog number'|trans }}: {{ inquiry.product.catnum }} + + {{ 'Catalog number'|trans }}: {{ inquiry.productCatnum }} + + {{ 'Product associated with this inquiry is no longer available in the catalog.'|trans }} +
+
+
+ + {% embed '@ShopsysFramework/Admin/Inline/FixedBar/fixedBar.html.twig' %} + {% block fixed_bar_content %} +
{{ 'Back to overview'|trans }} + {% endblock %} + {% endembed %} +{% endblock %} From 0188225a4c15556dc2fde6f8c53fd65df2388426 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Tue, 1 Oct 2024 18:07:56 +0200 Subject: [PATCH 09/24] inquiry is now aware of its domain origin --- src/Migrations/Version20240926162253.php | 1 + src/Model/Inquiry/Inquiry.php | 15 +++++++++++++++ src/Model/Inquiry/InquiryData.php | 5 +++++ src/Model/Inquiry/InquiryDataFactory.php | 8 ++++++-- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Migrations/Version20240926162253.php b/src/Migrations/Version20240926162253.php index 15771b18ba..fd7cad31b3 100644 --- a/src/Migrations/Version20240926162253.php +++ b/src/Migrations/Version20240926162253.php @@ -17,6 +17,7 @@ public function up(Schema $schema): void $this->sql(' CREATE TABLE inquiries ( id SERIAL NOT NULL, + domain_id INT NOT NULL, product_id INT DEFAULT NULL, product_catnum VARCHAR(100) NOT NULL, first_name VARCHAR(100) NOT NULL, diff --git a/src/Model/Inquiry/Inquiry.php b/src/Model/Inquiry/Inquiry.php index dc02e0ac6b..184d06eb59 100644 --- a/src/Model/Inquiry/Inquiry.php +++ b/src/Model/Inquiry/Inquiry.php @@ -21,6 +21,12 @@ class Inquiry */ protected $id; + /** + * @var int + * @ORM\Column(type="integer") + */ + protected $domainId; + /** * @var string * @ORM\Column(type="string", length=100) @@ -94,6 +100,7 @@ class Inquiry public function __construct(InquiryData $inquiryData) { $this->createdAt = $inquiryData->createdAt ?? new DateTimeImmutable(); + $this->domainId = $inquiryData->domainId; $this->setData($inquiryData); } @@ -139,6 +146,14 @@ public function getLastName() return $this->lastName; } + /** + * @return int + */ + public function getDomainId() + { + return $this->domainId; + } + /** * @return string */ diff --git a/src/Model/Inquiry/InquiryData.php b/src/Model/Inquiry/InquiryData.php index e08506c741..672605d25e 100644 --- a/src/Model/Inquiry/InquiryData.php +++ b/src/Model/Inquiry/InquiryData.php @@ -6,6 +6,11 @@ class InquiryData { + /** + * @var int|null + */ + public $domainId; + /** * @var string|null */ diff --git a/src/Model/Inquiry/InquiryDataFactory.php b/src/Model/Inquiry/InquiryDataFactory.php index c63fe40205..5a314b596e 100644 --- a/src/Model/Inquiry/InquiryDataFactory.php +++ b/src/Model/Inquiry/InquiryDataFactory.php @@ -15,10 +15,14 @@ protected function createInstance(): InquiryData } /** + * @param int $domainId * @return \Shopsys\FrameworkBundle\Model\Inquiry\InquiryData */ - public function create(): InquiryData + public function create(int $domainId): InquiryData { - return $this->createInstance(); + $inquiryData = $this->createInstance(); + $inquiryData->domainId = $domainId; + + return $inquiryData; } } From 234d74be924dd15bb2faabb972a98c14916fa927 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Tue, 1 Oct 2024 18:37:34 +0200 Subject: [PATCH 10/24] info about new inquiry is now sent to customer and admin - both email templates are administrable in administration --- src/Component/Image/ImageFacade.php | 9 + src/Migrations/Version20241001113136.php | 100 +++++++++ src/Model/Inquiry/Inquiry.php | 8 + src/Model/Inquiry/Mail/InquiryMail.php | 191 ++++++++++++++++++ src/Model/Inquiry/Mail/InquiryMailFacade.php | 43 ++++ .../InquiryMailTemplateVariablesProvider.php | 91 +++++++++ src/Model/Mail/MailTemplateConfiguration.php | 14 ++ src/Resources/config/services.yaml | 3 +- src/Resources/translations/dataFixtures.cs.po | 12 ++ src/Resources/translations/dataFixtures.en.po | 12 ++ src/Resources/translations/messages.cs.po | 21 ++ src/Resources/translations/messages.en.po | 21 ++ 12 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 src/Migrations/Version20241001113136.php create mode 100644 src/Model/Inquiry/Mail/InquiryMail.php create mode 100644 src/Model/Inquiry/Mail/InquiryMailFacade.php create mode 100644 src/Model/Inquiry/Mail/InquiryMailTemplateVariablesProvider.php diff --git a/src/Component/Image/ImageFacade.php b/src/Component/Image/ImageFacade.php index 8862b194e5..bd7bb9a653 100644 --- a/src/Component/Image/ImageFacade.php +++ b/src/Component/Image/ImageFacade.php @@ -240,6 +240,15 @@ function () use ($image, $domainConfig) { . $this->imageLocator->getRelativeImageFilepathWithSlug($image, $friendlyUrlSeoEntityName); } + /** + * @param \Shopsys\FrameworkBundle\Component\Domain\Config\DomainConfig $domainConfig + * @return string + */ + public function getEmptyImageUrl(DomainConfig $domainConfig): string + { + return $this->cdnFacade->resolveDomainUrlForAssets($domainConfig) . '/public/frontend/images/noimage.png'; + } + /** * @param object $imageOrEntity * @param string|null $type diff --git a/src/Migrations/Version20241001113136.php b/src/Migrations/Version20241001113136.php new file mode 100644 index 0000000000..b66326c74e --- /dev/null +++ b/src/Migrations/Version20241001113136.php @@ -0,0 +1,100 @@ +createMailTemplateIfNotExist(InquiryMail::CUSTOMER_MAIL_TEMPLATE_NAME); + $this->createMailTemplateIfNotExist(InquiryMail::ADMIN_MAIL_TEMPLATE_NAME); + + foreach ($this->getAllDomainIds() as $domainId) { + $domainLocale = $this->getDomainLocale($domainId); + + $this->updateMailTemplate( + InquiryMail::CUSTOMER_MAIL_TEMPLATE_NAME, + t('Thank you for your product inquiry', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainLocale), + t('

Dear {fullName},

Thank you for your interest in {productName}.

We have received your inquiry and will be reaching out to you shortly with further information.

If you need immediate assistance or have additional questions, feel free to reply to this email or contact us.

Best regards

', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainLocale), + $domainId, + ); + + $this->updateMailTemplate( + InquiryMail::ADMIN_MAIL_TEMPLATE_NAME, + t('New product inquiry received', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainLocale), + t('

A new product inquiry has just been received from {fullName}.

Please find the details below:

  • Customer name: {fullName}
  • Customer email: {email}
  • Customer telephone: {telephone}
  • Company name: {companyName}
  • Company number: {companyNumber}
  • Company tax number: {companyTaxNumber}

Note from customer:

{note}

Inquired product:

{productName} (Catalog number: {productCatnum})

{productName}

Please review the inquiry and take the necessary steps to follow up.

Review Inquiry

+', [], Translator::DATA_FIXTURES_TRANSLATION_DOMAIN, $domainLocale), + $domainId, + ); + } + } + + /** + * @param string $mailTemplateName + */ + private function createMailTemplateIfNotExist( + string $mailTemplateName, + ): void { + foreach ($this->getAllDomainIds() as $domainId) { + $mailTemplateCount = $this->sql( + 'SELECT count(*) FROM mail_templates WHERE name = :mailTemplateName and domain_id = :domainId', + [ + 'mailTemplateName' => $mailTemplateName, + 'domainId' => $domainId, + ], + )->fetchOne(); + + if ($mailTemplateCount !== 0) { + continue; + } + + $this->sql( + 'INSERT INTO mail_templates (name, domain_id, send_mail) VALUES (:mailTemplateName, :domainId, :sendMail)', + [ + 'mailTemplateName' => $mailTemplateName, + 'domainId' => $domainId, + 'sendMail' => true, + ], + ); + } + } + + /** + * @param string $mailTemplateName + * @param string $subject + * @param string $body + * @param int $domainId + */ + private function updateMailTemplate(string $mailTemplateName, string $subject, string $body, int $domainId): void + { + $this->sql( + 'UPDATE mail_templates SET subject = :subject, body = :body WHERE name = :mailTemplateName AND domain_id = :domainId', + [ + 'subject' => $subject, + 'body' => $body, + 'mailTemplateName' => $mailTemplateName, + 'domainId' => $domainId, + ], + ); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + */ + public function down(Schema $schema): void + { + } +} diff --git a/src/Model/Inquiry/Inquiry.php b/src/Model/Inquiry/Inquiry.php index 184d06eb59..1acbe32614 100644 --- a/src/Model/Inquiry/Inquiry.php +++ b/src/Model/Inquiry/Inquiry.php @@ -146,6 +146,14 @@ public function getLastName() return $this->lastName; } + /** + * @return string + */ + public function getFullName(): string + { + return $this->firstName . ' ' . $this->lastName; + } + /** * @return int */ diff --git a/src/Model/Inquiry/Mail/InquiryMail.php b/src/Model/Inquiry/Mail/InquiryMail.php new file mode 100644 index 0000000000..39b9b4c44d --- /dev/null +++ b/src/Model/Inquiry/Mail/InquiryMail.php @@ -0,0 +1,191 @@ +setting->getForDomain(MailSetting::MAIN_ADMIN_MAIL, $inquiry->getDomainId()), + $template->getBccEmail(), + $template->getBody(), + $template->getSubject(), + $this->setting->getForDomain(MailSetting::MAIN_ADMIN_MAIL, $inquiry->getDomainId()), + $this->setting->getForDomain(MailSetting::MAIN_ADMIN_MAIL_NAME, $inquiry->getDomainId()), + $this->getBodyVariablesReplacementsForAdmin($inquiry), + $this->getSubjectVariablesReplacements($inquiry), + ); + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Mail\MailTemplate $template + * @param \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry $inquiry + * @return \Shopsys\FrameworkBundle\Model\Mail\MessageData + */ + public function createMessageForCustomer(MailTemplate $template, Inquiry $inquiry): MessageData + { + return new MessageData( + $inquiry->getEmail(), + $template->getBccEmail(), + $template->getBody(), + $template->getSubject(), + $this->setting->getForDomain(MailSetting::MAIN_ADMIN_MAIL, $inquiry->getDomainId()), + $this->setting->getForDomain(MailSetting::MAIN_ADMIN_MAIL_NAME, $inquiry->getDomainId()), + $this->getBodyVariablesReplacements($inquiry), + $this->getSubjectVariablesReplacements($inquiry), + ); + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry $inquiry + * @return array + */ + protected function getSubjectVariablesReplacements(Inquiry $inquiry): array + { + return [ + self::VARIABLE_FULL_NAME => htmlspecialchars($inquiry->getFullName(), ENT_QUOTES), + self::VARIABLE_EMAIL => htmlspecialchars($inquiry->getEmail(), ENT_QUOTES), + self::VARIABLE_TELEPHONE => htmlspecialchars($inquiry->getTelephone(), ENT_QUOTES), + self::VARIABLE_COMPANY_NAME => $this->escapeOptionalString($inquiry->getCompanyName()), + self::VARIABLE_COMPANY_NUMBER => $this->escapeOptionalString($inquiry->getCompanyNumber()), + self::VARIABLE_COMPANY_TAX_NUMBER => $this->escapeOptionalString($inquiry->getCompanyTaxNumber()), + self::VARIABLE_PRODUCT_NAME => $this->escapeOptionalString($inquiry->getProduct()?->getName()), + self::VARIABLE_PRODUCT_CATALOG_NUMBER => htmlspecialchars($inquiry->getProductCatnum(), ENT_QUOTES), + ]; + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry $inquiry + * @return array + */ + protected function getBodyVariablesReplacements(Inquiry $inquiry): array + { + return [ + ...$this->getSubjectVariablesReplacements($inquiry), + self::VARIABLE_NOTE => $this->escapeOptionalString($inquiry->getNote()), + self::VARIABLE_PRODUCT_URL => $this->getProductUrl($inquiry), + self::VARIABLE_PRODUCT_IMAGE => $this->getProductImageUrl($inquiry), + ]; + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry $inquiry + * @return array + */ + protected function getBodyVariablesReplacementsForAdmin(Inquiry $inquiry): array + { + return [ + ...$this->getBodyVariablesReplacements($inquiry), + self::VARIABLE_ADMIN_INQUIRY_DETAIL_URL => $this->domainRouterFactory->getRouter(Domain::MAIN_ADMIN_DOMAIN_ID)->generate( + 'admin_inquiry_detail', + ['id' => $inquiry->getId()], + UrlGeneratorInterface::ABSOLUTE_URL, + ), + ]; + } + + /** + * @param string|null $string + * @return string + */ + protected function escapeOptionalString(?string $string): string + { + if ($string === null) { + return '-'; + } + + return htmlspecialchars($string, ENT_QUOTES); + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry $inquiry + * @return string + */ + protected function getProductUrl(Inquiry $inquiry): string + { + if ($inquiry->getProduct() === null) { + return ''; + } + + return $this->domainRouterFactory->getRouter($inquiry->getDomainId())->generate( + 'front_product_detail', + ['id' => $inquiry->getProduct()->getId()], + UrlGeneratorInterface::ABSOLUTE_URL, + ); + } + + /** + * @param \Shopsys\FrameworkBundle\Model\Inquiry\Inquiry $inquiry + * @return string + */ + protected function getProductImageUrl(Inquiry $inquiry): string + { + $domainConfig = $this->domain->getDomainConfigById($inquiry->getDomainId()); + + if ($inquiry->getProduct() === null) { + return $this->imageFacade->getEmptyImageUrl($domainConfig); + } + + try { + $imageUrl = $this->imageFacade->getImageUrl( + $domainConfig, + $inquiry->getProduct(), + ); + + return $imageUrl . '?width=100'; + } catch (ImageNotFoundException) { + return $this->imageFacade->getEmptyImageUrl($domainConfig); + } + } +} diff --git a/src/Model/Inquiry/Mail/InquiryMailFacade.php b/src/Model/Inquiry/Mail/InquiryMailFacade.php new file mode 100644 index 0000000000..550dbca3bb --- /dev/null +++ b/src/Model/Inquiry/Mail/InquiryMailFacade.php @@ -0,0 +1,43 @@ +mailTemplateFacade->get(InquiryMail::ADMIN_MAIL_TEMPLATE_NAME, $inquiry->getDomainId()); + $messageData = $this->inquiryMail->createMessageForAdmin($mailTemplate, $inquiry); + $messageData->attachments = $this->uploadedFileFacade->getUploadedFilesByEntity($mailTemplate); + $this->mailer->sendForDomain($messageData, $inquiry->getDomainId()); + + $mailTemplate = $this->mailTemplateFacade->get(InquiryMail::CUSTOMER_MAIL_TEMPLATE_NAME, $inquiry->getDomainId()); + $messageData = $this->inquiryMail->createMessageForCustomer($mailTemplate, $inquiry); + $messageData->attachments = $this->uploadedFileFacade->getUploadedFilesByEntity($mailTemplate); + $this->mailer->sendForDomain($messageData, $inquiry->getDomainId()); + } +} diff --git a/src/Model/Inquiry/Mail/InquiryMailTemplateVariablesProvider.php b/src/Model/Inquiry/Mail/InquiryMailTemplateVariablesProvider.php new file mode 100644 index 0000000000..dc096ace0d --- /dev/null +++ b/src/Model/Inquiry/Mail/InquiryMailTemplateVariablesProvider.php @@ -0,0 +1,91 @@ +addVariable( + InquiryMail::VARIABLE_FULL_NAME, + t('Customer name'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_EMAIL, + t('Customer email address'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_TELEPHONE, + t('Customer phone number'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_COMPANY_NAME, + t('Company name'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_COMPANY_NUMBER, + t('Company number'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_COMPANY_TAX_NUMBER, + t('Company Tax number'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_NOTE, + t('Note'), + MailTemplateVariables::CONTEXT_BODY, + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_PRODUCT_NAME, + t('Product name'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_PRODUCT_CATALOG_NUMBER, + t('Product catnum'), + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_PRODUCT_URL, + t('Product detail URL'), + MailTemplateVariables::CONTEXT_BODY, + ); + + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_PRODUCT_IMAGE, + t('Product image URL'), + MailTemplateVariables::CONTEXT_BODY, + ); + + if ($mailTemplateType === InquiryMail::ADMIN_MAIL_TEMPLATE_NAME) { + $mailTemplateVariables->addVariable( + InquiryMail::VARIABLE_ADMIN_INQUIRY_DETAIL_URL, + t('Admin inquiry detail URL'), + MailTemplateVariables::CONTEXT_BODY, + ); + } + + return $mailTemplateVariables; + } +} diff --git a/src/Model/Mail/MailTemplateConfiguration.php b/src/Model/Mail/MailTemplateConfiguration.php index 22167b38da..74f20af4bd 100644 --- a/src/Model/Mail/MailTemplateConfiguration.php +++ b/src/Model/Mail/MailTemplateConfiguration.php @@ -10,6 +10,8 @@ use Shopsys\FrameworkBundle\Model\Customer\Mail\CustomerActivationMail; use Shopsys\FrameworkBundle\Model\Customer\Mail\RegistrationMail; use Shopsys\FrameworkBundle\Model\Customer\Mail\ResetPasswordMail; +use Shopsys\FrameworkBundle\Model\Inquiry\Mail\InquiryMail; +use Shopsys\FrameworkBundle\Model\Inquiry\Mail\InquiryMailTemplateVariablesProvider; use Shopsys\FrameworkBundle\Model\Mail\Exception\InvalidMailTemplateVariablesConfigurationException; use Shopsys\FrameworkBundle\Model\Mail\Exception\MailTemplateNotFoundException; use Shopsys\FrameworkBundle\Model\Order\Mail\OrderMail; @@ -34,16 +36,19 @@ class MailTemplateConfiguration /** * @param \Shopsys\FrameworkBundle\Model\Order\Status\OrderStatusFacade $orderStatusFacade * @param \Shopsys\FrameworkBundle\Model\Complaint\Status\ComplaintStatusFacade $complaintStatusFacade + * @param \Shopsys\FrameworkBundle\Model\Inquiry\Mail\InquiryMailTemplateVariablesProvider $inquiryMailTemplateVariablesProvider */ public function __construct( protected readonly OrderStatusFacade $orderStatusFacade, protected readonly ComplaintStatusFacade $complaintStatusFacade, + protected readonly InquiryMailTemplateVariablesProvider $inquiryMailTemplateVariablesProvider, ) { $this->registerStaticMailTemplates(); $this->registerOrderStatusMailTemplates(); $this->registerComplaintStatusMailTemplates(); $this->registerTwoFactorAuthenticationCodeMailTemplate(); $this->registerCustomerActivationMailTemplate(); + $this->registerInquiryMailTemplates(); } /** @@ -323,4 +328,13 @@ protected function registerCustomerActivationMailTemplate(): void $this->addMailTemplateVariables(CustomerActivationMail::CUSTOMER_ACTIVATION_NAME, $mailTemplateVariables); } + + protected function registerInquiryMailTemplates(): void + { + $inquiryMailTemplateVariables = $this->inquiryMailTemplateVariablesProvider->create(InquiryMail::CUSTOMER_MAIL_TEMPLATE_NAME); + $this->addMailTemplateVariables(InquiryMail::CUSTOMER_MAIL_TEMPLATE_NAME, $inquiryMailTemplateVariables); + + $inquiryMailTemplateVariables = $this->inquiryMailTemplateVariablesProvider->create(InquiryMail::ADMIN_MAIL_TEMPLATE_NAME); + $this->addMailTemplateVariables(InquiryMail::ADMIN_MAIL_TEMPLATE_NAME, $inquiryMailTemplateVariables); + } } diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml index 24fe820d74..c4f5052f78 100644 --- a/src/Resources/config/services.yaml +++ b/src/Resources/config/services.yaml @@ -746,7 +746,6 @@ services: Shopsys\FrameworkBundle\Model\Heureka\HeurekaShopCertificationLocaleHelper: ~ - Shopsys\FrameworkBundle\Model\ImageSitemap\ImageSitemapDumperFactory: arguments: - '@event_dispatcher' @@ -762,6 +761,8 @@ services: tags: - { name: 'kernel.event_subscriber', priority: 100 } + Shopsys\FrameworkBundle\Model\Inquiry\Mail\InquiryMail: ~ + Shopsys\FrameworkBundle\Model\Localization\Localization: arguments: $adminLocale: '%shopsys.admin_locale%' diff --git a/src/Resources/translations/dataFixtures.cs.po b/src/Resources/translations/dataFixtures.cs.po index 77aa94cb8d..bef548350a 100644 --- a/src/Resources/translations/dataFixtures.cs.po +++ b/src/Resources/translations/dataFixtures.cs.po @@ -4,9 +4,21 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: cs\n" +msgid "

A new product inquiry has just been received from {fullName}.

Please find the details below:

  • Customer name: {fullName}
  • Customer email: {email}
  • Customer telephone: {telephone}
  • Company name: {companyName}
  • Company number: {companyNumber}
  • Company tax number: {companyTaxNumber}

Note from customer:

{note}

Inquired product:

{productName} (Catalog number: {productCatnum})

\"{productName}\"

Please review the inquiry and take the necessary steps to follow up.

Review Inquiry

" +msgstr "

Nová poptávka produktu byla právě přijata od {fullName}.

Podrobnosti naleznete níže:

  • Jméno zákazníka: {fullName}
  • E-mail zákazníka: {email}
  • Telefon zákazníka: {telephone}
  • Název firmy: {companyName}
  • IČ: {companyNumber}
  • DIČ: {companyTaxNumber}

Poznámka od zákazníka:

{note}

Poptávaný produkt:

{productName} (Katalogové číslo: {productCatnum})

\"{productName}\"

Prosím zkontrolujte tuto poptávku a proveďte potřebné kroky k jejímu vyřízení.

Zobrazit poptávku

" + +msgid "

Dear {fullName},

Thank you for your interest in {productName}.

We have received your inquiry and will be reaching out to you shortly with further information.

If you need immediate assistance or have additional questions, feel free to reply to this email or contact us.

Best regards

" +msgstr "

Vážený/á {fullName},

Děkujeme za váš zájem o {productName}.

Obdrželi jsme vaši poptávku a brzy se vám ozveme s dalšími informacemi.

Pokud potřebujete okamžitou pomoc nebo máte další otázky, neváhejte odpovědět na tento email nebo nás kontaktovat.

S pozdravem

" + msgid "

Payment for order number {number} has been successful.

Track the status of your order.
{transport_instructions}

" msgstr "

Platba pro objednávku číslo {number} proběhla úspěšně.

Stav Vaší objednávky můžete sledovat zde.
{transport_instructions}

" msgid "

Payment for order number {number} has failed.

Please contact us to resolve the issue.

" msgstr "

Platba pro objednávku číslo {number} nebyla úspěšná.

Prosím kontaktujte nás pro vyřešení problému.

" +msgid "New product inquiry received" +msgstr "Byla přijata nová poptávka produktu" + +msgid "Thank you for your product inquiry" +msgstr "Děkujeme za vaši poptávku produktu" + diff --git a/src/Resources/translations/dataFixtures.en.po b/src/Resources/translations/dataFixtures.en.po index a8f63a5d75..cef65066ff 100644 --- a/src/Resources/translations/dataFixtures.en.po +++ b/src/Resources/translations/dataFixtures.en.po @@ -4,9 +4,21 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Language: en\n" +msgid "

A new product inquiry has just been received from {fullName}.

Please find the details below:

  • Customer name: {fullName}
  • Customer email: {email}
  • Customer telephone: {telephone}
  • Company name: {companyName}
  • Company number: {companyNumber}
  • Company tax number: {companyTaxNumber}

Note from customer:

{note}

Inquired product:

{productName} (Catalog number: {productCatnum})

\"{productName}\"

Please review the inquiry and take the necessary steps to follow up.

Review Inquiry

" +msgstr "" + +msgid "

Dear {fullName},

Thank you for your interest in {productName}.

We have received your inquiry and will be reaching out to you shortly with further information.

If you need immediate assistance or have additional questions, feel free to reply to this email or contact us.

Best regards

" +msgstr "" + msgid "

Payment for order number {number} has been successful.

Track the status of your order.
{transport_instructions}

" msgstr "" msgid "

Payment for order number {number} has failed.

Please contact us to resolve the issue.

" msgstr "" +msgid "New product inquiry received" +msgstr "" + +msgid "Thank you for your product inquiry" +msgstr "" + diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index 9c34693ba6..a008bc6fd4 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -172,6 +172,9 @@ msgstr "Doplňující informace k přihlášení" msgid "Address" msgstr "Adresa" +msgid "Admin inquiry detail URL" +msgstr "URL detailu poptávky v administraci" + msgid "Administration" msgstr "Administrace" @@ -694,6 +697,9 @@ msgstr "Firma" msgid "Company (Company number)" msgstr "Firma (IČ)" +msgid "Company Tax number" +msgstr "DIČ firmy" + msgid "Company data" msgstr "Firemní údaje" @@ -2149,6 +2155,12 @@ msgstr "Poptávka od" msgid "Inquiry detail" msgstr "Detail poptávky" +msgid "Inquiry info sent to administrator" +msgstr "Informace o poptávce odesílaná administrátorovi" + +msgid "Inquiry info sent to customer" +msgstr "Informace o poptávce odesílaná zákazníkovi" + msgid "Instagram URL" msgstr "Instagram URL" @@ -3127,9 +3139,18 @@ msgstr "Produkt {{ product|productDisplayName }} byl smazán" msgid "Product associated with this inquiry is no longer available in the catalog." msgstr "Produkt v této poptávce není již dostupný v katalogu." +msgid "Product catnum" +msgstr "Katalogové číslo produktu" + +msgid "Product detail URL" +msgstr "URL detailu produktu" + msgid "Product excluded from sale" msgstr "Zboží je vyřazeno z prodeje" +msgid "Product image URL" +msgstr "URL obrázku produktu" + msgid "Product in order" msgstr "Zboží v objednávce" diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index 2194cca920..5a368dd9bb 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -172,6 +172,9 @@ msgstr "" msgid "Address" msgstr "" +msgid "Admin inquiry detail URL" +msgstr "" + msgid "Administration" msgstr "" @@ -694,6 +697,9 @@ msgstr "" msgid "Company (Company number)" msgstr "" +msgid "Company Tax number" +msgstr "" + msgid "Company data" msgstr "" @@ -2149,6 +2155,12 @@ msgstr "" msgid "Inquiry detail" msgstr "" +msgid "Inquiry info sent to administrator" +msgstr "" + +msgid "Inquiry info sent to customer" +msgstr "" + msgid "Instagram URL" msgstr "" @@ -3127,9 +3139,18 @@ msgstr "" msgid "Product associated with this inquiry is no longer available in the catalog." msgstr "" +msgid "Product catnum" +msgstr "" + +msgid "Product detail URL" +msgstr "" + msgid "Product excluded from sale" msgstr "" +msgid "Product image URL" +msgstr "" + msgid "Product in order" msgstr "" From e03bc8cb97455373477fbd0ddf7e8dee9b987ad2 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 2 Oct 2024 08:36:06 +0200 Subject: [PATCH 11/24] inquiry products are not propagated to feeds anymore --- src/Model/Product/ProductRepository.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Model/Product/ProductRepository.php b/src/Model/Product/ProductRepository.php index 4484ec90a6..f7e50c4ad4 100644 --- a/src/Model/Product/ProductRepository.php +++ b/src/Model/Product/ProductRepository.php @@ -115,6 +115,20 @@ public function getAllVisibleQueryBuilder($domainId, PricingGroup $pricingGroup) return $queryBuilder; } + /** + * @param int $domainId + * @param \Shopsys\FrameworkBundle\Model\Pricing\Group\PricingGroup $pricingGroup + * @return \Doctrine\ORM\QueryBuilder + */ + public function getAllVisibleWithoutInquiriesQueryBuilder(int $domainId, PricingGroup $pricingGroup): QueryBuilder + { + $queryBuilder = $this->getAllVisibleQueryBuilder($domainId, $pricingGroup); + $queryBuilder->andWhere('p.productType != :inquiryProductType') + ->setParameter('inquiryProductType', ProductTypeEnum::TYPE_INQUIRY); + + return $queryBuilder; + } + /** * @param \Doctrine\ORM\QueryBuilder $queryBuilder * @param string $locale From 5e4a1c07149e4d1a1274dd96d498b328883ff87d Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 2 Oct 2024 15:15:51 +0200 Subject: [PATCH 12/24] products upon inquiry are now removed from cart --- src/Model/Cart/Watcher/CartWatcher.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Model/Cart/Watcher/CartWatcher.php b/src/Model/Cart/Watcher/CartWatcher.php index c5491f57b3..570e6de969 100644 --- a/src/Model/Cart/Watcher/CartWatcher.php +++ b/src/Model/Cart/Watcher/CartWatcher.php @@ -10,6 +10,7 @@ use Shopsys\FrameworkBundle\Model\Customer\User\CurrentCustomerUser; use Shopsys\FrameworkBundle\Model\Product\Exception\ProductNotFoundException; use Shopsys\FrameworkBundle\Model\Product\Pricing\ProductPriceCalculationForCustomerUser; +use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum; use Shopsys\FrameworkBundle\Model\Product\ProductVisibilityFacade; class CartWatcher @@ -60,6 +61,13 @@ public function getNotListableItems(Cart $cart, CurrentCustomerUser $currentCust foreach ($cart->getItems() as $item) { try { $product = $item->getProduct(); + + if ($product->getProductType() === ProductTypeEnum::TYPE_INQUIRY) { + $notListableItems[] = $item; + + continue; + } + $productVisibility = $this->productVisibilityFacade ->getProductVisibility( $product, From 45311858a249cf83dd449377c62a547b3b1fc068 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 2 Oct 2024 15:39:03 +0200 Subject: [PATCH 13/24] filtering by price always shows inquiry products --- src/Model/Product/Search/FilterQuery.php | 56 ++++++++++++++---------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/Model/Product/Search/FilterQuery.php b/src/Model/Product/Search/FilterQuery.php index 053ec16527..7d06196773 100644 --- a/src/Model/Product/Search/FilterQuery.php +++ b/src/Model/Product/Search/FilterQuery.php @@ -7,6 +7,7 @@ use Shopsys\FrameworkBundle\Component\Money\Money; use Shopsys\FrameworkBundle\Model\Pricing\Group\PricingGroup; use Shopsys\FrameworkBundle\Model\Product\Listing\ProductListOrderingConfig; +use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum; use stdClass; class FilterQuery @@ -241,40 +242,48 @@ public function filterByPrices( } $clone->filters[] = [ - 'nested' => [ - 'path' => 'prices', - 'query' => [ - 'bool' => [ - 'must' => [ - 'match_all' => new stdClass(), - ], - 'filter' => [ - 'bool' => [ - 'must' => [ - [ - 'range' => [ - 'prices.filtering_minimal_price' => [ - 'gte' => $priceGte, + 'bool' => [ + 'should' => [ + [ + 'nested' => [ + 'path' => 'prices', + 'query' => [ + 'bool' => [ + 'must' => [ + 'match_all' => new stdClass(), + ], + 'filter' => [ + [ + 'range' => [ + 'prices.filtering_minimal_price' => [ + 'gte' => $priceGte, + ], ], ], - ], - [ - 'range' => [ - 'prices.filtering_maximal_price' => [ - 'lte' => $priceLte, + [ + 'range' => [ + 'prices.filtering_maximal_price' => [ + 'lte' => $priceLte, + ], ], ], - ], - [ - 'term' => [ - 'prices.pricing_group_id' => $pricingGroup->getId(), + [ + 'term' => [ + 'prices.pricing_group_id' => $pricingGroup->getId(), + ], ], ], ], ], ], ], + [ + 'term' => [ + 'product_type' => ProductTypeEnum::TYPE_INQUIRY, + ], + ], ], + 'minimum_should_match' => 1, ], ]; @@ -830,7 +839,6 @@ public function getAggregationQueryForProductFilterConfig(int $pricingGroupId): ], ], ], - ]; return $query; From 9836f5794bc9b92bd5a2e8cf264e03cd572800ae Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Thu, 3 Oct 2024 10:43:44 +0200 Subject: [PATCH 14/24] inquiry products are now alway sorted last when sorting by price --- src/Model/Product/Search/FilterQuery.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Model/Product/Search/FilterQuery.php b/src/Model/Product/Search/FilterQuery.php index 7d06196773..38bed8e494 100644 --- a/src/Model/Product/Search/FilterQuery.php +++ b/src/Model/Product/Search/FilterQuery.php @@ -94,6 +94,15 @@ public function applyOrdering(string $orderingModeId, PricingGroup $pricingGroup } if ($orderingModeId === ProductListOrderingConfig::ORDER_BY_PRICE_ASC) { + $clone->sorting['_script'] = [ + 'type' => 'number', + 'script' => [ + 'lang' => 'painless', + 'source' => 'doc[\'product_type\'].value == \'inquiry\' ? 1 : 0', + ], + 'order' => 'asc', + ]; + $clone->sorting['prices.price_with_vat'] = [ 'order' => 'asc', 'nested' => [ @@ -112,6 +121,15 @@ public function applyOrdering(string $orderingModeId, PricingGroup $pricingGroup } if ($orderingModeId === ProductListOrderingConfig::ORDER_BY_PRICE_DESC) { + $clone->sorting['_script'] = [ + 'type' => 'number', + 'script' => [ + 'lang' => 'painless', + 'source' => 'doc[\'product_type\'].value == \'inquiry\' ? 1 : 0', + ], + 'order' => 'asc', + ]; + $clone->sorting['prices.price_with_vat'] = [ 'order' => 'desc', 'nested' => [ From d74c6ff22a90af26c336482df1b354230f0f3469 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Thu, 3 Oct 2024 17:25:38 +0200 Subject: [PATCH 15/24] inquiry can be now associated with the customer user --- src/Migrations/Version20240926162253.php | 10 ++++++++++ src/Model/Inquiry/Inquiry.php | 16 ++++++++++++++++ src/Model/Inquiry/InquiryData.php | 5 +++++ src/Resources/translations/messages.cs.po | 3 +++ src/Resources/translations/messages.en.po | 3 +++ .../views/Admin/Content/Inquiry/detail.html.twig | 9 ++++++++- 6 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/Migrations/Version20240926162253.php b/src/Migrations/Version20240926162253.php index fd7cad31b3..f8b6c46e5b 100644 --- a/src/Migrations/Version20240926162253.php +++ b/src/Migrations/Version20240926162253.php @@ -29,6 +29,7 @@ public function up(Schema $schema): void company_tax_number VARCHAR(50) DEFAULT NULL, note TEXT DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, + customer_user_id INT DEFAULT NULL, PRIMARY KEY(id) )'); $this->sql('CREATE INDEX IDX_1CCE4D54584665A ON inquiries (product_id)'); @@ -41,6 +42,15 @@ public function up(Schema $schema): void NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->sql('COMMENT ON COLUMN inquiries.created_at IS \'(DC2Type:datetime_immutable)\''); + + $this->sql(' + ALTER TABLE + inquiries + ADD + CONSTRAINT FK_1CCE4D5BBB3772B FOREIGN KEY (customer_user_id) REFERENCES customer_users (id) ON DELETE + SET + NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->sql('CREATE INDEX IDX_1CCE4D5BBB3772B ON inquiries (customer_user_id)'); } /** diff --git a/src/Model/Inquiry/Inquiry.php b/src/Model/Inquiry/Inquiry.php index 1acbe32614..78640abfb2 100644 --- a/src/Model/Inquiry/Inquiry.php +++ b/src/Model/Inquiry/Inquiry.php @@ -94,6 +94,13 @@ class Inquiry */ protected $productCatnum; + /** + * @var \Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser|null + * @ORM\ManyToOne(targetEntity="Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser") + * @ORM\JoinColumn(nullable=true, name="customer_user_id", referencedColumnName="id", onDelete="SET NULL") + */ + protected $customerUser; + /** * @param \Shopsys\FrameworkBundle\Model\Inquiry\InquiryData $inquiryData */ @@ -120,6 +127,7 @@ protected function setData(InquiryData $inquiryData): void $this->note = $inquiryData->note; $this->product = $inquiryData->product; $this->productCatnum = $inquiryData->product->getCatnum(); + $this->customerUser = $inquiryData->customerUser; } /** @@ -233,4 +241,12 @@ public function getProductCatnum() { return $this->productCatnum; } + + /** + * @return \Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser|null + */ + public function getCustomerUser() + { + return $this->customerUser; + } } diff --git a/src/Model/Inquiry/InquiryData.php b/src/Model/Inquiry/InquiryData.php index 672605d25e..bb881c547a 100644 --- a/src/Model/Inquiry/InquiryData.php +++ b/src/Model/Inquiry/InquiryData.php @@ -60,4 +60,9 @@ class InquiryData * @var \DateTimeImmutable|null */ public $createdAt; + + /** + * @var \Shopsys\FrameworkBundle\Model\Customer\User\CustomerUser|null + */ + public $customerUser; } diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index a008bc6fd4..a1f24bbbd9 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -1987,6 +1987,9 @@ msgstr "Vygenerované dávky" msgid "Go to Frontend" msgstr "Přejít na Frontend" +msgid "Go to associated customer user" +msgstr "Přejít na přiřazeného zákazníka" + msgid "Go to page:" msgstr "Přejít na stránku:" diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index 5a368dd9bb..1445191202 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -1987,6 +1987,9 @@ msgstr "" msgid "Go to Frontend" msgstr "" +msgid "Go to associated customer user" +msgstr "" + msgid "Go to page:" msgstr "" diff --git a/src/Resources/views/Admin/Content/Inquiry/detail.html.twig b/src/Resources/views/Admin/Content/Inquiry/detail.html.twig index 268e849573..341e6c2c63 100644 --- a/src/Resources/views/Admin/Content/Inquiry/detail.html.twig +++ b/src/Resources/views/Admin/Content/Inquiry/detail.html.twig @@ -14,7 +14,14 @@ {{ 'Last name'|trans }} - {{ inquiry.lastName }} + + {{ inquiry.lastName }} + {% if inquiry.customerUser %} + + {{ icon('forward-page') }} {{ 'Go to associated customer user'|trans }} + + {% endif %} + {{ 'Email'|trans }} From 26e5ddf336323330595a9cb069ec2647f68d29d2 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Fri, 4 Oct 2024 11:06:41 +0200 Subject: [PATCH 16/24] prices of inquiry products are not used for price filter config creation --- .../ProductFilterConfigIdsDataFactory.php | 2 +- src/Model/Product/Search/FilterQuery.php | 41 ++++++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Model/Product/Filter/ProductFilterConfigIdsDataFactory.php b/src/Model/Product/Filter/ProductFilterConfigIdsDataFactory.php index 506d67e9d8..d5388983d4 100644 --- a/src/Model/Product/Filter/ProductFilterConfigIdsDataFactory.php +++ b/src/Model/Product/Filter/ProductFilterConfigIdsDataFactory.php @@ -75,7 +75,7 @@ protected function extractFlagIds(array $aggregationResult): array */ protected function extractPriceRange(array $aggregationResult): PriceRange { - $pricesData = $aggregationResult['prices']['filter_pricing_group']; + $pricesData = $aggregationResult['prices']['nested_prices']['filter_pricing_group']; $minPrice = Money::create((string)($pricesData['min_price']['value'] ?? 0)); $maxPrice = Money::create((string)($pricesData['max_price']['value'] ?? 0)); diff --git a/src/Model/Product/Search/FilterQuery.php b/src/Model/Product/Search/FilterQuery.php index 38bed8e494..431ff58bff 100644 --- a/src/Model/Product/Search/FilterQuery.php +++ b/src/Model/Product/Search/FilterQuery.php @@ -833,25 +833,38 @@ public function getAggregationQueryForProductFilterConfig(int $pricingGroupId): $query = $this->getAbsoluteNumbersWithParametersQuery(); $query['body']['aggs']['prices'] = [ - 'nested' => [ - 'path' => 'prices', - ], - 'aggs' => [ - 'filter_pricing_group' => [ - 'filter' => [ + 'filter' => [ + 'bool' => [ + 'must_not' => [ 'term' => [ - 'prices.pricing_group_id' => $pricingGroupId, + 'product_type' => ProductTypeEnum::TYPE_INQUIRY, ], ], + ], + ], + 'aggs' => [ + 'nested_prices' => [ + 'nested' => [ + 'path' => 'prices', + ], 'aggs' => [ - 'min_price' => [ - 'min' => [ - 'field' => 'prices.price_with_vat', + 'filter_pricing_group' => [ + 'filter' => [ + 'term' => [ + 'prices.pricing_group_id' => $pricingGroupId, + ], ], - ], - 'max_price' => [ - 'max' => [ - 'field' => 'prices.price_with_vat', + 'aggs' => [ + 'min_price' => [ + 'min' => [ + 'field' => 'prices.price_with_vat', + ], + ], + 'max_price' => [ + 'max' => [ + 'field' => 'prices.price_with_vat', + ], + ], ], ], ], From 28a3d672dd8ea44facbeba06621ba9f4a0a8d92d Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 9 Oct 2024 13:21:25 +0200 Subject: [PATCH 17/24] inquiry can be created programmatically without the product --- src/Model/Inquiry/Inquiry.php | 11 ++++++++++- src/Model/Inquiry/InquiryData.php | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Model/Inquiry/Inquiry.php b/src/Model/Inquiry/Inquiry.php index 78640abfb2..f3d944c358 100644 --- a/src/Model/Inquiry/Inquiry.php +++ b/src/Model/Inquiry/Inquiry.php @@ -6,6 +6,7 @@ use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; +use LogicException; /** * @ORM\Table(name="inquiries") @@ -117,6 +118,14 @@ public function __construct(InquiryData $inquiryData) */ protected function setData(InquiryData $inquiryData): void { + if ($inquiryData->product === null && $inquiryData->productCatnum === null) { + throw new LogicException('Either product or productCatnum must be set to properly create an inquiry.'); + } + + if ($inquiryData->product !== null && $inquiryData->productCatnum !== null) { + throw new LogicException('Only one of product or productCatnum can be set to properly create an inquiry.'); + } + $this->firstName = $inquiryData->firstName; $this->lastName = $inquiryData->lastName; $this->email = $inquiryData->email; @@ -126,7 +135,7 @@ protected function setData(InquiryData $inquiryData): void $this->companyTaxNumber = $inquiryData->companyTaxNumber; $this->note = $inquiryData->note; $this->product = $inquiryData->product; - $this->productCatnum = $inquiryData->product->getCatnum(); + $this->productCatnum = $inquiryData->product ? $inquiryData->product->getCatnum() : $inquiryData->productCatnum; $this->customerUser = $inquiryData->customerUser; } diff --git a/src/Model/Inquiry/InquiryData.php b/src/Model/Inquiry/InquiryData.php index bb881c547a..968a28065b 100644 --- a/src/Model/Inquiry/InquiryData.php +++ b/src/Model/Inquiry/InquiryData.php @@ -56,6 +56,11 @@ class InquiryData */ public $product; + /** + * @var string|null + */ + public $productCatnum; + /** * @var \DateTimeImmutable|null */ From 0de192045721dcb953358564fc3635dd673cacb2 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 23 Oct 2024 17:19:39 +0200 Subject: [PATCH 18/24] main variant product type is now determined by variant product type setting --- .../Elasticsearch/ProductExportRepository.php | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Model/Product/Elasticsearch/ProductExportRepository.php b/src/Model/Product/Elasticsearch/ProductExportRepository.php index ca7770c58f..e9d29904c2 100644 --- a/src/Model/Product/Elasticsearch/ProductExportRepository.php +++ b/src/Model/Product/Elasticsearch/ProductExportRepository.php @@ -23,6 +23,7 @@ use Shopsys\FrameworkBundle\Model\Product\Product; use Shopsys\FrameworkBundle\Model\Product\ProductFacade; use Shopsys\FrameworkBundle\Model\Product\ProductRepository; +use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum; use Shopsys\FrameworkBundle\Model\Product\ProductVisibility; use Shopsys\FrameworkBundle\Model\Product\ProductVisibilityFacade; use Shopsys\FrameworkBundle\Model\Seo\HreflangLinksFacade; @@ -185,7 +186,7 @@ protected function getExportedFieldValue(int $domainId, Product $product, string ProductExportFieldProvider::SEO_META_DESCRIPTION => $product->getSeoMetaDescription($domainId), ProductExportFieldProvider::ACCESSORIES => $this->extractAccessoriesIds($product), ProductExportFieldProvider::HREFLANG_LINKS => $this->hreflangLinksFacade->getForProduct($product, $domainId), - ProductExportFieldProvider::PRODUCT_TYPE => $product->getProductType(), + ProductExportFieldProvider::PRODUCT_TYPE => $this->extractProductType($product, $domainId), default => throw new InvalidArgumentException(sprintf('There is no definition for exporting "%s" field to Elasticsearch', $field)), }; } @@ -310,6 +311,26 @@ protected function extractParameters(string $locale, Product $product): array return $parameters; } + /** + * @param \Shopsys\FrameworkBundle\Model\Product\Product $product + * @param int $domainId + * @return string + */ + protected function extractProductType(Product $product, int $domainId): string + { + if ($product->isMainVariant()) { + foreach ($this->getVariantsForDefaultPricingGroup($product, $domainId) as $variant) { + if ($variant->getProductType() === ProductTypeEnum::TYPE_BASIC) { + return ProductTypeEnum::TYPE_BASIC; + } + } + + return ProductTypeEnum::TYPE_INQUIRY; + } + + return $product->getProductType(); + } + /** * @param int $domainId * @param \Shopsys\FrameworkBundle\Model\Product\Product $product From 372b5b1b93180e48afe354dc4b863d1024ba8001 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 23 Oct 2024 17:20:06 +0200 Subject: [PATCH 19/24] variants upon inquiry are not used for price calculation of main variant --- src/Model/Product/Pricing/ProductPriceCalculation.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Model/Product/Pricing/ProductPriceCalculation.php b/src/Model/Product/Pricing/ProductPriceCalculation.php index 2b11ba25fb..9cfb36b9f7 100644 --- a/src/Model/Product/Pricing/ProductPriceCalculation.php +++ b/src/Model/Product/Pricing/ProductPriceCalculation.php @@ -13,6 +13,7 @@ use Shopsys\FrameworkBundle\Model\Product\Pricing\Exception\MainVariantPriceCalculationException; use Shopsys\FrameworkBundle\Model\Product\Product; use Shopsys\FrameworkBundle\Model\Product\ProductRepository; +use Shopsys\FrameworkBundle\Model\Product\ProductTypeEnum; class ProductPriceCalculation { @@ -61,6 +62,11 @@ protected function calculateMainVariantPrice(Product $mainVariant, $domainId, Pr $pricingGroup, ); + $variants = array_filter( + $variants, + static fn (Product $variant) => $variant->getProductType() !== ProductTypeEnum::TYPE_INQUIRY, + ); + if (count($variants) === 0) { $message = 'Main variant ID = ' . $mainVariant->getId() . ' has no sellable variants.'; From 66db46bc1377faa37a9804cdbf021f6bfb1d532c Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 23 Oct 2024 17:23:29 +0200 Subject: [PATCH 20/24] products upon inquiry are now sorted last when sorting by priority --- src/Model/Product/Search/FilterQuery.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Model/Product/Search/FilterQuery.php b/src/Model/Product/Search/FilterQuery.php index 431ff58bff..94428b0661 100644 --- a/src/Model/Product/Search/FilterQuery.php +++ b/src/Model/Product/Search/FilterQuery.php @@ -75,6 +75,7 @@ public function applyOrdering(string $orderingModeId, PricingGroup $pricingGroup ]; if ($orderingModeId === ProductListOrderingConfig::ORDER_BY_PRIORITY) { + $clone->sorting['product_type'] = 'asc'; $clone->sorting['ordering_priority'] = 'desc'; $clone->sorting['name.keyword'] = 'asc'; From be6de341774ab27871bf9814dca4ac0eab5e6239 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 23 Oct 2024 18:00:44 +0200 Subject: [PATCH 21/24] inquiries search in admin now searches in product name and catnum --- src/Model/Inquiry/InquiryFacade.php | 6 ++++++ src/Resources/translations/messages.cs.po | 6 +++--- src/Resources/translations/messages.en.po | 6 +++--- .../Admin/Content/Inquiry/quickSearchFormContent.html.twig | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Model/Inquiry/InquiryFacade.php b/src/Model/Inquiry/InquiryFacade.php index ec04d9ce9e..3f6a935b68 100644 --- a/src/Model/Inquiry/InquiryFacade.php +++ b/src/Model/Inquiry/InquiryFacade.php @@ -60,6 +60,12 @@ public function getInquiryListQueryBuilderByQuickSearchData( if ($quickSearchData->text !== null && $quickSearchData->text !== '') { $queryBuilder ->andWhere('( + NORMALIZED(i.productCatnum) LIKE NORMALIZED(:text) + OR + NORMALIZED(p.catnum) LIKE NORMALIZED(:text) + OR + NORMALIZED(pt.name) LIKE NORMALIZED(:text) + OR i.companyNumber LIKE :text OR NORMALIZED(i.lastName) LIKE NORMALIZED(:text) diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index a1f24bbbd9..295397bec7 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -3457,9 +3457,6 @@ msgstr "Vyhledávání ve nahraných souborech podle názvu souboru, přípony n msgid "Searching in all currencies" msgstr "Vyhledává se ve všech měnách" -msgid "Searching searches in company number, company name, customer last name, and customer email. It is possible to use wildcards * and ?." -msgstr "Vyhledávání hledá v IČ, názvu firmy, příjmení zákazníka, a e-mailu zákazníka. Je možné používat operátory * a ?." - msgid "Searching searches in complaint number, order number, delivery last name, delivery company name, last name from order, email from order, complaint author last name, complaint author company name. It is possible to use wildcards * and ?." msgstr "Vyhledávání hledá v čísle reklamace, čísle objednávky, příjmení pro doručení, názvu firmy pro doručení, příjmení z objednávky, e-mailu objednávky, příjmení zadavatele reklamace a názvu firmy zadavatele reklamace. Je možné používat operátory * a ?." @@ -3475,6 +3472,9 @@ msgstr "Vyhledávání hledá v názvu. Je možné používat operátory * a ?." msgid "Searching searches in order number, registered customer email, customer's email in order, customer's last name and company name. It is possible to use wildcards * and ?." msgstr "Vyhledávání hledá v číslu objednávky, e-mailu registrovaného zákazníka, e-mailu zákazníka v objednávce, příjmení zákazníka a názvu firmy. Je možné používat operátory * a ?." +msgid "Searching searches in product name, product catalog number, company number, company name, customer last name, and customer email. It is possible to use wildcards * and ?." +msgstr "Vyhledávání hledá v názvu produktu, katalogovém čísle produktu, IČ, názvu firmy, příjmení zákazníka, a e-mailu zákazníka. Je možné používat operátory * a ?." + msgid "Searching searches in slugs. It is possible to use wildcards * and ?." msgstr "Vyhledávání hledá ve sluzích. Je možné používat operátory * a ?." diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index 1445191202..ab69c063e6 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -3457,9 +3457,6 @@ msgstr "" msgid "Searching in all currencies" msgstr "" -msgid "Searching searches in company number, company name, customer last name, and customer email. It is possible to use wildcards * and ?." -msgstr "" - msgid "Searching searches in complaint number, order number, delivery last name, delivery company name, last name from order, email from order, complaint author last name, complaint author company name. It is possible to use wildcards * and ?." msgstr "" @@ -3475,6 +3472,9 @@ msgstr "" msgid "Searching searches in order number, registered customer email, customer's email in order, customer's last name and company name. It is possible to use wildcards * and ?." msgstr "" +msgid "Searching searches in product name, product catalog number, company number, company name, customer last name, and customer email. It is possible to use wildcards * and ?." +msgstr "" + msgid "Searching searches in slugs. It is possible to use wildcards * and ?." msgstr "" diff --git a/src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig b/src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig index c9fae1ef87..8089abdc94 100644 --- a/src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig +++ b/src/Resources/views/Admin/Content/Inquiry/quickSearchFormContent.html.twig @@ -4,7 +4,7 @@ {{ form_widget(quickSearchForm.text) }} {{ form_widget(quickSearchForm.submit, { label: 'Search [verb]'|trans, attr: { class: 'box-quick-search__btn'}, full_name: '' }) }} From 6aef288c8ae9465f03a3e78de9e957e35b03734f Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Wed, 23 Oct 2024 18:05:46 +0200 Subject: [PATCH 22/24] added domain filter to inquiry list in admin --- src/Controller/Admin/InquiryController.php | 14 ++++++++++++++ .../views/Admin/Content/Inquiry/list.html.twig | 2 ++ 2 files changed, 16 insertions(+) diff --git a/src/Controller/Admin/InquiryController.php b/src/Controller/Admin/InquiryController.php index 3a4d81c00c..b4c00b4eb1 100644 --- a/src/Controller/Admin/InquiryController.php +++ b/src/Controller/Admin/InquiryController.php @@ -4,6 +4,7 @@ namespace Shopsys\FrameworkBundle\Controller\Admin; +use Shopsys\FrameworkBundle\Component\Domain\AdminDomainFilterTabsFacade; use Shopsys\FrameworkBundle\Form\Admin\QuickSearch\QuickSearchFormData; use Shopsys\FrameworkBundle\Form\Admin\QuickSearch\QuickSearchFormType; use Shopsys\FrameworkBundle\Model\Inquiry\InquiryFacade; @@ -19,11 +20,13 @@ class InquiryController extends AdminBaseController * @param \Shopsys\FrameworkBundle\Model\Inquiry\InquiryGridFactory $inquiryGridFactory * @param \Shopsys\FrameworkBundle\Model\Inquiry\InquiryFacade $inquiryFacade * @param \Shopsys\FrameworkBundle\Model\Localization\Localization $localization + * @param \Shopsys\FrameworkBundle\Component\Domain\AdminDomainFilterTabsFacade $adminDomainFilterTabsFacade */ public function __construct( protected readonly InquiryGridFactory $inquiryGridFactory, protected readonly InquiryFacade $inquiryFacade, protected readonly Localization $localization, + protected readonly AdminDomainFilterTabsFacade $adminDomainFilterTabsFacade, ) { } @@ -34,6 +37,8 @@ public function __construct( #[Route(path: '/inquiry/list/')] public function listAction(Request $request): Response { + $domainFilterNamespace = 'inquiries'; + $quickSearchForm = $this->createForm(QuickSearchFormType::class, new QuickSearchFormData()); $quickSearchForm->handleRequest($request); @@ -42,8 +47,17 @@ public function listAction(Request $request): Response $this->localization->getAdminLocale(), ); + $selectedDomainId = $this->adminDomainFilterTabsFacade->getSelectedDomainId($domainFilterNamespace); + + if ($selectedDomainId !== null) { + $queryBuilder + ->andWhere('i.domainId = :selectedDomainId') + ->setParameter('selectedDomainId', $selectedDomainId); + } + return $this->render('@ShopsysFramework/Admin/Content/Inquiry/list.html.twig', [ 'gridView' => $this->inquiryGridFactory->createView($queryBuilder, $this->getCurrentAdministrator()), + 'domainFilterNamespace' => $domainFilterNamespace, 'quickSearchForm' => $quickSearchForm->createView(), ]); } diff --git a/src/Resources/views/Admin/Content/Inquiry/list.html.twig b/src/Resources/views/Admin/Content/Inquiry/list.html.twig index 4779a9be6c..bad0087466 100644 --- a/src/Resources/views/Admin/Content/Inquiry/list.html.twig +++ b/src/Resources/views/Admin/Content/Inquiry/list.html.twig @@ -20,5 +20,7 @@ + {{ render(controller('Shopsys\\FrameworkBundle\\Controller\\Admin\\DomainFilterController::domainFilterTabsAction', { namespace: domainFilterNamespace })) }} + {{ gridView.render() }} {% endblock %} From 23853459da6b0cbbbb56b8125cd26318754c381c Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Fri, 25 Oct 2024 13:01:47 +0200 Subject: [PATCH 23/24] sorting products by type is now more extendable --- .../Elasticsearch/ProductExportRepository.php | 18 ++++++++++++++++++ .../Scope/ProductExportFieldProvider.php | 1 + src/Model/Product/Search/FilterQuery.php | 2 +- .../Search/ProductElasticsearchConverter.php | 1 + .../ProductElasticsearchConverterTest.php | 2 ++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Model/Product/Elasticsearch/ProductExportRepository.php b/src/Model/Product/Elasticsearch/ProductExportRepository.php index e9d29904c2..efe2d477d1 100644 --- a/src/Model/Product/Elasticsearch/ProductExportRepository.php +++ b/src/Model/Product/Elasticsearch/ProductExportRepository.php @@ -187,6 +187,8 @@ protected function getExportedFieldValue(int $domainId, Product $product, string ProductExportFieldProvider::ACCESSORIES => $this->extractAccessoriesIds($product), ProductExportFieldProvider::HREFLANG_LINKS => $this->hreflangLinksFacade->getForProduct($product, $domainId), ProductExportFieldProvider::PRODUCT_TYPE => $this->extractProductType($product, $domainId), + ProductExportFieldProvider::PRIORITY_BY_PRODUCT_TYPE => $this->extractPriorityByProductType($product, $domainId), + default => throw new InvalidArgumentException(sprintf('There is no definition for exporting "%s" field to Elasticsearch', $field)), }; } @@ -331,6 +333,22 @@ protected function extractProductType(Product $product, int $domainId): string return $product->getProductType(); } + /** + * @param \Shopsys\FrameworkBundle\Model\Product\Product $product + * @param int $domainId + * @return int + */ + protected function extractPriorityByProductType(Product $product, int $domainId): int + { + $productType = $this->extractProductType($product, $domainId); + + return match ($productType) { + ProductTypeEnum::TYPE_BASIC => 20, + ProductTypeEnum::TYPE_INQUIRY => 10, + default => -100, + }; + } + /** * @param int $domainId * @param \Shopsys\FrameworkBundle\Model\Product\Product $product diff --git a/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php b/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php index e75f992756..5c77b9efe0 100644 --- a/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php +++ b/src/Model/Product/Elasticsearch/Scope/ProductExportFieldProvider.php @@ -44,6 +44,7 @@ class ProductExportFieldProvider public const string ACCESSORIES = 'accessories'; public const string HREFLANG_LINKS = 'hreflang_links'; public const string PRODUCT_TYPE = 'product_type'; + public const string PRIORITY_BY_PRODUCT_TYPE = 'priority_by_product_type'; /** * @return string[] diff --git a/src/Model/Product/Search/FilterQuery.php b/src/Model/Product/Search/FilterQuery.php index 94428b0661..ad1fc50c0f 100644 --- a/src/Model/Product/Search/FilterQuery.php +++ b/src/Model/Product/Search/FilterQuery.php @@ -75,7 +75,7 @@ public function applyOrdering(string $orderingModeId, PricingGroup $pricingGroup ]; if ($orderingModeId === ProductListOrderingConfig::ORDER_BY_PRIORITY) { - $clone->sorting['product_type'] = 'asc'; + $clone->sorting['priority_by_product_type'] = 'desc'; $clone->sorting['ordering_priority'] = 'desc'; $clone->sorting['name.keyword'] = 'asc'; diff --git a/src/Model/Product/Search/ProductElasticsearchConverter.php b/src/Model/Product/Search/ProductElasticsearchConverter.php index 5747819c53..3f0fab70fc 100644 --- a/src/Model/Product/Search/ProductElasticsearchConverter.php +++ b/src/Model/Product/Search/ProductElasticsearchConverter.php @@ -55,6 +55,7 @@ public function fillEmptyFields(array $product): array $result['seo_meta_description'] = $product['seo_meta_description'] ?? null; $result['hreflang_links'] = $product['hreflang_links'] ?? []; $result['product_type'] = $product['product_type'] ?? ProductTypeEnum::TYPE_BASIC; + $result['priority_by_product_type'] = $product['priority_by_product_type'] ?? 0; return $result; } diff --git a/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php b/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php index 50532c3db3..5ba1a1d797 100644 --- a/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php +++ b/tests/Unit/Model/Product/Search/ProductElasticsearchConverterTest.php @@ -54,6 +54,7 @@ public function testFillEmptyFields(): void 'seo_meta_description' => null, 'hreflang_links' => [], 'product_type' => 'basic', + 'priority_by_product_type' => 0, ]; $converter = new ProductElasticsearchConverter(); @@ -120,6 +121,7 @@ public function testFillEmptyParameterFields(): void 'seo_meta_description' => null, 'hreflang_links' => [], 'product_type' => 'basic', + 'priority_by_product_type' => 0, ]; $converter = new ProductElasticsearchConverter(); From e20c0e23eb9610a985b91a2deba054399b4b4bd7 Mon Sep 17 00:00:00 2001 From: Martin Grossmann Date: Mon, 4 Nov 2024 12:00:28 +0100 Subject: [PATCH 24/24] added demo product upon inquiry --- src/Resources/translations/messages.cs.po | 7 ++++--- src/Resources/translations/messages.en.po | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Resources/translations/messages.cs.po b/src/Resources/translations/messages.cs.po index 295397bec7..bc52d62f09 100644 --- a/src/Resources/translations/messages.cs.po +++ b/src/Resources/translations/messages.cs.po @@ -3667,12 +3667,12 @@ msgstr "Krátký popis se nastavuje u hlavní varianty." msgid "Short description of category" msgstr "Krátký popis kategorie" -msgid "Show detail" -msgstr "Zobrazit detail" - msgid "Show combinations" msgstr "Zobrazit kombinace" +msgid "Show detail" +msgstr "Zobrazit detail" + msgid "Show in the category" msgstr "Zobrazit v rozcestníku" @@ -4506,3 +4506,4 @@ msgstr "{1} Byl vytvořen %count% slevový kupón|[2,4] Byly vy msgid "{1}Sales representative \"%label%\" is assigned to %count% customer and will be removed if you proceed.

Customer:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[2,10]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[11,Inf]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%
+%extraCount% more

Do you really want to remove sales representative \"%label%\" permanently?" msgstr "{1}Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkovi a bude odstraněn, pokud budete pokračovat.

Zákazník:
%customersEnumeration%

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?|[2,10]Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkům a bude odstraněn, pokud budete pokračovat.

Zákazníci:
%customersEnumeration%

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?|[11,14]Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkům a bude odstraněn, pokud budete pokračovat.

Zákazníci:
%customersEnumeration%
+%extraCount% další

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?|[15,Inf]Obchodní zástupce \"%label%\" je přiřazen k %count% zákazníkům a bude odstraněn, pokud budete pokračovat.

Zákazníci:
%customersEnumeration%
+%extraCount% dalších

Opravdu chcete obchodního zástupce \"%label%\" trvale odstranit?" + diff --git a/src/Resources/translations/messages.en.po b/src/Resources/translations/messages.en.po index ab69c063e6..e2c5eb72fc 100644 --- a/src/Resources/translations/messages.en.po +++ b/src/Resources/translations/messages.en.po @@ -3667,10 +3667,10 @@ msgstr "" msgid "Short description of category" msgstr "" -msgid "Show detail" +msgid "Show combinations" msgstr "" -msgid "Show combinations" +msgid "Show detail" msgstr "" msgid "Show in the category" @@ -4506,3 +4506,4 @@ msgstr "" msgid "{1}Sales representative \"%label%\" is assigned to %count% customer and will be removed if you proceed.

Customer:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[2,10]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%

Do you really want to remove sales representative \"%label%\" permanently? |[11,Inf]Sales representative \"%label%\" is assigned to %count% customers and will be removed if you proceed.

Customers:
%customersEnumeration%
+%extraCount% more

Do you really want to remove sales representative \"%label%\" permanently?" msgstr "" +