Newer
Older
<?php
/**
* This file contains QUI\ERP\Shipping\Rules\ShippingRule
*/
namespace QUI\ERP\Shipping\Rules;
use QUI;
use QUI\CRUD\Factory;
use QUI\ERP\Products\Handler\Fields;
use QUI\ERP\Products\Handler\Products;
use QUI\ERP\Products\Utils\Fields as FieldUtils;
use QUI\ERP\Shipping\Exceptions\ShippingCanNotBeUsed;
use QUI\ERP\Shipping\Rules\Factory as RuleFactory;
use QUI\Permissions\Permission;
use QUI\Translator;
use function array_filter;
use function array_flip;
use function array_map;
use function array_merge;
use function array_unique;
use function is_array;
use function is_numeric;
use function is_string;
use function json_decode;
use function json_encode;
use function round;
use function strtotime;
use function time;
use function trim;
/**
* Class ShippingEntry
* A user created shipping entry
*
* @package QUI\ERP\Shipping\Types
*/
class ShippingRule extends QUI\CRUD\Child
{
/**
* Shipping constructor.
*
* @param int $id
* @param Factory $Factory
*/
public function __construct($id, Factory $Factory)
{
parent::__construct($id, $Factory);
$this->Events->addEvent('onDeleteBegin', function () {
Permission::checkPermission('quiqqer.shipping.delete');
// delete locale
$id = $this->getId();
QUI\Translator::delete('quiqqer/shipping', 'shipping.' . $id . '.rule.title');
QUI\Translator::delete('quiqqer/shipping', 'shipping.' . $id . '.rule.workingTitle');
});
$this->Events->addEvent('onSaveBegin', function () {
Permission::checkPermission('quiqqer.shipping.edit');
'quiqqer/shipping',
$attributes['workingTitle']
);
$attributes['discount'] = floatval($attributes['discount']);
if (is_numeric($attributes['discount_type']) || empty($attributes['discount_type'])) {
$attributes['discount_type'] = (int)$attributes['discount_type'];
}
if (
$attributes['discount_type'] === RuleFactory::DISCOUNT_TYPE_PERCENTAGE ||
$attributes['discount_type'] === 'PERCENTAGE'
) {
$attributes['discount_type'] = RuleFactory::DISCOUNT_TYPE_PERCENTAGE;
} elseif (
$attributes['discount_type'] === RuleFactory::DISCOUNT_TYPE_PC_ORDER ||
$attributes['discount_type'] === 'PERCENTAGE_ORDER'
) {
$attributes['discount_type'] = RuleFactory::DISCOUNT_TYPE_PC_ORDER;
} else {
$attributes['discount_type'] = RuleFactory::DISCOUNT_TYPE_ABS;
}
$attributes['articles_only'] = 0;
} else {
$attributes['articles_only'] = (int)$attributes['articles_only'];
}
$attributes['no_rule_after'] = 0;
} else {
$attributes['no_rule_after'] = (int)$attributes['no_rule_after'];
}
if (isset($attributes['unit_terms']) && is_array($attributes['unit_terms'])) {
$attributes['unit_terms'] = json_encode($attributes['unit_terms']);
$nullEmpty = [
'purchase_quantity_from',
'purchase_quantity_until',
'purchase_value_from',
'purchase_value_until',
];
foreach ($nullEmpty as $k) {
if (empty($attributes[$k])) {
$attributes[$k] = null;
}
// update for saving
$this->setAttributes($attributes);
/**
* Return the payment as an array
*
* @return array
*/
{
$lg = 'quiqqer/shipping';
$id = $this->getId();
$attributes = $this->getAttributes();
$availableLanguages = QUI\Translator::getAvailableLanguages();
foreach ($availableLanguages as $language) {
$attributes['title'][$language] = $Locale->getByLang(
$language,
$lg,
);
$attributes['workingTitle'][$language] = $Locale->getByLang(
$language,
$lg,
$attributes['unit_terms'] = $this->getUnitTerms();
/**
* Return the shipping rule title
*
public function getTitle(QUI\Locale $Locale = null): string
{
if ($Locale === null) {
$Locale = QUI::getLocale();
}
$language = $Locale->getCurrent();
return $Locale->getByLang(
$language,
'quiqqer/shipping',
);
}
/**
* Return the shipping rule priority
*
* @return int
*/
{
return (int)$this->getAttribute('priority');
}
/**
* Return the shipping rule discount value
*
* @return float
*/
return floatval($this->getAttribute('discount'));
/**
* is the user allowed to use this shipping
*
* @param QUI\Interfaces\Users\User $User
* @return boolean
*/
public function canUsedBy(QUI\Interfaces\Users\User $User): bool
Debug::addLog("{$this->getTitle()} :: is not active");
if ($User instanceof QUI\ERP\User) {
try {
try {
QUI::getEvents()->fireEvent('quiqqerShippingCanUsedBy', [$this, $User]);
return false;
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
return false;
}
// usage definitions / limits
$dateUntil = $this->getAttribute('date_until');
if ($dateFrom && strtotime($dateFrom) > $now) {
Debug::addLog("{$this->getTitle()} :: date from is not valid $dateFrom > $now");
if ($dateUntil && strtotime($dateUntil) < $now) {
Debug::addLog("{$this->getTitle()} :: date from is not valid $dateFrom < $now");
return false;
}
// assignment
$userGroupValue = $this->getAttribute('user_groups');
// if groups and areas are empty, everybody is allowed
if (empty($userGroupValue) && empty($areasValue)) {
Debug::addLog("{$this->getTitle()} :: empty user and areas [ok]");
// address need to be checked via erp entity
$userGroups = QUI\Utils\UserGroups::parseUsersGroupsString(
$this->getAttribute('user_groups')
);
$discountGroups = $userGroups['groups'];
if (empty($discountUsers) && empty($discountGroups)) {
Debug::addLog("{$this->getTitle()} :: empty discount [ok]");
return true;
}
foreach ($discountUsers as $uid) {
if ($User->getId() == $uid || $User->getUUID() === $uid) {
Debug::addLog("{$this->getTitle()} :: user is found in users [ok]");
return true;
}
}
// group checking
$groupsOfUser = $User->getGroups();
/* @var $Group QUI\Groups\Group */
foreach ($discountGroups as $gid) {
foreach ($groupsOfUser as $Group) {
if ($Group->getId() == $gid || $Group->getUUID() == $gid) {
Debug::addLog("{$this->getTitle()} :: group is found in groups [ok]");
Debug::addLog("{$this->getTitle()} :: shipping rule is not valid. is not found in users and groups");
/**
* Is this shipping rule allowed for this address?
*
* @param Address|QUI\Users\Address|null $Address
public function canUsedWithAddress(QUI\ERP\Address|QUI\Users\Address $Address = null): bool
if (!$Address) {
return false;
}
$areasValue = $this->getAttribute('areas');
if ($areasValue) {
$areasValue = explode(',', $areasValue);
$areasValue = array_filter($areasValue);
}
if (!empty($areasValue) && !AreaUtils::isAddressInArea($Address, $areasValue)) {
Debug::addLog("{$this->getTitle()} :: user address is not in areas");
return false;
}
return true;
}
* is the shipping allowed in this erp entity?
* @param QUI\ERP\ErpEntityInterface|null $ErpEntity
public function canUsedIn(QUI\ERP\ErpEntityInterface $ErpEntity = null): bool
Debug::addLog("{$this->getTitle()} :: is not valid");
if (!$this->canUsedBy($ErpEntity->getCustomer())) {
Debug::addLog("{$this->getTitle()} :: can not be used by {$ErpEntity->getCustomer()->getUUID()}");
$DeliveryAddress = $ErpEntity->getDeliveryAddress();
if (!$this->canUsedWithAddress($DeliveryAddress)) {
Debug::addLog("{$this->getTitle()} :: can not be used with address");
return false;
}
$articleList = $Articles->getArticles();
if (!$Articles->count()) {
return false;
}
$articleOnly = (int)$this->getAttribute('articles_only');
$categories = $this->getAttribute('categories');
$categories = trim($categories, ',');
$categories = explode(',', $categories);
$categories = array_map('intval', $categories);
$quantityFrom = $this->getAttribute('purchase_quantity_from'); // Einkaufsmenge ab
$quantityUntil = $this->getAttribute('purchase_quantity_until'); // Einkaufsmenge bis
$purchaseFrom = $this->getAttribute('purchase_value_from'); // Einkaufswert ab
$purchaseUntil = $this->getAttribute('purchase_value_until'); // Einkaufswert bis
// article checks
$Shipping = QUI\ERP\Shipping\Shipping::getInstance();
$unitIds = $Shipping->getShippingRuleUnitFieldIds();
if (!empty($articles)) {
$articleFound = false;
if (is_string($articles)) {
$articles = explode(',', $articles);
$articles = array_filter($articles);
$checkCategoryIds = function (array $catIds) use ($categories) {
foreach ($catIds as $categoryId) {
if (in_array($categoryId, $categories)) {
return true;
}
}
return false;
};
$articleQuantity = $Article->getQuantity();
$productCategories = $Product->getCategories();
$categoryIds = array_map(function ($Category) {
return $Category->getId();
}, $productCategories);
// check categories
if ($checkCategoryIds($categoryIds) === false) {
return false;
}
$categoryIdsInEntity = array_merge($categoryIdsInEntity, $categoryIds);
foreach ($unitIds as $unitId) {
$Weight = $Product->getField($unitId);
$weight = $Weight->getValue();
if ((int)$unitId === Fields::FIELD_WEIGHT) {
$weight = FieldUtils::weightFieldToKilogram($Weight);
}
$weight = $weight['quantity'];
}
if (!isset($articleUnits[$unitId])) {
$articleUnits[$unitId] = 0;
}
$articleUnits[$unitId] = $articleUnits[$unitId] + ($weight * $articleQuantity);
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
}
if (empty($articles)) {
continue;
}
if (isset($articles[$aid])) {
$articleFound = true;
break;
}
}
if (QUI\ERP\Shipping\Debug::isEnabled()) {
foreach ($debugUnits as $debugUnit) {
QUI\ERP\Shipping\Debug::addLog(
"- Check units {$debugUnit['field']} -> {$debugUnit['amount']}"
);
}
}
if ($articleFound && $articleOnly && count($articleList) !== 1) {
QUI\ERP\Shipping\Debug::addLog(
"{$this->getTitle()} :: is not a single article"
);
return false;
}
QUI\ERP\Shipping\Debug::addLog(
"{$this->getTitle()} :: article not found for this rule"
);
$categoryIdsInEntity = array_unique($categoryIdsInEntity);
if (!empty($categoryIdsInEntity) && (count($categories) === 1 && $categories[0] !== 0)) {
$found = false;
foreach ($categories as $cid) {
if (in_array($cid, $categoryIdsInEntity)) {
$found = true;
break;
}
}
if ($found === false) {
return false;
}
}
// unit terms check
if (!empty($unitTerms)) {
foreach ($unitTerms as $unitTerm) {

Henning Leutz
committed
if (!isset($unitTerm['value'])) {
$unitTerm['value'] = '';
}
if (!isset($unitTerm['value2'])) {
$unitTerm['value2'] = '';
}
if ($unitTerm['value'] === '' && $unitTerm['value2'] === '') {

Henning Leutz
committed
if (empty($unitTerm['term'])) {
$unitTerm['term'] = 'gt';
}
if (empty($unitTerm['term2'])) {
$unitTerm['term2'] = 'gt';
}
$id = (int)$unitTerm['id'];
$unit = $unitTerm['unit'];
if (!isset($articleUnits[$id])) {
continue;
}
if ($id === Fields::FIELD_WEIGHT) {
$unitValue = FieldUtils::weightToKilogram($value, $unit);
$compare = FieldUtils::compare($articleUnits[$id], $unitValue, $term);
"{$this->getTitle()} :: weight is not valid $articleUnits[$id] $hTerm $unitValue"

Henning Leutz
committed
// term 2
if (!empty($unitTerm['value2'])) {

Henning Leutz
committed
$unitValue = FieldUtils::weightToKilogram($value2, $unit);
$compare2 = FieldUtils::compare($articleUnits[$id], $unitValue, $term2);

Henning Leutz
committed
if ($compare2 === false) {
"{$this->getTitle()} :: weight is not valid $articleUnits[$id] $hTerm2 $unitValue"

Henning Leutz
committed
return false;
}
}
continue;
}
$compare = FieldUtils::compare($articleUnits[$id], $value, $term);
if ($compare === false) {
"{$this->getTitle()} :: unit term is not valid $articleUnits[$id] $hTerm $value"

Henning Leutz
committed
// term 2
if (!empty($unitTerm['value2'])) {
$value2 = floatval($unitTerm['value2']);
$term2 = $unitTerm['term2'];
$hTerm2 = FieldUtils::termToHuman($term2);

Henning Leutz
committed
$compare2 = FieldUtils::compare($articleUnits[$id], $value2, $term2);
if ($compare2 === false) {
"{$this->getTitle()} :: unit term is not valid $articleUnits[$id] $hTerm2 $value2"

Henning Leutz
committed
return false;
}
}
if (!empty($quantityFrom) && $quantityFrom < $count) {
"{$this->getTitle()} :: quantity from is not valid, $count < $quantityFrom"
return false;
}
if (!empty($quantityUntil) && $quantityFrom > $count) {
"{$this->getTitle()} :: quantity until is not valid, $count > $quantityFrom"
return false;
}
// purchase
try {
$Calculation = $ErpEntity->getPriceCalculation();
$Currency = $ErpEntity->getCurrency();
$PriceFactors = $ErpEntity->getArticles()->getPriceFactors();
$ShippingFactor = null;
/* @var $Factor QUI\ERP\Products\Interfaces\PriceFactorInterface */
foreach ($PriceFactors as $Factor) {
if (str_contains($Factor->getIdentifier(), 'shipping-pricefactor')) {
$ShippingFactor = $Factor;
break;
}
}
$Sum = $Calculation->getNettoSum();
$sum = $Sum->precision($Currency->getPrecision())->get();
/* quiqqer/shipping#25 */
if ($ShippingFactor) {
$sum = $sum - round($ShippingFactor->getNettoSum(), $Currency->getPrecision());
} catch (QUI\Exception $Exception) {
QUI\System\Log::writeDebugException($Exception);
return false;
}
if (!empty($purchaseFrom)) {
$purchaseFrom = QUI\ERP\Money\Price::validatePrice($purchaseFrom);
if ($purchaseFrom >= $sum) {
"{$this->getTitle()} :: purchase from is not valid, $purchaseFrom < $sum"
return false;
}
}
if (!empty($purchaseUntil)) {
$purchaseUntil = QUI\ERP\Money\Price::validatePrice($purchaseUntil);
if ($purchaseUntil <= $sum) {
"{$this->getTitle()} :: purchase from is not valid, $purchaseFrom > $sum"
QUI::getEvents()->fireEvent('shippingCanUsedInOrder', [$this, $ErpEntity]);
QUI::getEvents()->fireEvent('shippingCanUsedInEntity', [$this, $ErpEntity]);
return false;
} catch (QUI\Exception $Exception) {
QUI\System\Log::addDebug($Exception->getMessage());
return false;
}
QUI\ERP\Shipping\Debug::addLog(
"{$this->getTitle()} :: is valid [ok]"
);
/**
* no further rules are used after this rule
*
* @return bool
*/
{
if ($this->existsAttribute('no_rule_after')) {
return !!$this->getAttribute('no_rule_after');
}
return false;
}
/**
* Return the validation status of this rule
* can the rule be used?
*/
return false;
}
// check date
$usageUntil = $this->getAttribute('date_until');
$this->getTitle() . " :: usage from is not ok, $usageFrom > $time"
return false;
}
}
if (!empty($usageUntil)) {
$this->getTitle() . " :: usage from is not ok, $usageFrom < $time"
$this->getTitle() . " :: is valid, date from to is valid"
/**
* Return the discount type
*
* @return int
*/
{
return (int)$this->getAttribute('discount_type');
}
/**
* Return the unit terms
*
* @return bool|array
*/
{
$unitTerms = $this->getAttribute('unit_terms');
if (empty($unitTerms)) {
return false;
}
if ($unitTerms) {
return $unitTerms;
}
return false;
}
// region activation / deactivation
/**
* Activate the shipping type
*
* @throws QUI\ExceptionStack|QUI\Exception
*/
{
$this->setAttribute('active', 1);
$this->update();
$this->refresh();
}
/**
* Is the shipping active?
*
* @return bool
*/
{
return !!$this->getAttribute('active');
}
/**
* Deactivate the shipping type
*
* @throws QUI\ExceptionStack|QUI\Exception
*/
{
$this->setAttribute('active', 0);
$this->update();
$this->refresh();
}
//endregion
//region setter
/**
* Set the title
*
* @param array $titles
*/
$titles
);
}
/**
* Set the working title
*
* @param array $titles
*/
'shipping.' . $this->getId() . '.rule.workingTitle',
$titles
);
}
/**
* Creates a locale
*
* @param string $var
* @param array $title
*/
protected function setLocaleVar(string $var, array $title): void
{
$data = [
'datatype' => 'php,js',
];
$languages = QUI::availableLanguages();
foreach ($languages as $language) {
if (!isset($title[$language])) {
continue;
}
$data[$language . '_edit'] = $title[$language];
}
$exists = Translator::getVarData('quiqqer/shipping', $var, 'quiqqer/shipping');
try {
if (empty($exists)) {
Translator::addUserVar('quiqqer/shipping', $var, $data);
} else {
Translator::edit('quiqqer/shipping', $var, 'quiqqer/shipping', $data);
}
} catch (QUI\Exception $Exception) {
QUI\System\Log::addNotice($Exception->getMessage());
}
try {
Translator::publish('quiqqer/shipping');
} catch (QUi\Exception $Exception) {
QUI\System\Log::writeException($Exception);
}
}
//endregion