diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f4da20891502a3992abf2981d8f6c46e33868ab6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+tools/
+phpstan.neon
+.phpunit.result.cache
+phpunit.xml
\ No newline at end of file
diff --git a/.phive/phars.xml b/.phive/phars.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a1315a09b4adad780a9c5e52f74835c708c5c7d5
--- /dev/null
+++ b/.phive/phars.xml
@@ -0,0 +1,4 @@
+<?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"/>
+</phive>
diff --git a/ajax/create.php b/ajax/create.php
index d3c9297a26bae45c973006f7f36fa888313b9019..ce167d37d4d53215420149ec458fb9464c445be8 100644
--- a/ajax/create.php
+++ b/ajax/create.php
@@ -10,6 +10,7 @@
 use QUI\ERP\Coupons\CouponCodeException;
 use QUI\ERP\Coupons\Handler;
 use QUI\ERP\Discount\Handler as DiscountsHandler;
+use QUI\Translator;
 use QUI\Utils\Security\Orthos;
 
 QUI::$Ajax->registerFunction(
@@ -27,7 +28,6 @@ function ($attributes) {
 
         try {
             $amount = 1;
-            $couponCodes = [];
 
             if (!empty($attributes['amount'])) {
                 $amount = (int)$attributes['amount'];
@@ -49,14 +49,10 @@ function ($attributes) {
             }
 
             // Create discount
-            switch ($attributes['discountType']) {
-                case 'percentage':
-                    $discountType = DiscountsHandler::DISCOUNT_TYPE_PERCENT;
-                    break;
-
-                default:
-                    $discountType = DiscountsHandler::DISCOUNT_TYPE_CURRENCY;
-            }
+            $discountType = match ($attributes['discountType']) {
+                'percentage' => DiscountsHandler::DISCOUNT_TYPE_PERCENT,
+                default => DiscountsHandler::DISCOUNT_TYPE_CURRENCY,
+            };
 
             $Discounts = DiscountsHandler::getInstance();
             $NewDiscount = $Discounts->createChild([
@@ -71,7 +67,7 @@ function ($attributes) {
                 $NewCouponCode = Handler::createCouponCode([$NewDiscount->getId()], $attributes);
 
                 if ($i === 0) {
-                    \QUI\Translator::update(
+                    Translator::update(
                         'quiqqer/discount',
                         'discount.' . $NewDiscount->getId() . '.title',
                         'quiqqer/discount',
@@ -85,10 +81,8 @@ function ($attributes) {
                         ]
                     );
 
-                    \QUI\Translator::publish('quiqqer/discount');
+                    Translator::publish('quiqqer/discount');
                 }
-
-                $couponCodes[] = $NewCouponCode;
             }
         } catch (CouponCodeException $Exception) {
             QUI::getMessagesHandler()->addError(
@@ -104,7 +98,7 @@ function ($attributes) {
             return false;
         } catch (QUI\Permissions\Exception $Exception) {
             throw $Exception;
-        } catch (\Exception $Exception) {
+        } catch (Exception $Exception) {
             QUI\System\Log::writeException($Exception);
 
             QUI::getMessagesHandler()->addError(
diff --git a/ajax/delete.php b/ajax/delete.php
index 9ba41611b20aa1749188910acea1e055357de185..ca71f5c79e1430e125224a6c13954af01456a12e 100644
--- a/ajax/delete.php
+++ b/ajax/delete.php
@@ -4,6 +4,7 @@
  * This file contains package_quiqqer_coupons_ajax_delete
  */
 
+use QUI\ERP\Coupons\CouponCodeException;
 use QUI\ERP\Coupons\Handler;
 use QUI\Utils\Security\Orthos;
 
@@ -16,14 +17,14 @@
 QUI::$Ajax->registerFunction(
     'package_quiqqer_coupons_ajax_delete',
     function ($ids) {
-        $ids = Orthos::clearArray(\json_decode($ids, true));
+        $ids = Orthos::clearArray(json_decode($ids, true));
 
         try {
             foreach ($ids as $id) {
                 $CouponCode = Handler::getCouponCode((int)$id);
                 $CouponCode->delete();
             }
-        } catch (\QUI\ERP\Coupons\CouponCodeException $Exception) {
+        } catch (CouponCodeException $Exception) {
             QUI::getMessagesHandler()->addError(
                 QUI::getLocale()->get(
                     'quiqqer/coupons',
@@ -37,7 +38,7 @@ function ($ids) {
             return false;
         } catch (QUI\Permissions\Exception $Exception) {
             throw $Exception;
-        } catch (\Exception $Exception) {
+        } catch (Exception $Exception) {
             QUI\System\Log::writeException($Exception);
 
             QUI::getMessagesHandler()->addError(
diff --git a/ajax/edit.php b/ajax/edit.php
index 1285a40bf1ebedd65d7d5f04185803ca210d93e0..38fb3b3d318f1f7cc12d6396884a7d709345c0a3 100644
--- a/ajax/edit.php
+++ b/ajax/edit.php
@@ -4,6 +4,7 @@
  * This file contains package_quiqqer_coupons_ajax_create
  */
 
+use QUI\ERP\Coupons\CouponCodeException;
 use QUI\ERP\Coupons\Handler;
 use QUI\Utils\Security\Orthos;
 
@@ -16,24 +17,24 @@
 QUI::$Ajax->registerFunction(
     'package_quiqqer_coupons_ajax_edit',
     function ($id, $attributes) {
-        $id         = (int)$id;
-        $attributes = Orthos::clearArray(\json_decode($attributes, true));
+        $id = (int)$id;
+        $attributes = Orthos::clearArray(json_decode($attributes, true));
 
         try {
             $discountIds = [];
 
             if (!empty($attributes['discountIds'])) {
-                $discountIds = \explode(',', $attributes['discountIds']);
+                $discountIds = explode(',', $attributes['discountIds']);
             }
 
-            $couponCodes[] = Handler::editCouponCode($id, $discountIds, $attributes);
-        } catch (\QUI\ERP\Coupons\CouponCodeException $Exception) {
+            Handler::editCouponCode($id, $discountIds, $attributes);
+        } catch (CouponCodeException $Exception) {
             QUI::getMessagesHandler()->addError(
                 QUI::getLocale()->get(
                     'quiqqer/coupons',
                     'message.ajax.edit.error',
                     [
-                        'id'    => $id,
+                        'id' => $id,
                         'error' => $Exception->getMessage()
                     ]
                 )
@@ -42,7 +43,7 @@ function ($id, $attributes) {
             return false;
         } catch (QUI\Permissions\Exception $Exception) {
             throw $Exception;
-        } catch (\Exception $Exception) {
+        } catch (Exception $Exception) {
             QUI\System\Log::writeException($Exception);
 
             QUI::getMessagesHandler()->addError(
diff --git a/ajax/frontend/redeem.php b/ajax/frontend/redeem.php
index ac93c3b9ae9582069fcf38538bc3048dee9943b1..0866ff8f0e28031c0e352f694592b3d9a78210dd 100644
--- a/ajax/frontend/redeem.php
+++ b/ajax/frontend/redeem.php
@@ -26,7 +26,7 @@ function ($code, $orderHash) {
             QUI::getMessagesHandler()->addError($Exception->getMessage());
 
             return false;
-        } catch (\Exception $Exception) {
+        } catch (Exception $Exception) {
             QUI\System\Log::writeException($Exception);
 
             QUI::getMessagesHandler()->addError(
@@ -95,7 +95,7 @@ function ($code, $orderHash) {
 
         $coupons[] = $code;
 
-        $coupons = \array_unique($coupons);
+        $coupons = array_unique($coupons);
 
         $Order->setData('quiqqer-coupons', $coupons);
         $Order->update();
diff --git a/ajax/getList.php b/ajax/getList.php
index 23a6489d551d3b22a48611175069ece395174d94..43e87dabe047b7ebf315f16d6594a1760a729f66 100644
--- a/ajax/getList.php
+++ b/ajax/getList.php
@@ -20,7 +20,7 @@
     function ($searchParams) {
         Permission::hasPermission(Handler::PERMISSION_VIEW);
 
-        $searchParams = Orthos::clearArray(\json_decode($searchParams, true));
+        $searchParams = Orthos::clearArray(json_decode($searchParams, true));
         $couponCodes = [];
         $Users = QUI::getUsers();
         $L = QUI::getLocale();
@@ -33,7 +33,7 @@ function ($searchParams) {
                     // User
                     try {
                         $User = $Users->get($usage['userId']);
-                    } catch (QUI\Exception $Exception) {
+                    } catch (QUI\Exception) {
                         continue;
                     }
 
@@ -46,7 +46,7 @@ function ($searchParams) {
 
                 $couponCodes[] = $couponCode;
             }
-        } catch (\Exception $Exception) {
+        } catch (Exception $Exception) {
             QUI\System\Log::writeException($Exception);
 
             QUI::getMessagesHandler()->addError(
diff --git a/composer.json b/composer.json
index 3b95539fee64ccbca9652f9d926f087c37613496..925f04f4c931c09ab0cb5065bdd4a8b41f196386 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
 {
-  "name": "quiqqer\/coupons",
+  "name": "quiqqer/coupons",
   "type": "quiqqer-module",
   "description": "Coupons for QUIQQER",
   "license": "GPL-3.0+",
@@ -7,7 +7,7 @@
     {
       "name": "Patrick M\u00fcller",
       "email": "support@pcsg.de",
-      "homepage": "https:\/\/www.pcsg.de",
+      "homepage": "https://www.pcsg.de",
       "role": "Developer"
     }
   ],
@@ -15,13 +15,14 @@
     "email": "support@pcsg.de"
   },
   "require": {
-    "quiqqer\/quiqqer": "^1.2|*@dev",
-    "quiqqer\/discount": "^1.1|*@dev",
-    "quiqqer/products": "^1.3|*@dev"
+    "quiqqer/core": "^2",
+    "quiqqer/erp": "^3.2",
+    "quiqqer/discount": "^2",
+    "quiqqer/products": "^2"
   },
   "autoload": {
     "psr-4": {
-      "QUI\\ERP\\Coupons\\": "src\/QUI\/ERP\/Coupons"
+      "QUI\\ERP\\Coupons\\": "src/QUI/ERP/Coupons"
     }
   }
 }
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/phpstan.dist.neon b/phpstan.dist.neon
new file mode 100644
index 0000000000000000000000000000000000000000..a545a041a3c5b0c758d99bf8c1c49b2a83252eaa
--- /dev/null
+++ b/phpstan.dist.neon
@@ -0,0 +1,10 @@
+includes:
+    - phpstan-baseline.neon
+
+parameters:
+    level: 1
+    paths:
+        - src
+        - ajax
+    bootstrapFiles:
+        - tests/phpstan-bootstrap.php
\ No newline at end of file
diff --git a/src/QUI/ERP/Coupons/CodeGeneratorInterface.php b/src/QUI/ERP/Coupons/CodeGeneratorInterface.php
index 7918f69f1e75f5ad650a6738f8a453e8ff625ec4..b433d1f682558521f4b57b5f9d4a0bff7448306d 100644
--- a/src/QUI/ERP/Coupons/CodeGeneratorInterface.php
+++ b/src/QUI/ERP/Coupons/CodeGeneratorInterface.php
@@ -15,5 +15,5 @@ interface CodeGeneratorInterface
      * @param string $prefix (optional)
      * @return string
      */
-    public static function generate($prefix = '');
+    public static function generate(string $prefix = ''): string;
 }
diff --git a/src/QUI/ERP/Coupons/CodeGenerators/SimpleString.php b/src/QUI/ERP/Coupons/CodeGenerators/SimpleString.php
index 060098deb26547bd67be13046ffea1bc04adc79d..d6f93435a480dcc2c9803ea5fabd54a8dfd95ce5 100644
--- a/src/QUI/ERP/Coupons/CodeGenerators/SimpleString.php
+++ b/src/QUI/ERP/Coupons/CodeGenerators/SimpleString.php
@@ -6,6 +6,7 @@
 
 namespace QUI\ERP\Coupons\CodeGenerators;
 
+use Exception;
 use QUI\ERP\Coupons\CodeGeneratorInterface;
 use QUI\ERP\Coupons\Handler;
 
@@ -27,9 +28,9 @@ class SimpleString implements CodeGeneratorInterface
      *
      * @param string $prefix (optional)
      * @return string
-     * @throws \Exception
+     * @throws Exception
      */
-    public static function generate($prefix = ''): string
+    public static function generate(string $prefix = ''): string
     {
         $characters = array_merge(
             range('A', 'Z'),
diff --git a/src/QUI/ERP/Coupons/CodeGenerators/Uuid.php b/src/QUI/ERP/Coupons/CodeGenerators/Uuid.php
index 662b3c23d6ef3e4bd3d8bf9884fdcdebbda94297..8a1497b6ca6486a56bcfd9222e7702050d72742c 100644
--- a/src/QUI/ERP/Coupons/CodeGenerators/Uuid.php
+++ b/src/QUI/ERP/Coupons/CodeGenerators/Uuid.php
@@ -13,7 +13,7 @@ class Uuid implements CodeGeneratorInterface
      * @param string $prefix (optional)
      * @return string
      */
-    public static function generate($prefix = ''): string
+    public static function generate(string $prefix = ''): string
     {
         return $prefix . UuidCreator::uuid1()->toString();
     }
diff --git a/src/QUI/ERP/Coupons/CouponCode.php b/src/QUI/ERP/Coupons/CouponCode.php
index 479324527a8eb2db5d7e76cab257cc65d2e4eb64..87a36449817fe9ad355061565101bfbac5bab07b 100644
--- a/src/QUI/ERP/Coupons/CouponCode.php
+++ b/src/QUI/ERP/Coupons/CouponCode.php
@@ -6,7 +6,10 @@
 use Exception;
 use QUI;
 use QUI\ERP\Discount\Handler as DiscountHandler;
+use QUI\ERP\Order\AbstractOrder;
 use QUI\ERP\Order\OrderInterface;
+use QUI\ExceptionStack;
+use QUI\Interfaces\Users\User;
 use QUI\Permissions\Permission;
 
 use function array_key_first;
@@ -15,6 +18,7 @@
 use function in_array;
 use function is_array;
 use function is_null;
+use function is_numeric;
 use function json_decode;
 use function json_encode;
 use function method_exists;
@@ -36,35 +40,35 @@ class CouponCode
      *
      * @var string
      */
-    protected $code;
+    protected mixed $code;
 
     /**
      * IDs of users that this CouponCode is restricted to
      *
      * @var int[]
      */
-    protected $userIds = [];
+    protected mixed $userIds = [];
 
     /**
      * IDs of groups that this CouponCode is restricted to
      *
      * @var int[]
      */
-    protected $groupIds = [];
+    protected mixed $groupIds = [];
 
     /**
      * IDs of all linked discounts
      *
-     * @var int[]
+     * @var array
      */
-    protected $discountIds = [];
+    protected array $discountIds = [];
 
     /**
      * List of usages of this CouponCode
      *
      * @var array
      */
-    protected $usages = [];
+    protected mixed $usages = [];
 
     /**
      * Creation Date
@@ -83,9 +87,9 @@ class CouponCode
     /**
      * CouponCode title
      *
-     * @var string
+     * @var string|null
      */
-    protected $title = null;
+    protected ?string $title = null;
 
     /**
      * Flag - Is the CouponCode valid?
@@ -99,7 +103,7 @@ class CouponCode
      *
      * @var string
      */
-    protected $maxUsages = Handler::MAX_USAGE_ONCE_PER_USER;
+    protected string $maxUsages = Handler::MAX_USAGE_ONCE_PER_USER;
 
     /**
      * CouponCode constructor.
@@ -118,7 +122,7 @@ public function __construct(int $id)
                     'id' => $id
                 ]
             ]);
-        } catch (QUI\DataBase\Exception $Exception) {
+        } catch (QUI\Database\Exception $Exception) {
             QUI\System\Log::addError($Exception->getMessage());
 
             throw new CouponCodeException([
@@ -154,10 +158,30 @@ public function __construct(int $id)
             $this->userIds = json_decode($data['userIds'], true);
         }
 
+        // migrate user ids
+        foreach ($this->userIds as $k => $userId) {
+            if (is_numeric($userId)) {
+                try {
+                    $this->userIds[$k] = QUI::getUsers()->get($userId)->getUUID();
+                } catch (QUI\Exception) {
+                }
+            }
+        }
+
         if (!empty($data['groupIds'])) {
             $this->groupIds = json_decode($data['groupIds'], true);
         }
 
+        // migrate user ids
+        foreach ($this->groupIds as $k => $groupId) {
+            if (is_numeric($groupId)) {
+                try {
+                    $this->groupIds[$k] = QUI::getGroups()->get($groupId)->getUUID();
+                } catch (QUI\Exception) {
+                }
+            }
+        }
+
         if (!empty($data['maxUsages'])) {
             $this->maxUsages = $data['maxUsages'];
         }
@@ -219,7 +243,7 @@ public function getValidUntilDate(): ?DateTime
     }
 
     /**
-     * @return string
+     * @return string|null
      */
     public function getTitle(): ?string
     {
@@ -276,13 +300,14 @@ public function getGroupIds(): array
      *
      * Hint: This may invalidate the code for future use
      *
-     * @param QUI\Interfaces\Users\User $User - The user that redeems the CouponCode [if omitted use Session User]
-     * @param QUI\ERP\Order\Order|null $Order (optional) - Link redemption to a specific Order
+     * @param User|null $User - The user that redeems the CouponCode [if omitted use Session User]
+     * @param AbstractOrder|null $Order (optional) - Link redemption to a specific Order
      * @return void
      * @throws CouponCodeException
-     * @throws QUI\Exception
+     * @throws QUI\Database\Exception
+     * @throws ExceptionStack
      */
-    public function redeem(QUI\Interfaces\Users\User $User = null, QUI\ERP\Order\Order $Order = null)
+    public function redeem(QUI\Interfaces\Users\User $User = null, AbstractOrder $Order = null): void
     {
         if (is_null($User)) {
             $User = QUI::getUserBySession();
@@ -293,13 +318,13 @@ public function redeem(QUI\Interfaces\Users\User $User = null, QUI\ERP\Order\Ord
         $Now = new DateTime();
 
         $usage = [
-            'userId' => $User->getId(),
+            'userId' => $User->getUUID(),
             'date' => $Now->format('Y-m-d H:i:s'),
             'orderPrefixedId' => false
         ];
 
         if ($Order instanceof QUI\ERP\Order\Order) {
-            $usage['orderPrefixedId'] = $Order->getPrefixedId();
+            $usage['orderPrefixedId'] = $Order->getPrefixedNumber();
         }
 
         $this->usages[] = $usage;
@@ -328,7 +353,7 @@ public function redeem(QUI\Interfaces\Users\User $User = null, QUI\ERP\Order\Ord
      * @return void
      * @throws CouponCodeException - Thrown if not redeemable by the given User
      */
-    public function checkRedemption(QUI\Interfaces\Users\User $User)
+    public function checkRedemption(QUI\Interfaces\Users\User $User): void
     {
         if (!$this->isValid()) {
             throw new CouponCodeException([
@@ -396,7 +421,7 @@ public function checkRedemption(QUI\Interfaces\Users\User $User)
 
         // Restriction to QUIQQER user(s)
         if (!empty($this->userIds)) {
-            if (in_array($User->getId(), $this->userIds)) {
+            if (in_array($User->getUUID(), $this->userIds)) {
                 if (
                     $this->maxUsages !== Handler::MAX_USAGE_UNLIMITED
                     && $this->hasUserRedeemed($User)
@@ -450,7 +475,7 @@ public function checkRedemption(QUI\Interfaces\Users\User $User)
      * @param OrderInterface|null $Order
      * @throws CouponCodeException
      */
-    public function checkOrderRedemption(?OrderInterface $Order)
+    public function checkOrderRedemption(?OrderInterface $Order): void
     {
         if ($Order === null) {
             return;
@@ -496,7 +521,7 @@ public function checkOrderRedemption(?OrderInterface $Order)
     /**
      * Check if the given User can redeem this CouponCode
      *
-     * @param QUI\Interfaces\Users\User $User - If omitted, use session user
+     * @param User|null $User - If omitted, use session user
      * @param OrderInterface|null $Order
      * @return bool
      */
@@ -504,7 +529,7 @@ public function isRedeemable(QUI\Interfaces\Users\User $User = null, OrderInterf
     {
         try {
             $this->checkRedemption($User);
-        } catch (CouponCodeException $Exception) {
+        } catch (CouponCodeException) {
             return false;
         }
 
@@ -539,7 +564,7 @@ public function isValid(): bool
      */
     public function hasUserRedeemed(QUI\Interfaces\Users\User $User): bool
     {
-        $userId = $User->getId();
+        $userId = $User->getUUID();
 
         foreach ($this->usages as $usage) {
             if ($usage['userId'] === $userId) {
@@ -556,7 +581,7 @@ public function hasUserRedeemed(QUI\Interfaces\Users\User $User): bool
      * @return void
      * @throws QUI\Permissions\Exception
      */
-    public function delete()
+    public function delete(): void
     {
         Permission::checkPermission(Handler::PERMISSION_DELETE);
 
@@ -565,11 +590,11 @@ public function delete()
                 Handler::getTable(),
                 ['id' => $this->id]
             );
-        } catch (QUI\DataBase\Exception $Exception) {
+        } catch (QUI\Database\Exception $Exception) {
             QUI\System\Log::addError($Exception->getMessage());
         }
 
-        // If hidden discount are connected to this coupon -> delete them aswell
+        // If hidden discount are connected to this coupon -> delete them as well
         foreach ($this->getDiscounts() as $Discount) {
             if (!empty($Discount->getAttribute('hidden'))) {
                 try {
@@ -616,7 +641,7 @@ public function toArray(): array
      *
      * @return void
      */
-    protected function checkValidity()
+    protected function checkValidity(): void
     {
         // Check if the expiration date has been reached
         if (!empty($this->ValidUntilDate)) {
@@ -665,7 +690,7 @@ protected function checkValidity()
      * @param QUI\ERP\Order\OrderInProcess $Order
      * @throws QUI\Exception
      */
-    public function addToOrder(QUI\ERP\Order\OrderInProcess $Order)
+    public function addToOrder(QUI\ERP\Order\OrderInProcess $Order): void
     {
         $coupons = $Order->getDataEntry('quiqqer-coupons');
 
@@ -691,7 +716,7 @@ public function addToOrder(QUI\ERP\Order\OrderInProcess $Order)
             /* @var $Coupon CouponCode */
             try {
                 $Coupon = Handler::getCouponCodeByCode($coupon);
-            } catch (Exception $Exception) {
+            } catch (Exception) {
                 continue;
             }
 
@@ -721,7 +746,7 @@ public function addToOrder(QUI\ERP\Order\OrderInProcess $Order)
                 // @todo wenn fest preis (zb 10$), dann eigener produkt typ hinzufügen
 
                 $articles[] = new QUI\ERP\Accounting\Articles\Text([
-                    'id' => '-',
+                    'id' => -1,
                     'articleNo' => $Coupon->getCode(),
                     'title' => $PriceFactor->getTitle(),
                     'description' => '',
diff --git a/src/QUI/ERP/Coupons/Events.php b/src/QUI/ERP/Coupons/Events.php
index 7f4f137e8c006c24ca3b1be626d87806fdf6c1f2..59637b6c396df42aca2cbcc4b9ff2e73dc71787e 100644
--- a/src/QUI/ERP/Coupons/Events.php
+++ b/src/QUI/ERP/Coupons/Events.php
@@ -13,10 +13,9 @@
 use QUI\ERP\Discount\EventHandling as DiscountEvents;
 use QUI\ERP\Order\AbstractOrder;
 use QUI\ERP\Order\Basket\Basket;
-use QUI\ERP\Order\Basket\BasketGuest;
 use QUI\ERP\Products\Handler\Fields;
 use QUI\ERP\Products\Interfaces\ProductInterface;
-use Quiqqer\Engine\Collector;
+use QUI\Smarty\Collector;
 
 use function array_merge;
 use function array_search;
@@ -40,7 +39,7 @@ class Events
      * @param QUI\Package\Package $Package
      * @return void
      */
-    public static function onPackageSetup(QUI\Package\Package $Package)
+    public static function onPackageSetup(QUI\Package\Package $Package): void
     {
         try {
             self::createProductFields();
@@ -52,7 +51,7 @@ public static function onPackageSetup(QUI\Package\Package $Package)
     /**
      * event : on admin load footer
      */
-    public static function onAdminLoadFooter()
+    public static function onAdminLoadFooter(): void
     {
         echo '<script src="' . URL_OPT_DIR . 'quiqqer/coupons/bin/backend/load.js"></script>';
     }
@@ -61,11 +60,14 @@ public static function onAdminLoadFooter()
      * Template event quiqqer/order: onQuiqqer::order::orderProcessBasketEnd
      *
      * @param Collector $Collector
-     * @param BasketGuest $Basket
-     * @param $Order
+     * @param mixed $Basket
+     * @param AbstractOrder|null $Order
      */
-    public static function templateOrderProcessBasketEnd(Collector $Collector, $Basket, $Order)
-    {
+    public static function templateOrderProcessBasketEnd(
+        Collector $Collector,
+        mixed $Basket,
+        AbstractOrder $Order = null
+    ): void {
         if (
             !($Basket instanceof Basket)
             && !($Basket instanceof QUI\ERP\Order\Basket\BasketOrder)
@@ -79,8 +81,11 @@ public static function templateOrderProcessBasketEnd(Collector $Collector, $Bask
 
                 $CouponCode = Handler::getCouponCodeByCode($code);
                 $CouponCode->checkRedemption(QUI::getUserBySession());
-                $CouponCode->addToOrder($Order);
-            } catch (Exception $Exception) {
+
+                if ($Order instanceof QUI\ERP\Order\OrderInProcess) {
+                    $CouponCode->addToOrder($Order);
+                }
+            } catch (Exception) {
             }
         }
 
@@ -94,7 +99,7 @@ public static function templateOrderProcessBasketEnd(Collector $Collector, $Bask
      * @throws QUI\ERP\Order\Exception
      * @throws QUI\Exception
      */
-    public static function onOrderProcess(QUI\ERP\Order\OrderProcess $OrderProcess)
+    public static function onOrderProcess(QUI\ERP\Order\OrderProcess $OrderProcess): void
     {
         $CurrentStep = $OrderProcess->getCurrentStep();
         $currentStep = $CurrentStep->getType();
@@ -137,17 +142,17 @@ public static function onOrderProcess(QUI\ERP\Order\OrderProcess $OrderProcess)
     public static function onQuiqqerOrderBasketRemovePos(
         QUI\ERP\Order\Basket\Basket $Basket,
         $pos
-    ) {
+    ): void {
         $Order = null;
 
         try {
             $Order = $Basket->getOrder();
-        } catch (QUI\Exception $Exception) {
+        } catch (QUI\Exception) {
             $Orders = QUI\ERP\Order\Handler::getInstance();
 
             try {
                 $Order = $Orders->getLastOrderInProcessFromUser(QUI::getUserBySession());
-            } catch (QUI\Exception $Exception) {
+            } catch (QUI\Exception) {
             }
         }
 
@@ -177,7 +182,7 @@ public static function onQuiqqerOrderBasketRemovePos(
             return;
         }
 
-        // custom data has code params, so article is an coupon code
+        // custom data has code params, so article is a coupon code
         // we need to delete it
         if (in_array($articleCouponCode, $orderCoupons)) {
             $pos = array_search($articleCouponCode, $orderCoupons);
@@ -187,7 +192,7 @@ public static function onQuiqqerOrderBasketRemovePos(
 
             try {
                 $Order->save();
-            } catch (QUI\Exception $Exception) {
+            } catch (QUI\Exception) {
             }
         }
 
@@ -227,15 +232,16 @@ public static function onQuiqqerOrderBasketRemovePos(
     /**
      * event - on price factor init
      *
-     * @param $Basket
+     * @param mixed $Basket
      * @param QUI\ERP\Order\AbstractOrder $Order
      * @param QUI\ERP\Products\Product\ProductList $Products
+     * @throws QUI\Exception
      */
     public static function onQuiqqerOrderBasketToOrder(
-        $Basket,
+        mixed $Basket,
         QUI\ERP\Order\AbstractOrder $Order,
         QUI\ERP\Products\Product\ProductList $Products
-    ) {
+    ): void {
         $coupons = $Order->getDataEntry('quiqqer-coupons');
         $sessionCoupons = QUI::getSession()->get('quiqqer-coupons');
 
@@ -264,7 +270,7 @@ public static function onQuiqqerOrderBasketToOrder(
         $productCount = $Products->count();
         $subSum = $products['calculations']['subSum'];
 
-        $checkRedeemable = !$Order->isSuccessful(); // if order is successful we dont need a check
+        $checkRedeemable = !$Order->isSuccessful(); // if order is successful we don't need a check
         $OrderInProcess = $Order->getAttribute('OrderInProcess');
         $added = false;
 
@@ -283,7 +289,7 @@ public static function onQuiqqerOrderBasketToOrder(
             /* @var $Coupon CouponCode */
             try {
                 $Coupon = Handler::getCouponCodeByCode($coupon);
-            } catch (Exception $Exception) {
+            } catch (Exception) {
                 continue;
             }
 
@@ -302,7 +308,7 @@ public static function onQuiqqerOrderBasketToOrder(
 
                 if ($Discount->getAttribute('scope') === QUI\ERP\Discount\Handler::DISCOUNT_SCOPE_GRAND_TOTAL) {
                     // do nothing for this scope
-                    // since this scope requires all price factors etc, this cannot be calculated here
+                    // since this scope requires all price factors etc., this cannot be calculated here
                 } elseif (!DiscountEvents::isDiscountUsableWithPurchaseValue($Discount, $subSum)) {
                     continue;
                 }
@@ -361,10 +367,10 @@ public static function onQuiqqerOrderBasketToOrder(
      *
      * Redeem coupons used in (completed) orders
      *
-     * @param QUI\ERP\Order\Order|QUI\ERP\Order\OrderInProcess $Order
+     * @param AbstractOrder $Order
      * @return void
      */
-    public static function onQuiqqerOrderSuccessful($Order)
+    public static function onQuiqqerOrderSuccessful(AbstractOrder $Order): void
     {
         $coupons = $Order->getDataEntry('quiqqer-coupons');
 
@@ -386,7 +392,7 @@ public static function onQuiqqerOrderSuccessful($Order)
      * @param $Order
      * @param $coupons
      */
-    protected static function addSessionCouponsToOrder($Order, $coupons)
+    protected static function addSessionCouponsToOrder($Order, $coupons): void
     {
         if (!is_array($coupons)) {
             return;
@@ -417,7 +423,7 @@ protected static function addSessionCouponsToOrder($Order, $coupons)
      * @param $Order
      * @param $coupon
      */
-    protected static function addCouponToOrder($Order, $coupon)
+    protected static function addCouponToOrder($Order, $coupon): void
     {
         if (!($Order instanceof QUI\ERP\Order\OrderInProcess)) {
             return;
@@ -439,7 +445,7 @@ protected static function addCouponToOrder($Order, $coupon)
             $Order->update();
 
             $CouponCode->addToOrder($Order);
-        } catch (Exception $Exception) {
+        } catch (Exception) {
         }
     }
 
@@ -448,7 +454,7 @@ protected static function addCouponToOrder($Order, $coupon)
      *
      * @return void
      */
-    public static function removeCouponsFromSession()
+    public static function removeCouponsFromSession(): void
     {
         QUI::getSession()->remove('quiqqer-coupons');
     }
@@ -459,7 +465,7 @@ public static function removeCouponsFromSession()
      * @return void
      * @throws QUI\Exception
      */
-    protected static function createProductFields()
+    protected static function createProductFields(): void
     {
         $fields = [
             CouponProductsHandler::PRODUCT_FIELD_ID_TRANSFERABLE => [
@@ -627,7 +633,7 @@ protected static function createProductFields()
             try {
                 Fields::getField($fieldId);
                 continue;
-            } catch (Exception $Exception) {
+            } catch (Exception) {
                 // Field does not exist -> create it
             }
 
@@ -665,7 +671,7 @@ protected static function createProductFields()
      *
      * @throws QUI\Exception
      */
-    public static function onQuiqqerProductsProductCreate(ProductInterface $Product)
+    public static function onQuiqqerProductsProductCreate(ProductInterface $Product): void
     {
         if (!($Product instanceof DigitalCouponProductType) && !($Product instanceof PhysicalCouponProductType)) {
             return;
@@ -725,7 +731,7 @@ public static function onQuiqqerProductsProductCreate(ProductInterface $Product)
      * @throws CouponProductException
      * @throws QUI\Exception
      */
-    public static function onQuiqqerProductsProductActivate(ProductInterface $Product)
+    public static function onQuiqqerProductsProductActivate(ProductInterface $Product): void
     {
         if (!($Product instanceof DigitalCouponProductType) && !($Product instanceof PhysicalCouponProductType)) {
             return;
@@ -766,7 +772,7 @@ public static function onQuiqqerProductsProductActivate(ProductInterface $Produc
      * @param AbstractOrder $Order
      * @return void
      */
-    public static function onQuiqqerOrderCreated(AbstractOrder $Order)
+    public static function onQuiqqerOrderCreated(AbstractOrder $Order): void
     {
         QUI\ERP\Coupons\Products\Handler::createCouponCodesFromOrder($Order);
     }
diff --git a/src/QUI/ERP/Coupons/Handler.php b/src/QUI/ERP/Coupons/Handler.php
index 83bbc92819a776f07cbf5fd5efbf5eeccd5ba926..ae0a2b0ff2c0e4d6a37c2813bb73e4734015ec7c 100644
--- a/src/QUI/ERP/Coupons/Handler.php
+++ b/src/QUI/ERP/Coupons/Handler.php
@@ -181,10 +181,17 @@ public static function createCouponCode(array $discountIds, array $settings = []
             $couponCode['groupIds'] = json_encode(explode(",", $settings['groupIds']));
         }
 
-        QUI::getDataBase()->insert(
-            self::getTable(),
-            $couponCode
-        );
+        try {
+            QUI::getDataBase()->insert(
+                self::getTable(),
+                $couponCode
+            );
+        } catch (QUI\Database\Exception $e) {
+            throw new CouponCodeException([
+                $e->getMessage(),
+                $e->getCode()
+            ]);
+        }
 
         return self::getCouponCode(QUI::getPDO()->lastInsertId());
     }
@@ -301,9 +308,9 @@ public static function editCouponCode(int $id, array $discountIds, array $settin
      * @param array $searchParams
      * @param bool $countOnly (optional) - get result count only [default: false]
      * @return CouponCode[]|int
-     * @throws CouponCodeException
+     * @throws CouponCodeException|QUI\Exception
      */
-    public static function search(array $searchParams, bool $countOnly = false)
+    public static function search(array $searchParams, bool $countOnly = false): array|int
     {
         $couponCodes = [];
         $Grid = new Grid($searchParams);
@@ -333,14 +340,12 @@ public static function search(array $searchParams, bool $countOnly = false)
                 $whereOr[] = '`' . $searchColumn . '` LIKE :search';
             }
 
-            if (!empty($whereOr)) {
-                $where[] = '(' . implode(' OR ', $whereOr) . ')';
+            $where[] = '(' . implode(' OR ', $whereOr) . ')';
 
-                $binds['search'] = [
-                    'value' => '%' . $searchParams['search'] . '%',
-                    'type' => PDO::PARAM_STR
-                ];
-            }
+            $binds['search'] = [
+                'value' => '%' . $searchParams['search'] . '%',
+                'type' => PDO::PARAM_STR
+            ];
         }
 
         // build WHERE query string
@@ -429,7 +434,7 @@ public static function existsCode(string $code): bool
      *
      * @return QUI\Projects\Site|false
      */
-    public static function getRegistrationSite()
+    public static function getRegistrationSite(): bool|QUI\Projects\Site
     {
         try {
             $Conf = QUI::getPackage('quiqqer/coupons')->getConfig();
@@ -446,7 +451,7 @@ public static function getRegistrationSite()
 
         try {
             return QUI\Projects\Site\Utils::getSiteByLink($regSite);
-        } catch (Exception $Exception) {
+        } catch (Exception) {
             return false;
         }
     }
@@ -459,7 +464,7 @@ public static function getRegistrationSite()
      *
      * @throws Exception
      */
-    public static function deleteExpiredCouponCodes(int $days = null)
+    public static function deleteExpiredCouponCodes(int $days = null): void
     {
         $Now = new DateTime();
         $where = [
@@ -493,7 +498,7 @@ public static function deleteExpiredCouponCodes(int $days = null)
      *
      * @throws Exception
      */
-    public static function deleteRedeemedCouponCodes(int $days = null)
+    public static function deleteRedeemedCouponCodes(int $days = null): void
     {
         $where = [
             'useDate' => [
diff --git a/src/QUI/ERP/Coupons/Products/DigitalCouponProductType.php b/src/QUI/ERP/Coupons/Products/DigitalCouponProductType.php
index 87109fa20e350e7018950d3263773b6291d113a7..6dbd81c5327b27c32c5cad6929e27e5733554126 100644
--- a/src/QUI/ERP/Coupons/Products/DigitalCouponProductType.php
+++ b/src/QUI/ERP/Coupons/Products/DigitalCouponProductType.php
@@ -21,7 +21,7 @@ class DigitalCouponProductType extends DigitalProduct
      * @throws QUI\ERP\Products\Product\Exception
      * @throws QUI\Exception
      */
-    public function __construct($pid, $product = [])
+    public function __construct(int $pid, array $product = [])
     {
         parent::__construct($pid, $product);
 
@@ -49,10 +49,10 @@ public function __construct($pid, $product = [])
     }
 
     /**
-     * @param QUI\Locale $Locale
+     * @param QUI\Locale|null $Locale
      * @return string
      */
-    public static function getTypeTitle($Locale = null): string
+    public static function getTypeTitle(QUI\Locale $Locale = null): string
     {
         if ($Locale === null) {
             $Locale = QUI::getLocale();
diff --git a/src/QUI/ERP/Coupons/Products/Handler.php b/src/QUI/ERP/Coupons/Products/Handler.php
index a4647378a0c72f4b13703a5dbf2b0e14e3379ac2..6ffeafeb71aea4da55913ca1be210557cbd6e54a 100644
--- a/src/QUI/ERP/Coupons/Products/Handler.php
+++ b/src/QUI/ERP/Coupons/Products/Handler.php
@@ -2,6 +2,7 @@
 
 namespace QUI\ERP\Coupons\Products;
 
+use Exception;
 use QUI;
 use QUI\ERP\Coupons\CouponCode;
 use QUI\ERP\Coupons\Handler as CouponsHandler;
@@ -10,7 +11,18 @@
 use QUI\ERP\Discount\Handler as DiscountHandler;
 use QUI\ERP\Products\Handler\Products as ProductsHandler;
 use QUI\ERP\Products\Product\Product;
+use QUI\ExceptionStack;
 use QUI\HtmlToPdf\Document;
+use QUI\Translator;
+
+use function array_merge;
+use function basename;
+use function date;
+use function date_create;
+use function is_numeric;
+use function mb_substr;
+use function rename;
+use function str_replace;
 
 /**
  * Class Handler
@@ -49,7 +61,7 @@ public static function createCouponCodesFromOrder(QUI\ERP\Order\AbstractOrder $O
         foreach ($Order->getArticles() as $Article) {
             try {
                 // Do not parse coupon codes / discounts
-                if (empty($Article->getId()) || !\is_numeric($Article->getId())) {
+                if (empty($Article->getId()) || !is_numeric($Article->getId())) {
                     continue;
                 }
 
@@ -99,23 +111,23 @@ public static function createCouponCodesFromOrder(QUI\ERP\Order\AbstractOrder $O
                         'quiqqer/coupons',
                         'DownloadProduct.pdf.filename',
                         [
-                            'productTitle' => \str_replace(' ', '_', $productTitelSanitized),
-                            'date' => \date('Y_m_d')
+                            'productTitle' => str_replace(' ', '_', $productTitelSanitized),
+                            'date' => date('Y_m_d')
                         ]
                     );
 
-                    $fileName .= '__' . \mb_substr($Order->getHash(), 0, 6);
+                    $fileName .= '__' . mb_substr($Order->getUUID(), 0, 6);
 
-                    $newCouponPdfFile = \str_replace(\basename($couponPdfFile, '.pdf'), $fileName, $couponPdfFile);
+                    $newCouponPdfFile = str_replace(basename($couponPdfFile, '.pdf'), $fileName, $couponPdfFile);
 
-                    \rename($couponPdfFile, $newCouponPdfFile);
+                    rename($couponPdfFile, $newCouponPdfFile);
 
                     // Add PDF file to customer files
-                    CustomerFiles::addFileToCustomer($Customer->getId(), $newCouponPdfFile);
-                    CustomerFiles::addFileToDownloadEntry($Customer->getId(), \basename($newCouponPdfFile));
+                    CustomerFiles::addFileToCustomer($Customer->getUUID(), $newCouponPdfFile);
+                    CustomerFiles::addFileToDownloadEntry($Customer->getUUID(), basename($newCouponPdfFile));
 
                     $customerDir = CustomerFiles::getFolderPath($Customer);
-                    $couponFilePathCustomerDir = $customerDir . DIRECTORY_SEPARATOR . \basename($newCouponPdfFile);
+                    $couponFilePathCustomerDir = $customerDir . DIRECTORY_SEPARATOR . basename($newCouponPdfFile);
                 }
 
                 // Send coupon via email
@@ -128,16 +140,10 @@ public static function createCouponCodesFromOrder(QUI\ERP\Order\AbstractOrder $O
                 );
 
                 if ($deliveryTypeFieldData) {
-                    switch ($deliveryTypeFieldData['value']) {
-                        // by mail
-                        case 1:
-                            $sendMail = false;
-                            break;
-
-                        // by email
-                        default:
-                            $sendMail = true;
-                    }
+                    $sendMail = match ($deliveryTypeFieldData['value']) {
+                        1 => false,
+                        default => true,
+                    };
                 } else {
                     $sendMail = $Product->getFieldValue(self::PRODUCT_FIELD_ID_SEND_MAIL);
                 }
@@ -156,7 +162,7 @@ public static function createCouponCodesFromOrder(QUI\ERP\Order\AbstractOrder $O
                         )
                     );
                 }
-            } catch (\Exception $Exception) {
+            } catch (Exception $Exception) {
                 if ($Exception->getCode() === 404) {
                     QUI\System\Log::writeDebugException($Exception);
                 } else {
@@ -175,7 +181,7 @@ public static function createCouponCodesFromOrder(QUI\ERP\Order\AbstractOrder $O
      * @return CouponCode
      *
      * @throws QUI\Exception
-     * @throws \Exception
+     * @throws Exception
      */
     protected static function createCouponCodeFromProduct(
         Product $Product,
@@ -188,7 +194,7 @@ protected static function createCouponCodeFromProduct(
                 'quiqqer/coupons',
                 'ProductCoupon.coupon_title',
                 [
-                    'orderId' => $Order->getPrefixedId(),
+                    'orderId' => $Order->getPrefixedNumber(),
                     'productId' => $Product->getId()
                 ]
             ),
@@ -202,7 +208,7 @@ protected static function createCouponCodeFromProduct(
             $daysValid = 1095; // 3 years;
         }
 
-        $ValidUntil = \date_create('+ ' . $daysValid . ' days');
+        $ValidUntil = date_create('+ ' . $daysValid . ' days');
         $couponAttributes['validUntilDate'] = $ValidUntil->format('Y-m-d');
 
         // Transferable (=usable by other users or guests)
@@ -210,7 +216,7 @@ protected static function createCouponCodeFromProduct(
             $isTransferable = $Product->getFieldValue(self::PRODUCT_FIELD_ID_TRANSFERABLE);
 
             if (empty($isTransferable)) {
-                $couponAttributes['userIds'] = [$User->getId()];
+                $couponAttributes['userIds'] = [$User->getUUID()];
             }
         }
 
@@ -263,14 +269,14 @@ protected static function createDiscountFromProduct(Product $Product): Discount
             ]);
         }
 
-        \QUI\Translator::update(
+        Translator::update(
             'quiqqer/discount',
             'discount.' . $NewDiscount->getId() . '.title',
             'quiqqer/discount',
             $discountTitle
         );
 
-        \QUI\Translator::publish('quiqqer/discount');
+        Translator::publish('quiqqer/discount');
 
         return $NewDiscount;
     }
@@ -279,6 +285,7 @@ protected static function createDiscountFromProduct(Product $Product): Discount
      * @param CouponCode $CouponCode - The coupon code
      * @param Product $Product - The product the coupon code was created from
      * @return string - PDF file path
+     * @throws QUI\Exception
      */
     public static function createCouponCodePdf(CouponCode $CouponCode, Product $Product): string
     {
@@ -296,7 +303,7 @@ public static function createCouponCodePdf(CouponCode $CouponCode, Product $Prod
         $Engine = QUI::getTemplateManager()->getEngine();
 
         $Engine->assign(
-            \array_merge(
+            array_merge(
                 [
                     'CouponCode' => $CouponCode,
                     'Product' => $Product->getViewFrontend(),
@@ -311,7 +318,7 @@ public static function createCouponCodePdf(CouponCode $CouponCode, Product $Prod
             $Document->setHeaderHTML($Engine->fetch($tplDir . 'CouponCode.header.html'));
             $Document->setContentHTML($Engine->fetch($tplDir . 'CouponCode.body.html'));
             $Document->setFooterHTML($Engine->fetch($tplDir . 'CouponCode.footer.html'));
-        } catch (\Exception $Exception) {
+        } catch (Exception $Exception) {
             QUI\System\Log::writeException($Exception);
         }
 
@@ -326,19 +333,19 @@ public static function createCouponCodePdf(CouponCode $CouponCode, Product $Prod
      * @param QUI\Interfaces\Users\User $Customer
      * @param string|null $couponPdfFile (optional) - Coupon PDF that is attached to the email
      *
-     * @throws \Exception
+     * @throws Exception
      */
     public static function sendCouponMail(
         CouponCode $CouponCode,
         Product $Product,
         QUI\Interfaces\Users\User $Customer,
         ?string $couponPdfFile
-    ) {
+    ): void {
         $recipient = QUI\ERP\Customer\Utils::getInstance()->getEmailByCustomer($Customer);
 
         if (empty($recipient)) {
             QUI\System\Log::addWarning(
-                'Cannot send coupon code e-mail to customer #' . $Customer->getUniqueId() . ' because user has no'
+                'Cannot send coupon code e-mail to customer #' . $Customer->getUUID() . ' because user has no'
                 . ' email address!'
             );
 
@@ -378,7 +385,7 @@ public static function sendCouponMail(
             QUI::getLocale()->get(
                 'quiqqer/coupons',
                 'ProductCoupon.mail.body',
-                \array_merge(
+                array_merge(
                     $couponViewData,
                     [
                         'customerName' => $Customer->getName(),
@@ -400,8 +407,10 @@ public static function sendCouponMail(
      * @param Product $Product
      * @return array
      *
+     * @throws QUI\Database\Exception
      * @throws QUI\ERP\Products\Product\Exception
-     * @throws QUI\Users\Exception
+     * @throws QUI\Exception
+     * @throws ExceptionStack
      */
     protected static function getCouponViewData(CouponCode $CouponCode, Product $Product): array
     {
@@ -456,6 +465,7 @@ protected static function getCouponViewData(CouponCode $CouponCode, Product $Pro
      * which are NOT taxed at checkout but only when they are redeemed for a product.
      *
      * @return QUI\ERP\Tax\TaxType[]
+     * @throws QUI\Exception
      */
     public static function getNoVatTaxTypes(): array
     {
diff --git a/src/QUI/ERP/Coupons/Products/PhysicalCouponProductType.php b/src/QUI/ERP/Coupons/Products/PhysicalCouponProductType.php
index c9975338a5e06beb2aab14f2cce5ae4e2ae702b9..9b8298b10bc40b9790c02a8c34726ce63d8fa760 100644
--- a/src/QUI/ERP/Coupons/Products/PhysicalCouponProductType.php
+++ b/src/QUI/ERP/Coupons/Products/PhysicalCouponProductType.php
@@ -20,7 +20,7 @@ class PhysicalCouponProductType extends QUI\ERP\Products\Product\Types\AbstractT
      * @throws QUI\ERP\Products\Product\Exception
      * @throws QUI\Exception
      */
-    public function __construct($pid, $product = [])
+    public function __construct(int $pid, array $product = [])
     {
         parent::__construct($pid, $product);
 
@@ -48,10 +48,10 @@ public function __construct($pid, $product = [])
     }
 
     /**
-     * @param QUI\Locale $Locale
+     * @param QUI\Locale|null $Locale
      * @return string
      */
-    public static function getTypeTitle($Locale = null): string
+    public static function getTypeTitle(QUI\Locale $Locale = null): string
     {
         if ($Locale === null) {
             $Locale = QUI::getLocale();
diff --git a/tests/phpstan-bootstrap.php b/tests/phpstan-bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..b61ff4b8300a965e0f46025ba64745a22b118e74
--- /dev/null
+++ b/tests/phpstan-bootstrap.php
@@ -0,0 +1,13 @@
+<?php
+
+if (!defined('QUIQQER_SYSTEM')) {
+    define('QUIQQER_SYSTEM', true);
+}
+
+if (!defined('QUIQQER_AJAX')) {
+    define('QUIQQER_AJAX', true);
+}
+
+putenv("QUIQQER_OTHER_AUTOLOADERS=KEEP");
+
+require_once __DIR__ . '/../../../../bootstrap.php';