From 73fd2756f4daf33e3672234901d35f0bbbc85c31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Patrick=20M=C3=BCller?= <p.mueller@pcsg.de>
Date: Tue, 28 Aug 2018 17:43:26 +0200
Subject: [PATCH] temp commit

---
 bin/controls/Manager.Create.html             |  20 +-
 bin/controls/Manager.css                     |  30 +--
 bin/controls/Manager.html                    |   2 +-
 bin/controls/Manager.js                      | 189 ++++---------------
 bin/controls/settings/CodeGeneratorSelect.js |   2 +-
 locale.xml                                   | 143 +++++++++++++-
 src/QUI/ERP/Coupons/CouponCode.php           |  15 +-
 src/QUI/ERP/Coupons/Handler.php              | 156 +++++++--------
 8 files changed, 302 insertions(+), 255 deletions(-)

diff --git a/bin/controls/Manager.Create.html b/bin/controls/Manager.Create.html
index 3f8d80e..906a68b 100644
--- a/bin/controls/Manager.Create.html
+++ b/bin/controls/Manager.Create.html
@@ -1,16 +1,24 @@
-<div class="quiqqer-invitecode-manager-create">
+<div class="quiqqer-coupons-manager-create">
     <form>
         <label>
             <span>{{labelTitle}}</span>
             <input type="text" name="title"/>
         </label>
         <label>
-            <span>{{labelEmail}}</span>
-            <input type="email" name="email"/>
+            <span>{{labelCode}}</span>
+            <input type="text" name="code"/>
         </label>
-        <label class="quiqqer-invitecode-manager-create-sendmail">
-            <span>{{labelSendMail}}</span>
-            <input type="checkbox" name="sendmail" disabled/>
+        <label>
+            <span>{{labelUsers}}</span>
+            <input type="hidden" name="userIds" data-qui="controls/users/Select"/>
+        </label>
+        <label>
+            <span>{{labelUsers}}</span>
+            <input type="hidden" name="groupIds" data-qui="controls/groups/Select"/>
+        </label>
+        <label>
+            <span>{{labelReusable}}</span>
+            <input type="checkbox" name="reusable"/>
         </label>
         <label>
             <span>{{labelDate}}</span>
