From a54c8b9b76ab5ec52ed7bacf616d742f187d15e1 Mon Sep 17 00:00:00 2001
From: Henning Leutz <leutz@pcsg.de>
Date: Tue, 28 Feb 2017 08:09:26 +0100
Subject: [PATCH] refactor: development

---
 ajax/invoices/temporary/product/calc.php      |   6 +
 ajax/invoices/temporary/save.php              |   7 +
 bin/backend/controls/InvoiceItems.css         |   9 +-
 bin/backend/controls/InvoiceItems.html        |   3 +
 bin/backend/controls/InvoiceItems.js          | 102 +++++++--
 bin/backend/controls/InvoiceItemsProduct.css  |  78 -------
 bin/backend/controls/InvoiceItemsProduct.html |   7 -
 bin/backend/controls/articles/Article.css     |  84 +++++++
 bin/backend/controls/articles/Article.html    |   8 +
 .../Article.js}                               | 211 +++++++++++-------
 bin/backend/controls/articles/Text.css        |  59 +++++
 bin/backend/controls/articles/Text.html       |   2 +
 bin/backend/controls/articles/Text.js         | 172 ++++++++++++++
 .../panels/TemporaryInvoice.Data.html         |  18 +-
 .../controls/panels/TemporaryInvoice.js       | 120 +++++++---
 database.xml                                  |   4 +-
 locale.xml                                    |  48 ++++
 .../Accounting/Invoice/Articles/Article.php   | 133 +++++++++++
 .../Invoice/Articles/ArticleInterface.php     |  57 +++++
 .../ERP/Accounting/Invoice/Articles/Text.php  | 121 ++++++++++
 .../Accounting/Invoice/TemporaryInvoice.php   | 123 +++++++++-
 21 files changed, 1145 insertions(+), 227 deletions(-)
 delete mode 100644 bin/backend/controls/InvoiceItemsProduct.css
 delete mode 100644 bin/backend/controls/InvoiceItemsProduct.html
 create mode 100644 bin/backend/controls/articles/Article.css
 create mode 100644 bin/backend/controls/articles/Article.html
 rename bin/backend/controls/{InvoiceItemsProduct.js => articles/Article.js} (64%)
 create mode 100644 bin/backend/controls/articles/Text.css
 create mode 100644 bin/backend/controls/articles/Text.html
 create mode 100644 bin/backend/controls/articles/Text.js
 create mode 100644 src/QUI/ERP/Accounting/Invoice/Articles/Article.php
 create mode 100644 src/QUI/ERP/Accounting/Invoice/Articles/ArticleInterface.php
 create mode 100644 src/QUI/ERP/Accounting/Invoice/Articles/Text.php

diff --git a/ajax/invoices/temporary/product/calc.php b/ajax/invoices/temporary/product/calc.php
index a83787f..d388f82 100644
--- a/ajax/invoices/temporary/product/calc.php
+++ b/ajax/invoices/temporary/product/calc.php
@@ -19,6 +19,12 @@ function ($params) {
             throw new QUI\Exception('Not found');
         }
 
+        if (empty($params['type'])) {
+            $Article = new QUI\ERP\Accounting\Invoice\Articles\Article($params);
+
+            return $Article->toArray();
+        }
+
         \QUI\System\Log::writeRecursive($params);
 
         if ($params['type'] == QUI\ERP\Products\Product\Product::class) {
diff --git a/ajax/invoices/temporary/save.php b/ajax/invoices/temporary/save.php
index 83034d2..014362f 100644
--- a/ajax/invoices/temporary/save.php
+++ b/ajax/invoices/temporary/save.php
@@ -17,6 +17,13 @@ function ($invoiceId, $data) {
         $Invoice  = $Invoices->getTemporaryInvoice($invoiceId);
         $data     = json_decode($data, true);
 
+        $Invoice->clearArticles();
+
+        if (isset($data['articles'])) {
+            $Invoice->importArticles($data['articles']);
+            unset($data['articles']);
+        }
+
         $Invoice->setAttributes($data);
         $Invoice->save();
 
diff --git a/bin/backend/controls/InvoiceItems.css b/bin/backend/controls/InvoiceItems.css
index 78e9a97..617cc3e 100644
--- a/bin/backend/controls/InvoiceItems.css
+++ b/bin/backend/controls/InvoiceItems.css
@@ -1,6 +1,7 @@
 .quiqqer-invoice-backend-invoiceItems {
     display: block;
     min-height: 300px;
+    min-width: 1000px;
     position: relative;
     width: 100%;
 }
@@ -22,13 +23,15 @@
 }
 
 .quiqqer-invoice-backend-invoiceItems-header-description {
-    width: calc(100% - 600px);
+    width: calc(100% - 700px);
 }
 
 .quiqqer-invoice-backend-invoiceItems-header-quantity {
+    text-align: right;
     width: 100px;
 }
 
+.quiqqer-invoice-backend-invoiceItems-header-articleNo,
 .quiqqer-invoice-backend-invoiceItems-header-unitPrice,
 .quiqqer-invoice-backend-invoiceItems-header-price,
 .quiqqer-invoice-backend-invoiceItems-header-vat,
@@ -40,6 +43,10 @@
     white-space: nowrap;
 }
 
+.quiqqer-invoice-backend-invoiceItems-header-articleNo {
+    text-align: left;
+}
+
 .quiqqer-invoice-backend-invoiceItems-header-total {
     width: 150px;
 }
diff --git a/bin/backend/controls/InvoiceItems.html b/bin/backend/controls/InvoiceItems.html
index 372a250..e294021 100644
--- a/bin/backend/controls/InvoiceItems.html
+++ b/bin/backend/controls/InvoiceItems.html
@@ -2,6 +2,9 @@
     <div class="quiqqer-invoice-backend-invoiceItems-header-pos header-cell">
         #
     </div>
+    <div class="quiqqer-invoice-backend-invoiceItems-header-articleNo header-cell">
+        {{titleArticleNo}}
+    </div>
     <div class="quiqqer-invoice-backend-invoiceItems-header-description header-cell">
         {{titleDescription}}
     </div>
diff --git a/bin/backend/controls/InvoiceItems.js b/bin/backend/controls/InvoiceItems.js
index 78eb304..ae5351a 100644
--- a/bin/backend/controls/InvoiceItems.js
+++ b/bin/backend/controls/InvoiceItems.js
@@ -17,12 +17,12 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItems', [
     'qui/controls/Control',
     'Mustache',
     'Locale',
-    'package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct',
+    'package/quiqqer/invoice/bin/backend/controls/articles/Article',
 
     'text!package/quiqqer/invoice/bin/backend/controls/InvoiceItems.html',
     'css!package/quiqqer/invoice/bin/backend/controls/InvoiceItems.css'
 
