From 4dfdd231dd2d14b9a9fd6281cb518fdba2398013 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patrick=20M=C3=BCller?= <p.mueller@pcsg.de>
Date: Tue, 8 Jun 2021 16:28:56 +0200
Subject: [PATCH] temp commit

---
 bin/backend/controls/Manager.js               |   2 +-
 database.xml                                  |   4 -
 events.xml                                    |   4 +
 locale.xml                                    |  58 ++++
 products.xml                                  |   2 +
 src/QUI/ERP/Coupons/CouponCode.php            |  16 +
 src/QUI/ERP/Coupons/Events.php                |  81 ++---
 .../Coupons/Products/CouponProductType.php    |   8 +-
 src/QUI/ERP/Coupons/Products/Handler.php      | 282 +++++++++++++++---
 templates/CouponCode.body.html                | 110 +++++++
 templates/CouponCode.footer.html              |  41 +++
 templates/CouponCode.header.html              | 144 +++++++++
 12 files changed, 650 insertions(+), 102 deletions(-)
 create mode 100644 templates/CouponCode.body.html
 create mode 100644 templates/CouponCode.footer.html
 create mode 100644 templates/CouponCode.header.html

diff --git a/bin/backend/controls/Manager.js b/bin/backend/controls/Manager.js
index 57b4b97..625738a 100644
--- a/bin/backend/controls/Manager.js
+++ b/bin/backend/controls/Manager.js
@@ -344,7 +344,7 @@ define('package/quiqqer/coupons/bin/backend/controls/Manager', [
                 Popup.Loader.show();
 
                 if (CouponData) {
-                    CouponCodes.edit(CouponData.id, QUIFormUtils.getFormData(Form)).then(function (couponCodeId) {
+                    CouponCodes.edit(CouponData.id, QUIFormUtils.getFormData(Form)).then((couponCodeId) => {
                         if (!couponCodeId) {
                             Popup.Loader.hide();
                             return;
diff --git a/database.xml b/database.xml
index 717ebb7..7d721f3 100644
--- a/database.xml
+++ b/database.xml
@@ -2,10 +2,6 @@
 <database>
 
     <global>
-        <table name="products">
-            <field type="BIGINT(20) NULL DEFAULT NULL">couponDiscountId</field>
-        </table>
-
         <table name="quiqqer_coupons">
             <field type="BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY">id</field>
             <field type="MEDIUMTEXT NULL">discountIds</field>
diff --git a/events.xml b/events.xml
index 0af8a32..eee1145 100644
--- a/events.xml
+++ b/events.xml
@@ -24,6 +24,10 @@
            fire="\QUI\ERP\Coupons\Events::onQuiqqerOrderSuccessful"
     />
 
+    <event on="onQuiqqerOrderCreated"
+           fire="\QUI\ERP\Coupons\Events::onQuiqqerOrderCreated"
+    />
+
     <event on="onOrderProcess"
            fire="\QUI\ERP\Coupons\Events::onOrderProcess"
     />
diff --git a/locale.xml b/locale.xml
index 5ac287b..a6ddf62 100644
--- a/locale.xml
+++ b/locale.xml
@@ -89,6 +89,64 @@
     </groups>
 
     <groups name="quiqqer/coupons" datatype="php">
+        
+        <locale name="Coupon.mail.subject">
+            <de><![CDATA[Ihr persönlicher Gutschein-Code]]></de>
+            <en><![CDATA[Your personal coupon code]]></en>
+        </locale>
+        <locale name="Coupon.mail.body">
+            <de><![CDATA[
+<p>Hallo [customerName],</p>
+<p>vielen Dank für Ihren Gutschein-Einkauf. Hiermit erhalten Sie Ihren exklusiven Gutschein-Code für "[couponDescription]".</p>
+<p>
+<b>Gutschein-Code:</b> [couponCode]<br/>
+<b>Wert:</b> [discountAmountFormatted]<br/>
+<b>Ausgestellt am:</b> [createDateFormatted]<br/>
+<b>Einlösbar bis:</b> [validUntilDateFormatted]<br/>
+</p>
+<p>[transferableInfo]</p>
+<p>
+Beste Grüße<br/>
+[company]
+</p>
+]]></de>
+            <en><![CDATA[
+
+]]></en>
+        </locale>
+        <locale name="Coupon.mail.transferable_info.is_transferable">
+            <de><![CDATA[Sie dürfen diesen Gutschein verschenken! Der Gutschein-Code ist nicht personalisiert und kann von jedem in unserem Shop eingelöst werden. Bewahren Sie ihn daher gut auf!]]></de>
+            <en><![CDATA[You may give this coupn as a gift! The coupon code is not personalized and can be redeemed by anyone in our store. Therefore, keep it safe!]]></en>
+        </locale>
+        <locale name="Coupon.mail.transferable_info.is_not_transferable">
+            <de><![CDATA[Bitte bachten Sie: Dieser Gutschein kann nur von Ihnen persönlich eingesetzt werden, wenn Sie in unserem Shop eingeloggt sind. Dritte können den Gutschein-Code nicht einlösen.]]></de>
+            <en><![CDATA[Please note: This voucher can only be used by you personally when you are logged in to our store. Third parties can not redeem the voucher code.]]></en>
+        </locale>
+
+        <locale name="DownloadProduct.pdf.filename">
+            <de><![CDATA[Gutschein_[productTitle]__[date]]]></de>
+            <en><![CDATA[Coupon_[productTitle]__[date]]]></en>
+        </locale>
+        <locale name="CouponCode.tpl.header">
+            <de><![CDATA[G U T S C H E I N]]></de>
+            <en><![CDATA[C O U P O N]]></en>
+        </locale>
+        <locale name="CouponCode.tpl.labelAmount">
+            <de><![CDATA[über]]></de>
+            <en><![CDATA[for]]></en>
+        </locale>
+        <locale name="CouponCode.tpl.labelCreateDate">
+            <de><![CDATA[ausgestellt am]]></de>
+            <en><![CDATA[issued at]]></en>
+        </locale>
+        <locale name="CouponCode.tpl.labelValidUntilDate">
+            <de><![CDATA[einlösbar bis]]></de>
+            <en><![CDATA[redeemable until]]></en>
+        </locale>
+        <locale name="CouponCode.tpl.labelUser">
+            <de><![CDATA[für]]></de>
+            <en><![CDATA[for]]></en>
+        </locale>
 
         <locale name="Discount.default_title">
             <de><![CDATA[Coupon-Code [couponCode]]]></de>
diff --git a/products.xml b/products.xml
index e21d978..6a634fa 100644
--- a/products.xml
+++ b/products.xml
@@ -10,6 +10,8 @@
                     <field>670</field>
                     <field>671</field>
                     <field>672</field>
+                    <field>673</field>
+                    <field>674</field>
                 </fields>
             </fieldCategory>
         </fieldCategories>
diff --git a/src/QUI/ERP/Coupons/CouponCode.php b/src/QUI/ERP/Coupons/CouponCode.php
index 1ccd815..87e2f9a 100644
--- a/src/QUI/ERP/Coupons/CouponCode.php
+++ b/src/QUI/ERP/Coupons/CouponCode.php
@@ -240,6 +240,22 @@ public function getDiscounts(): array
         return $discounts;
     }
 
+    /**
+     * @return int[]
+     */
+    public function getUserIds(): array
+    {
+        return $this->userIds;
+    }
+
+    /**
+     * @return int[]
+     */
+    public function getGroupIds(): array
+    {
+        return $this->groupIds;
+    }
+
     /**
      * Redeems this CouponCode
      *
diff --git a/src/QUI/ERP/Coupons/Events.php b/src/QUI/ERP/Coupons/Events.php
index cb1b43e..52c4269 100644
--- a/src/QUI/ERP/Coupons/Events.php
+++ b/src/QUI/ERP/Coupons/Events.php
@@ -421,7 +421,7 @@ protected static function addCouponToOrder($Order, $coupon)
     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'
@@ -430,23 +430,41 @@ protected static function createProductFields()
                 'public'   => false,
                 'standard' => false
             ],
-            CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF  => [
+            CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF       => [
                 'title'    => [
                     'de' => 'Gutschein-Code als PDF bereitstellen',
-                    'en' => 'Provice coupon code as PDF'
+                    'en' => 'Provide coupon code as PDF'
                 ],
                 'type'     => Fields::TYPE_BOOL,
                 'public'   => false,
                 'standard' => false
             ],
-            CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT => [
+            CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT      => [
                 'title'    => [
-                    'de' => 'Gutschein-Code Wert',
-                    'en' => 'Coupon code amount'
+                    'de' => 'Gutschein Wert',
+                    'en' => 'Coupon amount'
                 ],
                 'type'     => Fields::TYPE_FLOAT,
                 'public'   => false,
                 'standard' => false
+            ],
+            CouponProductType::PRODUCT_FIELD_ID_DAYS_VALID         => [
+                'title'    => [
+                    'de' => 'Gutschein-Code Gültigkeit (Tage)',
+                    'en' => 'Coupon code validity (days)'
+                ],
+                'type'     => Fields::TYPE_INT,
+                'public'   => false,
+                'standard' => false
+            ],
+            CouponProductType::PRODUCT_FIELD_ID_COUPON_DESCRIPTION => [
+                'title'    => [
+                    'de' => 'Gutschein-Beschreibung',
+                    'en' => 'Coupon description'
+                ],
+                'type'     => Fields::TYPE_INPUT_MULTI_LANG,
+                'public'   => false,
+                'standard' => false
             ]
         ];
 
@@ -497,9 +515,11 @@ public static function onQuiqqerProductsProductCreate(ProductInterface $Product)
         }
 
         $fields = [
-            CouponProductType::PRODUCT_FIELD_ID_TRANSFERABLE  => true,
-            CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF  => true,
-            CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT => null
+            CouponProductType::PRODUCT_FIELD_ID_TRANSFERABLE       => true,
+            CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF       => true,
+            CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT      => null,
+            CouponProductType::PRODUCT_FIELD_ID_DAYS_VALID         => 1095, // 3 years
+            CouponProductType::PRODUCT_FIELD_ID_COUPON_DESCRIPTION => null
         ];
 
         foreach ($fields as $fieldId => $value) {
@@ -520,24 +540,6 @@ public static function onQuiqqerProductsProductCreate(ProductInterface $Product)
         }
     }
 
-    /**
-     * quiqqer/products: onQuiqqerProductsProductSave
-     *
-     * Create or update a discount that is associated with a product
-     *
-     * @param QUI\ERP\Products\Product\Product $Product
-     */
-    public static function onQuiqqerProductsProductSave(QUI\ERP\Products\Product\Product $Product)
-    {
-        if ($Product instanceof CouponProductType) {
-            try {
-                CouponProductsHandler::updateProductDiscount($Product);
-            } catch (\Exception $Exception) {
-                QUI\System\Log::writeException($Exception);
-            }
-        }
-    }
-
     /**
      * quiqqer/order: onQuiqqerOrderCreated
      *
@@ -548,29 +550,6 @@ public static function onQuiqqerProductsProductSave(QUI\ERP\Products\Product\Pro
      */
     public static function onQuiqqerOrderCreated(AbstractOrder $Order)
     {
-        /** @var QUI\ERP\Accounting\Article $Article */
-        foreach ($Order->getArticles() as $Article) {
-            try {
-                // Do not parse coupon codes / discounts
-                if (empty($Article->getId()) || !\is_numeric($Article->getId())) {
-                    continue;
-                }
-
-                $Product = Products::getProduct($Article->getId());
-
-                // Only parse download products
-                if (!($Product->getType() instanceof CouponProductType)) {
-                    continue;
-                }
-
-                CouponProductsHandler::createCouponCodeFromProduct($Product);
-            } catch (\Exception $Exception) {
-                if ($Exception->getCode() === 404) {
-                    QUI\System\Log::writeDebugException($Exception);
-                } else {
-                    QUI\System\Log::writeException($Exception);
-                }
-            }
-        }
+        QUI\ERP\Coupons\Products\Handler::createCouponCodesFromOrder($Order);
     }
 }
diff --git a/src/QUI/ERP/Coupons/Products/CouponProductType.php b/src/QUI/ERP/Coupons/Products/CouponProductType.php
index 88aa1eb..fd6d892 100644
--- a/src/QUI/ERP/Coupons/Products/CouponProductType.php
+++ b/src/QUI/ERP/Coupons/Products/CouponProductType.php
@@ -15,9 +15,11 @@ 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_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;
 
     /**
      * @param QUI\Locale $Locale
diff --git a/src/QUI/ERP/Coupons/Products/Handler.php b/src/QUI/ERP/Coupons/Products/Handler.php
index 9787329..90468b6 100644
--- a/src/QUI/ERP/Coupons/Products/Handler.php
+++ b/src/QUI/ERP/Coupons/Products/Handler.php
@@ -3,10 +3,14 @@
 namespace QUI\ERP\Coupons\Products;
 
 use QUI;
-use QUI\ERP\Order\AbstractOrder;
 use QUI\ERP\Products\Product\Product;
 use QUI\ERP\Discount\Discount;
 use QUI\ERP\Discount\Handler as DiscountHandler;
+use QUI\ERP\Coupons\Handler as CouponsHandler;
+use QUI\ERP\Coupons\CouponCode;
+use QUI\ERP\Products\Handler\Products as ProductsHandler;
+use QUI\HtmlToPdf\Document;
+use QUI\ERP\Customer\CustomerFiles;
 
 /**
  * Class Handler
@@ -16,42 +20,97 @@
 class Handler
 {
     /**
-     * Create coupon codes from an order
+     * Create coupons from all coupon products of an order.
      *
-     * @param Product $Product
+     * Each coupon created is sent via e-mail (either as PDF or text).
+     *
+     * @param QUI\ERP\Order\AbstractOrder $Order
+     * @return void
      */
-    public static function createCouponCodeFromProduct(Product $Product)
+    public static function createCouponCodesFromOrder(QUI\ERP\Order\AbstractOrder $Order): void
     {
+        $Customer = $Order->getCustomer();
+
+        /** @var QUI\ERP\Accounting\Article $Article */
+        foreach ($Order->getArticles() as $Article) {
+            try {
+                // Do not parse coupon codes / discounts
+                if (empty($Article->getId()) || !\is_numeric($Article->getId())) {
+                    continue;
+                }
+
+                $Product = ProductsHandler::getProduct($Article->getId());
+
+                // Only parse coupon products
+                if (!($Product instanceof CouponProductType)) {
+                    continue;
+                }
+
+                $CouponCode    = self::createCouponCodeFromProduct($Product, $Customer);
+                $couponPdfFile = null;
+
+                // Generate coupon PDF
+                if ($Product->getFieldValue(CouponProductType::PRODUCT_FIELD_ID_GENERATE_PDF)) {
+                    $couponPdfFile = self::createCouponCodePdf($CouponCode, $Product);
 
+                    // Add PDF file to customer files
+                    CustomerFiles::addFileToCustomer($Customer->getId(), $couponPdfFile);
+
+                    \unlink($couponPdfFile);
+
+                    $customerDir     = CustomerFiles::getFolderPath($Customer);
+                    $pdfFileCustomer = $customerDir.DIRECTORY_SEPARATOR.\basename($couponPdfFile, '.pdf');
+
+                    CustomerFiles::addFileToDownloadEntry($Customer->getId(), $pdfFileCustomer);
+                }
+
+                // Send coupon via email
+                self::sendCouponMail($CouponCode, $Product, $Customer, $couponPdfFile);
+            } catch (\Exception $Exception) {
+                if ($Exception->getCode() === 404) {
+                    QUI\System\Log::writeDebugException($Exception);
+                } else {
+                    QUI\System\Log::writeException($Exception);
+                }
+            }
+        }
     }
 
     /**
-     * Create and/or updates the discount that is associated with a coupon code product.
+     * Create coupon codes from an order
      *
      * @param Product $Product
-     * @return void
+     * @param QUI\Interfaces\Users\User $User - The user that bought the product
+     * @return CouponCode
      *
      * @throws QUI\Exception
+     * @throws \Exception
      */
-    public static function updateProductDiscount(Product $Product): void
+    protected static function createCouponCodeFromProduct(Product $Product, QUI\Interfaces\Users\User $User): CouponCode
     {
-        $Discount = self::getProductDiscount($Product);
+        $Discount         = self::createDiscountFromProduct($Product);
+        $couponAttributes = [
+            'maxUsages' => CouponsHandler::MAX_USAGE_ONCE
+        ];
+
+        // Valid until
+        $daysValid = $Product->getFieldValue(CouponProductType::PRODUCT_FIELD_ID_DAYS_VALID);
 
-        if (!$Discount) {
-            $Discount = self::createProductDiscount($Product);
+        if (empty($daysValid)) {
+            $daysValid = 1095; // 3 years;
         }
 
-        $discountAmount = $Product->getFieldValue(CouponProductType::PRODUCT_FIELD_ID_COUPON_AMOUNT);
+        $ValidUntil                         = \date_create('+ '.$daysValid.' days');
+        $couponAttributes['validUntilDate'] = $ValidUntil->format('Y-m-d');
 
-        if (empty($discountAmount)) {
-            $discountAmount = $Product->getPrice()->getValue();
-        }
+        // Transferable (=usable by other users or guests)
+        $isTransferable = $Product->getFieldValue(CouponProductType::PRODUCT_FIELD_ID_TRANSFERABLE);
 
-        $Discount->setAttributes([
-            'discount' => $discountAmount
-        ]);
+        if (empty($isTransferable)) {
+            $couponAttributes['userIds'] = [$User->getId()];
+        }
 
-        $Discount->update();
+        return CouponsHandler::createCouponCode([$Discount->getId()], $couponAttributes);
     }
 
     /**
@@ -62,7 +121,7 @@ public static function updateProductDiscount(Product $Product): void
      *
      * @throws QUI\Exception
      */
-    protected static function createProductDiscount(Product $Product): Discount
+    protected static function createDiscountFromProduct(Product $Product): Discount
     {
         $Handler = DiscountHandler::getInstance();
 
@@ -76,7 +135,8 @@ protected static function createProductDiscount(Product $Product): Discount
         $NewDiscount = $Handler->createChild([
             'active'        => 1,
             'discount'      => $discountAmount,
-            'discount_type' => DiscountHandler::DISCOUNT_TYPE_CURRENCY
+            'discount_type' => DiscountHandler::DISCOUNT_TYPE_CURRENCY,
+            'hidden'        => 1
         ]);
 
         $L             = new QUI\Locale();
@@ -100,44 +160,180 @@ protected static function createProductDiscount(Product $Product): Discount
 
         \QUI\Translator::publish('quiqqer/discount');
 
-        QUI::getDataBase()->update(
-            QUI\ERP\Products\Utils\Tables::getProductTableName(),
-            [
-                'couponDiscountId' => $NewDiscount->getId()
-            ],
+        return $NewDiscount;
+    }
+
+    /**
+     * @param CouponCode $CouponCode - The coupon code
+     * @param Product $Product - The product the coupon code was created from
+     * @return string - PDF file path
+     */
+    public static function createCouponCodePdf(CouponCode $CouponCode, Product $Product): string
+    {
+        $fileName = QUI::getLocale()->get(
+            'quiqqer/coupons',
+            'DownloadProduct.pdf.filename',
             [
-                'id' => $Product->getId()
+                'productTitle' => \str_replace(' ', '_', $Product->getTitle()),
+                'date'         => \date('Y_m_d')
             ]
         );
 
-        return $NewDiscount;
+        $Document = new Document([
+            'filename' => $fileName.'.pdf',
+        ]);
+
+//        $Document->setAttribute('foldingMarks', true);
+        $Document->setAttribute('disableSmartShrinking', true);
+        $Document->setAttribute('headerSpacing', 0);
+        $Document->setAttribute('marginTop', 85);
+        $Document->setAttribute('marginBottom', 40);
+        $Document->setAttribute('marginLeft', 0);
+        $Document->setAttribute('marginRight', 0);
+
+        $Engine = QUI::getTemplateManager()->getEngine();
+
+        $Engine->assign(\array_merge(
+            [
+                'CouponCode' => $CouponCode,
+                'Product'    => $Product->getViewFrontend(),
+            ],
+            self::getCouponViewData($CouponCode, $Product)
+        ));
+
+        $tplDir = QUI::getPackage('quiqqer/coupons')->getDir().'templates/';
+
+        try {
+            $Document->setHeaderHTML($Engine->fetch($tplDir.'CouponCode.header.html'));
+            $Document->setContentHTML($Engine->fetch($tplDir.'CouponCode.body.html'));
+            $Document->setFooterHTML($Engine->fetch($tplDir.'CouponCode.footer.html'));
+        } catch (\Exception $Exception) {
+            QUI\System\Log::writeException($Exception);
+        }
+
+        return $Document->createPDF();
+    }
+
+    /**
+     * Send coupon code via e-mail to a customer.
+     *
+     * @param CouponCode $CouponCode - The coupon code
+     * @param Product $Product - The product the coupon code was created from
+     * @param QUI\Interfaces\Users\User $Customer
+     * @param string|null $couponPdfFile (optional) - Coupon PDF that is attached to the email
+     *
+     * @throws \Exception
+     */
+    public static function sendCouponMail(
+        CouponCode $CouponCode,
+        Product $Product,
+        QUI\Interfaces\Users\User $Customer,
+        ?string $couponPdfFile
+    ) {
+        $recipient = QUI\ERP\Customer\Utils::getInstance()->getEmailByCustomer($Customer);
+
+        if (empty($recipient)) {
+            QUI\System\Log::addWarning(
+                'Cannot send coupon code e-mail to customer #'.$Customer->getUniqueId().' because user has no'
+                .' email address!'
+            );
+
+            return;
+        }
+
+        $Mailer = QUI::getMailManager()->getMailer();
+        $Mailer->addRecipient($recipient);
+
+        $Mailer->setSubject(QUI::getLocale()->get('quiqqer/coupons', 'Coupon.mail.subject'));
+
+        $couponViewData = self::getCouponViewData($CouponCode, $Product);
+
+        if ($couponViewData['isTransferable']) {
+            $transferableInfo = QUI::getLocale()->get(
+                'quiqqer/coupons',
+                'Coupon.mail.transferable_info.is_transferable'
+            );
+        } else {
+            $transferableInfo = QUI::getLocale()->get(
+                'quiqqer/coupons',
+                'Coupon.mail.transferable_info.is_not_transferable'
+            );
+        }
+
+        $Mailer->setBody(
+            QUI::getLocale()->get(
+                'quiqqer/coupons',
+                'Coupon.mail.body',
+                \array_merge(
+                    $couponViewData,
+                    [
+                        'customerName'     => $Customer->getName(),
+                        'company'          => QUI\ERP\Defaults::conf('company', 'name'),
+                        'transferableInfo' => $transferableInfo
+                    ]
+                )
+            )
+        );
+
+        if ($couponPdfFile) {
+            $Mailer->addAttachment($couponPdfFile);
+        }
+
+        $Mailer->send();
     }
 
     /**
-     * Get the discount that is associated with a coupon code product.
+     * Get view data of a coupon.
      *
+     * @param CouponCode $CouponCode
      * @param Product $Product
-     * @return Discount|false - Discount or false if no discount created yet
+     * @return array
      *
-     * @throws QUI\Exception
+     * @throws QUI\ERP\Products\Product\Exception
+     * @throws QUI\Users\Exception
      */
-    public static function getProductDiscount(Product $Product)
+    protected static function getCouponViewData(CouponCode $CouponCode, Product $Product): array
     {
-        $result = QUI::getDataBase()->fetch([
-            'select' => ['couponDiscountId'],
-            'from'   => QUI\ERP\Products\Utils\Tables::getProductTableName(),
-            'where'  => [
-                'id' => $Product->getId()
-            ]
-        ]);
+        $discounts      = $CouponCode->getDiscounts();
+        $Discount       = $discounts[0];
+        $Currency       = QUI\ERP\Defaults::getCurrency();
+        $DiscountAmount = new QUI\ERP\Money\Price($Discount->getAttribute('discount'), $Currency);
 
-        if (empty($result)) {
-            return false;
+        $Locale = QUI::getLocale();
+
+        // Check if coupon is restricted to specific user
+        $User                = false;
+        $restrictedToUserIds = $CouponCode->getUserIds();
+
+        if (!empty($restrictedToUserIds)) {
+            $User = QUI::getUsers()->get($restrictedToUserIds[0]);
         }
 
-        /** @var Discount $Discount */
-        $Discount = DiscountHandler::getInstance()->getChild($result[0]['couponDiscountId']);
+        // Parse valid until date
+        $dateFormat              = QUI\ERP\Defaults::getDateFormat();
+        $ValidUntilDate          = $CouponCode->getValidUntilDate();
+        $validUntilDateFormatted = '-';
+
+        if ($ValidUntilDate) {
+            $validUntilDateFormatted = $Locale->formatDate(
+                $ValidUntilDate->getTimestamp(),
+                $dateFormat
+            );
+        }
 
-        return $Discount;
+        return [
+            'isTransferable'          => empty($User),
+            'couponCode'              => $CouponCode->getCode(),
+            'userName'                => $User ? $User->getName() : false,
+            'couponDescription'       => $Product->getFieldValueByLocale(
+                CouponProductType::PRODUCT_FIELD_ID_COUPON_DESCRIPTION
+            ),
+            'discountAmountFormatted' => $DiscountAmount->getDisplayPrice(),
+            'createDateFormatted'     => $Locale->formatDate(
+                $CouponCode->getCreateDate()->getTimestamp(),
+                $dateFormat
+            ),
+            'validUntilDateFormatted' => $validUntilDateFormatted,
+        ];
     }
 }
diff --git a/templates/CouponCode.body.html b/templates/CouponCode.body.html
new file mode 100644
index 0000000..656ab79
--- /dev/null
+++ b/templates/CouponCode.body.html
@@ -0,0 +1,110 @@
+<style>
+    *, *:before, *:after {
+        box-sizing: border-box;
+    }
+
+    .couponcode-body {
+        padding: 5mm 15mm 0 20mm;
+        text-align: center;
+        width: 210mm;
+    }
+
+    .couponcode-body-extra p {
+        line-height: 20px;
+        margin: 0;
+        padding: 0;
+    }
+
+    .couponcode-body-code {
+        background-color: #dedede;
+        border-radius: 5px;
+        font-family: monospace;
+        font-size: 2em;
+        margin: 20px auto;
+        padding: 20px;
+        width: 225px;
+    }
+
+    .couponcode-body-data {
+        margin-top: 80px;
+    }
+
+    .couponcode-body-data table {
+        margin: auto;
+        text-align: left;
+        width: 50%;
+    }
+
+    .couponcode-body-data-label {
+        font-size: 12px;
+    }
+
+    .couponcode-body-data-value {
+        background-color: #f5f5f5;
+        display: block;
+        padding: 10px;
+    }
+</style>
+
+<div class="couponcode-body">
+    <h1>
+        {locale group="quiqqer/coupons" var="CouponCode.tpl.header"}
+    </h1>
+
+    {if !empty($couponDescription)}
+    <div class="couponcode-body-description">
+        {$couponDescription}
+    </div>
+    {/if}
+
+    <div class="couponcode-body-code">
+        {$CouponCode->getCode()}
+    </div>
+
+    <div class="couponcode-body-data">
+        <table>
+            <tbody>
+            <tr>
+                <td colspan="2">
+                        <span class="couponcode-body-data-label">
+                            {locale group="quiqqer/coupons" var="CouponCode.tpl.labelAmount"}
+                        </span>
+                    <span class="couponcode-body-data-value">
+                            {$discountAmountFormatted}
+                        </span>
+                </td>
+            </tr>
+            {if $userName}
+            <tr>
+                <td colspan="2">
+                        <span class="couponcode-body-data-label">
+                            {locale group="quiqqer/coupons" var="CouponCode.tpl.labelUser"}
+                        </span>
+                    <span class="couponcode-body-data-value">
+                            {$userName}
+                        </span>
+                </td>
+            </tr>
+            {/if}
+            <tr>
+                <td>
+                        <span class="couponcode-body-data-label">
+                            {locale group="quiqqer/coupons" var="CouponCode.tpl.labelCreateDate"}
+                        </span>
+                    <span class="couponcode-body-data-value">
+                            {$createDateFormatted}
+                        </span>
+                </td>
+                <td>
+                        <span class="couponcode-body-data-label">
+                            {locale group="quiqqer/coupons" var="CouponCode.tpl.labelValidUntilDate"}
+                        </span>
+                    <span class="couponcode-body-data-value">
+                            {$validUntilDateFormatted}
+                        </span>
+                </td>
+            </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
diff --git a/templates/CouponCode.footer.html b/templates/CouponCode.footer.html
new file mode 100644
index 0000000..00e6501
--- /dev/null
+++ b/templates/CouponCode.footer.html
@@ -0,0 +1,41 @@
+<style>
+    *, *:before, *:after {
+        box-sizing: border-box;
+    }
+
+    .couponcode-footer {
+        height: 40mm;
+        clear: both;
+        display: inline-block;
+        margin-top: 20px;
+        margin-left: 0;
+        padding: 0 15mm 0 20mm;
+        position: relative;
+        top: 0;
+        width: 210mm;
+    }
+
+    #pages {
+        font-size: 0.75rem;
+        position: absolute;
+        right: 13mm;
+        top: 30mm;
+        white-space: nowrap;
+    }
+
+    .couponcode-footer-text {
+        font-size: 0.7em;
+        position: relative;
+        text-align: center;
+        top: 15mm;
+        width: 100%;
+    }
+
+</style>
+
+<footer class="couponcode-footer">
+    <div class="couponcode-footer-text">
+        {locale group="quiqqer/erp-accounting-templates" var="header"
+        shopownerAdressShort=QUI\ERP\Defaults::getShortAddress()}
+    </div>
+</footer>
\ No newline at end of file
diff --git a/templates/CouponCode.header.html b/templates/CouponCode.header.html
new file mode 100644
index 0000000..7443c6d
--- /dev/null
+++ b/templates/CouponCode.header.html
@@ -0,0 +1,144 @@
+<style>
+    *, *:before, *:after {
+        box-sizing: border-box;
+    }
+
+    .couponcode-header {
+        display: inline-block;
+        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+        height: 85mm;
+        margin-left: 0;
+        padding: 5mm 15mm 5mm 20mm;
+        position: relative;
+        text-align: center;
+        width: 210mm;
+    }
+
+    .couponcode-header-image {
+        height: 40mm;
+        margin-top: 5mm;
+        margin-bottom: 0;
+        position: relative;
+    }
+
+    .couponcode-header-image img {
+        height: auto;
+        margin: 5mm 0 0 0;
+        max-height: 20mm;
+        max-width: 350px;
+    }
+
+    .couponcode-body-header-text {
+        border-bottom: 1px solid #999;
+        clear: both;
+        display: inline-block;
+        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+        font-size: 10px;
+        line-height: 2rem;
+        margin-bottom: 1rem;
+        width: 100%;
+    }
+
+    .couponcode-body-header-text p {
+        margin: 0;
+    }
+
+    .couponcode-customer,
+    .couponcode-customer address {
+        display: block;
+        float: left;
+        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+        width: 55%;
+        font-style: normal !important;
+        font-size: 14px !important;
+    }
+
+    .couponcode-data {
+        float: right;
+        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+        width: 45%;
+    }
+
+    .value-id,
+    .value-date,
+    .value-customer {
+        font-weight: bold;
+    }
+
+    .couponcode-data-highlight {
+        background: #e5e5e5;
+        border-radius: 3px;
+        margin-left: auto;
+        padding: 10px;
+        width: 100%;
+    }
+
+    .delivery-address {
+        font-size: 12px;
+        margin-top: 0;
+        margin-left: auto;
+        padding: 10px;
+        width: 100%;
+    }
+
+    .delivery-address-label {
+        font-weight: bold;
+    }
+
+    .couponcode-data table {
+        border-spacing: 0;
+        border-collapse: collapse;
+    }
+
+    .couponcode-data table td + td {
+        line-height: 20px;
+        padding: 0 0 0 10px;
+    }
+
+    .couponcode-data-highlight h2 {
+        font-size: 1.2rem;
+        margin: 0 0 10px 0;
+        padding: 0;
+        text-align: left;
+        text-transform: uppercase;
+    }
+
+    .couponcode-data-highlight table {
+        border-collapse: separate;
+        width: 100%;
+    }
+
+    .couponcode-data-highlight td {
+        font-size: 14px;
+    }
+
+    .couponcode-data-highlight td:first-child {
+        width: 35%;
+    }
+
+    .couponcode-data-highlight td span {
+        background: #fff;
+        border-radius: 3px;
+        display: inline-block;
+        margin: 0 0 2px;
+        padding: 0 10px;
+        width: 160px;
+    }
+
+    .couponcode-customer h3 {
+        margin: 0 0 10px 0;
+    }
+</style>
+
+<!-- Workaround to achieve full a4 format as PDF file -->
+<div style="position: absolute; left: 0; top: 0; width: 210mm; background: #fff; height: 5mm; z-index: -1;"></div>
+<div style="position: absolute; left: 0; top: 0; width: 5mm; background: #fff; height: 297mm; z-index: -1;"></div>
+
+<div class="couponcode-header couponcode-header-top">
+    <div class="couponcode-header-image">
+        {assign var=Logo value=\QUI\ERP\Defaults::getLogo()}
+        {image image=$Logo height="120" svgtopng=1 host=1}
+    </div>
+
+    <div class="couponcode-header-line"></div>
+</div>
-- 
GitLab