Newer
Older
use QUI\ERP\Accounting\Calc as ErpCalc;
use QUI\ERP\Coupons\Handler as CouponsHandler;
use QUI\ERP\Coupons\Products\CouponProductException;
use QUI\ERP\Coupons\Products\DigitalCouponProductType;
use QUI\ERP\Coupons\Products\Handler as CouponProductsHandler;
use QUI\ERP\Coupons\Products\PhysicalCouponProductType;
use QUI\ERP\Discount\EventHandling as DiscountEvents;
use QUI\ERP\Products\Handler\Fields;
use QUI\ERP\Products\Interfaces\ProductInterface;
use QUI\Smarty\Collector;
use function array_merge;
use function array_search;
use function array_unique;
use function in_array;
use function is_array;
use function is_string;
use function json_decode;
use function json_encode;
/**
* Class Events
*
* Global Event Handler for quiqqer/payment-paypal
*/
class Events
{
* quiqqer/core: onPackageSetup
*
* @param QUI\Package\Package $Package
* @return void
*/
public static function onPackageSetup(QUI\Package\Package $Package): void
public static function onAdminLoadFooter(): void
{
echo '<script src="' . URL_OPT_DIR . 'quiqqer/coupons/bin/backend/load.js"></script>';
}
/**
* Template event quiqqer/order: onQuiqqer::order::orderProcessBasketEnd
*
* @param Collector $Collector
* @param mixed $Basket
* @param AbstractOrder|null $Order
public static function templateOrderProcessBasketEnd(
Collector $Collector,
mixed $Basket,
AbstractOrder $Order = null
): void {

Henning Leutz
committed
&& !($Basket instanceof QUI\ERP\Order\Basket\BasketOrder)
) {
if (isset($Order) && isset($_GET['coupon'])) {
try {
$code = Handler::sanitizeCode($_GET['coupon']);
$CouponCode = Handler::getCouponCodeByCode($code);
$CouponCode->checkRedemption(QUI::getUserBySession());
if ($Order instanceof QUI\ERP\Order\OrderInProcess) {
$CouponCode->addToOrder($Order);
}
} catch (Exception) {
$Collector->append(
'<div data-qui="package/quiqqer/coupons/bin/frontend/controls/CouponCodeInput"></div>'
);
}
/**
* @param QUI\ERP\Order\OrderProcess $OrderProcess
* @throws QUI\ERP\Order\Exception
* @throws QUI\Exception
*/
public static function onOrderProcess(QUI\ERP\Order\OrderProcess $OrderProcess): void
{
$CurrentStep = $OrderProcess->getCurrentStep();
$currentStep = $CurrentStep->getType();
if ($currentStep !== QUI\ERP\Order\Controls\OrderProcess\Basket::class) {
return;
}
$coupons = QUI::getSession()->get('quiqqer-coupons');
if (is_string($coupons)) {
$coupons = json_decode($coupons, true);
$coupons = [];
}
if (isset($_GET['coupon'])) {
$coupons[] = $_GET['coupon'];
}
$Order = $OrderProcess->getOrder();
foreach ($coupons as $coupon) {
self::addCouponToOrder($Order, $coupon);
}
}
/**
* @param QUI\ERP\Order\Basket\Basket $Basket
* @param $pos
*/
public static function onQuiqqerOrderBasketRemovePos(
QUI\ERP\Order\Basket\Basket $Basket,
$pos
): void {
} catch (QUI\Exception) {
$Orders = QUI\ERP\Order\Handler::getInstance();
try {
$Order = $Orders->getLastOrderInProcessFromUser(QUI::getUserBySession());
} catch (QUI\Exception) {
if (!$Order) {
QUI::getSession()->remove('quiqqer-coupons');
$Article = $Order->getArticles()->getArticle($pos);

Patrick Müller
committed
if (!$Article) {
return;
}
$orderCoupons = $Order->getDataEntry('quiqqer-coupons');
$articleCouponCode = false;
if (isset($customData['package']) && isset($customData['code'])) {
$articleCouponCode = $customData['code'];
}
if (!$articleCouponCode) {
return;
}
// custom data has code params, so article is a coupon code
if (in_array($articleCouponCode, $orderCoupons)) {
$pos = array_search($articleCouponCode, $orderCoupons);
unset($orderCoupons[$pos]);
$Order->setData('quiqqer-coupons', $orderCoupons);
try {
$Order->save();
} catch (QUI\Exception) {
}
}
// look at session coupons
// we need to delete it
$coupons = QUI::getSession()->get('quiqqer-coupons');
if (is_string($coupons)) {
$coupons = json_decode($coupons, true);
return;
}
// remove code from session
// because code is deleted
$newCouponList = [];
foreach ($coupons as $coupon) {
if ($customData['code'] !== $coupon) {
$newCouponList[] = $coupon;
}
}
if (empty($newCouponList)) {
QUI::getSession()->remove('quiqqer-coupons');
} else {
QUI::getSession()->set('quiqqer-coupons', json_encode($newCouponList));
/**
* event - on price factor init
*
* @param mixed $Basket
* @param QUI\ERP\Order\AbstractOrder $Order

Henning Leutz
committed
* @param QUI\ERP\Products\Product\ProductList $Products
* @throws QUI\Exception
*/
public static function onQuiqqerOrderBasketToOrder(
mixed $Basket,

Henning Leutz
committed
QUI\ERP\Products\Product\ProductList $Products
): void {
$coupons = $Order->getDataEntry('quiqqer-coupons');
$sessionCoupons = QUI::getSession()->get('quiqqer-coupons');
if (is_string($sessionCoupons)) {
$sessionCoupons = json_decode($sessionCoupons, true);
if (is_array($sessionCoupons)) {
$coupons = array_merge($coupons, $sessionCoupons);
self::addSessionCouponsToOrder($Order, $sessionCoupons);

Henning Leutz
committed
if (empty($coupons)) {
return;
}

Henning Leutz
committed
$PriceFactors = $Products->getPriceFactors();
$productCount = $Products->count();
$subSum = $products['calculations']['subSum'];

Henning Leutz
committed
$checkRedeemable = !$Order->isSuccessful(); // if order is successful we don't need a check
$OrderInProcess = $Order->getAttribute('OrderInProcess');
$added = false;

Henning Leutz
committed
if ($Order->getAttribute('inOrderCreation')) {
$checkRedeemable = false;
}
if (
$OrderInProcess instanceof QUI\ERP\Order\OrderInProcess

Henning Leutz
committed
&& $OrderInProcess->getAttribute('inOrderCreation')
) {
$checkRedeemable = false;
}
foreach ($coupons as $coupon) {
/* @var $Coupon CouponCode */
try {
$Coupon = Handler::getCouponCodeByCode($coupon);
} catch (Exception) {
continue;
}
// coupon check

Henning Leutz
committed
if ($checkRedeemable && !$Coupon->isRedeemable($Order->getCustomer())) {
continue;
}
/* @var $Discount QUI\ERP\Discount\Discount */
$discounts = $Coupon->getDiscounts();
foreach ($discounts as $Discount) {
if (!DiscountEvents::isDiscountUsableWithQuantity($Discount, $productCount)) {
continue;
}
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
} elseif (!DiscountEvents::isDiscountUsableWithPurchaseValue($Discount, $subSum)) {
continue;
}
$PriceFactor = $Discount->toPriceFactor(null, $Order->getCustomer());

Henning Leutz
committed
QUI::getLocale()->get('quiqqer/coupons', 'coupon.discount.title', [
'code' => $Coupon->getCode()
])
$scope = $Discount->getAttribute('scope');
$isUnique = $scope === QUI\ERP\Discount\Handler::DISCOUNT_SCOPE_UNIQUE;
$everyProduct = $scope === QUI\ERP\Discount\Handler::DISCOUNT_SCOPE_EVERY_PRODUCT;
if ($Discount->canUsedWith($Product) === false) {
continue;
}
if ($isUnique && $alreadyAdded) {
continue;
}
if ($Product instanceof QUI\ERP\Products\Product\UniqueProduct) {
$Product->getPriceFactors()->add($PriceFactor);

Henning Leutz
committed
$added = true;
$PriceFactors->addToEnd($PriceFactor);
}
}

Henning Leutz
committed
if ($added) {
try {
$Products->recalculation();
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
}

Henning Leutz
committed
}
}
/**
* quiqqer/order: onQuiqqerOrderSuccessful
*
* Redeem coupons used in (completed) orders
*
* @param AbstractOrder $Order
public static function onQuiqqerOrderSuccessful(AbstractOrder $Order): void
{
$coupons = $Order->getDataEntry('quiqqer-coupons');
if (empty($coupons)) {
return;
}
foreach ($coupons as $couponCode) {
try {
$CouponCode = CouponsHandler::getCouponCodeByCode($couponCode);
$CouponCode->redeem($Order->getCustomer(), $Order);
QUI\System\Log::writeException($Exception);
}
}
}
/**
* @param $Order
* @param $coupons
*/
protected static function addSessionCouponsToOrder($Order, $coupons): void
return;
}
// coupons as article if not added
$isInArticles = function ($code) use ($Articles) {
foreach ($Articles as $Article) {
$customData = $Article->getCustomData();
if (isset($customData['code']) && $customData['code'] === $code) {
return true;
}
}
return false;
};
foreach ($coupons as $coupon) {
if ($isInArticles($coupon) === false) {
self::addCouponToOrder($Order, $coupon);
}
}
}
/**
* @param $Order
* @param $coupon
*/
protected static function addCouponToOrder($Order, $coupon): void
if (!($Order instanceof QUI\ERP\Order\OrderInProcess)) {
return;
}
try {
$code = Handler::sanitizeCode($coupon);
$CouponCode = Handler::getCouponCodeByCode($code);
$CouponCode->checkRedemption(QUI::getUserBySession());
$coupons = $Order->getDataEntry('quiqqer-coupons');
$Order->setData('quiqqer-coupons', $coupons);
$Order->update();
$CouponCode->addToOrder($Order);
} catch (Exception) {
* Removes all coupons from the current session.
public static function removeCouponsFromSession(): void
{
QUI::getSession()->remove('quiqqer-coupons');
}
/**
* Create all fixed product fields that quiqqer/stock-management provides
*
* @return void
* @throws QUI\Exception
*/
protected static function createProductFields(): void
CouponProductsHandler::PRODUCT_FIELD_ID_TRANSFERABLE => [
'title' => [
'de' => 'Gutschein-Code ist übertragbar',
'en' => 'Coupon code is transferable'
],
'de' => 'Übertragbare Gutscheine sind auch von anderen Personen als dem Käufer einlösbar.'
. ' Nicht übertragbare Gutscheine können nur vom Käufer eingelöst werden, wenn dieser'
. ' eingeloggt ist.',
'en' => 'Transferable coupons are also redeemable by persons other than the buyer.'
. ' Non-transferable vouchers can only be redeemed by the buyer when logged in.'
'type' => Fields::TYPE_BOOL,
'public' => false,
'standard' => false,
CouponProductsHandler::PRODUCT_FIELD_ID_SEND_MAIL => [
'title' => [

Patrick Müller
committed
'de' => 'Gutschein-Code per E-Mail senden',
'en' => 'Send coupon code via email'
],
'de' => 'Der Gutschein-Code wird dem Käufer per E-Mail gesendet. Gilt nicht, wenn der Benutzer zw.'
. ' Post- und Mail-Versand wählen kann und den Postversand auswählt.'
. ' Wird der Gutschein auch als PDF-Datei generiert, wird die PDF-Datei an diese E-Mail angehanden.',
'en' => 'The coupon code is sent to the buyer via email. It is not sent if the customer is able to'
. ' choose between email and mail delivery type and chooses delivery by mail.'
. ' If the coupon is also generated as a PDF file, the file is attached to this email.'
'type' => Fields::TYPE_BOOL,
'public' => false,
'standard' => false,

Patrick Müller
committed
'requiredField' => false
],
CouponProductsHandler::PRODUCT_FIELD_ID_GENERATE_PDF => [
'title' => [
'de' => 'Der Gutschein wird auch als PDF-Datei erstellt und dem Käufer (je nach Wahl) per E-Mail gesendet.'
. ' Zusätzlich wird die PDF-Datei dem Käufer in seinem Frontend-Profil (sofern eingerichtet)'
. ' bereitgestellt.',
'en' => 'The coupon is also generated as a PDF file and is sent to the customer (depending on choice)'
. ' via email. Additionally, the PDF file is made available via the customers frontend profile'
. ' (if set up).'
'type' => Fields::TYPE_BOOL,
'public' => false,
'standard' => false,
CouponProductsHandler::PRODUCT_FIELD_ID_COUPON_AMOUNT => [
'title' => [
'type' => Fields::TYPE_FLOAT,
'public' => false,
'standard' => false,
CouponProductsHandler::PRODUCT_FIELD_ID_DAYS_VALID => [
'title' => [
'de' => 'Gutschein-Code Gültigkeit (Tage)',
'en' => 'Coupon code validity (days)'
],
'type' => Fields::TYPE_INT,
'public' => false,
'standard' => false,
CouponProductsHandler::PRODUCT_FIELD_ID_COUPON_DESCRIPTION => [
'title' => [
'de' => 'Gutschein-Beschreibung',
'en' => 'Coupon description'
],
'de' => 'Diese Beschreibung taucht unter dem Titel "GUTSCHEIN" auf der generierten PDF-Datei auf.',
'en' => 'This description appears under the title caption "COUPON" on the generated PDF file.'
],
'type' => Fields::TYPE_INPUT_MULTI_LANG,
'public' => false,
'standard' => false,
'requiredField' => false
CouponProductsHandler::PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON => [
'title' => [

Patrick Müller
committed
'de' => 'Ist Einzweck-Gutschein (Besteuerung bei Gutschein-Kauf)',
'en' => 'Is single purpose coupon (taxation on voucher purchase)'
'de' => 'Einzweck-Gutscheine sind solche, bei denen die Besteuerung und der Leistungsort bereits'
. ' beim Gutschein-Kauf feststehen. Beispiel: Gutschein für eine Massage in einem Spa.'
. ' Alles andere (wie z.B. Wertgutscheine für den Einsatz unabhängig vom Artikel) sind'
. ' Mehrzweck-Gutscheine und werden bei Einkauf nicht besteuert.',
'en' => 'Single-purpose coupons are those for which the taxation and place of performance are already'
. ' determined at the time of coupon purchase. Example: voucher for a massage in a spa.'
. ' Everything else (such as money value coupons for use regardless of the item) are'
. ' multi-purpose coupons and are not taxed at the time of purchase.'
'type' => Fields::TYPE_BOOL,
'public' => false,
'standard' => false,

Patrick Müller
committed
],
CouponProductsHandler::PRODUCT_FIELD_ID_USER_DELIVERY_TYPE_SELECT => [
'title' => [
'de' => 'Gutschein - Versand',

Patrick Müller
committed
'en' => 'Coupon delivery'
],
'type' => Fields::TYPE_ATTRIBUTE_LIST,
'public' => true,
'standard' => false,

Patrick Müller
committed
'requiredField' => true,

Patrick Müller
committed
'entries' => [
[
'de' => 'per E - Mail',

Patrick Müller
committed
'en' => 'via email'
],
'sum' => 0,
'type' => ErpCalc::CALCULATION_COMPLEMENT,
'selected' => true,

Patrick Müller
committed
'userinput' => false
],
[

Patrick Müller
committed
'de' => 'per Post',
'en' => 'via mail'
],
'sum' => 0,
'type' => ErpCalc::CALCULATION_COMPLEMENT,
'selected' => false,

Patrick Müller
committed
'userinput' => false
]
]
]
],
CouponProductsHandler::PRODUCT_FIELD_ID_USER_DELIVERY_TYPE_SELECT_ALLOW => [
'de' => 'Kunde darf Gutschein - Versandart wählen',

Patrick Müller
committed
'en' => 'Customer can choose coupon delivery type'
],

Patrick Müller
committed
'de' => 'Ist diese Funktion aktiviert, kann der Kunde beim Artikel im Shop wählen, ob der Gutschein'
'en' => 'if this option is enabled, the customer can choose at the article in the store whether the'
. ' coupon should be sent by e - mail or by(physical) mail.'

Patrick Müller
committed
],
'type' => Fields::TYPE_BOOL,
'public' => false,
'standard' => false,

Patrick Müller
committed
'requiredField' => false
]
];
$fieldsCreated = false;
foreach ($fields as $fieldId => $field) {
try {
Fields::getField($fieldId);
continue;
} catch (Exception) {
// Field does not exist -> create it
}
try {
Fields::createField([
'id' => $fieldId,
'type' => $field['type'],
'titles' => $field['title'],
'description' => !empty($field['description']) ? $field['description'] : null,
'systemField' => 0,
'publicField' => !empty($field['public']) ? 1 : 0,
'options' => !empty($field['options']) ? $field['options'] : null,
'requiredField' => !empty($field['requiredField']) ? 1 : 0
QUI\System\Log::writeException($Exception);
continue;
}
$fieldsCreated = true;
}
if ($fieldsCreated) {
QUI\Translator::publish('quiqqer / products');
}
}
/**
* Assign plan product fields to a product
*
* @param ProductInterface $Product
* @return void

Patrick Müller
committed
*
* @throws QUI\Exception
public static function onQuiqqerProductsProductCreate(ProductInterface $Product): void
if (!($Product instanceof DigitalCouponProductType) && !($Product instanceof PhysicalCouponProductType)) {
$isDigital = $Product instanceof DigitalCouponProductType;
$UniqueProduct = $Product->createUniqueProduct();
$UniqueProduct->calc();
// CouponProductsHandler::PRODUCT_FIELD_ID_TRANSFERABLE => true,
CouponProductsHandler::PRODUCT_FIELD_ID_COUPON_AMOUNT => $UniqueProduct->getPrice()->getValue(),
CouponProductsHandler::PRODUCT_FIELD_ID_DAYS_VALID => 1095, // 3 years
CouponProductsHandler::PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON => false
// Digital coupons get some extra fields
if ($isDigital) {
$fields[CouponProductsHandler::PRODUCT_FIELD_ID_SEND_MAIL] = true;
$fields[CouponProductsHandler::PRODUCT_FIELD_ID_GENERATE_PDF] = true;
$fields[CouponProductsHandler::PRODUCT_FIELD_ID_USER_DELIVERY_TYPE_SELECT] = false;
$fields[CouponProductsHandler::PRODUCT_FIELD_ID_COUPON_DESCRIPTION] = null;
}
foreach ($fields as $fieldId => $value) {
try {
$Field = Fields::getField($fieldId);
$Field->setValue($value);
$Product->addOwnField($Field);
QUI\System\Log::writeException($Exception);
}
}

Patrick Müller
committed
// No VAT tax types -> Choose first one found
$noVatTaxTypes = QUI\ERP\Coupons\Products\Handler::getNoVatTaxTypes();
if (!empty($noVatTaxTypes)) {
$Product->getField(Fields::FIELD_VAT)->setValue($noVatTaxTypes[0]->getId());
}
try {
$Product->update(QUI::getUsers()->getSystemUser());
QUI\System\Log::writeException($Exception);
}
}

Patrick Müller
committed
/**
* quiqqer/products: onQuiqqerProductsProductActivate
*
* Check if a coupon product has the correct tax type!
*
* @param ProductInterface $Product
*
* @throws CouponProductException
* @throws QUI\Exception
*/
public static function onQuiqqerProductsProductActivate(ProductInterface $Product): void

Patrick Müller
committed
{
if (!($Product instanceof DigitalCouponProductType) && !($Product instanceof PhysicalCouponProductType)) {

Patrick Müller
committed
return;
}
$isSinglePurposeCoupon = $Product->getFieldValue(
CouponProductsHandler::PRODUCT_FIELD_ID_IS_SINGLE_PURPOSE_COUPON
);

Patrick Müller
committed
if (!empty($isSinglePurposeCoupon)) {
return;
}
$productTaxTypeId = (int)$Product->getFieldValue(Fields::FIELD_VAT);
$noVatTaxTypes = QUI\ERP\Coupons\Products\Handler::getNoVatTaxTypes();

Patrick Müller
committed
foreach ($noVatTaxTypes as $TaxType) {
if ($TaxType->getId() === $productTaxTypeId) {
return;
}
}
throw new CouponProductException([
'quiqqer / coupons',

Patrick Müller
committed
'exception.CouponProduct.no_vat_tax_type_required',
[
'productTitle' => $Product->getTitle(),

Patrick Müller
committed
]
]);
}
/**
* quiqqer/order: onQuiqqerOrderCreated
*
* Parse coupon attributes from order and create coupon codes for the buyer.
*
* @param AbstractOrder $Order
* @return void
*/
public static function onQuiqqerOrderCreated(AbstractOrder $Order): void
QUI\ERP\Coupons\Products\Handler::createCouponCodesFromOrder($Order);