diff --git a/events.xml b/events.xml index de2021cc66c7ac6038149a377093b99504c6744f..ae15fb722fb8109dabb62b011398375229f88098 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 1389bafc1f0efde4ba84c47198bfed06ac16a253..75e45f9d2ddf007b93d601a331456d5a78a0d697 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 cb83a200233686b2968e608445da0e8497511830..7c2d97d16acb6f0c4fe611e2a91e17a28dd62ff8 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 87e2f9a83ac2deabd103c2df1a283f8e60e7cc67..ff0f3bf5bbd5d620b2db0eedcc538eb0d08adf96 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 676d3fda336e8a49ea4538ce7a7028a0eda7f079..8d3543af255667e0245129e325129c4aa851130c 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 0000000000000000000000000000000000000000..262d514fa0767cc7349e2fa99b086211c2c38546 --- /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 3d5ea3b97cf0b9d11808dcf17b16935c29e32a14..38a4c0100e0a3c1a477ceaf9bf7fd31f0c092633 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 ab2c75c8df7a15e7111fe7617aec9e7229c0249c..68a392a9f069ff91086caaf0063ba5ff75d929e9 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; + } }