1089 lines
32 KiB
PHP
1089 lines
32 KiB
PHP
<?php
|
|
|
|
use KupShop\AdminBundle\Util\ActivityLog;
|
|
use KupShop\DeliveryPriceListBundle\DeliveryPriceCalculator;
|
|
use KupShop\I18nBundle\Translations\DeliveryTypeDeliveriesTranslation;
|
|
use KupShop\KupShopBundle\Context\CountryContext;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Context\LanguageContext;
|
|
use KupShop\KupShopBundle\Context\VatContext;
|
|
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
|
use KupShop\KupShopBundle\Util\Contexts;
|
|
use KupShop\KupShopBundle\Util\DateUtil;
|
|
use KupShop\KupShopBundle\Util\Delivery\RestrictionParams;
|
|
use KupShop\KupShopBundle\Util\Delivery\RestrictionUtils;
|
|
use KupShop\KupShopBundle\Util\Price\Price;
|
|
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
|
|
use KupShop\KupShopBundle\Util\Price\PriceUtil;
|
|
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
|
|
use KupShop\OrderingBundle\Event\DeliveryCheckEvent;
|
|
use KupShop\OrderingBundle\Exception\DeliveryException;
|
|
use KupShop\OrderingBundle\Exception\PaymentException;
|
|
use Query\Translation;
|
|
use Symfony\Component\DependencyInjection\Container;
|
|
|
|
#[AllowDynamicProperties]
|
|
class DeliveryBase implements ArrayAccess
|
|
{
|
|
public const TYPE_ADDRESS = 1;
|
|
public const TYPE_IN_PERSON = 2;
|
|
public const TYPE_POINT = 3;
|
|
public const TYPE_VIRTUAL = 4;
|
|
|
|
// Delivery class info
|
|
public static $type = self::TYPE_ADDRESS;
|
|
public static $className;
|
|
private static $cache;
|
|
|
|
/** @var Exception */
|
|
public $exception;
|
|
|
|
// Shop Settings
|
|
public $config = [];
|
|
|
|
public $id;
|
|
|
|
public $name;
|
|
public $description;
|
|
public $cart_description;
|
|
|
|
public $totalWeight;
|
|
public $maxItemWeight;
|
|
|
|
/**
|
|
* @var Decimal
|
|
*/
|
|
protected $price;
|
|
protected $priceCache;
|
|
protected $currency;
|
|
protected $delivery_type_currency;
|
|
public $defaultIcon = '../../common/static/deliveries/generic.svg';
|
|
public $photo;
|
|
public $class;
|
|
public $time_days;
|
|
public $time_hours;
|
|
public $is_open;
|
|
|
|
/**
|
|
* @var bool
|
|
*/
|
|
private $freeDelivery = false;
|
|
|
|
/** @var Price */
|
|
private $totalPrice;
|
|
private $freeShipping;
|
|
|
|
/**
|
|
* @var Price
|
|
*/
|
|
protected $price_dont_countin_from;
|
|
protected $price_dont_countin_from_reg;
|
|
protected $price_dont_countin_from_delivery;
|
|
protected $price_dont_countin_from_reg_delivery;
|
|
protected $deliveryCurrency;
|
|
public $data = [];
|
|
protected $countries;
|
|
|
|
// Šablona, která se zobrazí v košíku u výběru dopravy. Musí umět se vypsat víckrát, může být víc doprav se stejným dopravcem.
|
|
protected $templateDescription;
|
|
// Šablona, která se zobrazá v košíku u výběru dopravy ale pouze jednou pro jednoho dopravce. Pro inicializaci widgetů a pod.
|
|
protected $templateInit;
|
|
|
|
// Šablona v administraci na detailu objednávky
|
|
protected $AdminTemplateSettings;
|
|
// Šablona v nastavení konkrétní dopravy s možností upravit nastavení dopravce
|
|
protected $adminWindowTemplate;
|
|
|
|
protected $heureka_delivery_id;
|
|
protected $zbozi_delivery_id;
|
|
protected $track_and_trace;
|
|
|
|
protected $custom_data;
|
|
public $id_pricelist;
|
|
|
|
/** @var RestrictionUtils */
|
|
protected $restrictionUtils;
|
|
|
|
/** @var RestrictionParams */
|
|
protected $restrictionParams;
|
|
|
|
protected $pricelist;
|
|
|
|
/** @var PurchaseState */
|
|
protected $purchaseState;
|
|
|
|
public static function getSettingsConfiguration(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public static function includeClass($name)
|
|
{
|
|
global $cfg;
|
|
|
|
$name = preg_replace('/[^a-zA-Z-]/', '', $name);
|
|
$class = "deliveries/class.{$name}.php";
|
|
|
|
if (file_exists($cfg['Path']['web_root'].$class) && !isFunctionalTests()) {
|
|
require_once $cfg['Path']['web_root'].$class;
|
|
|
|
return;
|
|
}
|
|
|
|
if (!file_exists($cfg['Path']['shared_class'].$class)) {
|
|
throw new Exception('Dopravní modul '.$name.' neexistuje');
|
|
}
|
|
|
|
require_once $cfg['Path']['shared_class'].$class;
|
|
}
|
|
|
|
/** Factory function for Delivery classes.
|
|
* @param string $name Delivery class name
|
|
*
|
|
* @return Delivery
|
|
*/
|
|
public static function getClass(?string $name)
|
|
{
|
|
if ($deliveryClass = ServiceContainer::getService('kupshop.delivery.'.strtolower($name ?? ''), Container::NULL_ON_INVALID_REFERENCE)) {
|
|
return $deliveryClass;
|
|
}
|
|
|
|
if ($name) {
|
|
Delivery::includeClass($name);
|
|
} else {
|
|
$name = 'Delivery';
|
|
}
|
|
|
|
return new $name();
|
|
}
|
|
|
|
/** List all Delivery implementations.
|
|
*/
|
|
public static function listClasses()
|
|
{
|
|
global $cfg;
|
|
|
|
$classDir = $cfg['Path']['shared_class'].'deliveries/';
|
|
|
|
$shopClassDir = $cfg['Path']['web_root'].'deliveries/';
|
|
|
|
$classesDir = array_merge(glob($classDir.'class.*.php'), glob($shopClassDir.'class.*.php'));
|
|
|
|
foreach ($classesDir as $class) {
|
|
$class = basename($class);
|
|
preg_match('/class.([a-zA-Z]+).php/', $class, $matches);
|
|
$class = $matches[1];
|
|
|
|
Delivery::includeClass($class);
|
|
|
|
if (!$class::isEnabled()) {
|
|
continue;
|
|
}
|
|
|
|
$classes[$class] = $class::$className;
|
|
}
|
|
|
|
return $classes;
|
|
}
|
|
|
|
public function createFromArray($row)
|
|
{
|
|
foreach ($row as $key => $value) {
|
|
if ($key == 'data') {
|
|
$this->custom_data = $value;
|
|
} else {
|
|
$this->$key = $value;
|
|
}
|
|
}
|
|
|
|
if (!empty($this->price_registered) && User::getCurrentUser()) {
|
|
$this->price = $this->price_registered;
|
|
}
|
|
|
|
// Potrebuju menu z dopravy, nechci tu z delivery pricelistu
|
|
$this->deliveryCurrency = $this->currency;
|
|
|
|
if (findModule(Modules::DELIVERY_PRICELIST)) {
|
|
if (isset($this->id_pricelist) && !empty($this->pricelist_currency)) {
|
|
$this->currency = $this->pricelist_currency;
|
|
}
|
|
}
|
|
|
|
$priceUtil = ServiceContainer::getService(PriceUtil::class);
|
|
if ($this->getPriceDontCountinFrom() !== null) {
|
|
$this->price_dont_countin_from = new Price(toDecimal($this->getPriceDontCountinFrom()), $priceUtil->getCurrencyById($this->delivery_type_currency), 0);
|
|
}
|
|
|
|
if (User::getCurrentUser() && $this->price_dont_countin_from_reg) {
|
|
$this->price_dont_countin_from = new Price(toDecimal($this->price_dont_countin_from_reg), $priceUtil->getCurrencyById($this->delivery_type_currency), 0);
|
|
}
|
|
|
|
if ($this->getPriceDontCountinFromDelivery() !== null) {
|
|
$this->price_dont_countin_from_delivery = new Price(toDecimal($this->getPriceDontCountinFromDelivery()), $priceUtil->getCurrencyById($this->deliveryCurrency), 0);
|
|
}
|
|
|
|
if (User::getCurrentUser() && $this->price_dont_countin_from_reg_delivery) {
|
|
$this->price_dont_countin_from_delivery = new Price(toDecimal($this->price_dont_countin_from_reg_delivery), $priceUtil->getCurrencyById($this->deliveryCurrency), 0);
|
|
}
|
|
|
|
if ($this->price_dont_countin_from_reg_delivery !== null) {
|
|
$this->price_dont_countin_from_reg_delivery = new Price(toDecimal($this->price_dont_countin_from_reg_delivery), $priceUtil->getCurrencyById($this->deliveryCurrency), 0);
|
|
}
|
|
|
|
if (!empty($this->custom_data) && is_string($this->custom_data)) {
|
|
$this->custom_data = json_decode($this->custom_data, true);
|
|
}
|
|
}
|
|
|
|
/** Get payment HTML when payment selected.
|
|
*/
|
|
public function getCartDescription()
|
|
{
|
|
if ($this->templateDescription) {
|
|
$smarty = createSmarty(false, true);
|
|
$smarty->assign([
|
|
'object' => $this,
|
|
'deliveryPrefix' => "delivery_data[{$this->id}]",
|
|
]);
|
|
|
|
return $smarty->fetch($this->templateDescription);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getInitTemplate($smarty)
|
|
{
|
|
if ($this->templateInit) {
|
|
$params = [
|
|
'object' => $this,
|
|
];
|
|
|
|
$_smarty_tpl_vars = $smarty->tpl_vars;
|
|
|
|
$ret = $smarty->_subTemplateRender($this->templateInit, $smarty->cache_id, $smarty->compile_id, 0, null, $params, 0, false);
|
|
|
|
$smarty->tpl_vars = $_smarty_tpl_vars;
|
|
|
|
return $ret;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getName()
|
|
{
|
|
return static::$className;
|
|
}
|
|
|
|
public function getClassName()
|
|
{
|
|
return static::$className;
|
|
}
|
|
|
|
public function isInPerson()
|
|
{
|
|
return static::$type === self::TYPE_IN_PERSON;
|
|
}
|
|
|
|
public function isVirtual()
|
|
{
|
|
return static::$type === self::TYPE_VIRTUAL;
|
|
}
|
|
|
|
public function getType()
|
|
{
|
|
return static::$type;
|
|
}
|
|
|
|
public function __construct()
|
|
{
|
|
$this->loadConfig();
|
|
}
|
|
|
|
public function loadConfig()
|
|
{
|
|
if (!static::$className) {
|
|
return;
|
|
}
|
|
|
|
global $cfg;
|
|
|
|
$this->config = array_merge($this->config, getVal(get_class($this), $cfg['Modules']['deliveries'] ?? [], []));
|
|
}
|
|
|
|
public function storeDeliveryInfo($data)
|
|
{
|
|
$this->data = [];
|
|
|
|
if (findModule(Modules::DELIVERY_PRICELIST)) {
|
|
$this->data += $this->getPackageInfo();
|
|
}
|
|
|
|
return $this->data;
|
|
}
|
|
|
|
public function loadDeliveryInfo($data)
|
|
{
|
|
foreach ($data as $key => $value) {
|
|
$this->$key = $value;
|
|
}
|
|
|
|
$this->data = $data;
|
|
}
|
|
|
|
public function printDeliveryInfo()
|
|
{
|
|
return '';
|
|
}
|
|
|
|
public function getPointId()
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function setPointId($id): self
|
|
{
|
|
if ($this->getType() === Delivery::TYPE_POINT) {
|
|
throw new RuntimeException('Not implemented!');
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function printDeliverySettings($order)
|
|
{
|
|
if (!$this->AdminTemplateSettings) {
|
|
return '';
|
|
}
|
|
|
|
$info = $this->getInfo();
|
|
|
|
$smarty = createSmarty(false, true);
|
|
|
|
$smarty->assign([
|
|
'info' => $info,
|
|
'delivery_data' => $this['data'],
|
|
'object' => $this,
|
|
'order' => $order,
|
|
]);
|
|
|
|
return $smarty->fetch($this->AdminTemplateSettings);
|
|
}
|
|
|
|
/**
|
|
* @param $order Order
|
|
*/
|
|
public function applyToOrder(&$data, $order)
|
|
{
|
|
if (findModule(Modules::PRODUCTS, Modules::SUB_WEIGHT)) {
|
|
$this->totalWeight = $order->getTotalWeight();
|
|
$this->maxItemWeight = $order->getMaxItemWeight();
|
|
}
|
|
}
|
|
|
|
public function applyToCart(CartBase $cart)
|
|
{
|
|
if (findModule(Modules::PRODUCTS, Modules::SUB_WEIGHT)) {
|
|
$this->totalWeight = $cart->getTotalWeight();
|
|
$this->maxItemWeight = $cart->getMaxItemWeight();
|
|
}
|
|
}
|
|
|
|
public function getPackageInfo()
|
|
{
|
|
if (isset($this->id_pricelist) && findModule(Modules::DELIVERY_PRICELIST)) {
|
|
$priceList = $this->getPriceList();
|
|
$priceList->setDivide(false);
|
|
$priceList->setPricelistId($this->id_pricelist);
|
|
// set weight of cart
|
|
$priceList->setTotalWeight($this->totalWeight);
|
|
$priceList->setMaxItemWeight($this->maxItemWeight);
|
|
|
|
return $priceList->getCountOfPackages();
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
public function getPriceFromPricelist($pricelistId, $totalCartWeight, $maxItemWeight)
|
|
{
|
|
$priceList = $this->getPriceList();
|
|
$priceList->setDivide(false);
|
|
$priceList->setPricelistId($pricelistId);
|
|
// set weight of cart
|
|
$priceList->setTotalWeight($totalCartWeight);
|
|
$priceList->setMaxItemWeight($maxItemWeight);
|
|
|
|
// set country
|
|
$countryContext = Contexts::get(CountryContext::class);
|
|
$priceList->setCountry($countryContext->getActive()->getId());
|
|
|
|
if ($priceList->isDeliverySupported()) {
|
|
// check weightgroup maxweight
|
|
if ($checkWg = $priceList->checkWeightGroup()) {
|
|
// if weight of cart > maxweight of weightgroup && onMaxWeight is set to divide
|
|
if ($checkWg === 'divide') {
|
|
$priceList->setDivide(true);
|
|
}
|
|
} else {
|
|
// Vahovy limit pro tento typ dopravy byl prekrocen
|
|
$this->exception = new DeliveryException(translate_shop('weight_limit', 'delivery'), translate_shop('weight_limit_short', 'delivery'), DeliveryException::ERROR_DELIVERY_DISABLED);
|
|
}
|
|
} else {
|
|
$this->exception = new DeliveryException(translate_shop('unavailable', 'delivery'), translate_shop('unavailable_short', 'delivery'), DeliveryException::ERROR_DELIVERY_DISABLED);
|
|
}
|
|
|
|
// returns price without vat
|
|
return $priceList->getPriceWithoutVat(getAdminVat()['value'])->round(4);
|
|
}
|
|
|
|
public function processAdminWindowData($data)
|
|
{
|
|
return $data;
|
|
}
|
|
|
|
public function getDispatchDate()
|
|
{
|
|
$today = true;
|
|
if (DateUtil::compareTimes($this->time_hours)) {
|
|
$today = false;
|
|
}
|
|
|
|
return $this->calcWorkingDays($today ? 0 : 1, new DateTime());
|
|
}
|
|
|
|
// Delivery date
|
|
public function getDeliveryDate()
|
|
{
|
|
$days = $this->getDeliveryDays()['min'] ?? null;
|
|
|
|
if ($days === null) {
|
|
return false;
|
|
}
|
|
|
|
$fromDay = new DateTime();
|
|
|
|
return $this->calcWorkingDays($days, $fromDay);
|
|
}
|
|
|
|
// Returns delivery days - min & max
|
|
public function getDeliveryDays(?DateTime $fromDay = null)
|
|
{
|
|
if (findModule(Modules::DELIVERY_PRICELIST)) {
|
|
$days = $this->getDeliveryTimeFromPricelist();
|
|
}
|
|
|
|
if (is_null($this->time_days) && !isset($days)) {
|
|
return false;
|
|
}
|
|
|
|
if (!isset($days)) {
|
|
$days = ['min' => intval($this->time_days)];
|
|
}
|
|
|
|
$fromDay ??= new DateTime();
|
|
|
|
if ((!empty($this->time_hours) && DateUtil::compareTimes($this->time_hours, $fromDay->format('H:i:s')))
|
|
|| !$this->isWorkday($fromDay)
|
|
) {
|
|
$days['min']++;
|
|
if (isset($days['max']) && $days['max'] != null) {
|
|
$days['max']++;
|
|
}
|
|
}
|
|
|
|
return $days;
|
|
}
|
|
|
|
/**
|
|
* @param $date DateTime
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isWorkday($date, ?array $countries = null)
|
|
{
|
|
return DateUtil::isWorkday($date, $countries);
|
|
}
|
|
|
|
/**
|
|
* @param $fromDay DateTime
|
|
*
|
|
* @return DateTime
|
|
*/
|
|
public function calcWorkingDays($days, $fromDay = null)
|
|
{
|
|
$dbcfg = Settings::getDefault();
|
|
switch ($dbcfg->delivery_config['holidaysByCountry'] ?? null) {
|
|
// zeme doruceni
|
|
case 'delivery':
|
|
$countries = [Contexts::get(CountryContext::class)->getActiveId()];
|
|
break;
|
|
// zeme doruceni + zeme expedice
|
|
case 'dispatchDelivery':
|
|
$countries = array_unique(['CZ', Contexts::get(CountryContext::class)->getActiveId()]);
|
|
break;
|
|
// zeme expedice
|
|
default:
|
|
$countries = ['CZ'];
|
|
}
|
|
|
|
return DateUtil::calcWorkingDays($days, $fromDay, [$this, 'isWorkday'], $countries);
|
|
}
|
|
|
|
public function getSupportedCountries()
|
|
{
|
|
if (isset($this->countries)) {
|
|
return $this->countries;
|
|
}
|
|
|
|
$countries = $this->getCustomData()['restrictions']['countries'] ?? null;
|
|
$supported = array_keys(Contexts::get(CountryContext::class)->getSupported());
|
|
if (!$countries) {
|
|
$this->countries = $supported;
|
|
} else {
|
|
$invert = $this->getCustomData()['restrictions']['countries_invert'] ?? false;
|
|
if ($invert) {
|
|
$this->countries = array_diff($supported, $countries);
|
|
} else {
|
|
$this->countries = array_intersect($supported, $countries);
|
|
}
|
|
}
|
|
|
|
return $this->countries;
|
|
}
|
|
|
|
protected function getRestrictionUtils()
|
|
{
|
|
if (!isset($this->restrictionUtils)) {
|
|
$this->restrictionUtils = ServiceContainer::getService(RestrictionUtils::class);
|
|
}
|
|
|
|
return $this->restrictionUtils->setType('delivery');
|
|
}
|
|
|
|
/**
|
|
* @param Price $totalPrice
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function accept($totalPrice, $freeDelivery, ?PurchaseState $purchaseState = null)
|
|
{
|
|
$this->totalPrice = $totalPrice;
|
|
$this->freeShipping = $freeDelivery;
|
|
|
|
if ($purchaseState) {
|
|
$this->purchaseState = $purchaseState;
|
|
}
|
|
|
|
if ($freeDelivery
|
|
|| ($totalPrice && $this->getPriceDontCountinFromDelivery() && !PriceCalculator::firstLower($totalPrice, $this->getPriceDontCountinFromDelivery()))
|
|
|| ($totalPrice && $this->getPriceDontCountinFrom() && !PriceCalculator::firstLower($totalPrice, $this->getPriceDontCountinFrom()))) {
|
|
$this->freeDelivery = true;
|
|
}
|
|
|
|
return $this->getRestrictionUtils()->checkHardRequirements($this);
|
|
}
|
|
|
|
public function isCountrySupported($countryCode)
|
|
{
|
|
if (!$countryCode) {
|
|
return false;
|
|
}
|
|
|
|
return array_search(strtoupper($countryCode), $this->getSupportedCountries()) !== false;
|
|
}
|
|
|
|
/**
|
|
* @return Delivery[]
|
|
*/
|
|
public static function getAll($figure = true)
|
|
{
|
|
$language = Contexts::get(LanguageContext::class)->getActiveId();
|
|
|
|
if (!isset(self::$cache[$language])) {
|
|
$qb = sqlQueryBuilder()->select('dtd.*, MIN(dt.price_dont_countin_from) AS price_dont_countin_from,
|
|
MIN(dt.price_dont_countin_from_reg) AS price_dont_countin_from_reg,
|
|
dt.currency AS delivery_type_currency, dtd.price_dont_countin_from AS price_dont_countin_from_delivery,
|
|
dtd.price_dont_countin_from_reg as price_dont_countin_from_reg_delivery, MAX(dt.figure) as figure, dtd.date_updated as date_updated')
|
|
->from('delivery_type_delivery', 'dtd')
|
|
->leftJoin('dtd', 'delivery_type', 'dt', 'dt.id_delivery = dtd.id')
|
|
->andWhere(Translation::coalesceTranslatedFields(DeliveryTypeDeliveriesTranslation::class))
|
|
->groupBy('dtd.id')
|
|
->orderBy('dtd.position');
|
|
|
|
if (findModule(Modules::DELIVERY_PRICELIST)) {
|
|
$qb->addSelect('dp.id_currency as pricelist_currency')
|
|
->leftJoin('dtd', 'delivery_pricelists', 'dp', 'dtd.id_pricelist = dp.id');
|
|
}
|
|
|
|
$deliveries = [];
|
|
|
|
foreach ($qb->execute() as $row) {
|
|
$delivery = Delivery::getClass(getVal('class', $row));
|
|
|
|
$delivery->createFromArray($row);
|
|
|
|
$deliveries[$delivery->id] = $delivery;
|
|
}
|
|
|
|
if (findModule(Modules::DELIVERY_PRICELIST)) {
|
|
$deliveryPriceCalculator = ServiceContainer::getService(DeliveryPriceCalculator::class);
|
|
$deliveryPriceCalculator->multiFetchDeliveryTime($deliveries);
|
|
}
|
|
|
|
self::$cache[$language] = $deliveries;
|
|
}
|
|
|
|
$deliveries = self::$cache[$language];
|
|
|
|
if ($figure) {
|
|
$deliveries = array_filter($deliveries, function ($d) {return $d['figure'] == 'Y'; });
|
|
}
|
|
|
|
return $deliveries;
|
|
}
|
|
|
|
public static function clearCache()
|
|
{
|
|
self::$cache = null;
|
|
}
|
|
|
|
public function getHeurekaDeliveryID()
|
|
{
|
|
if (!empty($this->heureka_delivery_id)) {
|
|
return $this->heureka_delivery_id;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getZboziDeliveryID()
|
|
{
|
|
if (!empty($this->zbozi_delivery_id)) {
|
|
return $this->zbozi_delivery_id;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getAdminWindowTemplate()
|
|
{
|
|
return $this->adminWindowTemplate;
|
|
}
|
|
|
|
public function requiresDeliveryAddress()
|
|
{
|
|
return static::$type === self::TYPE_ADDRESS;
|
|
}
|
|
|
|
protected function getDeliveryTimeFromPricelist()
|
|
{
|
|
if (findModule(Modules::DELIVERY_PRICELIST)) {
|
|
return $this->pricelistDeliveryTime ?? null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getTrackAndTraceLink($package_id, ?Order $order = null)
|
|
{
|
|
if (empty($this->track_and_trace)) {
|
|
return null;
|
|
}
|
|
|
|
if (is_array($this->track_and_trace) && $order) {
|
|
$tat = $this->track_and_trace[$order->delivery_country] ?? reset($this->track_and_trace);
|
|
} else {
|
|
$tat = is_array($this->track_and_trace) ? reset($this->track_and_trace) : $this->track_and_trace;
|
|
}
|
|
|
|
if (!empty($package_id)) {
|
|
$tat = str_replace('[PACKAGE_ID]', $package_id, $tat);
|
|
}
|
|
|
|
return $tat;
|
|
}
|
|
|
|
public function setPrice(Price $price)
|
|
{
|
|
$this->priceCache = $price;
|
|
}
|
|
|
|
private function preparePrice()
|
|
{
|
|
if (isset($this->id_pricelist) && findModule(Modules::DELIVERY_PRICELIST)) {
|
|
return $this->getPriceFromPricelist($this->id_pricelist, $this->totalWeight, $this->maxItemWeight);
|
|
}
|
|
|
|
return toDecimal($this->price);
|
|
}
|
|
|
|
public function getPrice(?float $vat = null): Price
|
|
{
|
|
$vat = $this->getDeliveryVat($vat);
|
|
if (!empty($this->priceCache)) {
|
|
if ($this->priceCache->getVat()->equals(toDecimal($vat))) {
|
|
return $this->priceCache;
|
|
}
|
|
}
|
|
|
|
$price = $this->preparePrice();
|
|
|
|
// free delivery
|
|
if ($this->freeDelivery) {
|
|
$price = toDecimal(0);
|
|
}
|
|
|
|
return $this->priceCache = $this->getDeliveryPrice($price, $vat);
|
|
}
|
|
|
|
public function getPriceSelected(DeliveryType $deliveryType)
|
|
{
|
|
$totalPrice = $this->totalPrice;
|
|
$price = $this->preparePrice();
|
|
|
|
if ($this->freeShipping
|
|
|| ($totalPrice && $this->getPriceDontCountinFromDelivery() && !PriceCalculator::firstLower($totalPrice, $this->getPriceDontCountinFromDelivery()))
|
|
|| ($totalPrice && $deliveryType->getPriceDontCountinFrom() && !PriceCalculator::firstLower($totalPrice, $deliveryType->getPriceDontCountinFrom()))) {
|
|
$price = toDecimal(0);
|
|
}
|
|
|
|
return $this->getDeliveryPrice($price, $deliveryType->vat);
|
|
}
|
|
|
|
protected function getDeliveryPrice($price, $vat = null)
|
|
{
|
|
$dbcfg = Settings::getDefault();
|
|
$vat = $this->getDeliveryVat($vat);
|
|
$vatContext = Contexts::get(VatContext::class);
|
|
$noVat = ($vat == 0) && ($vatContext->getActive() == $vatContext::NO_VAT);
|
|
|
|
if (($dbcfg->delivery_config['vats_from_top'] ?? 'N') == 'Y') {
|
|
$price = $price->addVat(getAdminVat()['value'])->removeVat($vat)->value();
|
|
if ($noVat) {
|
|
$defaultVat = $vatContext->getDefault()['vat'] ?? 0;
|
|
$price = $price->removeVat($defaultVat)->value();
|
|
}
|
|
}
|
|
|
|
$priceUtil = ServiceContainer::getService(PriceUtil::class);
|
|
$currency = $priceUtil->getCurrencyById($this->currency);
|
|
$price = new Price($price, $currency, $vat);
|
|
|
|
$activeCurrency = Contexts::get(CurrencyContext::class)->getActive();
|
|
$price = PriceCalculator::convert($price, $activeCurrency);
|
|
$priceWithoutVat = $price->getPriceWithoutVat(); // kvuli spravnemu zaokrouhleni
|
|
$price = new Price($priceWithoutVat, $activeCurrency, $price->getVat());
|
|
|
|
return $price;
|
|
}
|
|
|
|
protected function getDeliveryVat($vat = null)
|
|
{
|
|
$dbcfg = Settings::getDefault();
|
|
|
|
return (($dbcfg->delivery_config['from_products'] ?? 'N') == 'Y' && $this->purchaseState)
|
|
? $this->purchaseState->getDeliveryVat() ?? $vat ?? getVat()
|
|
: $vat ?? getVat();
|
|
}
|
|
|
|
public function getCustomData()
|
|
{
|
|
return $this->custom_data;
|
|
}
|
|
|
|
/**
|
|
* @return Price
|
|
*/
|
|
public function getPriceDontCountinFromDelivery($reg = null)
|
|
{
|
|
if ($reg === true) {
|
|
return $this->price_dont_countin_from_reg_delivery;
|
|
}
|
|
|
|
return $this->price_dont_countin_from_delivery;
|
|
}
|
|
|
|
/**
|
|
* @return Price
|
|
*/
|
|
public function getPriceDontCountinFrom()
|
|
{
|
|
return $this->price_dont_countin_from;
|
|
}
|
|
|
|
/**
|
|
* Implements ArrayAccess interface.
|
|
*/
|
|
public function offsetSet($offset, $value): void
|
|
{
|
|
$this->{$offset} = $value;
|
|
}
|
|
|
|
public function offsetExists($offset): bool
|
|
{
|
|
return isset($this->{$offset});
|
|
}
|
|
|
|
public function offsetUnset($offset): void
|
|
{
|
|
unset($this->{$offset});
|
|
}
|
|
|
|
public function offsetGet($offset): mixed
|
|
{
|
|
if ($offset == 'price') {
|
|
return ServiceContainer::getService(\KupShop\KupShopBundle\Wrapper\PriceWrapper::class)->setObject($this->getPrice());
|
|
}
|
|
|
|
if ($offset == 'photo') {
|
|
return $this->getPhoto();
|
|
}
|
|
|
|
return isset($this->{$offset}) ? $this->{$offset} : null;
|
|
}
|
|
|
|
public function setException(Exception $exception)
|
|
{
|
|
$this->exception = $exception;
|
|
}
|
|
|
|
public function check(Cart $cart)
|
|
{
|
|
$deliveryAddress = array_merge($cart->invoice, array_filter($cart->delivery));
|
|
if ($cart->hasData('user') && $cart->hasData('delivery', false) && !$this->isCountrySupported($deliveryAddress['country'] ?? '')) {
|
|
return 18;
|
|
}
|
|
|
|
if ($this->exception && $cart->hasData('delivery', false)) {
|
|
throw $this->exception;
|
|
}
|
|
|
|
try {
|
|
$this->checkRestrictions($cart->getPurchaseState());
|
|
} catch (DeliveryException $exception) {
|
|
$this->exception = $exception;
|
|
throw $this->exception;
|
|
}
|
|
|
|
try {
|
|
ServiceContainer::getService('event_dispatcher')->dispatch(
|
|
new DeliveryCheckEvent($this, $cart)
|
|
);
|
|
} catch (DeliveryException $e) {
|
|
$this->exception = $e;
|
|
|
|
throw $e;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function checkRestrictions(PurchaseState $purchaseState)
|
|
{
|
|
$customData = $this->getCustomData();
|
|
|
|
$this->checkSoftRestrictions($purchaseState);
|
|
|
|
if ($productsFilter = $customData['restrictions']['productsFilter'] ?? null) {
|
|
$this->getRestrictionUtils()->checkProductsFilter($productsFilter, $purchaseState->getProductList());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function checkSoftRestrictions(PurchaseState $purchaseState): void
|
|
{
|
|
$customData = $this->getCustomData();
|
|
|
|
if (($customData['restrictions']['enabled'] ?? false) !== 'Y') {
|
|
return;
|
|
}
|
|
|
|
if (!isset($this->restrictionParams)) {
|
|
$this->restrictionParams = $this->getRestrictionUtils()
|
|
->createParamsFromConfig($this->config ?? [], $customData['restrictions'] ?? []);
|
|
}
|
|
|
|
$this->getRestrictionUtils()->checkSoftRequirements(
|
|
$this->getRestrictionParams($purchaseState),
|
|
$this->restrictionParams
|
|
);
|
|
}
|
|
|
|
protected function getRestrictionParams(PurchaseState $purchaseState): RestrictionParams
|
|
{
|
|
return $purchaseState->getDeliveryRestrictionParams();
|
|
}
|
|
|
|
public function checkSelected(Cart $cart, ?Payment $payment = null)
|
|
{
|
|
// check selected combination of delivery and payment
|
|
|
|
$error = $this->check($cart);
|
|
if ($error) {
|
|
return $error;
|
|
}
|
|
|
|
if ($payment) {
|
|
try {
|
|
$error = $payment->check($cart);
|
|
} catch (PaymentException $exception) {
|
|
$payment->exception = $exception;
|
|
throw $payment->exception;
|
|
}
|
|
}
|
|
|
|
if (($cart->max_step != 0) && ($payment instanceof Dobirka)) {
|
|
if (isset($this->restrictionParams) && ($limit = $this->restrictionParams->getMaxCOD())) {
|
|
// zapnute Přepravní omezení a nastavena Maximální hodnota dobírky
|
|
$rParams = new RestrictionParams();
|
|
$rParams->setMaxCOD($cart->getPurchaseState()->getTotalPrice()->getPriceWithVat()->asFloat());
|
|
try {
|
|
$this->getRestrictionUtils()->checkSoftRequirements($rParams, $this->restrictionParams);
|
|
} catch (DeliveryException $e) {
|
|
$printPrice = printPrice($limit);
|
|
throw new PaymentException(
|
|
sprintf($e->getMessage(), $printPrice),
|
|
sprintf($e->getShortMessage(), $printPrice),
|
|
DeliveryException::ERROR_DELIVERY_DISABLED
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $error;
|
|
}
|
|
|
|
public function zipExists($invoice)
|
|
{
|
|
if (!empty($invoice['zip']) && in_array($invoice['country'], ['CZ', 'SK'])) {
|
|
$zipInvoice = sqlQueryBuilder()->select('zip')->from('kupshop_shared.delivery_zip_codes')
|
|
->where(\Query\Operator::equals(['zip' => $invoice['zip']]))
|
|
->andWhere(\Query\Operator::equals(['country' => $invoice['country']]))
|
|
->execute()->fetch();
|
|
|
|
if (!$zipInvoice) {
|
|
throw new \KupShop\OrderingBundle\Exception\DeliveryException(translate('false_zip', 'order_error'));
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function isEnabled()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function importFromBalikobot($url = null, $where = null)
|
|
{
|
|
increaseMemoryLimit(1024);
|
|
$url = ($url ?: static::$jsonDeliveryBalikobot);
|
|
|
|
if (empty($url)) {
|
|
return false;
|
|
}
|
|
|
|
$json = file_get_contents($url);
|
|
$data = json_decode_strict($json, true);
|
|
|
|
if (!empty($data['branches'])) {
|
|
sqlGetConnection()->transactional(function () use ($data, $where) {
|
|
sqlQueryBuilder()->delete(static::$tableName)->andWhere($where)->execute();
|
|
|
|
$tableColumns = static::$tableColumns ?? [];
|
|
if (empty($tableColumns)) {
|
|
$tableColumns = [
|
|
'type' => 'type',
|
|
'id' => 'id',
|
|
'zip' => 'zip',
|
|
'name' => 'name',
|
|
'street' => 'street',
|
|
'city' => 'city',
|
|
'country' => 'country',
|
|
];
|
|
}
|
|
$excludeColumns = static::$excludeColumns ?? [];
|
|
$faultyBranches = [];
|
|
foreach ($data['branches'] as $id => $branch) {
|
|
$insert = [];
|
|
$custom_data = [];
|
|
foreach ($branch as $title => $value) {
|
|
if (array_key_exists($title, $tableColumns)) {
|
|
if ($title == 'zip') {
|
|
$value = str_replace(' ', '', $value);
|
|
}
|
|
$insert[$tableColumns[$title]] = $value;
|
|
} elseif (!in_array($title, $excludeColumns)) {
|
|
$custom_data[$title] = $value;
|
|
}
|
|
}
|
|
|
|
$insert = $this->importCustomData($custom_data, $insert);
|
|
try {
|
|
sqlQueryBuilder()->insert(static::$tableName)->directValues($insert)->execute();
|
|
} catch (\Exception $exception) {
|
|
$faultyBranches[] = $insert;
|
|
}
|
|
}
|
|
if (!empty($faultyBranches)) {
|
|
addActivityLog(ActivityLog::SEVERITY_NOTICE, ActivityLog::TYPE_SYNC, 'Duplicitní výdejní místa dopravy', $faultyBranches);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function importCustomData($custom_data, $insert)
|
|
{
|
|
if ($custom_data) {
|
|
$insert['data'] = json_encode($custom_data);
|
|
}
|
|
|
|
return $insert;
|
|
}
|
|
|
|
public function getPriceList(): DeliveryPriceCalculator
|
|
{
|
|
if (!$this->pricelist) {
|
|
$this->pricelist = ServiceContainer::getService(DeliveryPriceCalculator::class);
|
|
}
|
|
|
|
return $this->pricelist;
|
|
}
|
|
|
|
public function getPhoto()
|
|
{
|
|
if (empty($this->id)) {
|
|
return null;
|
|
}
|
|
|
|
$icon = $this->getPhotoPath();
|
|
|
|
return getImage($this->id, basename($icon), dirname($icon), 9, $this->name, strtotime($this->date_updated));
|
|
}
|
|
|
|
public function getPhotoPath()
|
|
{
|
|
if ($this->photo) {
|
|
return '../delivery/'.$this->photo;
|
|
}
|
|
|
|
return $this->defaultIcon;
|
|
}
|
|
|
|
public function __toString()
|
|
{
|
|
return static::class;
|
|
}
|
|
}
|
|
|
|
if (empty($subclass)) {
|
|
class Delivery extends DeliveryBase
|
|
{
|
|
}
|
|
}
|