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 5d49cc71d98b16e2ed3cc08be94e85f83584b1e5..a708841abb3c83ea2ba84c9f3a1fb8966eea8e46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,11 @@ tools/
 phpstan.neon
 .phpunit.result.cache
 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/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 5b4ba523f3480678dc06d11de414faf067222d6c..582afb9c85085a031cd2f1def83782d74d6739aa 100644
--- a/composer.json
+++ b/composer.json
@@ -41,5 +41,47 @@
       "QUI\\ERP\\Order\\": "src/QUI/ERP/Order",
       "QUITests\\ERP\\Order\\": "tests/QUITests/ERP/Order"
     }
+  },
+  "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)"
   }
 }
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81aefd0473a6ed2e483b9442bad0cc6c7b9cb3e0 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -0,0 +1,706 @@
+parameters:
+	ignoreErrors:
+		-
+			message: "#^Parameter \\#6 \\$User of static method QUI\\\\ERP\\\\Accounting\\\\Payments\\\\Transactions\\\\Factory\\:\\:createPaymentTransaction\\(\\) expects null, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/backend/addPayment.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: ajax/backend/createInvoice.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 1
+			path: ajax/backend/createInvoice.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\SalesOrders\\\\SalesOrder\\.$#"
+			count: 1
+			path: ajax/backend/createSalesOrder.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: ajax/backend/post.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 1
+			path: ajax/backend/post.php
+
+		-
+			message: "#^Parameter \\#2 \\$User of class QUI\\\\ERP\\\\Order\\\\Basket\\\\BasketOrder constructor expects QUI\\\\Users\\\\User\\|null, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/frontend/basket/controls/basket.php
+
+		-
+			message: "#^Parameter \\#1 \\$User of method QUI\\\\ERP\\\\Order\\\\Factory\\:\\:createBasket\\(\\) expects null, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/frontend/basket/getBasket.php
+
+		-
+			message: "#^Using nullsafe method call on non\\-nullable type QUI\\\\ERP\\\\Products\\\\Product\\\\ProductList\\. Use \\-\\> instead\\.$#"
+			count: 1
+			path: ajax/frontend/basket/removePos.php
+
+		-
+			message: "#^Parameter \\#2 \\$User of class QUI\\\\ERP\\\\Order\\\\Basket\\\\BasketOrder constructor expects QUI\\\\Users\\\\User\\|null, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: ajax/frontend/basket/save.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:getView\\(\\)\\.$#"
+			count: 1
+			path: ajax/frontend/order/getOrderControl.php
+
+		-
+			message: "#^Negated boolean expression is always false\\.$#"
+			count: 1
+			path: ajax/frontend/order/getStep.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Order\\\\Controls\\\\AbstractOrderingStep\\:\\:savePayment\\(\\)\\.$#"
+			count: 1
+			path: ajax/frontend/order/processing/savePayment.php
+
+		-
+			message: "#^Negated boolean expression is always false\\.$#"
+			count: 1
+			path: ajax/frontend/order/reload.php
+
+		-
+			message: "#^If condition is always true\\.$#"
+			count: 1
+			path: ajax/frontend/order/send.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:setQuantity\\(\\)\\.$#"
+			count: 1
+			path: ajax/frontend/order/setQuantity.php
+
+		-
+			message: "#^Call to method canUsedBy\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to method canUsedInErpEntity\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to method getId\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to method getId\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to method getType\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to method getType\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to method isValid\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to method setErpEntity\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Cannot call method getId\\(\\) on QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\|true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:getShipping\\(\\) has invalid return type QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:getShippingStatus\\(\\) has invalid return type QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Parameter \\#1 \\$Shipping of method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:validateShipping\\(\\) expects QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\|null, QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\|null given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Parameter \\#2 \\$User of class QUI\\\\ERP\\\\Order\\\\Basket\\\\BasketOrder constructor expects QUI\\\\Users\\\\User\\|null, QUI\\\\ERP\\\\User\\|null given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Parameter \\$Shipping of method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:setShipping\\(\\) has invalid type QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Parameter \\$Shipping of method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:validateShipping\\(\\) has invalid type QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Parameter \\$status of method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:setShippingStatus\\(\\) has invalid type QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Property QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:\\$ShippingStatus has unknown class QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status as its type\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/AbstractOrder.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:getProductSetParentUuid\\(\\)\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Basket/Basket.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:getQuantity\\(\\)\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Basket/Basket.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:getUuid\\(\\)\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Basket/Basket.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:toArticle\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Basket/Basket.php
+
+		-
+			message: "#^Class QUI\\\\ERP\\\\Order\\\\Exception referenced with incorrect case\\: QUI\\\\Erp\\\\Order\\\\Exception\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Basket/Basket.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:getQuantity\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Basket/BasketGuest.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:getQuantity\\(\\)\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Basket/BasketOrder.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:setQuantity\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Basket/BasketOrder.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:toArray\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Basket/BasketOrder.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:toArticle\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Basket/BasketOrder.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\FieldInterface\\:\\:getAttributesForUniqueField\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Basket/Product.php
+
+		-
+			message: "#^Class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary not found\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Controls/Order/Order.php
+
+		-
+			message: "#^Instanceof between null and QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary will always evaluate to false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Controls/Order/Order.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Order\\\\AbstractOrder\\:\\:save\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Controls/OrderProcess/Checkout.php
+
+		-
+			message: "#^Negated boolean expression is always false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Controls/OrderProcess/Checkout.php
+
+		-
+			message: "#^Negated boolean expression is always false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Controls/OrderProcess/CustomerData.php
+
+		-
+			message: "#^Parameter \\#2 \\$Step of method QUI\\\\ERP\\\\Order\\\\AbstractOrderProcessProvider\\:\\:getDisplay\\(\\) expects null, \\$this\\(QUI\\\\ERP\\\\Order\\\\Controls\\\\OrderProcess\\\\Processing\\) given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Controls/OrderProcess/Processing.php
+
+		-
+			message: "#^Call to method getData\\(\\) on an unknown class QUI\\\\ERP\\\\SalesOrders\\\\SalesOrder\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Call to method getGlobalProcessId\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Call to method getShipping\\(\\) on an unknown class QUI\\\\ERP\\\\SalesOrders\\\\SalesOrder\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Call to method getShippingStatus\\(\\) on an unknown class QUI\\\\ERP\\\\SalesOrders\\\\SalesOrder\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Instanceof between \\*NEVER\\* and QUI\\\\ERP\\\\Products\\\\Product\\\\Types\\\\VariantChild will always evaluate to false\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Parameter \\$Invoice of method QUI\\\\ERP\\\\Order\\\\EventHandling\\:\\:onQuiqqerInvoiceTemporaryInvoicePostEnd\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Parameter \\$InvoiceTemporary of method QUI\\\\ERP\\\\Order\\\\EventHandling\\:\\:onQuiqqerInvoiceTemporaryInvoicePostEnd\\(\\) has invalid type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Parameter \\$SalesOrder of method QUI\\\\ERP\\\\Order\\\\EventHandling\\:\\:onQuiqqerSalesOrdersSaveEnd\\(\\) has invalid type QUI\\\\ERP\\\\SalesOrders\\\\SalesOrder\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Result of \\|\\| is always true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Unreachable statement \\- code above always terminates\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/EventHandling.php
+
+		-
+			message: "#^Comparison operation \"\\!\\=\" between ''\\|'0'\\|array\\{\\}\\|false and 0 results in an error\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Factory.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/FrontendUsers/Controls/UserOpenedOrders.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/FrontendUsers/Controls/UserOpenedOrders.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductTypeInterface\\:\\:getImage\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/FrontendUsers/Controls/UserOrders.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/FrontendUsers/Controls/UserOrders.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/FrontendUsers/Controls/UserOrders.php
+
+		-
+			message: "#^Cannot call method getTitle\\(\\) on QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\|true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/FrontendUsers/Controls/UserOrders.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Order\\\\OrderInterface\\:\\:getAttribute\\(\\)\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Mail.php
+
+		-
+			message: "#^Call to method createPDF\\(\\) on an unknown class QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Mail.php
+
+		-
+			message: "#^Call to method setContentHTML\\(\\) on an unknown class QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Mail.php
+
+		-
+			message: "#^Instantiated class QUI\\\\HtmlToPdf\\\\Document not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Mail.php
+
+		-
+			message: "#^Method QUI\\\\Interfaces\\\\Users\\\\User\\:\\:getAddress\\(\\) invoked with 0 parameters, 1 required\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Mail.php
+
+		-
+			message: "#^Parameter \\#1 \\$id of method QUI\\\\Projects\\\\Media\\:\\:get\\(\\) expects int, string given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Mail.php
+
+		-
+			message: "#^Call to method getId\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Call to method toJSON\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Call to static method addressRequirement\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Utils\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Call to static method addressRequirementThreshold\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Utils\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Call to static method createSalesOrder\\(\\) on an unknown class QUI\\\\ERP\\\\SalesOrders\\\\Handler\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Factory\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Call to static method getInstance\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Handler\\.$#"
+			count: 6
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Cannot call method getId\\(\\) on QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\|true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^If condition is always true\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Instantiated class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\Order\\:\\:createInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\Order\\:\\:createInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\Order\\:\\:createSalesOrder\\(\\) has invalid return type QUI\\\\ERP\\\\SalesOrders\\\\SalesOrder\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\Order\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\Order\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\Order\\:\\:post\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\Order\\:\\:post\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^PHPDoc tag @throws with type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception\\|QUI\\\\Exception is not subtype of Throwable$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Throwing object of an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Order.php
+
+		-
+			message: "#^Call to an undefined method QUI\\\\ERP\\\\Products\\\\Interfaces\\\\ProductInterface\\:\\:toArticle\\(\\)\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Call to method getId\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Call to method toArray\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Cannot call method getId\\(\\) on QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\|true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Instantiated class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderInProcess\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderInProcess\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^PHPDoc tag @throws with type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception\\|QUI\\\\Exception is not subtype of Throwable$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Parameter \\#2 \\$User of class QUI\\\\ERP\\\\Order\\\\Basket\\\\BasketOrder constructor expects QUI\\\\Users\\\\User\\|null, QUI\\\\ERP\\\\User\\|null given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Throwing object of an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInProcess.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderInterface\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderInterface.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderInterface\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderInterface.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderInterface\\:\\:getShipping\\(\\) has invalid return type QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderInterface.php
+
+		-
+			message: "#^PHPDoc tag @throws with type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception is not subtype of Throwable$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderInterface.php
+
+		-
+			message: "#^Parameter \\$Shipping of method QUI\\\\ERP\\\\Order\\\\OrderInterface\\:\\:setShipping\\(\\) has invalid type QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderInterface.php
+
+		-
+			message: "#^Class QUI\\\\ERP\\\\Order\\\\Exception referenced with incorrect case\\: QUI\\\\Erp\\\\Order\\\\Exception\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderProcess.php
+
+		-
+			message: "#^Left side of && is always true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderProcess.php
+
+		-
+			message: "#^Parameter \\#1 \\$User of method QUI\\\\ERP\\\\Order\\\\Factory\\:\\:createBasket\\(\\) expects null, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderProcess.php
+
+		-
+			message: "#^Parameter \\#2 \\$User of class QUI\\\\ERP\\\\Order\\\\Basket\\\\BasketOrder constructor expects QUI\\\\Users\\\\User\\|null, QUI\\\\Interfaces\\\\Users\\\\User given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderProcess.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderView\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderView.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderView\\:\\:getInvoice\\(\\) has invalid return type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderView.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderView\\:\\:getShipping\\(\\) has invalid return type QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderView.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderView\\:\\:getShippingStatus\\(\\) has invalid return type QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderView.php
+
+		-
+			message: "#^Method QUI\\\\ERP\\\\Order\\\\OrderView\\:\\:toPDF\\(\\) has invalid return type QUI\\\\HtmlToPdf\\\\Document\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderView.php
+
+		-
+			message: "#^PHPDoc tag @throws with type QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Exception\\|QUI\\\\Exception is not subtype of Throwable$#"
+			count: 1
+			path: src/QUI/ERP/Order/OrderView.php
+
+		-
+			message: "#^Parameter \\$Shipping of method QUI\\\\ERP\\\\Order\\\\OrderView\\:\\:setShipping\\(\\) has invalid type QUI\\\\ERP\\\\Shipping\\\\Api\\\\ShippingInterface\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/OrderView.php
+
+		-
+			message: "#^Access to constant ECC_M on an unknown class chillerlan\\\\QRCode\\\\QRCode\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Output/OutputProviderOrder.php
+
+		-
+			message: "#^Access to constant OUTPUT_IMAGE_PNG on an unknown class chillerlan\\\\QRCode\\\\QRCode\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Output/OutputProviderOrder.php
+
+		-
+			message: "#^Access to constant VERSION_AUTO on an unknown class chillerlan\\\\QRCode\\\\QRCode\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Output/OutputProviderOrder.php
+
+		-
+			message: "#^Call to method render\\(\\) on an unknown class chillerlan\\\\QRCode\\\\QRCode\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Output/OutputProviderOrder.php
+
+		-
+			message: "#^Instantiated class chillerlan\\\\QRCode\\\\QRCode not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Output/OutputProviderOrder.php
+
+		-
+			message: "#^Instantiated class chillerlan\\\\QRCode\\\\QROptions not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Output/OutputProviderOrder.php
+
+		-
+			message: "#^Parameter \\#2 \\$key of method QUI\\\\Config\\:\\:setValue\\(\\) expects string\\|null, int given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/ProcessingStatus/Factory.php
+
+		-
+			message: "#^Parameter \\#2 \\$key of method QUI\\\\Config\\:\\:del\\(\\) expects string\\|null, int given\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/ProcessingStatus/Handler.php
+
+		-
+			message: "#^Parameter \\#2 \\$key of method QUI\\\\Config\\:\\:setValue\\(\\) expects string\\|null, int given\\.$#"
+			count: 2
+			path: src/QUI/ERP/Order/ProcessingStatus/Handler.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Call to method getAttribute\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Invoice\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Call to method getUUID\\(\\) on an unknown class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\InvoiceTemporary\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Cannot call method getColor\\(\\) on QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\|true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Cannot call method getId\\(\\) on QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\|true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Cannot call method getTitle\\(\\) on QUI\\\\ERP\\\\Shipping\\\\ShippingStatus\\\\Status\\|true\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Result of closure \\(void\\) is used\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Search.php
+
+		-
+			message: "#^Call to method getPrice\\(\\) on an unknown class QUI\\\\ERP\\\\Shipping\\\\Types\\\\ShippingEntry\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Utils/DataLayer.php
+
+		-
+			message: "#^Class QUI\\\\ERP\\\\Accounting\\\\Invoice\\\\Articles\\\\Text not found\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Utils/Utils.php
+
+		-
+			message: "#^Empty array passed to foreach\\.$#"
+			count: 1
+			path: src/QUI/ERP/Order/Utils/Utils.php
+
+		-
+			message: "#^Class QUI\\\\Database\\\\Exception referenced with incorrect case\\: QUI\\\\DataBase\\\\Exception\\.$#"
+			count: 1
+			path: types/shoppingCart.php
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
index aa10a371ae2d7b11f3021e6489cdb3fb8cc1edd7..76cb0b3a02bd02aa396104c3fbffcf8d9fcccfe0 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/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';