-], function (QUI, QUIControl, Mustache, QUILocale, InvoiceItemsProduct, template) {
+], function (QUI, QUIControl, Mustache, QUILocale, Article, template) {
     "use strict";
 
     var lg = 'quiqqer/invoice';
@@ -30,14 +30,14 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItems', [
     return new Class({
 
         Extends: QUIControl,
-        Type: 'package/quiqqer/invoice/bin/backend/controls/InvoiceItems',
+        Type   : 'package/quiqqer/invoice/bin/backend/controls/InvoiceItems',
 
         options: {},
 
         initialize: function (options) {
             this.parent(options);
 
-            this.$products = [];
+            this.$articles = [];
 
             this.$Container = null;
         },
@@ -54,12 +54,13 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItems', [
 
             this.$Elm.set({
                 html: Mustache.render(template, {
+                    titleArticleNo  : QUILocale.get(lg, 'invoice.products.articleNo'),
                     titleDescription: QUILocale.get(lg, 'invoice.products.description'),
-                    titleQuantity: QUILocale.get(lg, 'invoice.products.quantity'),
-                    titleUnitPrice: QUILocale.get(lg, 'invoice.products.unitPrice'),
-                    titlePrice: QUILocale.get(lg, 'invoice.products.price'),
-                    titleVAT: QUILocale.get(lg, 'invoice.products.vat'),
-                    titleSum: QUILocale.get(lg, 'invoice.products.sum')
+                    titleQuantity   : QUILocale.get(lg, 'invoice.products.quantity'),
+                    titleUnitPrice  : QUILocale.get(lg, 'invoice.products.unitPrice'),
+                    titlePrice      : QUILocale.get(lg, 'invoice.products.price'),
+                    titleVAT        : QUILocale.get(lg, 'invoice.products.vat'),
+                    titleSum        : QUILocale.get(lg, 'invoice.products.sum')
                 })
             });
 
@@ -68,24 +69,81 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItems', [
             return this.$Elm;
         },
 
+        /**
+         * Serialize the list
+         *
+         * @returns {Object}
+         */
+        serialize: function () {
+            var articles = this.$articles.map(function (Article) {
+                var attr     = Article.getAttributes();
+                attr.control = typeOf(Article);
+
+                return attr;
+            });
+
+            return {
+                articles: articles
+            };
+        },
+
+        /**
+         *
+         */
+        unserialize: function (list) {
+            var data = {};
+
+            if (typeOf(list) == 'string') {
+                try {
+                    data = JSON.stringify(list);
+                } catch (e) {
+                }
+            } else {
+                data = list;
+            }
+
+            if (!("articles" in data)) {
+                return;
+            }
+
+            var needles = data.articles.map(function (entry) {
+                return entry.control;
+            });
+
+            require(needles, function () {
+                var i, no, len, article, control;
+
+                for (i = 0, len = data.articles.length; i < len; i++) {
+                    article = data.articles[i];
+                    control = article.control;
+
+                    no = needles.indexOf(control);
+
+                    this.addArticle(
+                        new arguments[no](article)
+                    );
+                }
+            }.bind(this));
+        },
+
         /**
          * Add a product to the list
          * The product must be an instance of InvoiceItemsProduc
          *
          * @param {Object} Product
          */
-        addProduct: function (Product) {
+        addArticle: function (Product) {
             if (typeof Product !== 'object') {
                 return;
             }
 
-            if (!(Product instanceof InvoiceItemsProduct)) {
+            if (!(Product instanceof Article)) {
                 return;
             }
 
-            this.$products.push(Product);
+            this.$articles.push(Product);
 
-            Product.setPosition(this.$products.length);
+            Product.setPosition(this.$articles.length);
             Product.inject(this.$Container);
         },
 
@@ -93,7 +151,23 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItems', [
          * Insert a new empty product
          */
         insertNewProduct: function () {
-            this.addProduct(new InvoiceItemsProduct());
+            this.addArticle(new Article());
+        },
+
+        /**
+         * Return the articles as an array
+         *
+         * @return {Array}
+         */
+        save: function () {
+            console.log('###');
+
+            return this.$articles.map(function (Article) {
+                console.log(typeOf(Article));
+                console.log(Article.getAttributes());
+
+                return Article.getAttributes();
+            });
         }
     });
 });
