From 4845f05d8daa3a6c6bd28d88c866c068182e3ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCller?= <p.mueller@pcsg.de> Date: Tue, 29 Jun 2021 15:55:15 +0200 Subject: [PATCH] feat: coupon send via mail now optional; refactor: delete hidden discounts associated with coupons when deleted --- events.xml | 4 + locale.xml | 5 ++ products.xml | 1 + src/QUI/ERP/Coupons/CouponCode.php | 11 +++ src/QUI/ERP/Coupons/Events.php | 90 ++++++++++++++++--- .../Products/CouponProductException.php | 12 +++ .../Coupons/Products/CouponProductType.php | 13 +-- src/QUI/ERP/Coupons/Products/Handler.php | 41 ++++++++- 8 files changed, 155 insertions(+), 22 deletions(-) create mode 100644 src/QUI/ERP/Coupons/Products/CouponProductException.php diff --git a/events.xml b/events.xml index de2021c..ae15fb7 100644 --- a/events.xml +++ b/events.xml @@ -8,6 +8,10 @@ fire="\QUI\ERP\Coupons\Events::onQuiqqerProductsProductCreate" /> + <event on="onQuiqqerProductsProductActivate" + fire="\QUI\ERP\Coupons\Events::onQuiqqerProductsProductActivate" + /> + <event on="onQuiqqer::order::orderProcessBasketEnd" fire="\QUI\ERP\Coupons\Events::templateOrderProcessBasketEnd" /> diff --git a/locale.xml b/locale.xml index 1389baf..75e45f9 100644 --- a/locale.xml +++ b/locale.xml @@ -259,6 +259,11 @@ Beste Grüße<br/> <![CDATA[Bitte geben Sie mindestens einen Rabatt an, der mit dem Coupon-Code verknüpft werden soll.]]></de> <en><![CDATA[Please provide ad least on discount that shall be linked to the Coupon code.]]></en> </locale> + + <locale name="exception.CouponProduct.no_vat_tax_type_required" html="true"> + <de><![CDATA[Das Gutschein-Produkt "[productTitle]" (#[productId]) kann nicht aktiviert werden.<br/><br/>Es handelt sich um einen Mehrzweck- bzw. Geldwert-Gutschein, welcher beim Kauf nicht versteuert werden darf.<br/><br/>Der im Produkt ausgewählte Steuertyp (Feld "MwSt.") enthält jedoch Steuersätze, die über 0% liegen.<br/><br/>Bitte wähle einen Steuertyp, welcher ausschließlich 0%-Steuersätze enthält oder ändere den Gutschein-Typ zu "Einzweck-Gutschein".]]></de> + <en><![CDATA[The coupon product "[productTitle]" (#[productId]) cannot be activated.<br/><br/>It is a multipurpose / cash value voucher, which must not be taxed at the time of purchase.<br/><br/>However, the tax type selected for this product ("VAT" field) contains tax entries with tax values higher than 0%.<br/><br/>Please choose a tax type which contains only 0% tax rates or change the coupon type to "Single purpose coupon".]]></en> + </locale> </groups> <groups name="quiqqer/coupons" datatype="js"> diff --git a/products.xml b/products.xml index cb83a20..7c2d97d 100644 --- a/products.xml +++ b/products.xml @@ -8,6 +8,7 @@ <icon>fa fa-credit-card</icon> <fields> <field>670</field> + <field>676</field> <field>671</field> <field>672</field> <field>673</field> diff --git a/src/QUI/ERP/Coupons/CouponCode.php b/src/QUI/ERP/Coupons/CouponCode.php index 87e2f9a..ff0f3bf 100644 --- a/src/QUI/ERP/Coupons/CouponCode.php +++ b/src/QUI/ERP/Coupons/CouponCode.php @@ -549,6 +549,17 @@ public function delete() } catch (QUI\DataBase\Exception $Exception) { QUI\System\Log::addError($Exception->getMessage()); } + + // If hidden discount are connected to this coupon -> delete them aswell + foreach ($this->getDiscounts() as $Discount) { + if (!empty($Discount->getAttribute('hidden'))) { + try { + $Discount->delete(); + } catch (\Exception $Exception) { + QUI\System\Log::writeException($Exception); + } + } + } } /** diff --git a/src/QUI/ERP/Coupons/Events.php b/src/QUI/ERP/Coupons/Events.php index 676d3fd..8d3543a 100644 --- a/src/QUI/ERP/Coupons/Events.php +++ b/src/QUI/ERP/Coupons/Events.php @@ -12,6 +12,7 @@ use QUI\ERP\Coupons\Handler as CouponsHandler; use QUI\ERP\Discount\EventHandling as DiscountEvents; use QUI\ERP\Coupons\Products\CouponProductType; +use QUI\ERP\Coupons\Products\CouponProductException; /** * Class Events @@ -432,7 +433,7 @@ public static function removeCouponsFromSession() protected static function createProductFields() { $fields = [ - CouponProductType::PRODUCT_FIELD_ID_TRANSFERABLE => [ + CouponProductType::PRODUCT_FIELD_ID_TRANSFERABLE => [ 'title' => [ 'de' => 'Gutschein-Code ist übertragbar', 'en' => 'Coupon code is transferable' @@ -442,7 +443,17 @@ protected static function createProductFields() 'standard' => false, 'requiredField' => false ], - CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF => [ + CouponProductType::PRODUCT_FIELD_ID_SEND_MAIL => [ + 'title' => [ + 'de' => 'Gutschein-Code per E-Mail senden', + 'en' => 'Send coupon code via email' + ], + 'type' => Fields::TYPE_BOOL, + 'public' => false, + 'standard' => false, + 'requiredField' => false + ], + CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF => [ 'title' => [ 'de' => 'Gutschein-Code als PDF bereitstellen', 'en' => 'Provide coupon code as PDF' @@ -452,7 +463,7 @@ protected static function createProductFields() 'standard' => false, 'requiredField' => false ], - CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT => [ + CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT => [ 'title' => [ 'de' => 'Gutschein Wert', 'en' => 'Coupon amount' @@ -462,7 +473,7 @@ protected static function createProductFields() 'standard' => false, 'requiredField' => true ], - CouponProductType::PRODUCT_FIELD_ID_DAYS_VALID => [ + CouponProductType::PRODUCT_FIELD_ID_DAYS_VALID => [ 'title' => [ 'de' => 'Gutschein-Code Gültigkeit (Tage)', 'en' => 'Coupon code validity (days)' @@ -472,7 +483,7 @@ protected static function createProductFields() 'standard' => false, 'requiredField' => true ], - CouponProductType::PRODUCT_FIELD_ID_COUPON_DESCRIPTION => [ + CouponProductType::PRODUCT_FIELD_ID_COUPON_DESCRIPTION => [ 'title' => [ 'de' => 'Gutschein-Beschreibung', 'en' => 'Coupon description' @@ -482,10 +493,10 @@ protected static function createProductFields() 'standard' => false, 'requiredField' => true ], - CouponProductType::PRODUCT_FIELD_ID_IS_PRODUCT_SPECIFIC_COUPON => [ + CouponProductType::PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON => [ 'title' => [ - 'de' => 'Ist Gutschein für spezifische Produkte ("Einzweck-Gutschein")', - 'en' => 'Is coupon for specific products ("single purpose coupon")' + 'de' => 'Ist Einzweck-Gutschein (Besteuerung bei Gutschein-Kauf)', + 'en' => 'Is single purpose coupon (taxation on voucher purchase)' ], 'type' => Fields::TYPE_BOOL, 'public' => false, @@ -534,6 +545,8 @@ protected static function createProductFields() * * @param ProductInterface $Product * @return void + * + * @throws QUI\Exception */ public static function onQuiqqerProductsProductCreate(ProductInterface $Product) { @@ -545,12 +558,13 @@ public static function onQuiqqerProductsProductCreate(ProductInterface $Product) $UniqueProduct->calc(); $fields = [ - CouponProductType::PRODUCT_FIELD_ID_TRANSFERABLE => true, - CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF => true, - CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT => $UniqueProduct->getPrice()->getValue(), - CouponProductType::PRODUCT_FIELD_ID_DAYS_VALID => 1095, // 3 years - CouponProductType::PRODUCT_FIELD_ID_COUPON_DESCRIPTION => null, -// CouponProductType::PRODUCT_FIELD_ID_IS_PRODUCT_SPECIFIC_COUPON => false // @todo later + CouponProductType::PRODUCT_FIELD_ID_TRANSFERABLE => true, + CouponProductType::PRODUCT_FIELD_ID_SEND_MAIL => true, + CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF => true, + CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT => $UniqueProduct->getPrice()->getValue(), + CouponProductType::PRODUCT_FIELD_ID_DAYS_VALID => 1095, // 3 years + CouponProductType::PRODUCT_FIELD_ID_COUPON_DESCRIPTION => null, + CouponProductType::PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON => false, ]; foreach ($fields as $fieldId => $value) { @@ -564,6 +578,13 @@ public static function onQuiqqerProductsProductCreate(ProductInterface $Product) } } + // No VAT tax types -> Choose first one found + $noVatTaxTypes = QUI\ERP\Coupons\Products\Handler::getNoVatTaxTypes(); + + if (!empty($noVatTaxTypes)) { + $Product->getField(Fields::FIELD_VAT)->setValue($noVatTaxTypes[0]->getId()); + } + try { $Product->update(QUI::getUsers()->getSystemUser()); } catch (\Exception $Exception) { @@ -571,6 +592,47 @@ public static function onQuiqqerProductsProductCreate(ProductInterface $Product) } } + /** + * quiqqer/products: onQuiqqerProductsProductActivate + * + * Check if a coupon product has the correct tax type! + * + * @param ProductInterface $Product + * + * @throws CouponProductException + * @throws QUI\Exception + */ + public static function onQuiqqerProductsProductActivate(ProductInterface $Product) + { + if (!($Product instanceof CouponProductType)) { + return; + } + + $isSinglePurposeCoupon = $Product->getFieldValue(CouponProductType::PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON); + + if (!empty($isSinglePurposeCoupon)) { + return; + } + + $productTaxTypeId = (int)$Product->getFieldValue(Fields::FIELD_VAT); + $noVatTaxTypes = QUI\ERP\Coupons\Products\Handler::getNoVatTaxTypes(); + + foreach ($noVatTaxTypes as $TaxType) { + if ($TaxType->getId() === $productTaxTypeId) { + return; + } + } + + throw new CouponProductException([ + 'quiqqer/coupons', + 'exception.CouponProduct.no_vat_tax_type_required', + [ + 'productTitle' => $Product->getTitle(), + 'productId' => $Product->getId() + ] + ]); + } + /** * quiqqer/order: onQuiqqerOrderCreated * diff --git a/src/QUI/ERP/Coupons/Products/CouponProductException.php b/src/QUI/ERP/Coupons/Products/CouponProductException.php new file mode 100644 index 0000000..262d514 --- /dev/null +++ b/src/QUI/ERP/Coupons/Products/CouponProductException.php @@ -0,0 +1,12 @@ +<?php + +namespace QUI\ERP\Coupons\Products; + +use QUI; + +/** + * Class CouponProductException + */ +class CouponProductException extends QUI\Exception +{ +} diff --git a/src/QUI/ERP/Coupons/Products/CouponProductType.php b/src/QUI/ERP/Coupons/Products/CouponProductType.php index 3d5ea3b..38a4c01 100644 --- a/src/QUI/ERP/Coupons/Products/CouponProductType.php +++ b/src/QUI/ERP/Coupons/Products/CouponProductType.php @@ -15,12 +15,13 @@ class CouponProductType extends DigitalProduct /** * Special fields for coupon products */ - const PRODUCT_FIELD_ID_TRANSFERABLE = 670; - const PRODUCT_FIELD_ID_GENERATE_PDF = 671; - const PRODUCT_FIELD_ID_COUPON_AMOUNT = 672; - const PRODUCT_FIELD_ID_DAYS_VALID = 673; - const PRODUCT_FIELD_ID_COUPON_DESCRIPTION = 674; - const PRODUCT_FIELD_ID_IS_PRODUCT_SPECIFIC_COUPON = 675; + const PRODUCT_FIELD_ID_TRANSFERABLE = 670; + const PRODUCT_FIELD_ID_GENERATE_PDF = 671; + const PRODUCT_FIELD_ID_COUPON_AMOUNT = 672; + const PRODUCT_FIELD_ID_DAYS_VALID = 673; + const PRODUCT_FIELD_ID_COUPON_DESCRIPTION = 674; + const PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON = 675; + const PRODUCT_FIELD_ID_SEND_MAIL = 676; /** * @param QUI\Locale $Locale diff --git a/src/QUI/ERP/Coupons/Products/Handler.php b/src/QUI/ERP/Coupons/Products/Handler.php index ab2c75c..68a392a 100644 --- a/src/QUI/ERP/Coupons/Products/Handler.php +++ b/src/QUI/ERP/Coupons/Products/Handler.php @@ -80,7 +80,9 @@ public static function createCouponCodesFromOrder(QUI\ERP\Order\AbstractOrder $O } // Send coupon via email - self::sendCouponMail($CouponCode, $Product, $Customer, $couponFilePathCustomerDir); + if ($Product->getFieldValue(CouponProductType::PRODUCT_FIELD_ID_SEND_MAIL)) { + self::sendCouponMail($CouponCode, $Product, $Customer, $couponFilePathCustomerDir); + } } catch (\Exception $Exception) { if ($Exception->getCode() === 404) { QUI\System\Log::writeDebugException($Exception); @@ -160,7 +162,7 @@ protected static function createDiscountFromProduct(Product $Product): Discount // Determine discount calculation basis based on coupon type $isProductSpecificCoupon = $Product->getFieldValue( - CouponProductType::PRODUCT_FIELD_ID_IS_PRODUCT_SPECIFIC_COUPON + CouponProductType::PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON ); // @todo für Einzweck-Gutscheine muss ein spezieller Rabatt-Typ verwendet werden @@ -369,4 +371,39 @@ protected static function getCouponViewData(CouponCode $CouponCode, Product $Pro 'validUntilDateFormatted' => $validUntilDateFormatted, ]; } + + /** + * Get all tax types of which all tax entries have 0% vat! + * + * These tax types are relevant for unspecific coupons (german: "Mehrzweck-Gutschein") + * which are NOT taxed at checkout but only when they are redeemed for a product. + * + * @return QUI\ERP\Tax\TaxType[] + */ + public static function getNoVatTaxTypes(): array + { + $noVatTaxTypes = []; + $TaxHandler = QUI\ERP\Tax\Handler::getInstance(); + $taxTypes = $TaxHandler->getTaxTypes(); + + /** @var QUI\ERP\Tax\TaxType $TaxType */ + foreach ($taxTypes as $TaxType) { + $taxEntries = $TaxHandler->getChildren([ + 'where' => [ + 'taxTypeId' => $TaxType->getId() + ] + ]); + + /** @var QUI\ERP\Tax\TaxEntry $TaxEntry */ + foreach ($taxEntries as $TaxEntry) { + if ($TaxEntry->getValue() > 0) { + continue 2; + } + } + + $noVatTaxTypes[] = $TaxType; + } + + return $noVatTaxTypes; + } } -- GitLab