diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..9ab59b180fb670006ac910a2bc4524a1376dcea0
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,15 @@
+
+# Ignore developer files when exporting
+.gitattributes         export-ignore
+.gitignore             export-ignore
+.gitlab-ci.yml         export-ignore
+.phive                 export-ignore
+captainhook.json       export-ignore
+phpcs.xml.dist         export-ignore
+phpstan-baseline.neon  export-ignore
+phpstan.dist.neon      export-ignore
+phpunit.dist.xml       export-ignore
+tests                  export-ignore
+
+# Explicitly set file type and line endings for PHP files, improves git diff output
+*.php     text eol=lf diff=php
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f4da20891502a3992abf2981d8f6c46e33868ab6..e25391c1f85364060ac958b37e8e35c6c51beafe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,11 @@
 tools/
 phpstan.neon
 .phpunit.result.cache
-phpunit.xml
\ No newline at end of file
+phpunit.xml
+tools/
+
+phpstan.neon
+
+.phpunit.result.cache
+
+phpunit.xml
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b8d20acb6c365ad8036a7b36d47dabd876d08efa..b5a64b401e554341447c74d7cf93a89ac95a3fdb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,17 @@
 include:
-  - project: 'quiqqer/stabilization/semantic-release'
-    file: '/ci-templates/.gitlab-ci.yml'
+  - component: dev.quiqqer.com/quiqqer/stabilization/ci-cd-components/quiqqer-package-bundle/quiqqer-package-bundle@main
+
+# Remove the entire phpunit-php8.1 block, to allow PHPUnit to run on PHP 8.1 in your pipeline
+phpunit-php8.1:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.2 block, to allow PHPUnit to run on PHP 8.2 in your pipeline
+phpunit-php8.2:
+  rules:
+    - when: never
+
+# Remove the entire phpunit-php8.3 block, to allow PHPUnit to run on PHP 8.3 in your pipeline
+phpunit-php8.3:
+  rules:
+    - when: never
\ No newline at end of file
diff --git a/.phive/phars.xml b/.phive/phars.xml
index a1315a09b4adad780a9c5e52f74835c708c5c7d5..5bfa092bfad10dad9d23240281a5a2041acb815b 100644
--- a/.phive/phars.xml
+++ b/.phive/phars.xml
@@ -1,4 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <phive xmlns="https://phar.io/phive">
-  <phar name="phpstan" version="^1.10.67" installed="1.10.67" location="./tools/phpstan" copy="false"/>
+  <phar name="phpstan" version="1.11.8" installed="1.11.8" location="./tools/phpstan" copy="false"/>
+  <phar name="phpunit" version="^10.5.20" installed="10.5.20" location="./tools/phpunit" copy="false"/>
+  <phar name="phpcs" version="^3.10.1" installed="3.10.1" location="./tools/phpcs" copy="false"/>
+  <phar name="phpcbf" version="^3.10.1" installed="3.10.1" location="./tools/phpcbf" copy="false"/>
+  <phar name="captainhook" version="^5.23.3" installed="5.23.3" location="./tools/captainhook" copy="false"/>
 </phive>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a69a59b440e5beec561eca1e341509bd5a18688
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,3 @@
+# Contributing
+
+This package follows the [QUIQQER contribution guidelines](https://dev.quiqqer.com/quiqqer/stabilization/documentation/-/wikis/home).
\ No newline at end of file
diff --git a/ajax/backend/OpenItemsList/getUserOpenItems.php b/ajax/backend/OpenItemsList/getUserOpenItems.php
index 28bfbaf8eb2a808ebe2baf18221600bea403ecf0..26f074d5fa0acb68c9193e00001bed7b34417339 100644
--- a/ajax/backend/OpenItemsList/getUserOpenItems.php
+++ b/ajax/backend/OpenItemsList/getUserOpenItems.php
@@ -16,7 +16,6 @@
     'package_quiqqer_customer_ajax_backend_OpenItemsList_getUserOpenItems',
     function ($userId, $searchParams, $forceRefresh) {
         try {
-            $userId = (int)$userId;
             $cacheName = 'quiqqer/customer/openitems/' . $userId;
             $refresh = true;
             $openItems = [];
diff --git a/ajax/backend/create/createCustomer.php b/ajax/backend/create/createCustomer.php
index 91f0201342b4473a05c08c3c61a6e9dca6542899..d807f356832358a7264bbf93d0777b4a4763c58b 100644
--- a/ajax/backend/create/createCustomer.php
+++ b/ajax/backend/create/createCustomer.php
@@ -12,9 +12,10 @@
 
 QUI::$Ajax->registerFunction(
     'package_quiqqer_customer_ajax_backend_create_createCustomer',
-    function ($customerId, $address, $groups) {
+    function ($customerId, $address, $groups, $attributes) {
         $address = json_decode($address, true);
         $groups = json_decode($groups, true);
+        $attributes = json_decode($attributes, true);
 
         $User = QUI\ERP\Customer\Customers::getInstance()->createCustomer(
             $customerId,
@@ -22,8 +23,13 @@ function ($customerId, $address, $groups) {
             $groups
         );
 
+        if (is_array($attributes)) {
+            $User->setAttributes($attributes);
+            $User->save();
+        }
+
         return $User->getUUID();
     },
-    ['customerId', 'address', 'groups'],
+    ['customerId', 'address', 'groups', 'attributes'],
     'Permission::checkAdminUser'
 );
diff --git a/ajax/backend/files/downloadEntry/removeFile.php b/ajax/backend/files/downloadEntry/removeFile.php
index ac9ceee43427d6f94e7a29ff1b3e21fabc356994..49de65d1b65d835b7ed77204deba99b7e0fdce66 100644
--- a/ajax/backend/files/downloadEntry/removeFile.php
+++ b/ajax/backend/files/downloadEntry/removeFile.php
@@ -12,7 +12,7 @@
     'package_quiqqer_customer_ajax_backend_files_downloadEntry_removeFile',
     function ($file, $customerId) {
         try {
-            CustomerFiles::removeFileFromDownloadEntry((int)$customerId, $file);
+            CustomerFiles::removeFileFromDownloadEntry($customerId, $file);
         } catch (Exception $Exception) {
             QUI\System\Log::writeException($Exception);
 
diff --git a/ajax/backend/getBusinessType.php b/ajax/backend/getBusinessType.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb2a3ffb72c58ae1b921e01d876d770aef3dbc60
--- /dev/null
+++ b/ajax/backend/getBusinessType.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * This file contains package_quiqqer_customer_ajax_backend_getBusinessType
+ */
+
+/**
+ * Return the shop business type
+ */
+
+QUI::$Ajax->registerFunction(
+    'package_quiqqer_customer_ajax_backend_getBusinessType',
+    function () {
+        return QUI\ERP\Utils\Shop::getBusinessType();
+    },
+    false,
+    'Permission::checkAdminUser'
+);
diff --git a/bin/backend/controls/Administration.js b/bin/backend/controls/Administration.js
index 84628151c3bf21bb8edf9c29268b60d638adc393..e73747d1c1d24da1f057de852dcb221b94889835 100644
--- a/bin/backend/controls/Administration.js
+++ b/bin/backend/controls/Administration.js
@@ -131,6 +131,9 @@ define('package/quiqqer/customer/bin/backend/controls/Administration', [
             this.$SubmitButton = this.$Elm.getElement('[name="submit"]');
             this.$FilterButton = this.$Elm.getElement('button[name="filter"]');
 
+            this.$SearchContainer.setStyle('display', 'none');
+            this.$GridContainer.setStyle('display', 'none');
+
             this.$SearchContainer.getElement('form').addEvent('submit', function(event) {
                 event.stop();
             });
@@ -349,17 +352,21 @@ define('package/quiqqer/customer/bin/backend/controls/Administration', [
 
             this.$Grid.disable();
 
-            if (customerId && customerId !== '0') {
-                this.$openCustomer(this.getAttribute('customerId'));
-            } else {
+            CustomerHandler.getCustomerGroupId().then((customerGroup) => {
+                self.$customerGroup = customerGroup;
+
+                if (customerId && customerId !== '0') {
+                    return this.$openCustomer(this.getAttribute('customerId'));
+                }
+
                 if (this.isInWindow() && this.$SearchInput) {
                     this.$SearchInput.focus();
                 }
-            }
 
-            CustomerHandler.getCustomerGroupId().then(function(customerGroup) {
-                self.$customerGroup = customerGroup;
-                self.refresh().then(function() {
+                this.$SearchContainer.setStyle('display', null);
+                this.$GridContainer.setStyle('display', null);
+
+                return self.refresh().then(function() {
                     self.$Grid.enable();
                 });
             });
@@ -687,65 +694,80 @@ define('package/quiqqer/customer/bin/backend/controls/Administration', [
                 userId
             ]);
 
+            if (!self.isInWindow()) {
+                require([
+                    'utils/Panels',
+                    'package/quiqqer/customer/bin/backend/controls/customer/Panel'
+                ], (PanelUtils, Panel) => {
+                    PanelUtils.openPanelInTasks(
+                        new Panel({
+                            userId: userId
+                        })
+                    );
+                });
+
+                return;
+            }
+
+            moofx([
+                this.$SearchContainer,
+                this.$GridContainer
+            ]).animate({
+                opacity: 0
+            }, {
+                duration: 200,
+                callback: () => {
+                    this.$SearchContainer.setStyle('display', 'none');
+                    this.$GridContainer.setStyle('display', 'none');
+                }
+            });
+
+
             require([
-                'package/quiqqer/customer/bin/backend/controls/customer/Panel',
-                'utils/Panels'
-            ], function(Panel, PanelUtils) {
-                if (self.isInWindow()) {
-                    const Container = new Element('div', {
-                        'class': 'quiqqer-customer-administration-customer',
-                        styles: {
-                            left: -50,
-                            opacity: 0
-                        }
-                    }).inject(self.getElm());
+                'package/quiqqer/customer/bin/backend/controls/customer/Panel'
+            ], function(Panel) {
+                const Container = new Element('div', {
+                    'class': 'quiqqer-customer-administration-customer',
+                    styles: {
+                        opacity: 0
+                    }
+                }).inject(self.getElm());
 
-                    self.$CustomerPanel = new Panel({
-                        header: false,
-                        userId: userId,
-                        showUserButton: true,
-                        showDeleteButton: false,
-                        events: {
-                            onLoaded: () => {
-                                self.fireEvent('customerOpenEnd', [
-                                    this,
-                                    userId,
-                                    self.$CustomerPanel
-                                ]);
-                            },
-                            onError: (Instance) => {
-                                if (!Instance.$User) {
-                                    self.setAttribute('customerId', false);
-                                }
+                self.$CustomerPanel = new Panel({
+                    header: false,
+                    userId: userId,
+                    showUserButton: true,
+                    showDeleteButton: false,
+                    'hide-loader': true,
+                    events: {
+                        onLoaded: () => {
+                            self.fireEvent('customerOpenEnd', [
+                                this,
+                                userId,
+                                self.$CustomerPanel
+                            ]);
+
+                            moofx(Container).animate({
+                                opacity: 1
+                            });
+                        },
+                        onError: (Instance) => {
+                            if (!Instance.$User) {
+                                self.setAttribute('customerId', false);
                             }
                         }
-                    });
-
-                    self.$CustomerPanel.inject(Container);
-
-                    self.fireEvent('customerOpen', [
-                        this,
-                        userId,
-                        self.$CustomerPanel
-                    ]);
-
-                    moofx(Container).animate({
-                        left: 0,
-                        opacity: 1
-                    }, {
-                        callback: function() {
-                            self.$CustomerPanel.fireEvent('show');
-                        }
-                    });
+                    }
+                });
 
-                    return;
-                }
+                self.$CustomerPanel.inject(Container);
 
-                PanelUtils.openPanelInTasks(
-                    new Panel({
-                        userId: userId
-                    })
-                );
+                self.fireEvent('customerOpen', [
+                    this,
+                    userId,
+                    self.$CustomerPanel
+                ]);
+
+                self.$CustomerPanel.fireEvent('show');
             });
         },
 
@@ -757,17 +779,29 @@ define('package/quiqqer/customer/bin/backend/controls/Administration', [
                 return;
             }
 
-            const self = this,
-                Container = this.$CustomerPanel.getElm().getParent();
+            const Container = this.$CustomerPanel.getElm().getParent();
+            const hiedCustomer = new Promise((resolve) => {
+                moofx(Container).animate({
+                    opacity: 0
+                }, {
+                    callback: resolve
+                });
+            });
 
-            moofx(Container).animate({
-                left: 50,
-                opacity: 0
-            }, {
-                callback: function() {
-                    self.$CustomerPanel = null;
-                    Container.destroy();
-                }
+            this.$SearchContainer.setStyle('display', null);
+            this.$SearchContainer.setStyle('opacity', null);
+
+            this.$GridContainer.setStyle('display', null);
+            this.$GridContainer.setStyle('opacity', null);
+
+            Promise.all([
+                this.refresh(),
+                hiedCustomer
+            ]).then(() => {
+                this.$Grid.enable();
+
+                this.$CustomerPanel = null;
+                Container.destroy();
             });
         },
 
diff --git a/bin/backend/controls/OpenItems/OpenItems.js b/bin/backend/controls/OpenItems/OpenItems.js
index 20a905d2eabc624f167a3cb9beba7f16e54a607e..e81e8ef1b8bcc16cc17c3b06c11e9fba2f4a7ea1 100644
--- a/bin/backend/controls/OpenItems/OpenItems.js
+++ b/bin/backend/controls/OpenItems/OpenItems.js
@@ -992,44 +992,9 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [
             const submitTransaction = function(Win, Data) {
                 Win.Loader.show();
 
-                switch (erpEntity) {
-                    case 'Invoice':
-                        require(['package/quiqqer/invoice/bin/Invoices'], function(Invoices) {
-                            Invoices.addPaymentToInvoice(
-                                Row.documentNo,
-                                Data.amount,
-                                Data.payment_method
-                            ).then(function() {
-                                Win.close();
-
-                                self.$refreshUserEntry(self.$currentRecordsUserId).then(function() {
-                                    self.$refreshUserRecords(self.$GridDetails, true);
-                                });
-                            }).catch(function(err) {
-                                Win.Loader.hide();
-                            });
-                        });
-                        break;
-
-                    case 'Order':
-                        require(['package/quiqqer/order/bin/backend/Orders'], function(Orders) {
-                            Orders.addPaymentToOrder(
-                                Row.documentId,
-                                Data.amount,
-                                Data.payment_method,
-                                Data.date
-                            ).then(function() {
-                                Win.close();
-
-                                self.$refreshUserEntry(self.$currentRecordsUserId).then(function() {
-                                    self.$refreshUserRecords(self.$GridDetails, true);
-                                });
-                            }).catch(function(err) {
-                                Win.Loader.hide();
-                            });
-                        });
-                        break;
-                }
+                self.$refreshUserEntry(self.$currentRecordsUserId).then(function() {
+                    self.$refreshUserRecords(self.$GridDetails, true);
+                });
             };
 
             const linkTransaction = (txId, Win) => {
@@ -1070,7 +1035,7 @@ define('package/quiqqer/customer/bin/backend/controls/OpenItems/OpenItems', [
             };
 
             new AddPaymentWindow({
-                entityId: Row.documentNo,
+                entityId: Row.hash,
                 entityType: erpEntity,
                 events: {
                     onSubmit: submitTransaction,
diff --git a/bin/backend/controls/create/Customer.html b/bin/backend/controls/create/Customer.html
index 91fd442de5c276a263083bed0bf5986e852f2906..388ff10be476f8041175168321027d84d570b006 100644
--- a/bin/backend/controls/create/Customer.html
+++ b/bin/backend/controls/create/Customer.html
@@ -34,6 +34,20 @@
                                 </label>
                             </td>
                         </tr>
+                        <tr>
+                            <td>
+                                <label class="field-container">
+                                    <span class="field-container-item">
+                                        {{textIsNettoBruttoUser}}
+                                    </span>
+                                    <select name="quiqqer.erp.isNettoUser" class="field-container-field">
+                                        <option value=""></option>
+                                        <option value="2">{{textBrutto}}</option>
+                                        <option value="1">{{textNetto}}</option>
+                                    </select>
+                                </label>
+                            </td>
+                        </tr>
                         <tr>
                             <td>
                                 <label class="field-container">
diff --git a/bin/backend/controls/create/Customer.js b/bin/backend/controls/create/Customer.js
index 101a268a2725b053f464ed79a8d302b6b68f6ecb..8233f75430c4d9b43bcbb868d5f3fd53ea0f3ada 100644
--- a/bin/backend/controls/create/Customer.js
+++ b/bin/backend/controls/create/Customer.js
@@ -21,7 +21,7 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
 ], function(QUI, QUIControl, Countries, Handler, QUILocale, QUIAjax, Mustache, template) {
     'use strict';
 
-    var lg = 'quiqqer/customer';
+    const lg = 'quiqqer/customer';
 
     return new Class({
 
@@ -66,6 +66,9 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
                 customerGroupsHeader: QUILocale.get(lg, 'window.customer.creation.groups.title'),
                 customerGroupsText: QUILocale.get(lg, 'window.customer.creation.groups.text'),
                 labelPrefix: QUILocale.get(lg, 'window.customer.creation.customerNo.labelPrefix'),
+                textIsNettoBruttoUser: QUILocale.get(lg, 'customer.user.information.textBruttoNetto'),
+                textNetto: QUILocale.get('quiqqer/erp', 'user.settings.userNettoStatus.netto'),
+                textBrutto: QUILocale.get('quiqqer/erp', 'user.settings.userNettoStatus.brutto'),
 
                 textAddressCompany: QUILocale.get('quiqqer/core', 'company'),
                 textAddressSalutation: QUILocale.get('quiqqer/core', 'salutation'),
@@ -86,10 +89,10 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
             this.$Form = this.$Elm.getElement('form');
 
             // key events
-            var self = this;
-            var CustomerId = this.$Elm.getElement('[name="customerId"]');
-            var Company = this.$Elm.getElement('[name="address-company"]');
-            var Country = this.$Elm.getElement('[name="address-country"]');
+            const self = this;
+            const CustomerId = this.$Elm.getElement('[name="customerId"]');
+            const Company = this.$Elm.getElement('[name="address-company"]');
+            const Country = this.$Elm.getElement('[name="address-country"]');
 
             CustomerId.addEvent('keydown', function(event) {
                 if (event.key === 'tab') {
@@ -140,13 +143,13 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
          * event: on inject
          */
         $onInject: function() {
-            var self = this;
-            var Group = this.$Elm.getElement('[name="group"]');
+            const self = this;
+            const Group = this.$Elm.getElement('[name="group"]');
 
             Countries.getCountries().then(function(countries) {
-                var CountrySelect = self.$Elm.getElement('[name="address-country"]');
+                const CountrySelect = self.$Elm.getElement('[name="address-country"]');
 
-                for (var code in countries) {
+                for (let code in countries) {
                     if (!countries.hasOwnProperty(code)) {
                         continue;
                     }
@@ -160,10 +163,32 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
                 if (QUIQQER_CONFIG.globals.country) {
                     CountrySelect.value = QUIQQER_CONFIG.globals.country;
                 }
+            }).then(() => {
+                const Select = this.$Elm.getElement('[name="quiqqer.erp.isNettoUser"]')
+
+                return new Promise((resolve) => {
+                    QUIAjax.get('package_quiqqer_customer_ajax_backend_getBusinessType', (businessType) => {
+                        switch (businessType.toLowerCase()) {
+                            case 'b2b':
+                            case 'b2b-b2c':
+                                Select.value = '1';
+                                break;
+
+                            case 'b2c':
+                            case 'b2c-b2b':
+                                Select.value = '2';
+                                break;
+                        }
+
+                        resolve();
+                    }, {
+                        'package': 'quiqqer/customer',
+                    });
+                });
             }).then(function() {
                 return QUI.parse(self.$Elm);
             }).then(function() {
-                var GroupControl = QUI.Controls.getById(Group.get('data-quiid'));
+                const GroupControl = QUI.Controls.getById(Group.get('data-quiid'));
 
                 GroupControl.disable();
                 self.showCustomerNumber();
@@ -174,12 +199,12 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
          * Create the customer
          */
         createCustomer: function() {
-            var self = this;
-            var elements = this.$Form.elements;
-            var customerId = elements.customerId.value;
-            var groups = elements.groups.value.split(',');
+            const self = this;
+            const elements = this.$Form.elements;
+            const customerId = elements.customerId.value;
+            const groups = elements.groups.value.split(',');
 
-            var address = {
+            const address = {
                 'salutation': elements['address-salutation'].value,
                 'firstname': elements['address-firstname'].value,
                 'lastname': elements['address-lastname'].value,
@@ -200,7 +225,10 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
                 'package': 'quiqqer/customer',
                 customerId: customerId,
                 address: JSON.encode(address),
-                groups: JSON.encode(groups)
+                groups: JSON.encode(groups),
+                attributes: JSON.encode({
+                    'quiqqer.erp.isNettoUser': this.$Elm.getElement('[name="quiqqer.erp.isNettoUser"]').value
+                })
             });
         },
 
@@ -212,16 +240,16 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
                 return this.createCustomer();
             }
 
-            var self = this;
-            var steps = this.$List.getElements('li');
-            var pos = this.$List.getPosition(this.$Container);
-            var top = pos.y;
+            const self = this;
+            const steps = this.$List.getElements('li');
+            const pos = this.$List.getPosition(this.$Container);
+            const top = pos.y;
 
-            var height = this.$Container.getSize().y;
-            var scrollHeight = this.$Container.getScrollSize().y;
-            var newTop = this.$roundToStepPos(top - height);
+            const height = this.$Container.getSize().y;
+            const scrollHeight = this.$Container.getScrollSize().y;
+            const newTop = this.$roundToStepPos(top - height);
 
-            var step = 1;
+            let step = 1;
 
             if ((top * -1) / height) {
                 step = Math.round(((top * -1) / height) + 1);
@@ -239,11 +267,11 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
             }
 
             return new Promise(function(resolve) {
-                var checkPromises = [];
+                const checkPromises = [];
 
                 if (step === 1) {
-                    var elements = self.$Form.elements;
-                    var customerId = elements.customerId.value;
+                    const elements = self.$Form.elements;
+                    const customerId = elements.customerId.value;
 
                     checkPromises.push(Handler.validateCustomerNo(customerId));
                 }
@@ -267,12 +295,12 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
          * Previous next step
          */
         previous: function() {
-            var self = this;
-            var pos = this.$List.getPosition(this.$Container);
-            var top = pos.y;
+            const self = this;
+            const pos = this.$List.getPosition(this.$Container);
+            const top = pos.y;
 
-            var height = this.$Container.getSize().y;
-            var newTop = this.$roundToStepPos(top + height);
+            const height = this.$Container.getSize().y;
+            let newTop = this.$roundToStepPos(top + height);
 
             this.$Next.set('html', QUILocale.get(lg, 'window.customer.creation.next'));
             this.$Next.set('data-last', null);
@@ -297,11 +325,11 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
          * refresh the step display
          */
         refreshStepDisplay: function() {
-            var step = 1;
-            var steps = this.$List.getElements('li');
-            var pos = this.$List.getPosition(this.$Container);
-            var top = pos.y;
-            var height = this.$Container.getSize().y;
+            let step = 1;
+            const steps = this.$List.getElements('li');
+            const pos = this.$List.getPosition(this.$Container);
+            const top = pos.y;
+            const height = this.$Container.getSize().y;
 
             if ((top * -1) / height) {
                 step = Math.round(((top * -1) / height) + 1);
@@ -329,8 +357,8 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
          * @return {number}
          */
         $roundToStepPos: function(currentPos) {
-            var height = this.$Container.getSize().y;
-            var pos = Math.round(currentPos / height) * -1;
+            const height = this.$Container.getSize().y;
+            const pos = Math.round(currentPos / height) * -1;
 
             return pos * height * -1;
         },
@@ -339,7 +367,7 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
          * Show the customer number step
          */
         showCustomerNumber: function() {
-            var self = this;
+            const self = this;
 
             this.$Next.disabled = true;
 
@@ -347,8 +375,8 @@ define('package/quiqqer/customer/bin/backend/controls/create/Customer', [
                 Handler.getNewCustomerNo(),
                 Handler.getCustomerIdPrefix()
             ]).then(function(result) {
-                var Input = self.$Elm.getElement('input[name="customerId"]');
-                var InputPrefix = self.$Elm.getElement('input[name="prefix"]');
+                const Input = self.$Elm.getElement('input[name="customerId"]');
+                const InputPrefix = self.$Elm.getElement('input[name="prefix"]');
 
                 if (result[1]) {
                     InputPrefix.value = result[1];
diff --git a/bin/backend/controls/create/CustomerWindow.js b/bin/backend/controls/create/CustomerWindow.js
index c05f30e40c8c00f8fa86b54f9217c26e6a98bd6b..e48837162ead532919b8744cae5457ad876d4f71 100644
--- a/bin/backend/controls/create/CustomerWindow.js
+++ b/bin/backend/controls/create/CustomerWindow.js
@@ -24,7 +24,7 @@ define('package/quiqqer/customer/bin/backend/controls/create/CustomerWindow', [
         ],
 
         options: {
-            maxHeight: 700,
+            maxHeight: 750,
             maxWidth: 600,
             buttons: false
         },
@@ -50,6 +50,7 @@ define('package/quiqqer/customer/bin/backend/controls/create/CustomerWindow', [
 
             this.getContent().set('html', '');
             this.getContent().setStyle('padding', 0);
+            this.Loader.show();
 
             new CreateCustomer({
                 events: {
diff --git a/bin/backend/controls/customer/AddressCreateWindow.js b/bin/backend/controls/customer/AddressCreateWindow.js
index df57d5eaafab5ad33ed1f3ad5a78cceb0f66d319..b5854d7ddeb76e422ae45122a24bd30a317fffcc 100644
--- a/bin/backend/controls/customer/AddressCreateWindow.js
+++ b/bin/backend/controls/customer/AddressCreateWindow.js
@@ -83,6 +83,9 @@ define('package/quiqqer/customer/bin/backend/controls/customer/AddressCreateWind
                 self.addEmail();
             });
 
+            this.addPhone();
+            this.addEmail();
+
             this.Loader.hide();
         },
 
diff --git a/bin/backend/controls/customer/Panel.css b/bin/backend/controls/customer/Panel.css
index e5734638ded30a36f1343b298fe191f8d237a671..dcfe74beac720288ca44b90d0d97c123242182b8 100644
--- a/bin/backend/controls/customer/Panel.css
+++ b/bin/backend/controls/customer/Panel.css
@@ -47,6 +47,11 @@
     float: none;
 }
 
+.quiqqer-customer-panel form[name="customer-information"] {
+    display: inline-block;
+    width: 100%;
+}
+
 /** comments
  ===================================== */
 
@@ -164,7 +169,7 @@ form[name="customer-information"] .comments {
     height: 45px;
     bottom: 0;
     float: left;
-    width: 50%;
+    width: 100%;
 }
 
 .comments-pagination .quiqqer-sheets-desktop,
diff --git a/bin/backend/controls/customer/Panel.js b/bin/backend/controls/customer/Panel.js
index 5a29a0dc35a17096e3e23de226532a61a6966c97..453856491616279626a102fd290865e25993b2ad 100644
--- a/bin/backend/controls/customer/Panel.js
+++ b/bin/backend/controls/customer/Panel.js
@@ -10,6 +10,7 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Panel', [
     'qui/controls/desktop/Panel',
     'qui/controls/buttons/Button',
     'qui/controls/buttons/ButtonMultiple',
+    'qui/controls/loader/Loader',
     'qui/controls/windows/Confirm',
     'package/quiqqer/countries/bin/Countries',
     'package/quiqqer/customer/bin/backend/controls/customer/AddressEditWindow',
@@ -27,7 +28,7 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Panel', [
     'text!package/quiqqer/customer/bin/backend/controls/customer/Panel.EditId.html',
     'css!package/quiqqer/customer/bin/backend/controls/customer/Panel.css'
 
-], function(QUI, QUIPanel, QUIButton, QUIButtonMultiple, QUIConfirm, Countries, AddressEditWindow, Handler,
+], function(QUI, QUIPanel, QUIButton, QUIButtonMultiple, QUILoader, QUIConfirm, Countries, AddressEditWindow, Handler,
     FormUtils, Users, QUILocale, QUIAjax, Packages, Mustache, templateInformation, templateEditId
 ) {
     'use strict';
@@ -67,7 +68,8 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Panel', [
             icon: 'fa fa-user',
             userId: false,
             showUserButton: true,
-            showDeleteButton: true
+            showDeleteButton: true,
+            'hide-loader': false
         },
 
         initialize: function(options) {
@@ -179,6 +181,10 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Panel', [
         $onCreate: function() {
             const self = this;
 
+            if (this.getAttribute('hide-loader')) {
+                this.Loader = new QUILoader();
+            }
+
             this.getElm().addClass('quiqqer-customer-panel');
 
             // buttons
@@ -942,6 +948,12 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Panel', [
 
                 PaginationContainer.set('html', paginationHtml);
 
+                if (paginationHtml === '') {
+                    PaginationContainer.setStyle('display', 'none');
+                } else {
+                    PaginationContainer.setStyle('display', null);
+                }
+
                 return QUI.parse(PaginationContainer).then(function() {
                     if (paginationHtml) {
                         self.$CommentsPagination = QUI.Controls.getById(
@@ -985,6 +997,24 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Panel', [
                         Payments.getPayments().then(function(payments) {
                             let i, len, text;
 
+                            // payment sort
+                            let current = QUILocale.getCurrent();
+
+                            payments.sort((a, b) => {
+                                let titleA = a.title[current] ? a.title[current].toLowerCase() : '';
+                                let titleB = b.title[current] ? b.title[current].toLowerCase() : '';
+
+                                if (titleA < titleB) {
+                                    return -1;
+                                }
+
+                                if (titleA > titleB) {
+                                    return 1;
+                                }
+
+                                return 0;
+                            });
+
                             for (i = 0, len = payments.length; i < len; i++) {
                                 text = '';
 
diff --git a/bin/backend/controls/customer/Select.js b/bin/backend/controls/customer/Select.js
index 14a7af9e29f0b7edf1a4f5588dbc117d1ebdb8ed..3783a3f5c717e74c726cfcdd7f54eaa2eb908d42 100644
--- a/bin/backend/controls/customer/Select.js
+++ b/bin/backend/controls/customer/Select.js
@@ -33,7 +33,11 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Select', [
         Type: 'package/quiqqer/customer/bin/backend/controls/customer/Select',
 
         Binds: [
+            '$onCreate',
             '$onSearchButtonClick',
+            'openCustomerSearch',
+            'editCustomer',
+            'createCustomer',
             'userSearch'
         ],
 
@@ -54,11 +58,67 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Select', [
             );
 
             this.addEvents({
-                onSearchButtonClick: this.$onSearchButtonClick,
-                onCreate: () => {
-                    this.getElm().addClass('quiqqer-customer-select');
+                onCreate: this.$onCreate
+            });
+        },
+
+        $onCreate: function() {
+            this.getElm().addClass('quiqqer-customer-select');
+            this.getElm().set('data-qui', 'package/quiqqer/customer/bin/backend/controls/customer/Select');
+
+            this.$SearchButton.setAttribute('menuCorner', 'topRight');
+
+            this.$SearchButton.appendChild({
+                text: QUILocale.get(lg, 'customer.select.button.search'),
+                name: 'search',
+                icon: 'fa fa-search',
+                events: {
+                    click: this.openCustomerSearch
+                }
+            });
+
+            this.$SearchButton.appendChild({
+                text: QUILocale.get(lg, 'customer.select.button.create'),
+                name: 'create',
+                icon: 'fa fa-plus',
+                events: {
+                    click: this.createCustomer
                 }
             });
+
+            this.$SearchButton.appendChild({
+                text: QUILocale.get(lg, 'customer.select.button.edit'),
+                name: 'edit',
+                icon: 'fa fa-edit',
+                disabled: true,
+                events: {
+                    click: this.editCustomer
+                }
+            });
+
+            const Search = this.$SearchButton.getChildren().filter((Instance) => {
+                return Instance.getAttribute('name') === 'search';
+            })[0];
+
+
+            const Edit = this.$SearchButton.getChildren().filter((Instance) => {
+                return Instance.getAttribute('name') === 'edit';
+            })[0];
+
+            this.$SearchButton.getContextMenu((Menu) => {
+                Menu.setAttribute('menuCorner', 'topRight');
+                Menu.addEvent('show', () => {
+                    if (this.getValue()) {
+                        Edit.enable();
+                        Search.setAttribute('text', QUILocale.get(lg, 'customer.select.button.replace'));
+                    } else {
+                        Edit.disable();
+                        Search.setAttribute('text', QUILocale.get(lg, 'customer.select.button.search'));
+                    }
+
+                    Menu.getElm().setStyle('left', Menu.getElm().getPosition().x + 15);
+                });
+            });
         },
 
         /**
@@ -95,6 +155,14 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Select', [
             });
         },
 
+        addItem: function(id) {
+            if (id === '0' || id === 0) {
+                return this;
+            }
+
+            return this.parent(id);
+        },
+
         /**
          * event : on search click
          *
@@ -110,7 +178,7 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Select', [
 
             require([
                 'package/quiqqer/customer/bin/backend/controls/AdministrationWindow'
-            ], function(Window) {
+            ], (Window) => {
                 new Window({
                     autoclose: true,
                     multiple: self.getAttribute('multiple'),
@@ -129,6 +197,84 @@ define('package/quiqqer/customer/bin/backend/controls/customer/Select', [
                 Btn.setAttribute('icon', oldIcon);
                 Btn.enable();
             });
+        },
+
+        /**
+         * Opens the customer search window
+         */
+        openCustomerSearch: function() {
+            const oldIcon = this.$SearchButton.getAttribute('icon');
+
+            this.$SearchButton.setAttribute('icon', 'fa fa-spinner fa-spin');
+            this.$SearchButton.disable();
+
+            require([
+                'package/quiqqer/customer/bin/backend/controls/AdministrationWindow'
+            ], (Window) => {
+                new Window({
+                    autoclose: true,
+                    multiple: this.getAttribute('multiple'),
+                    search: this.getAttribute('search'),
+                    searchSettings: this.getAttribute('searchSettings'),
+                    events: {
+                        onSubmit: (Win, userIds) => {
+                            for (let i = 0, len = userIds.length; i < len; i++) {
+                                this.addItem(userIds[i]);
+                            }
+                        }
+                    }
+                }).open();
+
+                this.$SearchButton.setAttribute('icon', oldIcon);
+                this.$SearchButton.enable();
+            });
+        },
+
+        /**
+         * opens the customer creation dialog
+         */
+        createCustomer: function() {
+            const oldIcon = this.$SearchButton.getAttribute('icon');
+
+            this.$SearchButton.setAttribute('icon', 'fa fa-spinner fa-spin');
+            this.$SearchButton.disable();
+
+            require([
+                'package/quiqqer/customer/bin/backend/controls/create/CustomerWindow'
+            ], (CustomerWindow) => {
+                new CustomerWindow({
+                    events: {
+                        submit: (Instance, customerId) => {
+                            this.addItem(customerId);
+                        }
+                    }
+                }).open();
+
+                this.$SearchButton.setAttribute('icon', oldIcon);
+                this.$SearchButton.enable();
+            });
+        },
+
+        editCustomer: function() {
+            if (this.getValue() === '') {
+                return;
+            }
+
+            if (!this.getValue()) {
+                return;
+            }
+
+            const Item = this.getElm().getElement('.qui-elements-selectItem');
+
+            if (Item) {
+                Item.dispatchEvent(
+                    new MouseEvent('dblclick', {
+                        'view': window,
+                        'bubbles': true,
+                        'cancelable': true
+                    })
+                );
+            }
         }
     });
 });
diff --git a/bin/backend/controls/customer/SelectItem.js b/bin/backend/controls/customer/SelectItem.js
index 945ea440230080605cea396ec173d790223d5721..83dfcb2515cdbf9a9c2f98c4f77b11c21ebe45c3 100644
--- a/bin/backend/controls/customer/SelectItem.js
+++ b/bin/backend/controls/customer/SelectItem.js
@@ -17,12 +17,17 @@ define('package/quiqqer/customer/bin/backend/controls/customer/SelectItem', [
         Type: 'package/quiqqer/customer/bin/backend/controls/customer/SelectItem',
 
         Binds: [
-            'refresh'
+            'refresh',
+            '$onCustomerInject'
         ],
 
         initialize: function(options) {
             this.parent(options);
             this.setAttribute('icon', 'fa fa-user-o');
+
+            this.addEvents({
+                onInject: this.$onCustomerInject
+            });
         },
 
         /**
@@ -33,6 +38,11 @@ define('package/quiqqer/customer/bin/backend/controls/customer/SelectItem', [
         refresh: function() {
             const id = this.getAttribute('id');
 
+            if (!id || id === '' || id === '0' || id === 0) {
+                this.destroy();
+                return Promise.resolve();
+            }
+
             // user
             this.setAttribute('icon', 'fa fa-user-o');
 
@@ -55,6 +65,34 @@ define('package/quiqqer/customer/bin/backend/controls/customer/SelectItem', [
                     }
                 });
             });
+        },
+
+        $onCustomerInject: function() {
+            this.getElm().setStyle('cursor', 'pointer');
+            this.getElm().addEvent('dblclick', () => {
+                if (!this.getAttribute('id')) {
+                    return;
+                }
+
+                require([
+                    'package/quiqqer/customer/bin/backend/controls/AdministrationWindow'
+                ], (AdministrationWindow) => {
+                    new AdministrationWindow({
+                        customerId: this.getAttribute('id'),
+                        events: {
+                            onSubmit: (Win, userIds) => {
+                                const Parent = QUI.Controls.getById(
+                                    this.getElm().getParent('.quiqqer-customer-select').get('data-quiid')
+                                );
+
+                                for (let i = 0, len = userIds.length; i < len; i++) {
+                                    Parent.addItem(userIds[i]);
+                                }
+                            }
+                        }
+                    }).open();
+                });
+            });
         }
     });
 });
diff --git a/bin/backend/download.php b/bin/backend/download.php
index 707b617779466c26a6a7de742eefe0c4fafb6278..53c7afe39b37c19731b2c938ee2c4b4dd958f618 100644
--- a/bin/backend/download.php
+++ b/bin/backend/download.php
@@ -8,10 +8,13 @@
 define('QUIQQER_SYSTEM', true);
 define('QUIQQER_AJAX', true);
 
+if (!isset($_REQUEST['customerId'])) {
+    exit;
+}
+
 if (
-    !isset($_REQUEST['file'])
-    || !isset($_REQUEST['customerId'])
-    || !isset($_REQUEST['extension'])
+    !isset($_REQUEST['hash'])
+    && (!isset($_REQUEST['file']) || !isset($_REQUEST['extension']))
 ) {
     exit;
 }
@@ -25,6 +28,7 @@
 
 $Request = QUI::getRequest();
 $file = Orthos::clear($Request->query->get('file'));
+$hash = Orthos::clear($Request->query->get('hash'));
 $extension = Orthos::clear($Request->query->get('extension'));
 $customerId = $Request->query->get('customerId');
 
@@ -54,17 +58,26 @@
 }
 
 try {
-    $Customer = QUI::getUsers()->get($customerId);
-    $customerDir = QUI\ERP\Customer\CustomerFiles::getFolderPath($Customer);
+    if (!empty($hash)) {
+        $file = QUI\ERP\Customer\CustomerFiles::getFileByHash($customerId, $hash);
+        $filePath = $file['dirname'] . DIRECTORY_SEPARATOR . $file['basename'];
 
-    if (!empty($extension) && $extension !== 'false') {
-        $file .= '.' . $extension;
-    }
+        if (file_exists($filePath)) {
+            QUI\Utils\System\File::send($filePath, 0, $file['basename']);
+        }
+    } else {
+        $Customer = QUI::getUsers()->get($customerId);
+        $customerDir = QUI\ERP\Customer\CustomerFiles::getFolderPath($Customer);
+
+        if (!empty($extension) && $extension !== 'false') {
+            $file .= '.' . $extension;
+        }
 
-    $filePath = $customerDir . DIRECTORY_SEPARATOR . $file;
+        $filePath = $customerDir . DIRECTORY_SEPARATOR . $file;
 
-    if (file_exists($filePath)) {
-        QUI\Utils\System\File::send($filePath, 0, $file);
+        if (file_exists($filePath)) {
+            QUI\Utils\System\File::send($filePath, 0, $file);
+        }
     }
 } catch (\Exception $Exception) {
     QUI\System\Log::addDebug($Exception->getMessage());
diff --git a/captainhook.json b/captainhook.json
new file mode 100644
index 0000000000000000000000000000000000000000..3702e1a358868bedd5ff4c7eae40bb1abb589267
--- /dev/null
+++ b/captainhook.json
@@ -0,0 +1,13 @@
+{
+    "pre-commit": {
+        "enabled": true,
+        "actions": [
+            {
+                "action": "\\CaptainHook\\App\\Hook\\PHP\\Action\\Linting"
+            },
+            {
+                "action": "composer test"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 88a0e7c5ebad53fe8792af322c16948d93968c40..447c39f2a32fd0103685e6d032b5d721bb0b6e9b 100644
--- a/composer.json
+++ b/composer.json
@@ -1,31 +1,73 @@
 {
-  "name": "quiqqer/customer",
-  "type": "quiqqer-module",
-  "description": "With the customer administration you extend QUIQQER by a simpler administration of your customers.",
-  "license": "GPL-3.0+",
-  "authors": [
-    {
-      "name": "Henning Leutz",
-      "email": "leutz@pcsg.de",
-      "homepage": "https://www.pcsg.de",
-      "role": "Developer"
+    "name": "quiqqer/customer",
+    "type": "quiqqer-module",
+    "description": "With the customer administration you extend QUIQQER by a simpler administration of your customers.",
+    "license": "GPL-3.0+",
+    "authors": [
+        {
+            "name": "Henning Leutz",
+            "email": "leutz@pcsg.de",
+            "homepage": "https://www.pcsg.de",
+            "role": "Developer"
+        }
+    ],
+    "support": {
+        "email": "support@pcsg.de",
+        "url": "https://www.pcsg.de"
+    },
+    "require": {
+        "php": "^8",
+        "quiqqer/core": "^2",
+        "quiqqer/erp": "^3.2"
+    },
+    "suggest": {
+        "quiqqer/user-downloads": "^1"
+    },
+    "autoload": {
+        "psr-4": {
+            "QUI\\ERP\\Customer\\": "src/QUI/ERP/Customer"
+        }
+    },
+    "scripts": {
+        "test": [
+            "@dev:lint",
+            "@dev:phpunit"
+        ],
+        "dev:phpunit": "./tools/phpunit",
+        "dev:lint": [
+            "@dev:lint:phpstan",
+            "@dev:lint:style"
+        ],
+        "dev:lint:phpstan": "./tools/phpstan",
+        "dev:lint:style": "./tools/phpcs",
+        "dev:lint:style:fix": "./tools/phpcbf",
+        "dev:init": [
+            "@dev:init:check-requirements",
+            "@dev:init:tools",
+            "@dev:init:git-hooks"
+        ],
+        "dev:init:check-requirements": [
+            "which composer > /dev/null || (echo 'Error: composer has to be globally installed'; exit 1)",
+            "which phive > /dev/null || (echo 'Error: PHIVE has to be globally installed'; exit 1)"
+        ],
+        "dev:init:tools": "phive install --temporary",
+        "dev:init:git-hooks": "./tools/captainhook install --only-enabled --force"
+    },
+    "scripts-aliases": {
+        "test": [
+            "dev:test"
+        ]
+    },
+    "scripts-descriptions": {
+        "test": "Runs linting, static analysis, and unit tests.",
+        "dev:phpunit": "Run PHPUnit test suites",
+        "dev:lint": "Run PHPStan and code style check",
+        "dev:lint:phpstan": "Run PHPStan",
+        "dev:lint:style": "Run code style check (PHP_CodeSniffer)",
+        "dev:lint:style:fix": "Try to fix code style errors automatically",
+        "dev:init": "Initialize the developer tooling (tools and git hooks)",
+        "dev:init:check-requirements": "Check if the necessary requirements are met",
+        "dev:init:tools": "Install all developer tools (requires PHIVE)",
+        "dev:init:git-hooks": "Install all git hooks (may require tools to be installed)"
     }
-  ],
-  "support": {
-    "email": "support@pcsg.de",
-    "url": "https://www.pcsg.de"
-  },
-  "require": {
-    "php": "^8",
-    "quiqqer/core": "^2",
-    "quiqqer/erp": "^3.2"
-  },
-  "suggest": {
-    "quiqqer/user-downloads": "^1"
-  },
-  "autoload": {
-    "psr-4": {
-      "QUI\\ERP\\Customer\\": "src/QUI/ERP/Customer"
-    }
-  }
-}
+}
\ No newline at end of file
diff --git a/locale.xml b/locale.xml
index f4f1d6b6155281fb33d26d388f391c78a0f326ab..1b8283714188b97718664af05f44ffcb8433400c 100644
--- a/locale.xml
+++ b/locale.xml
@@ -76,6 +76,21 @@
             <en><![CDATA[Prefix that is prependend to all customer numbers.]]></en>
         </locale>
 
+        <locale name="customer.settings.setCustomerNoAtOrder">
+            <de><![CDATA[Automatische Kundennummer-Zuordnung bei Bestellung]]></de>
+            <en><![CDATA[Automatic customer number assignment when ordering]]></en>
+        </locale>
+        <locale name="customer.settings.setCustomerNoAtOrder.description">
+            <de><![CDATA[
+            Diese Einstellung ermöglicht es, dass bei jeder Bestellung automatisch die Kundennummer des Kunden hinzugefügt wird.
+            Das Bestellungsmodul (quiqqer/orders) muss dafür installiert sein.
+            ]]></de>
+            <en><![CDATA[
+            This setting allows the customer's customer number to be added automatically with every order.
+            The order module (quiqqer/orders) must be installed for this.
+            ]]></en>
+        </locale>
+
         <locale name="exception.customer.group.not.exists">
             <de><![CDATA[Kundengruppe existiert nicht.]]></de>
             <en><![CDATA[Customer group does not exist.]]></en>
@@ -1165,6 +1180,22 @@ Best regards
             <de><![CDATA[Neue Adresse anlegen]]></de>
             <en><![CDATA[Create new address]]></en>
         </locale>
+        <locale name="customer.select.button.create">
+            <de><![CDATA[Neuen Kunden anlegen]]></de>
+            <en><![CDATA[Create new customer]]></en>
+        </locale>
+        <locale name="customer.select.button.search">
+            <de><![CDATA[Kunden suchen]]></de>
+            <en><![CDATA[Search customer]]></en>
+        </locale>
+        <locale name="customer.select.button.replace">
+            <de><![CDATA[Kunden ersetzen]]></de>
+            <en><![CDATA[Replace customer]]></en>
+        </locale>
+        <locale name="customer.select.button.edit">
+            <de><![CDATA[Kunden editieren]]></de>
+            <en><![CDATA[Edit customer]]></en>
+        </locale>
 
     </groups>
 </locales>
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000000000000000000000000000000000000..d48084fbdc86a2dbfb78dd89ab2e0b0ff2322ba7
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<ruleset>
+    <!-- Use PSR-12 ruleset -->
+    <rule ref="PSR12"/>
+
+    <!-- Only scan *.php files -->
+    <arg name="extensions" value="php"/>
+
+    <!-- Ignore warnings -->
+    <arg name="warning-severity" value="0"/>
+
+    <!-- Process 64 (or number of CPU cores) files in parallel -->
+    <arg name="parallel" value="64"/>
+
+    <!-- Output relative file paths, by setting the current folder as the basepath -->
+    <arg name="basepath" value="."/>
+
+    <!-- Show colored output -->
+    <arg name="colors"/>
+
+    <!-- Scan everything in the current folder -->
+    <file>.</file>
+</ruleset>
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a2480f2b38bc0182387d66c7e66c0f08cda2a4a4 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -0,0 +1,326 @@
+parameters:
+	ignoreErrors:
+		-
+			message: "#^Parameter \\#1 \\$User of static method QUI\\\\ERP\\\\Comments\\:\\:getHistoryByUser\\(\\) expects QUI\\\\Users\\\\User, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/backend/customer/getCommentsAndHistory.php
+
+		-
+			message: "#^Parameter \\#1 \\$User of static method QUI\\\\ERP\\\\Comments\\:\\:getHistoryByUser\\(\\) expects QUI\\\\Users\\\\User, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/backend/customer/getHistory.php
+
+		-
+			message: "#^Parameter \\#1 \\$User of static method QUI\\\\ERP\\\\Comments\\:\\:getHistoryByUser\\(\\) expects QUI\\\\Users\\\\User, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/backend/customer/getPagination.php
+
+		-
+			message: "#^Parameter \\#1 \\$User of method QUI\\\\Users\\\\Auth\\\\Handler\\:\\:sendPasswordResetVerificationMail\\(\\) expects QUI\\\\Users\\\\User, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/backend/customer/passwordMail.php
+
+		-
+			message: "#^Call to method addUrl\\(\\) on an unknown class QUI\\\\UserDownloads\\\\DownloadEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Call to method delete\\(\\) on an unknown class QUI\\\\UserDownloads\\\\DownloadEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Call to method getUrls\\(\\) on an unknown class QUI\\\\UserDownloads\\\\DownloadEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Call to method update\\(\\) on an unknown class QUI\\\\UserDownloads\\\\DownloadEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Cannot call method getQuiqqerMediaUrls\\(\\) on QUI\\\\UserDownloads\\\\DownloadEntry\\|false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Cannot call method getUrls\\(\\) on QUI\\\\UserDownloads\\\\DownloadEntry\\|false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Cannot call method removeUrl\\(\\) on QUI\\\\UserDownloads\\\\DownloadEntry\\|false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Cannot call method update\\(\\) on QUI\\\\UserDownloads\\\\DownloadEntry\\|false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Customer\\\\CustomerFiles\\:\\:createDownloadEntry\\(\\) has invalid return type QUI\\\\UserDownloads\\\\DownloadEntry\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Customer\\\\CustomerFiles\\:\\:getDownloadEntry\\(\\) has invalid return type QUI\\\\UserDownloads\\\\DownloadEntry\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^PHPDoc tag @throws with type QUI\\\\Exception\\|QUI\\\\UserDownloads\\\\Exception is not subtype of Throwable$#"
+			count: 1
+			path: src/QUI/ERP/Customer/CustomerFiles.php
+
+		-
+			message: "#^Parameter \\#1 \\$code of method QUI\\\\Interfaces\\\\Users\\\\User\\:\\:activate\\(\\) expects string, false given\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/Customers.php
+
+		-
+			message: "#^Result of && is always false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/Customers.php
+
+		-
+			message: "#^Strict comparison using \\=\\=\\= between mixed and '' will always evaluate to false\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/Customers.php
+
+		-
+			message: "#^Strict comparison using \\=\\=\\= between mixed and null will always evaluate to false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/Customers.php
+
+		-
+			message: "#^Call to method getOrder\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\CustomerData\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/EventHandler.php
+
+		-
+			message: "#^Parameter \\$Step of method QUI\\\\ERP\\\\Customer\\\\EventHandler\\:\\:onQuiqqerOrderCustomerDataSaveEnd\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\CustomerData\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/EventHandler.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to method getCustomer\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to method getHash\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Payments\\\\Transactions\\\\Transaction\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Settings\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Events\\:\\:onQuiqqerInvoicePaymentStatusChanged\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Events\\:\\:onQuiqqerInvoiceTemporaryInvoicePostEnd\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Events\\:\\:onQuiqqerOrderCreated\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\AbstractOrder\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Events\\:\\:onQuiqqerOrderPaidStatusChanged\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Parameter \\$TempInvoice of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Events\\:\\:onQuiqqerInvoiceTemporaryInvoicePostEnd\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Parameter \\$Transaction of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Events\\:\\:onTransactionCreate\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Payments\\\\Transactions\\\\Transaction\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Events.php
+
+		-
+			message: "#^Call to method getArticles\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 6
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 3
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getCancelledStatus\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\ProcessingStatus\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getCleanId\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getCurrency\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getDate\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Payments\\\\Transactions\\\\Transaction\\.$#"
+			count: 4
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getGlobalProcessId\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getGlobalProcessId\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getId\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getPaidStatusInformation\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getPaidStatusInformation\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getPrefixedNumber\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getPrefixedNumber\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Dunning\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Payments\\\\Transactions\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Order\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Call to static method getTransactionsByInvoice\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Utils\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Instantiated class QUI\\\\ERP\\\\Order\\\\ProcessingStatus\\\\Handler not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Handler\\:\\:getOpenInvoices\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Handler\\:\\:getOpenOrders\\(\\) has invalid return type QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^PHPDoc tag @var for variable \\$TransactionA contains unknown class QUI\\\\ERP\\\\Accounting\\\\Payments\\\\Transactions\\\\Transaction\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^PHPDoc tag @var for variable \\$TransactionB contains unknown class QUI\\\\ERP\\\\Accounting\\\\Payments\\\\Transactions\\\\Transaction\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Handler\\:\\:parseInvoiceToOpenItem\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Parameter \\$Order of method QUI\\\\ERP\\\\Customer\\\\OpenItemsList\\\\Handler\\:\\:parseOrderToOpenItem\\(\\) has invalid type QUI\\\\ERP\\\\Order\\\\Order\\.$#"
+			count: 2
+			path: src/QUI/ERP/Customer/OpenItemsList/Handler.php
+
+		-
+			message: "#^Strict comparison using \\=\\=\\= between 'ad\\.firstname'\\|'ad\\.lastname'\\|'company'\\|'customerId'\\|'email'\\|'firstname'\\|'lastname'\\|'usergroup'\\|'userId'\\|'username' and 'users\\.customerId' will always evaluate to false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Customer/Search.php
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
index 28b833b0c7f267b8fd9421e8d34bc372bec7f20e..56d8beab0e0ae895340157b07790b8ad830f8d48 100644
--- a/phpstan.dist.neon
+++ b/phpstan.dist.neon
@@ -2,7 +2,7 @@ includes:
     - phpstan-baseline.neon
 
 parameters:
-    level: 1
+    level: 5
     paths:
         - src
         - ajax
diff --git a/phpunit.dist.xml b/phpunit.dist.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f6c7becf0c12757beb871a9333e2d81e02aa7cae
--- /dev/null
+++ b/phpunit.dist.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="tests/phpunit-bootstrap.php">
+    <testsuites>
+        <testsuite name="Tests">
+            <directory>tests/</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>
diff --git a/settings.xml b/settings.xml
index 7702e5b80459e3f4e3082248438e09d9ddac39c6..34348236a9b95cedea415d7cb1d4801d5287190c 100644
--- a/settings.xml
+++ b/settings.xml
@@ -5,7 +5,7 @@
         <config>
             <section name="customer">
                 <conf name="groupId">
-                    <type><![CDATA[integer]]></type>
+                    <type><![CDATA[string]]></type>
                 </conf>
                 <conf name="customerLogin">
                     <type>integer</type>
@@ -17,6 +17,10 @@
                 <conf name="customerNoPrefix">
                     <type><![CDATA[string]]></type>
                 </conf>
+                <conf name="setCustomerNoAtOrder">
+                    <type><![CDATA[boolean]]></type>
+                    <defaultvalue>1</defaultvalue>
+                </conf>
             </section>
 
             <section name="openItems">
@@ -74,6 +78,15 @@
                             </description>
                         </input>
 
+                        <input conf="customer.setCustomerNoAtOrder" type="checkbox">
+                            <text>
+                                <locale group="quiqqer/customer" var="customer.settings.setCustomerNoAtOrder"/>
+                            </text>
+                            <description>
+                                <locale group="quiqqer/customer" var="customer.settings.setCustomerNoAtOrder.description"/>
+                            </description>
+                        </input>
+
                     </settings>
 
                     <settings title="openItems" name="openItems">
diff --git a/src/QUI/ERP/Customer/CustomerFiles.php b/src/QUI/ERP/Customer/CustomerFiles.php
index 70cc46aa6f244c8d4801f9da7674bb2418e1ebc5..67933316e48d06b89dd6d4bb1ab28de004490276 100644
--- a/src/QUI/ERP/Customer/CustomerFiles.php
+++ b/src/QUI/ERP/Customer/CustomerFiles.php
@@ -58,12 +58,40 @@ public static function getFolderPath(QUI\Interfaces\Users\User $User): string
 
         $fileDir = $varDir . $User->getId();
 
-        // if dir with id exists, rename it to hash folder
+        // dir migration
         if (is_dir($fileDir)) {
             $fileUuidDir = $varDir . $User->getUUID();
 
-            rename($fileDir, $fileUuidDir);
+            if (!is_dir($fileUuidDir)) {
+                rename($fileDir, $fileUuidDir);
+            } else {
+                if (!($dh = opendir($fileDir))) {
+                    throw new QUI\Exception('Users without ID cannot have a customer file folder.');
+                }
+
+                while (($file = readdir($dh)) !== false) {
+                    if ($file == "." || $file == "..") {
+                        continue;
+                    }
+
+                    if (file_exists($fileUuidDir . $file)) {
+                        unlink($fileUuidDir . $file);
+                        continue;
+                    }
+
+                    rename(
+                        $fileDir . $file,
+                        $fileUuidDir . $file
+                    );
+                }
+
+                closedir($dh);
+                rmdir($fileDir);
+            }
+
             $fileDir = $fileUuidDir;
+        } else {
+            $fileDir = $varDir . $User->getUUID();
         }
 
         QUI\Utils\System\File::mkdir($fileDir);
diff --git a/src/QUI/ERP/Customer/Customers.php b/src/QUI/ERP/Customer/Customers.php
index 462b0e9dcb37d7ae74cafe55248ffe3a0be55c0c..04398f7b8d6f19f929f61b7f9878be83a7539ce8 100644
--- a/src/QUI/ERP/Customer/Customers.php
+++ b/src/QUI/ERP/Customer/Customers.php
@@ -67,11 +67,7 @@ public function createCustomer($customerId, array $address = [], array $groupIds
         $User->save();
 
         if (!empty($address)) {
-            try {
-                $Address = $User->getStandardAddress();
-            } catch (QUI\Exception) {
-                $Address = $User->addAddress();
-            }
+            $Address = $User->getStandardAddress();
 
             $needles = [
                 'salutation',
@@ -345,17 +341,14 @@ public function setAttributesToCustomer(bool|int|string $userId, array $attribut
         $groups = [];
 
         if (isset($attributes['group'])) {
-            $groups[] = (int)$attributes['group'];
-            $User->setAttribute('mainGroup', (int)$attributes['group']);
+            $groups[] = $attributes['group'];
+            $User->setAttribute('mainGroup', $attributes['group']);
         } elseif (isset($attributes['group']) && $attributes['group'] === null) {
             $User->setAttribute('mainGroup', false);
         }
 
-        if (isset($attributes['groups'])) {
-            if (str_contains($attributes['groups'], ',')) {
-                $attributes['groups'] = explode(',', $attributes['groups']);
-            }
-
+        if (!empty($attributes['groups'])) {
+            $attributes['groups'] = explode(',', $attributes['groups']);
             $groups = array_merge($groups, $attributes['groups']);
         }
 
diff --git a/src/QUI/ERP/Customer/EventHandler.php b/src/QUI/ERP/Customer/EventHandler.php
index 90bd411e22ec55cd1a0fe5023afc71924dc7a272..b150c1aa159fa5b6688ccf03f7ec27ce9cca8631 100644
--- a/src/QUI/ERP/Customer/EventHandler.php
+++ b/src/QUI/ERP/Customer/EventHandler.php
@@ -16,11 +16,13 @@
 
 use function array_merge;
 use function array_values;
+use function count;
 use function dirname;
 use function file_exists;
 use function is_array;
 use function is_numeric;
 use function json_decode;
+use function json_encode;
 use function md5;
 use function trim;
 
@@ -241,7 +243,7 @@ public static function onUserSaveEnd(QUI\Users\User $User): void
 
         if (isset($attributes['mainGroup'])) {
             try {
-                $mainGroup = (int)$attributes['mainGroup'];
+                $mainGroup = $attributes['mainGroup'];
                 QUI::getGroups()->get($mainGroup);
 
                 $data['mainGroup'] = $mainGroup;
@@ -297,14 +299,14 @@ public static function onUserSaveEnd(QUI\Users\User $User): void
     /**
      * @param QUI\Users\User $User
      * @param bool|string $code
-     * @param null|QUI\Interfaces\Users\User $ParentUser
+     * @param null|QUI\Interfaces\Users\User $PermissionUser
      *
      * @throws QUI\Users\Exception|QUI\Exception
      */
     public static function onUserActivateBegin(
         QUI\Users\User $User,
         bool|string $code,
-        ?QUI\Interfaces\Users\User $ParentUser
+        ?QUI\Interfaces\Users\User $PermissionUser
     ): void {
         $Group = Utils::getInstance()->getCustomerGroup();
 
@@ -352,6 +354,23 @@ public static function onQuiqqerOrderCustomerDataSaveEnd(
             QUI\ERP\Customer\Customers::getInstance()->addUserToCustomerGroup($User->getUUID());
         } catch (QUI\Exception $Exception) {
             QUI\System\Log::addDebug($Exception->getMessage());
+            return;
+        }
+
+        // setting: automatically add customer number when ordering
+        $Config = QUI::getPackage('quiqqer/customer')->getConfig();
+
+        if ($Config->get('customer', 'setCustomerNoAtOrder') && !$User->getAttribute('customerId')) {
+            $NumberRange = new NumberRange();
+            $nextCustomerNo = $NumberRange->getNextCustomerNo();
+
+            try {
+                $User->setAttribute('customerId', $nextCustomerNo);
+                $User->save(QUI::getUsers()->getSystemUser());
+
+                $NumberRange->setRange($nextCustomerNo + 1);
+            } catch (QUI\Exception) {
+            }
         }
     }
 
@@ -369,14 +388,7 @@ public static function onFrontendUserDataMiddle(
         QUI\Users\User $User,
         $Address
     ): void {
-        try {
-            $Engine = QUI::getTemplateManager()->getEngine();
-        } catch (QUI\Exception $Exception) {
-            QUI\System\Log::writeException($Exception);
-
-            return;
-        }
-
+        $Engine = QUI::getTemplateManager()->getEngine();
         $canEdit = QUI\Permissions\Permission::hasPermission('quiqqer.customer.FrontendUsers.contactPerson.edit');
         $canView = QUI\Permissions\Permission::hasPermission('quiqqer.customer.FrontendUsers.contactPerson.view');
 
@@ -429,6 +441,70 @@ public static function onQuiqqerMigrationV2(MigrationV2 $Console): void
             'userId'
         );
 
+        // users extra fields
+        $Console->writeLn('- Migrate customer attributes');
+
+        $userTable = QUI::getUsers()->table();
+        $tableAddresses = QUI::getUsers()->tableAddress();
+
+        $result = QUI::getDataBase()->fetch([
+            'from' => $userTable
+        ]);
+
+        foreach ($result as $entry) {
+            $extra = json_decode($entry['extra'], true);
+
+            if (!empty($extra['quiqqer.erp.customer.contact.person'])) {
+                if (is_numeric($extra['quiqqer.erp.customer.contact.person'])) {
+                    try {
+                        $extra['quiqqer.erp.customer.contact.person'] = QUI::getUsers()->get(
+                            $extra['quiqqer.erp.customer.contact.person']
+                        )->getUUID();
+                    } catch (QUI\Exception) {
+                    }
+                }
+            }
+
+            if (!empty($extra['quiqqer.erp.address'])) {
+                if (is_numeric($extra['quiqqer.erp.address'])) {
+                    try {
+                        $addressData = QUI::getDataBase()->fetch([
+                            'from' => $tableAddresses,
+                            'where' => [
+                                'id' => $extra['quiqqer.erp.address']
+                            ]
+                        ]);
+
+                        if (count($addressData)) {
+                            $extra['quiqqer.erp.address'] = $addressData[0]['uuid'];
+                        }
+                    } catch (QUI\Exception) {
+                    }
+                }
+            }
+
+            if (!empty($extra['quiqqer.erp.supplier.contact.person'])) {
+                if (is_numeric($extra['quiqqer.erp.supplier.contact.person'])) {
+                    try {
+                        $extra['quiqqer.erp.supplier.contact.person'] = QUI::getUsers()->get(
+                            $extra['quiqqer.erp.supplier.contact.person']
+                        )->getUUID();
+                    } catch (QUI\Exception) {
+                    }
+                }
+            }
+
+            try {
+                QUI::getDataBase()->update(
+                    $userTable,
+                    ['extra' => json_encode($extra)],
+                    ['id' => $entry['id']]
+                );
+            } catch (QUI\Exception) {
+            }
+        }
+
+
         // migrate settings
         $Console->writeLn('- Migrate customer settings');
 
diff --git a/src/QUI/ERP/Customer/OpenItemsList/Handler.php b/src/QUI/ERP/Customer/OpenItemsList/Handler.php
index f179b524e2e8f05c50559eadc501f93e57a04aac..f4b2a01e7daaa59307d9383107bdbcf412442ca2 100644
--- a/src/QUI/ERP/Customer/OpenItemsList/Handler.php
+++ b/src/QUI/ERP/Customer/OpenItemsList/Handler.php
@@ -195,7 +195,7 @@ protected static function parseInvoiceToOpenItem(Invoice $Invoice): Item
         $Item = new Item($Invoice->getId(), self::DOCUMENT_TYPE_INVOICE);
 
         // Basic data
-        $Item->setDocumentNo($Invoice->getId());
+        $Item->setDocumentNo($Invoice->getPrefixedNumber());
         $Item->setDate(date_create($Invoice->getAttribute('c_date')));
         $Item->setDueDate(date_create($Invoice->getAttribute('time_for_payment')));
         $Item->setGlobalProcessId($Invoice->getGlobalProcessId());
diff --git a/src/QUI/ERP/Customer/OpenItemsList/OutputProvider.php b/src/QUI/ERP/Customer/OpenItemsList/OutputProvider.php
index ec2d85adeafd644ffdb6faab33cf7ec4a998159c..8a78f318b1f3360a2599a809c72e38cf97ff9e06 100644
--- a/src/QUI/ERP/Customer/OpenItemsList/OutputProvider.php
+++ b/src/QUI/ERP/Customer/OpenItemsList/OutputProvider.php
@@ -76,7 +76,7 @@ public static function getDownloadFileName(int|string $entityId): string
 
         return $Locale->get('quiqqer/customer', 'OutputProvider.download_filename', [
             'date' => $Date->format('Y-m-d'),
-            'uid' => $ERPUser->getUUID()
+            'uid' => $ERPUser->getCustomerNo()
         ]);
     }
 
diff --git a/src/QUI/ERP/Customer/Search.php b/src/QUI/ERP/Customer/Search.php
index bec083a3576ffc61d1fdc05b945658465a9474e1..5c507cabbf46a91eb1a23aed5b1ca7062d0c375b 100644
--- a/src/QUI/ERP/Customer/Search.php
+++ b/src/QUI/ERP/Customer/Search.php
@@ -200,8 +200,25 @@ protected function parseListForGrid($data): array
             $Address = null;
             $uuid = '';
 
+            if (!empty($entry['user_uuid'])) {
+                $entry['uuid'] = $entry['user_uuid'];
+                $entry['user_id'] = $entry['user_uuid'];
+            }
+
+            if (!empty($entry['uuid'])) {
+                $entry['user_id'] = $entry['uuid'];
+            }
+
+            if (empty($entry['user_id']) && !empty($entry['id'])) {
+                $entry['user_id'] = $entry['id'];
+            }
+
+            if (empty($entry['user_id'])) {
+                continue;
+            }
+
             try {
-                $User = $Users->get((int)$entry['user_id']);
+                $User = $Users->get($entry['user_id']);
                 $uuid = $User->getUUID();
                 $Address = $User->getStandardAddress();
             } catch (QUI\Exception) {
@@ -257,7 +274,7 @@ protected function parseListForGrid($data): array
                 'id' => (int)$entry['id'],
                 'customerId' => $entry['customerId'],
                 'status' => !!$entry['active'],
-                'user_id' => (int)$entry['user_id'],
+                'user_id' => $entry['user_id'],
                 'user_uuid' => $uuid,
                 'username' => $entry['username'],
                 'firstname' => $entry['firstname'],
@@ -558,7 +575,7 @@ protected function getQuery(bool $count = false): array
                         users.`email` as user_email
                         FROM $table as users
                              LEFT JOIN users_address AS ad ON users.id = ad.uid 
-                             AND users.address = ad.id
+                             AND users.address = ad.uuid
                         {$whereQuery}
                     ) as search_query
                 ",
@@ -568,14 +585,17 @@ protected function getQuery(bool $count = false): array
 
         return [
             "query" => "
-                SELECT users.`id` as user_id,
-                users.`firstname` as user_firstname,
-                users.`lastname` as user_lastname,
-                users.`email` as user_email,
-                users.*, ad.*
+                SELECT 
+                    users.`id` as user_id,
+                    users.`firstname` as user_firstname,
+                    users.`lastname` as user_lastname,
+                    users.`email` as user_email,
+                    users.`uuid` as user_uuid,
+                    users.*, 
+                    ad.*
                 FROM $table as users
                      LEFT JOIN users_address AS ad ON users.id = ad.uid 
-                     AND users.address = ad.id
+                     AND users.address = ad.uuid
                 {$whereQuery}
                 ORDER BY {$order}
                 {$limit}
diff --git a/tests/phpunit-bootstrap.php b/tests/phpunit-bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..eca92fd67bed8ae4ec424ed82d300119d792f042
--- /dev/null
+++ b/tests/phpunit-bootstrap.php
@@ -0,0 +1,11 @@
+<?php
+
+if (!defined('QUIQQER_SYSTEM')) {
+    define('QUIQQER_SYSTEM', true);
+}
+
+if (!defined('QUIQQER_AJAX')) {
+    define('QUIQQER_AJAX', true);
+}
+
+require_once __DIR__ . '/../../../../bootstrap.php';