\ No newline at end of file
diff --git a/bin/backend/controls/InvoiceItemsProduct.css b/bin/backend/controls/InvoiceItemsProduct.css
deleted file mode 100644
index 2ce8db8..0000000
--- a/bin/backend/controls/InvoiceItemsProduct.css
+++ /dev/null
@@ -1,78 +0,0 @@
-.quiqqer-invoice-backend-invoiceItemsProduct {
-    align-items: stretch;
-    border-bottom: 1px solid #DEDEDE;
-    clear: both;
-    display: flex;
-    flex-direction: row;
-    float: left;
-    min-height: 50px;
-    position: relative;
-    width: 100%;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct .cell {
-    padding: 10px;
-    position: relative;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct .cell-editable:hover {
-    background: rgba(0, 0, 0, 0.1);
-    cursor: pointer;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-pos {
-    width: 50px;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-text {
-    width: calc(100% - 550px);
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-text-title {
-    clear: both;
-    font-weight: bold;
-    margin-bottom: 5px;
-    position: relative;
-    width: 100%;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-text-description {
-    clear: both;
-    position: relative;
-    width: 100%;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-text-description-edit {
-    background: #EFEFEF;
-    border: 1px solid #EFEFEF;
-    height: 100%;
-    left: 0;
-    overflow: hidden;
-    position: absolute;
-    top: 0;
-    width: 100%
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-text-description-buttons {
-    height: 50px;
-    text-align: center;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-quantity {
-    width: 100px;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-unitPrice,
-.quiqqer-invoice-backend-invoiceItemsProduct-price,
-.quiqqer-invoice-backend-invoiceItemsProduct-vat,
-.quiqqer-invoice-backend-invoiceItemsProduct-total {
-    overflow: hidden;
-    text-align: right;
-    text-overflow: ellipsis;
-    width: 100px;
-    white-space: nowrap;
-}
-
-.quiqqer-invoice-backend-invoiceItemsProduct-total {
-    width: 150px;
-}
diff --git a/bin/backend/controls/InvoiceItemsProduct.html b/bin/backend/controls/InvoiceItemsProduct.html
deleted file mode 100644
index 1148594..0000000
--- a/bin/backend/controls/InvoiceItemsProduct.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<div class="quiqqer-invoice-backend-invoiceItemsProduct-pos cell"></div>
-<div class="quiqqer-invoice-backend-invoiceItemsProduct-text cell"></div>
-<div class="quiqqer-invoice-backend-invoiceItemsProduct-quantity cell cell-editable"></div>
-<div class="quiqqer-invoice-backend-invoiceItemsProduct-unitPrice cell cell-editable"></div>
-<div class="quiqqer-invoice-backend-invoiceItemsProduct-price cell cell-editable"></div>
-<div class="quiqqer-invoice-backend-invoiceItemsProduct-vat cell cell-editable"></div>
-<div class="quiqqer-invoice-backend-invoiceItemsProduct-total cell"></div>
diff --git a/bin/backend/controls/articles/Article.css b/bin/backend/controls/articles/Article.css
new file mode 100644
index 0000000..2454453
--- /dev/null
+++ b/bin/backend/controls/articles/Article.css
@@ -0,0 +1,84 @@
+.quiqqer-invoice-backend-invoiceArticle {
+    align-items: stretch;
+    border-bottom: 1px solid #DEDEDE;
+    clear: both;
+    display: flex;
+    flex-direction: row;
+    float: left;
+    min-height: 50px;
+    position: relative;
+    width: 100%;
+}
+
+.quiqqer-invoice-backend-invoiceArticle .cell {
+    padding: 10px;
+    position: relative;
+}
+
+.quiqqer-invoice-backend-invoiceArticle .cell-editable:hover {
+    background: rgba(0, 0, 0, 0.1);
+    cursor: pointer;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-pos {
+    width: 50px;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-text {
+    width: calc(100% - 700px);
+}
+
+.quiqqer-invoice-backend-invoiceArticle-text-title {
+    clear: both;
+    font-weight: bold;
+    margin-bottom: 5px;
+    position: relative;
+    width: 100%;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-text-description {
+    clear: both;
+    position: relative;
+    width: 100%;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-text-description-edit {
+    background: #EFEFEF;
+    border: 1px solid #EFEFEF;
+    height: 100%;
+    left: 0;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    width: 100%
+}
+
+.quiqqer-invoice-backend-invoiceArticle-text-description-buttons {
+    height: 50px;
+    text-align: center;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-quantity {
+    text-align: right;
+    width: 100px;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-articleNo,
+.quiqqer-invoice-backend-invoiceArticle-unitPrice,
+.quiqqer-invoice-backend-invoiceArticle-price,
+.quiqqer-invoice-backend-invoiceArticle-vat,
+.quiqqer-invoice-backend-invoiceArticle-total {
+    overflow: hidden;
+    text-align: right;
+    text-overflow: ellipsis;
+    width: 100px;
+    white-space: nowrap;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-articleNo {
+    text-align: left;
+}
+
+.quiqqer-invoice-backend-invoiceArticle-total {
+    width: 150px;
+}
diff --git a/bin/backend/controls/articles/Article.html b/bin/backend/controls/articles/Article.html
new file mode 100644
index 0000000..9bae687
--- /dev/null
+++ b/bin/backend/controls/articles/Article.html
@@ -0,0 +1,8 @@
+<div class="quiqqer-invoice-backend-invoiceArticle-pos cell"></div>
+<div class="quiqqer-invoice-backend-invoiceArticle-articleNo cell cell-editable"></div>
+<div class="quiqqer-invoice-backend-invoiceArticle-text cell"></div>
+<div class="quiqqer-invoice-backend-invoiceArticle-quantity cell cell-editable"></div>
+<div class="quiqqer-invoice-backend-invoiceArticle-unitPrice cell cell-editable"></div>
+<div class="quiqqer-invoice-backend-invoiceArticle-price cell cell-editable"></div>
+<div class="quiqqer-invoice-backend-invoiceArticle-vat cell cell-editable"></div>
+<div class="quiqqer-invoice-backend-invoiceArticle-total cell"></div>
diff --git a/bin/backend/controls/InvoiceItemsProduct.js b/bin/backend/controls/articles/Article.js
similarity index 64%
rename from bin/backend/controls/InvoiceItemsProduct.js
rename to bin/backend/controls/articles/Article.js
index 9478a33..a5953b4 100644
--- a/bin/backend/controls/InvoiceItemsProduct.js
+++ b/bin/backend/controls/articles/Article.js
@@ -1,14 +1,20 @@
 /**
- * @module package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct
+ * @module package/quiqqer/invoice/bin/backend/controls/articles/Article
  *
  * Freies Produkt
  * - Dieses Produkt kann vom Benutzer komplett selbst bestimmt werden
  *
  * @require qui/QUI
  * @require qui/controls/Control
- * @require css!package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct.css
+ * @require qui/controls/buttons/Button
+ * @require Mustache
+ * @require Locale
+ * @require Ajax
+ * @require Editors
+ * @require text!package/quiqqer/invoice/bin/backend/controls/articles/Article.html
+ * @require css!package/quiqqer/invoice/bin/backend/controls/articles/Article.css
  */
-define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
+define('package/quiqqer/invoice/bin/backend/controls/articles/Article', [
 
     'qui/QUI',
     'qui/controls/Control',
@@ -18,18 +24,16 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
     'Ajax',
     'Editors',
 
-    'text!package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct.html',
-    'css!package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct.css'
+    'text!package/quiqqer/invoice/bin/backend/controls/articles/Article.html',
+    'css!package/quiqqer/invoice/bin/backend/controls/articles/Article.css'
 
 ], function (QUI, QUIControl, QUIButton, Mustache, QUILocale, QUIAjax, Editors, template) {
     "use strict";
 
-    var lg = 'quiqqer/invoice';
-
     return new Class({
 
         Extends: QUIControl,
-        Type: 'package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct',
+        Type   : 'package/quiqqer/invoice/bin/backend/controls/articles/Article',
 
         Binds: [
             '$onEditTitle',
@@ -39,37 +43,39 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
         ],
 
         options: {
-            position: 0,
-            title: '',
-            description: '',
-            quantity: 1,
-            unitPrice: 0,
-            price: 0,
-            vat: '',
-            type: ''
+            position   : 0,
+            title      : '---',
+            description: '---',
+            quantity   : 1,
+            unitPrice  : 0,
+            price      : 0,
+            vat        : '',
+            params     : false, // mixed value for API Articles
+            type       : 'QUI\\ERP\\Accounting\\Invoice\\Articles\\Article'
         },
 
         initialize: function (options) {
+            this.setAttributes(this.__proto__.options); // set the default values
             this.parent(options);
 
-            this.$Position = null;
-            this.$Quantity = null;
+            this.$Position  = null;
+            this.$Quantity  = null;
             this.$UnitPrice = null;
-            this.$Price = null;
-            this.$VAT = null;
-            this.$Total = null;
+            this.$Price     = null;
+            this.$VAT       = null;
+            this.$Total     = null;
 
-            this.$Text = null;
-            this.$Title = null;
+            this.$Text        = null;
+            this.$Title       = null;
             this.$Description = null;
-            this.$Editor = null;
+            this.$Editor      = null;
 
-            this.$Loader = null;
+            this.$Loader  = null;
             this.$created = false;
 
             // admin format
             this.$Formatter = QUILocale.getNumberFormatter({
-                style: 'currency',
+                style   : 'currency',
                 currency: 'EUR'
             });
         },
@@ -82,46 +88,49 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
         create: function () {
             this.$Elm = this.parent();
 
-            this.$Elm.addClass('quiqqer-invoice-backend-invoiceItemsProduct');
+            this.$Elm.addClass('quiqqer-invoice-backend-invoiceArticle');
 
             this.$Elm.set({
                 html: Mustache.render(template)
             });
 
-            this.$Position = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceItemsProduct-pos');
-            this.$Text = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceItemsProduct-text');
-            this.$Quantity = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceItemsProduct-quantity');
-            this.$UnitPrice = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceItemsProduct-unitPrice');
-            this.$Price = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceItemsProduct-price');
-            this.$VAT = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceItemsProduct-vat');
-            this.$Total = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceItemsProduct-total');
-
-            if (this.getAttribute('position')) {
-                this.setPosition(this.getAttribute('position'));
-            }
+            this.$Position  = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticle-pos');
+            this.$Text      = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticle-text');
+            this.$Quantity  = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticle-quantity');
+            this.$UnitPrice = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticle-unitPrice');
+            this.$Price     = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticle-price');
+            this.$VAT       = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticle-vat');
+            this.$Total     = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticle-total');
 
             this.$Quantity.addEvent('click', this.$onEditQuantity);
             this.$UnitPrice.addEvent('click', this.$onUnitPriceQuantity);
 
             this.$Loader = new Element('div', {
-                html: '<span class="fa fa-spinner fa-spin"></span>',
+                html  : '<span class="fa fa-spinner fa-spin"></span>',
                 styles: {
                     background: '#fff',
-                    display: 'none',
-                    left: 0,
-                    padding: 10,
-                    position: 'absolute',
-                    top: 0,
-                    width: '100%'
+                    display   : 'none',
+                    left      : 0,
+                    padding   : 10,
+                    position  : 'absolute',
+                    top       : 0,
+                    width     : '100%'
                 }
             }).inject(this.$Position);
 
+            new Element('span').inject(this.$Position);
+
+            if (this.getAttribute('position')) {
+                this.setPosition(this.getAttribute('position'));
+            }
+
+
             this.$Title = new Element('div', {
-                'class': 'quiqqer-invoice-backend-invoiceItemsProduct-text-title cell-editable'
+                'class': 'quiqqer-invoice-backend-invoiceArticle-text-title cell-editable'
             }).inject(this.$Text);
 
             this.$Description = new Element('div', {
-                'class': 'quiqqer-invoice-backend-invoiceItemsProduct-text-description cell-editable'
+                'class': 'quiqqer-invoice-backend-invoiceArticle-text-description cell-editable'
             }).inject(this.$Text);
 
             this.$Title.addEvent('click', this.$onEditTitle);
@@ -130,6 +139,8 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
             this.setQuantity(this.getAttribute('quantity'));
             this.setUnitPrice(this.getAttribute('unitPrice'));
             this.setVat(this.getAttribute('vat'));
+            this.setTitle(this.getAttribute('title'));
+            this.setDescription(this.getAttribute('description'));
 
             this.$created = true;
             this.calc();
@@ -139,6 +150,8 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
 
         /**
          * Calculates the total price of the invoice and refresh the display
+         *
+         * @return {Promise}
          */
         calc: function () {
             var self = this;
@@ -152,34 +165,32 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
             return new Promise(function (resolve, reject) {
                 QUIAjax.get('package_quiqqer_invoice_ajax_invoices_temporary_product_calc', function (product) {
 
-                    var total = self.$Formatter.format(product.calculated_sum);
+                    var total     = self.$Formatter.format(product.calculated_sum);
                     var unitPrice = self.$Formatter.format(product.calculated_basisPrice);
-                    var price = self.$Formatter.format(product.calculated_price);
+                    var price     = self.$Formatter.format(product.calculated_price);
 
                     self.$Total.set({
-                        html: total,
+                        html : total,
                         title: total
                     });
 
                     self.$UnitPrice.set({
-                        html: unitPrice,
+                        html : unitPrice,
                         title: unitPrice
                     });
 
                     self.$Price.set({
-                        html: price,
+                        html : price,
                         title: price
                     });
 
                     self.hideLoader();
 
-                    console.warn(product);
-
                     resolve(product);
                 }, {
                     'package': 'quiqqer/invoice',
-                    onError: reject,
-                    params: JSON.encode(self.getAttributes())
+                    onError  : reject,
+                    params   : JSON.encode(self.getAttributes())
                 });
             });
         },
@@ -192,6 +203,10 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
         setTitle: function (title) {
             this.setAttribute('title', title);
             this.$Title.set('html', title);
+
+            if (title === '') {
+                this.$Title.set('html', '&nbsp;');
+            }
         },
 
         /**
@@ -202,6 +217,10 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
         setDescription: function (description) {
             this.setAttribute('description', description);
             this.$Description.set('html', description);
+
+            if (description === '') {
+                this.$Description.set('html', '&nbsp;');
+            }
         },
 
         /**
@@ -213,7 +232,7 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
             this.setAttribute('position', parseInt(pos));
 
             if (this.$Position) {
-                this.$Position.set('html', this.getAttribute('position'));
+                this.$Position.getChildren('span').set('html', this.getAttribute('position'));
             }
         },
 
@@ -308,18 +327,18 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
             this.showLoader();
 
             var EditorContainer = new Element('div', {
-                    'class': 'quiqqer-invoice-backend-invoiceItemsProduct-text-description-edit'
+                    'class': 'quiqqer-invoice-backend-invoiceArticle-text-description-edit'
                 }).inject(this.$Description),
 
-                EditorParent = new Element('div', {
+                EditorParent    = new Element('div', {
                     styles: {
-                        height: 'calc(100% - 50px)',
+                        height : 'calc(100% - 50px)',
                         opacity: 0
                     }
                 }).inject(EditorContainer),
 
-                EditorSubmit = new Element('div', {
-                    'class': 'quiqqer-invoice-backend-invoiceItemsProduct-text-description-buttons'
+                EditorSubmit    = new Element('div', {
+                    'class': 'quiqqer-invoice-backend-invoiceArticle-text-description-buttons'
                 }).inject(EditorContainer);
 
 
@@ -339,13 +358,13 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
             };
 
             new QUIButton({
-                text: QUILocale.get('quiqqer/system', 'accept'),
+                text     : QUILocale.get('quiqqer/system', 'accept'),
                 textimage: 'fa fa-check',
-                styles: {
+                styles   : {
                     'float': 'none',
-                    margin: '10px 5px 0 5px'
+                    margin : '10px 5px 0 5px'
                 },
-                events: {
+                events   : {
                     onClick: function () {
                         this.setDescription(this.$Editor.getContent());
                         closeEditor();
@@ -354,10 +373,10 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
             }).inject(EditorSubmit);
 
             new QUIButton({
-                text: QUILocale.get('quiqqer/system', 'cancel'),
+                text  : QUILocale.get('quiqqer/system', 'cancel'),
                 styles: {
                     'float': 'none',
-                    margin: '10px 5px 0 5px'
+                    margin : '10px 5px 0 5px'
                 },
                 events: {
                     onClick: closeEditor
@@ -383,14 +402,36 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
                         this.$Editor.setAttribute('buttons', {
                             lines: [
                                 [[
-                                    {type: "button", button: "Bold"},
-                                    {type: "button", button: "Italic"},
-                                    {type: "button", button: "Underline"},
-                                    {type: "seperator"},
-                                    {type: "button", button: "RemoveFormat"},
-                                    {type: "seperator"},
-                                    {type: "button", button: "NumberedList"},
-                                    {type: "button", button: "BulletedList"}
+                                    {
+                                        type  : "button",
+                                        button: "Bold"
+                                    },
+                                    {
+                                        type  : "button",
+                                        button: "Italic"
+                                    },
+                                    {
+                                        type  : "button",
+                                        button: "Underline"
+                                    },
+                                    {
+                                        type: "seperator"
+                                    },
+                                    {
+                                        type  : "button",
+                                        button: "RemoveFormat"
+                                    },
+                                    {
+                                        type: "seperator"
+                                    },
+                                    {
+                                        type  : "button",
+                                        button: "NumberedList"
+                                    },
+                                    {
+                                        type  : "button",
+                                        button: "BulletedList"
+                                    }
                                 ]]
                             ]
                         });
@@ -456,17 +497,17 @@ define('package/quiqqer/invoice/bin/backend/controls/InvoiceItemsProduct', [
 
             return new Promise(function (resolve) {
                 var Edit = new Element('input', {
-                    type: type,
-                    value: value,
+                    type  : type,
+                    value : value,
                     styles: {
-                        border: 0,
-                        left: 0,
+                        border    : 0,
+                        left      : 0,
                         lineHeight: 20,
-                        height: '100%',
-                        padding: 0,
-                        position: 'absolute',
-                        top: 0,
-                        width: '100%'
+                        height    : '100%',
+                        padding   : 0,
+                        position  : 'absolute',
+                        top       : 0,
+                        width     : '100%'
                     }
                 }).inject(Container);
 
diff --git a/bin/backend/controls/articles/Text.css b/bin/backend/controls/articles/Text.css
new file mode 100644
index 0000000..ae2deb7
--- /dev/null
+++ b/bin/backend/controls/articles/Text.css
@@ -0,0 +1,59 @@
+.quiqqer-invoice-backend-invoiceArticleText {
+    align-items: stretch;
+    border-bottom: 1px solid #DEDEDE;
+    clear: both;
+    display: flex;
+    flex-direction: row;
+    float: left;
+    min-height: 50px;
+    position: relative;
+    width: 100%;
+}
+
+.quiqqer-invoice-backend-invoiceArticleText .cell {
+    padding: 10px;
+    position: relative;
+}
+
+.quiqqer-invoice-backend-invoiceArticleText .cell-editable:hover {
+    background: rgba(0, 0, 0, 0.1);
+    cursor: pointer;
+}
+
+.quiqqer-invoice-backend-invoiceArticleText-pos {
+    width: 50px;
+}
+
+.quiqqer-invoice-backend-invoiceArticleText-text {
+    width: calc(100% - 50px);
+}
+
+.quiqqer-invoice-backend-invoiceArticleText-text-title {
+    clear: both;
+    font-weight: bold;
+    margin-bottom: 5px;
+    position: relative;
+    width: 100%;
+}
+
+.quiqqer-invoice-backend-invoiceArticleText-text-description {
+    clear: both;
+    position: relative;
+    width: 100%;
+}
+
+.quiqqer-invoice-backend-invoiceArticleText-text-description-edit {
+    background: #EFEFEF;
+    border: 1px solid #EFEFEF;
+    height: 100%;
+    left: 0;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    width: 100%
+}
+
+.quiqqer-invoice-backend-invoiceArticleText-text-description-buttons {
+    height: 50px;
+    text-align: center;
+}
diff --git a/bin/backend/controls/articles/Text.html b/bin/backend/controls/articles/Text.html
new file mode 100644
index 0000000..db821ac
--- /dev/null
+++ b/bin/backend/controls/articles/Text.html
@@ -0,0 +1,2 @@
+<div class="quiqqer-invoice-backend-invoiceArticleText-pos cell"></div>
+<div class="quiqqer-invoice-backend-invoiceArticleText-text cell"></div>
diff --git a/bin/backend/controls/articles/Text.js b/bin/backend/controls/articles/Text.js
new file mode 100644
index 0000000..272c118
--- /dev/null
+++ b/bin/backend/controls/articles/Text.js
@@ -0,0 +1,172 @@
+/**
+ * @module package/quiqqer/invoice/bin/backend/controls/articles/Text
+ *
+ * Text Produkt
+ * - Dieses "Produkt" benhaltet nur text und hat keine Summe oder Preise
+ * - Dieses Produkt wird verwendet für Hinweise auf der Rechnung
+ *
+ * @require qui/QUI
+ * @require qui/controls/Control
+ */
+define('package/quiqqer/invoice/bin/backend/controls/articles/Text', [
+
+    'package/quiqqer/invoice/bin/backend/controls/articles/Article',
+    'Locale',
+    'Ajax',
+    'Mustache',
+
+    'text!package/quiqqer/invoice/bin/backend/controls/articles/Text.html',
+    'css!package/quiqqer/invoice/bin/backend/controls/articles/Text.css'
+
+], function (InvoiceArticle, QUILocale, QUIAjax, Mustache, template) {
+    "use strict";
+
+    return new Class({
+
+        Extends: InvoiceArticle,
+        Type   : 'package/quiqqer/invoice/bin/backend/controls/articles/Text',
+
+        Binds: [
+            '$onEditTitle',
+            '$onEditDescription'
+        ],
+
+        initialize: function (options) {
+            this.parent(options);
+
+            this.setAttributes({
+                type: 'QUI\\ERP\\Accounting\\Invoice\\Articles\\Text'
+            });
+        },
+
+        /**
+         * Create the DOMNode element
+         *
+         * @returns {HTMLDivElement}
+         */
+        create: function () {
+            this.$Elm = new Element('div');
+
+            this.$Elm.addClass('quiqqer-invoice-backend-invoiceArticleText');
+
+            this.$Elm.set({
+                html: Mustache.render(template)
+            });
+
+            this.$Position = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticleText-pos');
+            this.$Text     = this.$Elm.getElement('.quiqqer-invoice-backend-invoiceArticleText-text');
+
+            this.$Loader = new Element('div', {
+                html  : '<span class="fa fa-spinner fa-spin"></span>',
+                styles: {
+                    background: '#fff',
+                    display   : 'none',
+                    left      : 0,
+                    padding   : 10,
+                    position  : 'absolute',
+                    top       : 0,
+                    width     : '100%'
+                }
+            }).inject(this.$Position);
+
+            new Element('span').inject(this.$Position);
+
+            if (this.getAttribute('position')) {
+                this.setPosition(this.getAttribute('position'));
+            }
+
+            // text nodes
+            this.$Title = new Element('div', {
+                'class': 'quiqqer-invoice-backend-invoiceArticleText-text-title cell-editable'
+            }).inject(this.$Text);
+
+            this.$Description = new Element('div', {
+                'class': 'quiqqer-invoice-backend-invoiceArticleText-text-description cell-editable'
+            }).inject(this.$Text);
+
+            this.$Title.addEvent('click', this.$onEditTitle);
+            this.$Description.addEvent('click', this.$onEditDescription);
+
+            this.setTitle(this.getAttribute('title'));
+            this.setDescription(this.getAttribute('description'));
+
+            return this.$Elm;
+        },
+
+        /**
+         * Calculates nothing
+         * Text Article has no prices
+         *
+         * @return {Promise}
+         */
+        calc: function () {
+            return Promise.resolve();
+        },
+
+        /**
+         * Set the product title
+         *
+         * @param {String} title
+         */
+        setTitle: function (title) {
+            this.setAttribute('title', title);
+            this.$Title.set('html', title);
+
+            if (title === '') {
+                this.$Title.set('html', '&nbsp;');
+            }
+        },
+
+        /**
+         * Set the product description
+         *
+         * @param {String} description
+         */
+        setDescription: function (description) {
+            this.setAttribute('description', description);
+            this.$Description.set('html', description);
+
+            if (description === '') {
+                this.$Description.set('html', '&nbsp;');
+            }
+        },
+
+        /**
+         * Set the product quantity
+         *
+         * @return {Promise}
+         */
+        setQuantity: function () {
+            return Promise.resolve();
+        },
+
+        /**
+         * Set the product unit price
+         *
+         */
+        setUnitPrice: function () {
+            return Promise.resolve();
+        },
+
+        /**
+         * Set the product unit price
+         **/
+        setVat: function () {
+            return Promise.resolve();
+        },
+
+        /**
+         * Show the loader
+         */
+        showLoader: function () {
+            this.$Loader.setStyle('display', null);
+        },
+
+        /**
+         * Hide the loader
+         */
+        hideLoader: function () {
+            this.$Loader.setStyle('display', 'none');
+        }
+    });
+});
diff --git a/bin/backend/controls/panels/TemporaryInvoice.Data.html b/bin/backend/controls/panels/TemporaryInvoice.Data.html
index 7f0cb5c..546687a 100644
--- a/bin/backend/controls/panels/TemporaryInvoice.Data.html
+++ b/bin/backend/controls/panels/TemporaryInvoice.Data.html
@@ -3,7 +3,7 @@
 <table class="data-table data-table-flexbox invoice-data">
     <thead>
     <tr>
-        <th>Rechnungsdaten</th>
+        <th>{{textInvoiceData}}</th>
     </tr>
     </thead>
 
@@ -11,8 +11,8 @@
     <tr>
         <td>
             <label class="field-container">
-                <span class="field-container-item" title="Rechnungsdatum">
-                    Rechnungsdatum
+                <span class="field-container-item" title="{{textInvoiceDate}}">
+                    {{textInvoiceDate}}
                 </span>
                 <input class="field-container-field" type="datetime" name="date"/>
             </label>
@@ -21,8 +21,8 @@
     <tr>
         <td>
             <label class="field-container">
-                <span class="field-container-item" title="Zahlungsziel">
-                    Zahlungsziel
+                <span class="field-container-item" title="{{textTermOfPayment}}">
+                    {{textTermOfPayment}}
                 </span>
                 <span class="field-container-field"></span>
             </label>
@@ -31,8 +31,8 @@
     <tr>
         <td>
             <label class="field-container">
-                <span class="field-container-item" title="Projektname">
-                    Projektname
+                <span class="field-container-item" title="{{textProjectName}}">
+                    {{textProjectName}}
                 </span>
                 <input class="field-container-field" type="text" name="projectName"/>
             </label>
@@ -41,8 +41,8 @@
     <tr>
         <td>
             <label class="field-container">
-                <span class="field-container-item" title="Bestellt durch">
-                    Bestellt durch
+                <span class="field-container-item" title="{{textOrderedBy}}">
+                    {{textOrderedBy}}
                 </span>
                 <span class="field-container-field field-container-field-no-padding">
                     <input type="hidden"
diff --git a/bin/backend/controls/panels/TemporaryInvoice.js b/bin/backend/controls/panels/TemporaryInvoice.js
index e536bce..16b8c34 100644
--- a/bin/backend/controls/panels/TemporaryInvoice.js
+++ b/bin/backend/controls/panels/TemporaryInvoice.js
@@ -27,6 +27,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
     'qui/controls/windows/Confirm',
     'controls/users/address/Select',
     'package/quiqqer/invoice/bin/Invoices',
+    'package/quiqqer/invoice/bin/backend/controls/articles/Text',
     'Locale',
     'Mustache',
     'Users',
@@ -35,7 +36,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
     'css!package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice.css'
 
 ], function (QUI, QUIPanel, QUIButton, QUIButtonMultiple, QUISeparator, QUIConfirm,
-             AddressSelect, Invoices, QUILocale, Mustache, Users, templateData) {
+             AddressSelect, Invoices, TextArticle, QUILocale, Mustache, Users, templateData) {
     "use strict";
 
     var lg = 'quiqqer/invoice';
@@ -48,7 +49,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
         Binds: [
             'save',
             'openData',
-            'openProducts',
+            'openArticles',
             'openVerification',
             '$openCategory',
             '$closeCategory',
@@ -63,7 +64,8 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
             invoiceId  : false,
             data       : {},
             customer_id: false,
-            address_id : false
+            address_id : false,
+            articles   : []
         },
 
         initialize: function (options) {
@@ -73,10 +75,12 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
 
             this.parent(options);
 
-            this.$ProductList = null;
-            this.$AddProduct = null;
+            this.$ArticleList  = null;
+            this.$AddProduct   = null;
             this.$AddSeparator = null;
 
+            this.$serializedList = {};
+
             this.addEvents({
                 onCreate : this.$onCreate,
                 onInject : this.$onInject,
@@ -98,7 +102,8 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
 
             return Invoices.saveInvoice(this.getAttribute('invoiceId'), {
                 customer_id: this.getAttribute('customer_id'),
-                address_id : this.getAttribute('address_id')
+                address_id : this.getAttribute('address_id'),
+                articles   : this.getAttribute('articles')
             }).then(function () {
                 this.Loader.hide();
             }.bind(this)).catch(function (err) {
@@ -125,7 +130,13 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
                 var Container = self.getContent().getElement('.container');
 
                 Container.set({
-                    html: Mustache.render(templateData)
+                    html: Mustache.render(templateData, {
+                        textInvoiceData  : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.data.textInvoiceData'),
+                        textInvoiceDate  : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.data.textInvoiceDate'),
+                        textTermOfPayment: QUILocale.get(lg, 'erp.panel.temporary.invoice.category.data.textTermOfPayment'),
+                        textProjectName  : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.data.textProjectName'),
+                        textOrderedBy    : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.data.textOrderedBy')
+                    })
                 });
 
                 return QUI.parse(Container);
@@ -146,7 +157,26 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
                     self.getAttribute('address_id')
                 );
             }).then(function () {
+                var Container = self.getContent().getElement('.container');
+
+                new QUIButton({
+                    textimage: 'fa fa-list',
+                    text     : 'Artikel verwalten',
+                    styles   : {
+                        display: 'block',
+                        'float': 'none',
+                        margin : '0 auto'
+                    },
+                    events   : {
+                        onClick: function () {
+                            self.openArticles();
+                        }
+                    }
+                }).inject(Container);
+
+                self.getCategory('data').setActive();
                 self.Loader.hide();
+
                 return self.$openCategory();
             });
         },
@@ -156,7 +186,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
          *
          * @returns {Promise}
          */
-        openProducts: function () {
+        openArticles: function () {
             this.Loader.show();
 
             return this.$closeCategory().then(function (Container) {
@@ -164,14 +194,33 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
                     require([
                         'package/quiqqer/invoice/bin/backend/controls/InvoiceItems'
                     ], function (InvoiceItems) {
-                        this.$ProductList = new InvoiceItems({
+                        this.$ArticleList = new InvoiceItems({
                             events: {}
                         }).inject(Container);
 
+                        if (this.$serializedList) {
+                            this.$ArticleList.unserialize(this.$serializedList);
+                        }
+
                         this.$AddProduct.show();
                         this.$AddSeparator.show();
                         this.Loader.hide();
 
+                        this.getCategory('articles').setActive();
+
+                        new QUIButton({
+                            textimage: 'fa fa-check',
+                            text     : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.review'),
+                            styles   : {
+                                display: 'block',
+                                'float': 'none',
+                                margin : '0 auto'
+                            },
+                            events   : {
+                                onClick: this.openVerification
+                            }
+                        }).inject(Container);
+
                         resolve();
                     }.bind(this));
                 }.bind(this));
@@ -192,6 +241,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
 
             return this.$closeCategory().then(function () {
 
+                this.getCategory('verification').setActive();
                 this.Loader.hide();
             }.bind(this)).then(function () {
                 return self.$openCategory();
@@ -206,14 +256,14 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
 
             require([
                 'package/quiqqer/products/bin/controls/products/search/Window',
-                'package/quiqqer/products/bin/controls/invoice/Product'
-            ], function (ProductSearch, Product) {
+                'package/quiqqer/products/bin/controls/invoice/Article'
+            ], function (ProductSearch, Article) {
                 new ProductSearch({
                     events: {
                         onSubmit: function (Win, products) {
                             for (var i = 0, len = products.length; i < len; i++) {
-                                this.$ProductList.addProduct(
-                                    new Product({
+                                this.$ArticleList.addArticle(
+                                    new Article({
                                         productId: products[i]
                                     })
                                 );
@@ -257,9 +307,12 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
                 }, {
                     duration: 200,
                     callback: function () {
-                        if (this.$ProductList) {
-                            this.$ProductList.destroy();
-                            this.$ProductList = null;
+                        if (this.$ArticleList) {
+                            this.setAttribute('articles', this.$ArticleList.save());
+                            this.$serializedList = this.$ArticleList.serialize();
+
+                            this.$ArticleList.destroy();
+                            this.$ArticleList = null;
                         }
 
                         Container.set('html', '');
@@ -308,10 +361,10 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
 
             this.$AddProduct = new QUIButtonMultiple({
                 textimage: 'fa fa-plus',
-                text     : 'Artikel hinzufügen',
+                text     : QUILocale.get(lg, 'erp.panel.temporary.invoice.buttonAdd'),
                 events   : {
                     onClick: function () {
-                        if (self.$ProductList) {
+                        if (self.$ArticleList) {
                             self.openProductSearch();
                         }
                     }
@@ -321,22 +374,22 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
             this.$AddProduct.hide();
 
             this.$AddProduct.appendChild({
-                text  : 'Freier Artikel',
+                text  : QUILocale.get(lg, 'erp.panel.temporary.invoice.buttonAdd.custom'),
                 events: {
                     onClick: function () {
-                        if (self.$ProductList) {
-                            self.$ProductList.insertNewProduct();
+                        if (self.$ArticleList) {
+                            self.$ArticleList.insertNewProduct();
                         }
                     }
                 }
             });
 
             this.$AddProduct.appendChild({
-                text  : 'Text',
+                text  : QUILocale.get(lg, 'erp.panel.temporary.invoice.buttonAdd.text'),
                 events: {
                     onClick: function () {
-                        if (self.$ProductList) {
-
+                        if (self.$ArticleList) {
+                            self.$ArticleList.addArticle(new TextArticle());
                         }
                     }
                 }
@@ -346,6 +399,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
 
             // buttons
             this.addButton({
+                name     : 'save',
                 text     : QUILocale.get('quiqqer/system', 'save'),
                 textimage: 'fa fa-save',
                 events   : {
@@ -357,6 +411,7 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
             this.addButton(this.$AddProduct);
 
             this.addButton({
+                name  : 'delete',
                 icon  : 'fa fa-trash',
                 styles: {
                     'float': 'right'
@@ -368,24 +423,27 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
 
             // categories
             this.addCategory({
+                name  : 'data',
                 icon  : 'fa fa-info',
-                text  : 'Rechnungsdaten',
+                text  : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.data'),
                 events: {
                     onClick: this.openData
                 }
             });
 
             this.addCategory({
+                name  : 'articles',
                 icon  : 'fa fa-list',
-                text  : 'Positionen (Artikel)',
+                text  : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.pos'),
                 events: {
-                    onClick: this.openProducts
+                    onClick: this.openArticles
                 }
             });
 
             this.addCategory({
+                name  : 'verification',
                 icon  : 'fa fa-check',
-                text  : 'Überprüfung',
+                text  : QUILocale.get(lg, 'erp.panel.temporary.invoice.category.review'),
                 events: {
                     onClick: this.openVerification
                 }
@@ -407,6 +465,12 @@ define('package/quiqqer/invoice/bin/backend/controls/panels/TemporaryInvoice', [
                 this.setAttribute('title', data.id);
                 this.setAttributes(data);
 
+                if (data.articles.length) {
+                    this.$serializedList = {
+                        articles: data.articles
+                    };
+                }
+
                 this.refresh();
                 this.getCategoryBar().firstChild().click();
                 this.Loader.hide();
diff --git a/database.xml b/database.xml
index 119fe26..3b7ab4c 100644
--- a/database.xml
+++ b/database.xml
@@ -25,7 +25,7 @@
             <field type="INT(1) DEFAULT 0">canceled</field> <!-- storno -->
             <field type="INT NOT NULL">c_user</field>
             <field type="TEXT NULL">data</field>
-            <field type="TEXT NOT NULL">products</field>
+            <field type="TEXT NOT NULL">articles</field>
             <!--<field type="TEXT NULL">comments</field>--> <!-- als Modul auslagern -->
             <field type="TEXT NULL">history</field>
             <field type="TEXT NULL">customer_data</field>
@@ -75,7 +75,7 @@
             <field type="timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP">date</field>
             <field type="INT(11) NOT NULL">c_user</field>
             <field type="TEXT NULL">data</field>
-            <field type="TEXT NOT NULL">products</field>
+            <field type="TEXT NOT NULL">articles</field>
             <field type="TEXT NULL">history</field>
             <field type="TEXT NULL">customer_data</field>
             <field type="FLOAT NOT NULL">isbrutto</field>
diff --git a/locale.xml b/locale.xml
index 6697564..6108fe3 100644
--- a/locale.xml
+++ b/locale.xml
@@ -46,6 +46,10 @@
             <en><![CDATA[Invoice drafts]]></en>
         </locale>
 
+        <locale name="invoice.products.articleNo">
+            <de><![CDATA[Artikel Nr.]]></de>
+            <en><![CDATA[Article No.]]></en>
+        </locale>
         <locale name="invoice.products.description">
             <de><![CDATA[Produkt Beschreibung]]></de>
             <en><![CDATA[Product description]]></en>
@@ -252,6 +256,50 @@
             <de><![CDATA[Rechnungsentwürfe]]></de>
             <en><![CDATA[Invoice drafts]]></en>
         </locale>
+        <locale name="erp.panel.temporary.invoice.buttonAdd">
+            <de><![CDATA[Artikel hinzufügen]]></de>
+            <en><![CDATA[Add Article]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.buttonAdd.custom">
+            <de><![CDATA[Freier Artikel]]></de>
+            <en><![CDATA[Custom Article]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.buttonAdd.text">
+            <de><![CDATA[Text]]></de>
+            <en><![CDATA[Text]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.data">
+            <de><![CDATA[Rechnungsdaten]]></de>
+            <en><![CDATA[Invoice data]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.pos">
+            <de><![CDATA[Positionen (Artikel)]]></de>
+            <en><![CDATA[Position (Article)]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.review">
+            <de><![CDATA[Überprüfung]]></de>
+            <en><![CDATA[Review]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.data.textInvoiceData">
+            <de><![CDATA[Rechnungsdaten]]></de>
+            <en><![CDATA[Invoice data]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.data.textInvoiceDate">
+            <de><![CDATA[Rechnung Datum]]></de>
+            <en><![CDATA[Invoice Date]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.data.textTermOfPayment">
+            <de><![CDATA[Zahlungsziel]]></de>
+            <en><![CDATA[Term of Payment]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.data.textProjectName">
+            <de><![CDATA[Projektname]]></de>
+            <en><![CDATA[Projectname]]></en>
+        </locale>
+        <locale name="erp.panel.temporary.invoice.category.data.textOrderedBy">
+            <de><![CDATA[Bestellt durch]]></de>
+            <en><![CDATA[Ordered by]]></en>
+        </locale>
 
         <locale name="dialog.ti.delete.title">
             <de><![CDATA[Rechnung löschen]]></de>
diff --git a/src/QUI/ERP/Accounting/Invoice/Articles/Article.php b/src/QUI/ERP/Accounting/Invoice/Articles/Article.php
new file mode 100644
index 0000000..37c7acf
--- /dev/null
+++ b/src/QUI/ERP/Accounting/Invoice/Articles/Article.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * This file contains QUI\ERP\Accounting\Invoice\Articles\Article
+ */
+namespace QUI\ERP\Accounting\Invoice\Articles;
+
+use QUI;
+
+/**
+ * Article
+ * An temporary invoice article
+ *
+ * - Freier Artikel
+ *
+ * @package QUI\ERP\Accounting\Invoice
+ */
+class Article implements ArticleInterface
+{
+    /**
+     * @var array
+     */
+    protected $attributes = array();
+
+    /**
+     * Article constructor.
+     *
+     * @param array $attributes - (title, description, unitPrice, quantity)
+     */
+    public function __construct($attributes = array())
+    {
+        if (isset($attributes['title'])) {
+            $this->attributes['title'] = $attributes['title'];
+        }
+
+        if (isset($attributes['description'])) {
+            $this->attributes['description'] = $attributes['description'];
+        }
+
+        if (isset($attributes['unitPrice'])) {
+            $this->attributes['unitPrice'] = $attributes['unitPrice'];
+        }
+
+        if (isset($attributes['quantity'])) {
+            $this->attributes['quantity'] = $attributes['quantity'];
+        }
+    }
+
+    /**
+     * Returns the article title
+     *
+     * @param null|QUI\Locale $Locale
+     * @return string
+     */
+    public function getTitle($Locale = null)
+    {
+        if (isset($this->attributes['title'])) {
+            return $this->attributes['title'];
+        }
+
+        return '';
+    }
+
+    /**
+     * Returns the article description
+     *
+     * @param null|QUI\Locale $Locale
+     * @return string
+     */
+    public function getDescription($Locale = null)
+    {
+        if (isset($this->attributes['description'])) {
+            return $this->attributes['description'];
+        }
+
+        return '';
+    }
+
+    /**
+     * Returns the article unit price
+     *
+     * @return int|float
+     */
+    public function getUnitPrice()
+    {
+        if (isset($this->attributes['unitPrice'])) {
+            return $this->attributes['unitPrice'];
+        }
+
+        return 0;
+    }
+
+    /**
+     * Returns the article total sum
+     *
+     * @return int|float
+     */
+    public function getSum()
+    {
+        return 0;
+    }
+
+    /**
+     * Returns the article quantity
+     *
+     * @return int
+     */
+    public function getQuantity()
+    {
+        if (isset($this->attributes['quantity'])) {
+            return $this->attributes['quantity'];
+        }
+
+        return 1;
+    }
+
+    /**
+     * Return the article as an array
+     *
+     * @return array
+     */
+    public function toArray()
+    {
+        return array(
+            'title'       => $this->getTitle(),
+            'description' => $this->getDescription(),
+            'unitPrice'   => $this->getUnitPrice(),
+            'quantity'    => $this->getQuantity(),
+            'sum'         => $this->getSum(),
+            'control'     => 'package/quiqqer/invoice/bin/backend/controls/articles/Article'
+        );
+    }
+}
diff --git a/src/QUI/ERP/Accounting/Invoice/Articles/ArticleInterface.php b/src/QUI/ERP/Accounting/Invoice/Articles/ArticleInterface.php
new file mode 100644
index 0000000..cbd3b29
--- /dev/null
+++ b/src/QUI/ERP/Accounting/Invoice/Articles/ArticleInterface.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * This file contains QUI\ERP\Accounting\Invoice\Articles
+ */
+namespace QUI\ERP\Accounting\Invoice\Articles;
+
+use QUI;
+
+/**
+ * Article
+ * An temporary invoice article
+ *
+ * @package QUI\ERP\Accounting\Invoice
+ */
+interface ArticleInterface
+{
+    /**
+     * Article constructor.
+     *
+     * @param array $attributes - article attributes
+     * @throws \QUI\Exception
+     */
+    public function __construct($attributes = array());
+
+    /**
+     * @param null|QUI\Locale $Locale
+     * @return string
+     */
+    public function getTitle($Locale = null);
+
+    /**
+     * @param null|QUI\Locale $Locale
+     * @return string
+     */
+    public function getDescription($Locale = null);
+
+    /**
+     * @return integer|float
+     */
+    public function getUnitPrice();
+
+    /**
+     * @return integer|float
+     */
+    public function getSum();
+
+    /**
+     * @return integer|float
+     */
+    public function getQuantity();
+
+    /**
+     * @return array
+     */
+    public function toArray();
+}
diff --git a/src/QUI/ERP/Accounting/Invoice/Articles/Text.php b/src/QUI/ERP/Accounting/Invoice/Articles/Text.php
new file mode 100644
index 0000000..abc7939
--- /dev/null
+++ b/src/QUI/ERP/Accounting/Invoice/Articles/Text.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * This file contains QUI\ERP\Accounting\Invoice\Articles\Text
+ */
+namespace QUI\ERP\Accounting\Invoice\Articles;
+
+use QUI;
+
+/**
+ * Article
+ * An temporary invoice article
+ *
+ * - Freier Artikel
+ *
+ * @package QUI\ERP\Accounting\Invoice
+ */
+class Text implements ArticleInterface
+{
+    /**
+     * @var array
+     */
+    protected $attributes = array();
+
+    /**
+     * Article constructor.
+     *
+     * @param array $attributes - (title, description, unitPrice, quantity)
+     */
+    public function __construct($attributes = array())
+    {
+        if (isset($attributes['title'])) {
+            $this->attributes['title'] = $attributes['title'];
+        }
+
+        if (isset($attributes['description'])) {
+            $this->attributes['description'] = $attributes['description'];
+        }
+    }
+
+    /**
+     * Returns the article title
+     *
+     * @param null|QUI\Locale $Locale
+     * @return string
+     */
+    public function getTitle($Locale = null)
+    {
+        if (isset($this->attributes['title'])) {
+            return $this->attributes['title'];
+        }
+
+        return '';
+    }
+
+    /**
+     * Returns the article description
+     *
+     * @param null|QUI\Locale $Locale
+     * @return string
+     */
+    public function getDescription($Locale = null)
+    {
+        if (isset($this->attributes['description'])) {
+            return $this->attributes['description'];
+        }
+
+        return '';
+    }
+
+    /**
+     * Returns the article unit price
+     *
+     * @return int|float
+     */
+    public function getUnitPrice()
+    {
+        if (isset($this->attributes['unitPrice'])) {
+            return $this->attributes['unitPrice'];
+        }
+
+        return 0;
+    }
+
+    /**
+     * Returns the article total sum
+     *
+     * @return int|float
+     */
+    public function getSum()
+    {
+        return 0;
+    }
+
+    /**
+     * Returns the article quantity
+     *
+     * @return int
+     */
+    public function getQuantity()
+    {
+        return 1;
+    }
+
+    /**
+     * Return the article as an array
+     *
+     * @return array
+     */
+    public function toArray()
+    {
+        return array(
+            'title'       => $this->getTitle(),
+            'description' => $this->getDescription(),
+            'unitPrice'   => 0,
+            'quantity'    => 1,
+            'sum'         => 0,
+            'control'     => 'package/quiqqer/invoice/bin/backend/controls/articles/Text'
+        );
+    }
+}
diff --git a/src/QUI/ERP/Accounting/Invoice/TemporaryInvoice.php b/src/QUI/ERP/Accounting/Invoice/TemporaryInvoice.php
index b9ff1c2..a627cdc 100644
--- a/src/QUI/ERP/Accounting/Invoice/TemporaryInvoice.php
+++ b/src/QUI/ERP/Accounting/Invoice/TemporaryInvoice.php
@@ -24,6 +24,11 @@ class TemporaryInvoice extends QUI\QDOM
      */
     protected $id;
 
+    /**
+     * @var array
+     */
+    protected $articles = array();
+
     /**
      * Invoice constructor.
      *
@@ -32,7 +37,19 @@ class TemporaryInvoice extends QUI\QDOM
      */
     public function __construct($id, Handler $Handler)
     {
-        $this->setAttributes($Handler->getTemporaryInvoiceData($id));
+        $data = $Handler->getTemporaryInvoiceData($id);
+
+        if (isset($data['articles'])) {
+            $articles = json_decode($data['articles'], true);
+
+            if (is_array($articles)) {
+                $this->importArticles($articles);
+            }
+
+            unset($data['articles']);
+        }
+
+        $this->setAttributes($data);
 
         $this->id = (int)str_replace(self::ID_PREFIX, '', $id);
     }
@@ -95,6 +112,17 @@ public function save($User = null)
 //            vat_data
 //            processing_status
 
+        // articles
+        $articles = array_map(function ($Article) {
+            /* @var $Article QUI\ERP\Accounting\Invoice\Articles\ArticleInterface */
+            $result = $Article->toArray();
+
+            $result['type'] = get_class($Article);
+
+            return $result;
+        }, $this->articles);
+
+
         QUI::getDataBase()->update(
             Handler::getInstance()->temporaryInvoiceTable(),
             array(
@@ -113,8 +141,8 @@ public function save($User = null)
                 'canceled'          => '',
                 'date'              => '',
                 'data'              => '',
-                'products'          => '',
-                'history'           => '',
+                'articles'          => json_encode($articles),
+//                'history'           => '',
                 'customer_data'     => '',
                 'isbrutto'          => '',
                 'currency_data'     => '',
@@ -219,6 +247,95 @@ public function toArray()
         $attributes       = $this->getAttributes();
         $attributes['id'] = $this->getId();
 
+        // articles
+        $attributes['articles'] = array_map(function ($Article) {
+            /* @var $Article QUI\ERP\Accounting\Invoice\Articles\ArticleInterface */
+            $result = $Article->toArray();
+
+            $result['type'] = get_class($Article);
+
+            return $result;
+        }, $this->articles);
+
         return $attributes;
     }
+
+    /**
+     * Article
+     */
+
+    /**
+     * Add an article
+     *
+     * @param Articles\ArticleInterface $Article
+     */
+    public function addArticle(Articles\ArticleInterface $Article)
+    {
+        $this->articles[] = $Article;
+    }
+
+    /**
+     *
+     */
+    public function removeArticle()
+    {
+
+    }
+
+    /**
+     * Returns the article list
+     *
+     * @return array
+     */
+    public function getArticles()
+    {
+        return $this->articles;
+    }
+
+    /**
+     * Remove all articles from the invoice
+     */
+    public function clearArticles()
+    {
+        $this->articles = array();
+    }
+
+    /**
+     * Import an article array
+     *
+     * @param array $articles - array of articles, eq: [0] => Array
+     * (
+     *       [productId] => 5
+     *       [type] => QUI\ERP\Products\Product\Product
+     *       [position] => 1
+     *       [quantity] => 1
+     *       [unitPrice] => 0
+     *       [vat] =>
+     *       [title] => USS Enterprise NCC-1701-D
+     *       [description] => Raumschiff aus bekannter Serie
+     *       [control] => package/quiqqer/products/bin/controls/invoice/Product
+     * )
+     */
+    public function importArticles($articles = array())
+    {
+        if (!is_array($articles)) {
+            $articles = array();
+        }
+
+        foreach ($articles as $article) {
+            if (!isset($article['type'])) {
+                continue;
+            }
+
+            try {
+                $Article = new $article['type']($article);
+
+                if ($Article instanceof Articles\ArticleInterface) {
+                    $this->addArticle($Article);
+                }
+            } catch (QUI\Exception $Exception) {
+                QUI\System\Log::writeException($Exception);
+            }
+        }
+    }
 }
-- 
GitLab