diff --git a/bin/controls/Manager.css b/bin/controls/Manager.css
index 1eebca9..feaa18e 100644
--- a/bin/controls/Manager.css
+++ b/bin/controls/Manager.css
@@ -1,62 +1,64 @@
-.quiqqer-invitecode-manager {
+.quiqqer-coupons-manager {
     float: left;
     height: 100%;
     width: 100%;
 }
 
-.quiqqer-invitecode-manager-table {
+.quiqqer-coupons-manager-table {
     float: left;
     height: 100% !important;
     width: 100%;
 }
 
-.quiqqer-invitecode-manager-create label {
+.quiqqer-coupons-manager-create label {
     float: left;
     width: 100%;
 }
 
-.quiqqer-invitecode-manager-create label:not(:last-of-type) {
+.quiqqer-coupons-manager-create label:not(:last-of-type) {
     margin-bottom: 15px;
 }
 
-.quiqqer-invitecode-manager-create label span {
+.quiqqer-coupons-manager-create label > span {
     float: left;
     margin-bottom: 5px;
 }
 
-.quiqqer-invitecode-manager-create-sendmail span,
-.quiqqer-invitecode-manager-create-sendmail input {
+.quiqqer-coupons-manager-create-sendmail span,
+.quiqqer-coupons-manager-create-sendmail input {
     float: right !important;
     width: unset !important;
 }
 
-.quiqqer-invitecode-manager-create input {
+.quiqqer-coupons-manager-create input[type="text"],
+.quiqqer-coupons-manager-create input[type="number"],
+.quiqqer-coupons-manager-create input[type="date"] {
     width: 100%;
 }
 
-.quiqqer-invitecode-manager-tbl-status-unused {
+.quiqqer-coupons-manager-tbl-status-unused {
     color: #bb252a;
 }
 
-.quiqqer-invitecode-manager-tbl-status-used {
+.quiqqer-coupons-manager-tbl-status-used {
     color: #608a0e;
 }
 
-.quiqqer-invitecode-manager-tbl-status-invalid {
+.quiqqer-coupons-manager-tbl-status-invalid {
     color: #454545;
     font-style: italic;
 }
 
-.quiqqer-invitecode-manager-sendmail-resend {
+.quiqqer-coupons-manager-sendmail-resend {
     float: right;
     margin-top: 20px;
 }
 
-.quiqqer-invitecode-manager-sendmail-resend input {
+.quiqqer-coupons-manager-sendmail-resend input {
     float: left;
 }
 
-.quiqqer-invitecode-manager-tbl-user-not_exist {
+.quiqqer-coupons-manager-tbl-user-not_exist {
     color: #999;
     font-style: italic;
 }
\ No newline at end of file
diff --git a/bin/controls/Manager.html b/bin/controls/Manager.html
index 8529310..3c35646 100644
--- a/bin/controls/Manager.html
+++ b/bin/controls/Manager.html
@@ -1 +1 @@
-<div class="quiqqer-invitecode-manager-table"></div>
\ No newline at end of file
+<div class="quiqqer-coupons-manager-table"></div>
\ No newline at end of file
diff --git a/bin/controls/Manager.js b/bin/controls/Manager.js
index fde8614..36460b1 100644
--- a/bin/controls/Manager.js
+++ b/bin/controls/Manager.js
@@ -6,6 +6,7 @@
  */
 define('package/quiqqer/coupons/bin/controls/Manager', [
 
+    'qui/QUI',
     'qui/controls/desktop/Panel',
     'qui/controls/loader/Loader',
     'qui/controls/windows/Popup',
@@ -25,7 +26,7 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
     'text!package/quiqqer/coupons/bin/controls/Manager.Create.html',
     'css!package/quiqqer/coupons/bin/controls/Manager.css'
 
-], function (QUIPanel, QUILoader, QUIPopup, QUIConfirm, QUIButton, QUISeparator,
+], function (QUI, QUIPanel, QUILoader, QUIPopup, QUIConfirm, QUIButton, QUISeparator,
              Grid, QUIFormUtils, CouponCodes, QUILocale, Mustache, template, templateCreate) {
     "use strict";
 
@@ -105,17 +106,6 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
 
             this.addButton(new QUISeparator());
 
-            this.addButton({
-                name     : 'sendmail',
-                text     : QUILocale.get(lg, 'controls.manager.tbl.btn.sendmail'),
-                textimage: 'fa fa-envelope-o',
-                events   : {
-                    onClick: this.$sendMail
-                }
-            });
-
-            this.addButton(new QUISeparator());
-
             this.addButton({
                 name     : 'delete',
                 text     : QUILocale.get(lg, 'controls.manager.tbl.btn.delete'),
@@ -159,7 +149,7 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
             var Content = this.getContent();
 
             this.$GridParent = Content.getElement(
-                '.quiqqer-invitecode-manager-table'
+                '.quiqqer-coupons-manager-table'
             );
 
             this.$Grid = new Grid(this.$GridParent, {
@@ -183,27 +173,16 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
                     dataIndex: 'status',
                     dataType : 'node',
                     width    : 200
-                }, {
-                    header   : QUILocale.get(lg, 'controls.manager.tbl.header.email'),
-                    dataIndex: 'email',
-                    dataType : 'string',
-                    width    : 200
-                }, {
-                    header   : QUILocale.get(lg, 'controls.manager.tbl.header.mailSent'),
-                    dataIndex: 'mailSent',
-                    dataType : 'node',
-                    width    : 30
-                }, {
-                    header   : QUILocale.get(lg, 'controls.manager.tbl.header.user'),
-                    dataIndex: 'user',
-                    dataType : 'node',
-                    width    : 200,
-                    className: 'clickable'
                 }, {
                     header   : QUILocale.get(lg, 'controls.manager.tbl.header.validUntilDate'),
                     dataIndex: 'validUntilDate',
                     dataType : 'string',
                     width    : 150
+                }, {
+                    header   : QUILocale.get(lg, 'controls.manager.tbl.header.reusable'),
+                    dataIndex: 'reusable',
+                    dataType : 'node',
+                    width    : 150
                 }, {
                     header   : QUILocale.get(lg, 'controls.manager.tbl.header.createDate'),
                     dataIndex: 'createDate',
@@ -231,7 +210,6 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
                     var selected = self.$Grid.getSelectedData();
 
                     self.getButtons('delete').enable();
-                    self.getButtons('sendmail').enable();
 
                     if (!event.cell.hasClass('clickable')) {
                         return;
@@ -276,7 +254,6 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
             var self = this;
 
             self.getButtons('delete').disable();
-            self.getButtons('sendmail').disable();
 
             var GridParams = {
                 sortOn : Grid.getAttribute('sortOn'),
@@ -309,33 +286,29 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
          * @param {Object} GridData
          */
         $setGridData: function (GridData) {
-            var textUnused       = QUILocale.get(lg, 'controls.manager.tbl.status.unused');
-            var textUnlimited    = QUILocale.get(lg, 'controls.manager.tbl.validUntil.unlimited');
-            var textInvalid      = QUILocale.get(lg, 'controls.manager.tbl.status.invalid');
-            var textUserNotExist = QUILocale.get(lg, 'controls.manager.tbl.user.not_exist');
+            var textUnlimited = QUILocale.get(lg, 'controls.manager.tbl.validUntil.unlimited');
 
             for (var i = 0, len = GridData.data.length; i < len; i++) {
                 var Row = GridData.data[i];
 
-                if (!Row.email) {
-                    Row.email = '-';
-                }
-
                 var StatusElm = new Element('span', {
-                    'class': 'quiqqer-invitecode-manager-tbl-status'
+                    'class': 'quiqqer-coupons-manager-tbl-status'
                 });
 
-                if (!Row.valid) {
-                    StatusElm.set('html', textInvalid);
-                    StatusElm.addClass('quiqqer-invitecode-manager-tbl-status-invalid');
-                } else if (!Row.useDate) {
-                    StatusElm.set('html', textUnused);
-                    StatusElm.addClass('quiqqer-invitecode-manager-tbl-status-unused');
+                var usageCount = Row.usages.length;
+
+                if (!Row.isValid) {
+                    StatusElm.set('html', QUILocale.get(lg, 'controls.manager.tbl.status.invalid', {
+                        usageCount: usageCount
+                    }));
+
+                    StatusElm.addClass('quiqqer-coupons-manager-tbl-status-invalid');
                 } else {
-                    StatusElm.set('html', QUILocale.get(lg, 'controls.manager.tbl.status.used', {
-                        useDate: Row.useDate
+                    StatusElm.set('html', QUILocale.get(lg, 'controls.manager.tbl.status.valid', {
+                        usageCount: usageCount
                     }));
-                    StatusElm.addClass('quiqqer-invitecode-manager-tbl-status-used');
+
+                    StatusElm.addClass('quiqqer-coupons-manager-tbl-status-used');
                 }
 
                 Row.status = StatusElm;
@@ -344,37 +317,21 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
                     Row.validUntilDate = textUnlimited;
                 }
 
-                if (!Row.userId) {
-                    if (Row.useDate) {
-                        Row.user = new Element('span', {
-                            'class': 'quiqqer-invitecode-manager-tbl-user-not_exist',
-                            html   : textUserNotExist
-                        });
-                    } else {
-                        Row.user = new Element('span', {html: '-'});
-                    }
-                } else {
-                    Row.user = new Element('div', {
-                        'class': 'quiqqer-invitecode-manager-tbl-user',
-                        html   : Row.username
-                    });
-                }
-
                 if (!Row.title) {
                     Row.title = '-';
                 }
 
-                var MailSentElm = new Element('span', {
+                var ReusableElm = new Element('span', {
                     'class': 'fa'
                 });
 
-                if (!Row.mailSent) {
-                    MailSentElm.addClass('fa-close');
+                if (!Row.isReusable) {
+                    ReusableElm.addClass('fa-close');
                 } else {
-                    MailSentElm.addClass('fa-check');
+                    ReusableElm.addClass('fa-check');
                 }
 
-                Row.mailSent = MailSentElm;
+                Row.reusable = ReusableElm;
             }
 
             this.$Grid.setData(GridData);
@@ -427,7 +384,7 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
                 title      : QUILocale.get(
                     lg, 'controls.manager.create.popup.title'
                 ),
-                maxHeight  : 450,
+                maxHeight  : 800,
                 maxWidth   : 450,
                 events     : {
                     onOpen: function () {
@@ -439,30 +396,24 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
                             FuncSubmit();
                         });
 
-                        var EmailInput       = Content.getElement('input[name="email"]');
-                        var SendMailCheckbox = Content.getElement('input[name="sendmail"]');
-
-                        EmailInput.addEvent('keyup', function (event) {
-                            if (event.target.value.trim() === '') {
-                                SendMailCheckbox.checked  = false;
-                                SendMailCheckbox.disabled = true;
+                        Content.getElement('input[name="title"]').focus();
 
-                                return;
-                            }
+                        Popup.Loader.show();
 
-                            SendMailCheckbox.disabled = false;
+                        QUI.parse(Content).then(function () {
+                            Popup.Loader.hide();
                         });
-
-                        Content.getElement('input[name="title"]').focus();
                     }
                 },
                 closeButton: true,
                 content    : Mustache.render(templateCreate, {
                     labelTitle   : QUILocale.get(lg, lgPrefix + 'labelTitle'),
-                    labelEmail   : QUILocale.get(lg, lgPrefix + 'labelEmail'),
+                    labelCode    : QUILocale.get(lg, lgPrefix + 'labelCode'),
+                    labelUsers   : QUILocale.get(lg, lgPrefix + 'labelUsers'),
+                    labelGroups  : QUILocale.get(lg, lgPrefix + 'labelGroups'),
                     labelDate    : QUILocale.get(lg, lgPrefix + 'labelDate'),
-                    labelSendMail: QUILocale.get(lg, lgPrefix + 'labelSendMail'),
-                    labelAmount  : QUILocale.get(lg, lgPrefix + 'labelAmount')
+                    labelAmount  : QUILocale.get(lg, lgPrefix + 'labelAmount'),
+                    labelReusable: QUILocale.get(lg, lgPrefix + 'labelReusable')
                 })
             });
 
@@ -539,72 +490,6 @@ define('package/quiqqer/coupons/bin/controls/Manager', [
             Popup.open();
         },
 
-        /**
-         * Send CouponCodes via Mail
-         */
-        $sendMail: function () {
-            var self        = this;
-            var sendMailIds = [];
-            var rows        = this.$Grid.getSelectedData();
-
-            for (var i = 0, len = rows.length; i < len; i++) {
-                sendMailIds.push(rows[i].id);
-            }
-
-            // open popup
-            var Popup = new QUIConfirm({
-                'maxHeight': 300,
-                'autoclose': false,
-
-                'information': QUILocale.get(lg, 'controls.manager.sendmail.popup.info'),
-                'title'      : QUILocale.get(lg, 'controls.manager.sendmail.popup.title'),
-                'text'       : QUILocale.get(lg, 'controls.manager.sendmail.popup.title'),
-                'texticon'   : 'fa fa-envelope-o',
-                'icon'       : 'fa fa-envelope-o',
-
-                cancel_button: {
-                    text     : false,
-                    textimage: 'fa fa-remove'
-                },
-                ok_button    : {
-                    text     : false,
-                    textimage: 'fa fa-check'
-                },
-                events       : {
-                    onOpen  : function () {
-                        var Content = Popup.getContent();
-
-                        new Element('label', {
-                            'class': 'quiqqer-invitecode-manager-sendmail-resend',
-                            html   : '<span>' +
-                            QUILocale.get(lg, 'controls.manager.sendmail.popup.label.resend') +
-                            '</span>' +
-                            '<input type="checkbox" name="resend"/>'
-                        }).inject(Content);
-                    },
-                    onSubmit: function () {
-                        Popup.Loader.show();
-
-                        var SendMailInput = Popup.getContent().getElement(
-                            'input[name="resend"]'
-                        );
-
-                        CouponCodes.sendMail(sendMailIds, SendMailInput.checked).then(function (success) {
-                            if (!success) {
-                                Popup.Loader.hide();
-                                return;
-                            }
-
-                            Popup.close();
-                            self.refresh();
-                        });
-                    }
-                }
-            });
-
-            Popup.open();
-        },
-
         /**
          * Open user panel
          *
diff --git a/bin/controls/settings/CodeGeneratorSelect.js b/bin/controls/settings/CodeGeneratorSelect.js
index 3495cda..c30b830 100644
--- a/bin/controls/settings/CodeGeneratorSelect.js
+++ b/bin/controls/settings/CodeGeneratorSelect.js
@@ -85,7 +85,7 @@ define('package/quiqqer/coupons/bin/controls/settings/CodeGeneratorSelect', [
          */
         $getCodeGenerators: function () {
             return new Promise(function (resolve, reject) {
-                QUIAjax.get('package_quiqqer_invitecode_ajax_settings_getCodeGenerators', resolve, {
+                QUIAjax.get('package_quiqqer_coupons_ajax_settings_getCodeGenerators', resolve, {
                     'package': 'quiqqer/coupons',
                     onError  : reject
                 });
diff --git a/locale.xml b/locale.xml
index 3f5dae8..e4b05a3 100644
--- a/locale.xml
+++ b/locale.xml
@@ -115,20 +115,153 @@
         </locale>
 
         <!-- Class: Handler -->
-        <locale name="exception.Handler.user_already_exists">
-            <de><![CDATA[Es existiert bereits ein Benutzer mit der E-Mail-Adresse "[email]".]]></de>
-            <en><![CDATA[A user with e-mail address "[email]" already exists.]]></en>
+        <locale name="exception.Handler.code_already_exists">
+            <de><![CDATA[Der Coupon-Code "[code]" existiert bereits. Bitte verwenden Sie einen anderen Code oder lassen Sie das Code-Feld leer, um einen zufälligen Code zu erzeugen.]]></de>
+            <en><![CDATA[The Coupon code "[code]" already exists. Please use a differenct code or leave the code field empty to generate a random one.]]></en>
         </locale>
         <locale name="exception.Handler.code_not_found">
             <de><![CDATA[]]></de>
             <en><![CDATA[]]></en>
         </locale>
         <locale name="exception.invitecode.no_registration_site">
-            <de><![CDATA[Es wurde noch keine Registrierungs-Seite festgelegt, auf die in der Invite-Code E-Mail verwiesen wird.]]></de>
-            <en><![CDATA[No registration page has yet been specified for the Invite Code email.]]></en>
+            <de><![CDATA[Es wurde noch keine Registrierungs-Seite festgelegt, auf die in der Coupon-Code E-Mail verwiesen wird.]]></de>
+            <en><![CDATA[No registration page has yet been specified for the Coupon code email.]]></en>
         </locale>
     </groups>
 
     <groups name="quiqqer/coupons" datatype="js">
+        <!-- Control: Manager -->
+        <locale name="controls.manager.title">
+            <de><![CDATA[Coupon-Codes]]></de>
+            <en><![CDATA[Coupon codes]]></en>
+        </locale>
+        <locale name="controls.manager.create.template.labelDate">
+            <de><![CDATA[Einlösbar bis einschließlich (optional)]]></de>
+            <en><![CDATA[Redeemable until inclusive (optional)]]></en>
+        </locale>
+        <locale name="controls.manager.create.template.labelTitle">
+            <de><![CDATA[Verwendungszweck (optional)]]></de>
+            <en><![CDATA[Usage (optional)]]></en>
+        </locale>
+        <locale name="controls.manager.create.template.labelAmount">
+            <de><![CDATA[Anzahl]]></de>
+            <en><![CDATA[Amount]]></en>
+        </locale>
+        <locale name="controls.manager.create.template.labelUsers">
+            <de><![CDATA[Auf folgende Benutzer beschränken (optional)]]></de>
+            <en><![CDATA[Restrict to the following users (optional)]]></en>
+        </locale>
+        <locale name="controls.manager.create.template.labelGroups">
+            <de><![CDATA[Auf folgende Gruppen beschränken (optional)]]></de>
+            <en><![CDATA[Restrict to the following groups (optional)]]></en>
+        </locale>
+        <locale name="controls.manager.create.template.labelCode">
+            <de><![CDATA[Coupon-Code (optional - wird automatisch generiert, wenn Feld leergelassen wird)]]></de>
+            <en><![CDATA[Coupon code (optional - is generated automatically if left empty)]]></en>
+        </locale>
+        <locale name="controls.manager.create.template.labelReusable">
+            <de><![CDATA[Wiederverwendbar (Coupon-Code kann mehrfach verwendet werden)]]></de>
+            <en><![CDATA[Reusable (Coupon code can be used multiple times)]]></en>
+        </locale>
+        <locale name="controls.manager.create.popup.title">
+            <de><![CDATA[Neuen Coupon-Code erstellen]]></de>
+            <en><![CDATA[Create new Coupon code]]></en>
+        </locale>
+        <locale name="controls.manager.create.popup.btn.confirm_text">
+            <de><![CDATA[Coupon-Code erstellen]]></de>
+            <en><![CDATA[Create Coupon code]]></en>
+        </locale>
+        <locale name="controls.manager.create.popup.btn.confirm">
+            <de><![CDATA[Hier klicken, um neuen Coupon-Code erstellen]]></de>
+            <en><![CDATA[Click here to create a new Coupon code]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.btn.create">
+            <de><![CDATA[Erstellen]]></de>
+            <en><![CDATA[Create]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.btn.quickcreate">
+            <de><![CDATA[Schnelles Erstellen]]></de>
+            <en><![CDATA[Quick Create]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.btn.delete">
+            <de><![CDATA[Markierte(n) löschen]]></de>
+            <en><![CDATA[Delete marked]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.code">
+            <de><![CDATA[Code]]></de>
+            <en><![CDATA[Code]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.email">
+            <de><![CDATA[Verknüpfte E-Mail-Adresse]]></de>
+            <en><![CDATA[Linked e-mail address]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.mailSent">
+            <de><![CDATA[Mail]]></de>
+            <en><![CDATA[Mail]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.status">
+            <de><![CDATA[Status]]></de>
+            <en><![CDATA[Status]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.createDate">
+            <de><![CDATA[Erstellt am]]></de>
+            <en><![CDATA[Created at]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.validUntilDate">
+            <de><![CDATA[Einlösbar bis]]></de>
+            <en><![CDATA[Redeemable until]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.user">
+            <de><![CDATA[Registrierter Benutzer]]></de>
+            <en><![CDATA[Registered User]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.title">
+            <de><![CDATA[Verwendungszweck]]></de>
+            <en><![CDATA[Usage]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.header.reusable">
+            <de><![CDATA[Mehrfach verwendbar]]></de>
+            <en><![CDATA[Reusable]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.status.unused">
+            <de><![CDATA[nicht eingelöst]]></de>
+            <en><![CDATA[nicht eingelöst]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.status.invalid">
+            <de><![CDATA[nicht mehr einlösbar ([usageCount]x eingelöst)]]></de>
+            <en><![CDATA[nicht mehr einlösbar ([usageCount]x used)]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.status.valid">
+            <de><![CDATA[gültig - ([usageCount]x eingelöst)]]></de>
+            <en><![CDATA[valid - ([usageCount]x used)]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.validUntil.unlimited">
+            <de><![CDATA[unbegrenzt]]></de>
+            <en><![CDATA[not limited]]></en>
+        </locale>
+        <locale name="controls.manager.tbl.user.not_exist">
+            <de><![CDATA[Benutzer existiert nicht mehr]]></de>
+            <en><![CDATA[User does not exist anymore]]></en>
+        </locale>
+        <locale name="controls.manager.delete.popup.info">
+            <de><![CDATA[Sind Sie sicher, dass Sie die folgenden Coupon-Codes unwiderruflich löschen wollen?<br><br>[codes]]]></de>
+            <en><![CDATA[Are you sure you want to irrevocably delete the following Coupon codes?<br><br>[codes]]]></en>
+        </locale>
+        <locale name="controls.manager.delete.popup.title">
+            <de><![CDATA[Coupon-Codes löschen]]></de>
+            <en><![CDATA[Delete Coupon codes]]></en>
+        </locale>
+        <locale name="controls.manager.sendmail.popup.info">
+            <de><![CDATA[Sind Sie sicher, dass die markierten Coupon-Codes per E-Mail versandt werden sollen? InviteCodes, die keine zugewiesene E-Mail-Adresse haben, werden nicht versandt.]]></de>
+            <en><![CDATA[Are you sure that the marked Invite codes should be sent by e-mail? Coupon codes that do not have an assigned e-mail address will not be sent.]]></en>
+        </locale>
+        <locale name="controls.manager.sendmail.popup.title">
+            <de><![CDATA[Coupon-Codes versenden]]></de>
+            <en><![CDATA[Send Coupon codes]]></en>
+        </locale>
+        <locale name="controls.manager.sendmail.popup.label.resend">
+            <de><![CDATA[Coupon-Codes, die bereits versendet wurden, nochmals versenden]]></de>
+            <en><![CDATA[Resend Coupon codes that have already been sent]]></en>
+        </locale>
     </groups>
 </locales>
diff --git a/src/QUI/ERP/Coupons/CouponCode.php b/src/QUI/ERP/Coupons/CouponCode.php
index 14ac345..7eec7b2 100644
--- a/src/QUI/ERP/Coupons/CouponCode.php
+++ b/src/QUI/ERP/Coupons/CouponCode.php
@@ -71,7 +71,7 @@ class CouponCode
      *
      * @var bool
      */
-    protected $valid;
+    protected $valid = true;
 
     /**
      * Flag - Is the CouponCode reusable?
@@ -123,6 +123,10 @@ public function __construct($id)
             $this->groupIds = json_decode($data['groupIds'], true);
         }
 
+        if (!empty($data['isReusable'])) {
+            $this->reusable = true;
+        }
+
         $this->CreateDate = new \DateTime($data['createDate']);
 
         if (!empty($data['validUntilDate'])) {
@@ -190,6 +194,7 @@ public function getTitle()
      * @param QUI\Users\User $User - The user that redeems the CouponCode [if omitted use Session User]
      * @return void
      * @throws CouponCodeException
+     * @throws QUI\Exception
      */
     public function redeem($User = null)
     {
@@ -216,6 +221,14 @@ public function redeem($User = null)
         );
 
         $this->checkValidity();
+
+        QUI::getEvents()->fireEvent(
+            'quiqqerCouponsRedeem',
+            [
+                'User'       => $User,
+                'CouponCode' => $this
+            ]
+        );
     }
 
     /**
diff --git a/src/QUI/ERP/Coupons/Handler.php b/src/QUI/ERP/Coupons/Handler.php
index 405d99b..445dc34 100644
--- a/src/QUI/ERP/Coupons/Handler.php
+++ b/src/QUI/ERP/Coupons/Handler.php
@@ -16,16 +16,16 @@ class Handler
     /**
      * Permissions
      */
-    const PERMISSION_VIEW      = 'quiqqer.couponcode.view';
-    const PERMISSION_CREATE    = 'quiqqer.couponcode.create';
-    const PERMISSION_DELETE    = 'quiqqer.couponcode.delete';
+    const PERMISSION_VIEW   = 'quiqqer.couponcode.view';
+    const PERMISSION_CREATE = 'quiqqer.couponcode.create';
+    const PERMISSION_DELETE = 'quiqqer.couponcode.delete';
 
     /**
      * CouponCode runtime cache
      *
      * @var CouponCode[]
      */
-    protected static $couponCodes = array();
+    protected static $couponCodes = [];
 
     /**
      * Get CouponCode
@@ -55,25 +55,25 @@ public static function getCouponCode($id)
      */
     public static function getCouponCodeByCode($code)
     {
-        $result = QUI::getDataBase()->fetch(array(
-            'select' => array(
+        $result = QUI::getDataBase()->fetch([
+            'select' => [
                 'id'
-            ),
+            ],
             'from'   => self::getTable(),
-            'where'  => array(
+            'where'  => [
                 'code' => $code
-            ),
+            ],
             'limit'  => 1
-        ));
+        ]);
 
         if (empty($result)) {
-            throw new CouponCodeException(array(
+            throw new CouponCodeException([
                 'quiqqer/invitecode',
                 'exception.Handler.code_not_found',
-                array(
+                [
                     'code' => $code
-                )
-            ), 404);
+                ]
+            ], 404);
         }
 
         return self::getCouponCode($result[0]['id']);
@@ -91,40 +91,45 @@ public static function createCouponCode($data)
     {
         $Now = new \DateTime();
 
-        $inviteCode = array(
+        if (!empty($data['code'])) {
+            if (self::existsCode($data['code'])) {
+                throw new CouponCodeException([
+                    'quiqqer/coupons',
+                    'exception.Handler.code_already_exists',
+                    [
+                        'code' => $data['code']
+                    ]
+                ]);
+            }
+
+            $code = $data['code'];
+        } else {
+            $code = CodeGenerator::generate();
+        }
+
+        $couponCode = [
             'title'      => empty($data['title']) ? '' : $data['title'],
             'createDate' => $Now->format('Y-m-d H:i:s'),
-            'code'       => CodeGenerator::generate()
-        );
+            'code'       => $code,
+            'isReusable' => !empty($data['reusable'])
+        ];
 
         if (!empty($data['validUntil'])) {
             $ValidUntil                   = new \DateTime($data['validUntil']);
-            $inviteCode['validUntilDate'] = $ValidUntil->format('Y-m-d H:i:s');
+            $couponCode['validUntilDate'] = $ValidUntil->format('Y-m-d H:i:s');
         }
 
-        if (!empty($data['email'])) {
-            try {
-                QUI::getUsers()->getUserByMail($data['email']);
-
-                throw new CouponCodeException(array(
-                    'quiqqer/invitecode',
-                    'exception.Handler.user_already_exists',
-                    array(
-                        'email' => $data['email']
-                    )
-                ));
-            } catch (QUI\Users\Exception $Exception) {
-                if ($Exception->getCode() !== 404) {
-                    throw $Exception;
-                }
-            }
+        if (!empty($data['userIds'])) {
+            $couponCode['userIds'] = json_encode($data['userIds']);
+        }
 
-            $inviteCode['email'] = trim($data['email']);
+        if (!empty($data['groupIds'])) {
+            $couponCode['groupIds'] = json_encode($data['groupIds']);
         }
 
         QUI::getDataBase()->insert(
             self::getTable(),
-            $inviteCode
+            $couponCode
         );
 
         return self::getCouponCode(QUI::getPDO()->lastInsertId());
@@ -136,15 +141,16 @@ public static function createCouponCode($data)
      * @param array $searchParams
      * @param bool $countOnly (optional) - get result count only [default: false]
      * @return CouponCode[]|int
+     * @throws CouponCodeException
      */
     public static function search($searchParams, $countOnly = false)
     {
-        $couponCodes = array();
+        $couponCodes = [];
         $Grid        = new Grid($searchParams);
         $gridParams  = $Grid->parseDBParams($searchParams);
 
-        $binds = array();
-        $where = array();
+        $binds = [];
+        $where = [];
 
         if ($countOnly) {
             $sql = "SELECT COUNT(*)";
@@ -152,51 +158,51 @@ public static function search($searchParams, $countOnly = false)
             $sql = "SELECT id";
         }
 
-        $sql .= " FROM `" . self::getTable() . "`";
+        $sql .= " FROM `".self::getTable()."`";
 
         if (!empty($searchParams['search'])) {
-            $searchColumns = array(
+            $searchColumns = [
                 'id',
                 'code',
                 'email'
-            );
+            ];
 
-            $whereOr = array();
+            $whereOr = [];
 
             foreach ($searchColumns as $searchColumn) {
-                $whereOr[] = '`' . $searchColumn . '` LIKE :search';
+                $whereOr[] = '`'.$searchColumn.'` LIKE :search';
             }
 
             if (!empty($whereOr)) {
-                $where[] = '(' . implode(' OR ', $whereOr) . ')';
+                $where[] = '('.implode(' OR ', $whereOr).')';
 
-                $binds['search'] = array(
-                    'value' => '%' . $searchParams['search'] . '%',
+                $binds['search'] = [
+                    'value' => '%'.$searchParams['search'].'%',
                     'type'  => \PDO::PARAM_STR
-                );
+                ];
             }
         }
 
         // build WHERE query string
         if (!empty($where)) {
-            $sql .= " WHERE " . implode(" AND ", $where);
+            $sql .= " WHERE ".implode(" AND ", $where);
         }
 
         // ORDER
         if (!empty($searchParams['sortOn'])
         ) {
             $sortOn = Orthos::clear($searchParams['sortOn']);
-            $order  = "ORDER BY " . $sortOn;
+            $order  = "ORDER BY ".$sortOn;
 
             if (isset($searchParams['sortBy']) &&
                 !empty($searchParams['sortBy'])
             ) {
-                $order .= " " . Orthos::clear($searchParams['sortBy']);
+                $order .= " ".Orthos::clear($searchParams['sortBy']);
             } else {
                 $order .= " ASC";
             }
 
-            $sql .= " " . $order;
+            $sql .= " ".$order;
         } else {
             $sql .= " ORDER BY id DESC";
         }
@@ -205,10 +211,10 @@ public static function search($searchParams, $countOnly = false)
         if (!empty($gridParams['limit'])
             && !$countOnly
         ) {
-            $sql .= " LIMIT " . $gridParams['limit'];
+            $sql .= " LIMIT ".$gridParams['limit'];
         } else {
             if (!$countOnly) {
-                $sql .= " LIMIT " . (int)20;
+                $sql .= " LIMIT ".(int)20;
             }
         }
 
@@ -216,7 +222,7 @@ public static function search($searchParams, $countOnly = false)
 
         // bind search values
         foreach ($binds as $var => $bind) {
-            $Stmt->bindValue(':' . $var, $bind['value'], $bind['type']);
+            $Stmt->bindValue(':'.$var, $bind['value'], $bind['type']);
         }
 
         try {
@@ -224,10 +230,10 @@ public static function search($searchParams, $countOnly = false)
             $result = $Stmt->fetchAll(\PDO::FETCH_ASSOC);
         } catch (\Exception $Exception) {
             QUI\System\Log::addError(
-                self::class . ' :: search() -> ' . $Exception->getMessage()
+                self::class.' :: search() -> '.$Exception->getMessage()
             );
 
-            return array();
+            return [];
         }
 
         if ($countOnly) {
@@ -249,14 +255,14 @@ public static function search($searchParams, $countOnly = false)
      */
     public static function existsCode($code)
     {
-        $result = QUI::getDataBase()->fetch(array(
+        $result = QUI::getDataBase()->fetch([
             'select' => 'id',
             'from'   => self::getTable(),
-            'where'  => array(
+            'where'  => [
                 'code' => $code
-            ),
+            ],
             'limit'  => 1
-        ));
+        ]);
 
         return !empty($result);
     }
@@ -293,22 +299,22 @@ public static function getRegistrationSite()
     public static function deleteExpiredCouponCodes($days = null)
     {
         $Now   = new \DateTime();
-        $where = array(
-            'validUntilDate' => array(
+        $where = [
+            'validUntilDate' => [
                 'type'  => '<=',
                 'value' => $Now->format('Y-m-d H:i:s')
-            )
-        );
+            ]
+        ];
 
         if (!is_null($days)) {
             $days    = (int)$days;
             $OldDate = new \DateTime();
-            $OldDate->sub(new \DateInterval('P' . $days . 'D'));
+            $OldDate->sub(new \DateInterval('P'.$days.'D'));
 
-            $where['validUntilDate'] = array(
+            $where['validUntilDate'] = [
                 'type'  => '<=',
                 'value' => $OldDate->format('Y-m-d H:i:s')
-            );
+            ];
         }
 
         QUI::getDataBase()->delete(
@@ -327,22 +333,22 @@ public static function deleteExpiredCouponCodes($days = null)
      */
     public static function deleteRedeemedCouponCodes($days = null)
     {
-        $where = array(
-            'useDate' => array(
+        $where = [
+            'useDate' => [
                 'type'  => 'NOT',
                 'value' => null
-            )
-        );
+            ]
+        ];
 
         if (!is_null($days)) {
             $days    = (int)$days;
             $OldDate = new \DateTime();
-            $OldDate->sub(new \DateInterval('P' . $days . 'D'));
+            $OldDate->sub(new \DateInterval('P'.$days.'D'));
 
-            $where['useDate'] = array(
+            $where['useDate'] = [
                 'type'  => '<=',
                 'value' => $OldDate->format('Y-m-d H:i:s')
-            );
+            ];
         }
 
         QUI::getDataBase()->delete(
-- 
GitLab