Files
kupshop/class/class.Order.php
2025-08-02 16:30:27 +02:00

2728 lines
100 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use KupShop\AdminBundle\Util\ProductUtils;
use KupShop\BonusProgramBundle\Utils\BonusComputer;
use KupShop\CatalogBundle\ProductList\ProductCollection;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Context\UserContext;
use KupShop\KupShopBundle\Email\OrderStornoEmail;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use KupShop\KupShopBundle\Util\Mail\EmailLocator;
use KupShop\KupShopBundle\Util\Mail\SMSSender;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
use KupShop\KupShopBundle\Util\Price\ProductPrice;
use KupShop\KupShopBundle\Util\Price\TotalPrice;
use KupShop\KupShopBundle\Util\System\AsyncQueue;
use KupShop\KupShopBundle\Wrapper\PriceWrapper;
use KupShop\OrderingBundle\Entity\Order\OrderItem;
use KupShop\OrderingBundle\Entity\Purchase\ProductPurchaseItem;
use KupShop\OrderingBundle\Entity\Purchase\PurchaseItemInterface;
use KupShop\OrderingBundle\Entity\Purchase\PurchaseState;
use KupShop\OrderingBundle\Event\OrderEvent;
use KupShop\OrderingBundle\Event\OrderItemEvent;
use KupShop\OrderingBundle\Event\OrderItemInfoEvent;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use KupShop\OrderingBundle\Util\Order\OrderItemUtil;
use KupShop\OrderingBundle\Util\Purchase\PurchaseUtil;
use KupShop\ReservationBundle\Email\OrderReservationStornoEmail;
use KupShop\SellerBundle\Utils\SellerUtil;
use Query\Operator;
#[AllowDynamicProperties]
class OrderBase implements ArrayAccess
{
use DatabaseCommunication;
public const STATUS_UNPAID = 0;
public const STATUS_PAID = 1;
public const STATUS_UNDERPAID = 2;
public const STATUS_OVERPAID = 3;
/**
* Order ID.
*
* @var int
*/
public $id = -1;
public string $source = OrderInfo::ORDER_SOURCE_SHOP;
public $order_no;
public $invoice_no;
public $id_language;
public $id_user;
public $id_delivery;
public $currency;
public $currency_rate = 1;
/**
* Order dates.
*
* @var DateTime
*/
public $date_created;
public $date_accept;
public $date_handle;
public $date_updated;
public $date_delivered;
/**
* Statuses.
*
* @var int
*/
public $status = -1;
public $status_previous;
public $status_payed;
public $status_dispatch;
public $status_storno;
/**
* Price.
*
* @var Decimal
*/
public $total_price;
/**
* Price without vat.
*
* @var Decimal
*/
public $total_price_without_vat;
/**
* Invoice data.
*
* @var string
*/
public $invoice_name;
public $invoice_surname;
public $invoice_firm;
public $invoice_ico;
public $invoice_dic;
public $invoice_street;
public $invoice_city;
public $invoice_zip;
public $invoice_country;
public $invoice_phone;
public $invoice_email;
public $invoice_custom_address;
public $invoice_state;
public $invoice_copy_email;
/**
* Invoice data.
*
* @var string
*/
public $delivery_name;
public $delivery_surname;
public $delivery_firm;
public $delivery_street;
public $delivery_city;
public $delivery_zip;
public $delivery_country;
public $delivery_type;
public $delivery_complete;
public $delivery_custom_address;
public $delivery_state;
public $delivery_phone;
public $delivery_email;
public $package_id;
/**
* Notes.
*
* @var string
*/
public $note_user;
public $user_order_no;
public $note_admin;
public $items_table = 'order_items';
public $editMode = false;
public $items = [];
public $vats = [];
/** @var ProductCollection */
public $productList;
public $admin;
private $placeholders = [];
private $suspendLogging = false;
protected $currencyContext;
protected $languageContext;
/** @var Decimal[] */
public $total_price_array;
/** @var PurchaseUtil */
private $purchaseUtil;
/** @var OrderItemUtil */
private $orderItemUtil;
private $purchaseState;
public $url;
public $flags;
public ?array $history = null;
/**
* @var TotalPrice
*/
private $totalPrice;
protected $total_weight;
protected ?int $store = null;
public function __construct($id = -1)
{
$this->id = $id;
$this->currencyContext = $currencyContext = ServiceContainer::getService(CurrencyContext::class);
$this->languageContext = ServiceContainer::getService(LanguageContext::class);
$this->purchaseUtil = ServiceContainer::getService(PurchaseUtil::class);
$this->orderItemUtil = ServiceContainer::getService(OrderItemUtil::class);
// prepare productList
$this->productList = new ProductCollection();
$this->productList->setEntityType(FilterParams::ENTITY_VARIATION);
}
public function createFromDB($id)
{
$this->id = $id;
return $this->fetchFields('*');
}
public static function get(int $id): Order
{
$orderList = ServiceContainer::getService(\KupShop\OrderingBundle\OrderList\OrderList::class);
$orders = $orderList->andSpec(Operator::equals(['o.id' => $id]))
->fetchItems()
->getOrders();
if (!($order = $orders->current())) {
throw new InvalidArgumentException(sprintf('Order with ID "%s" was not found!', $id));
}
return $order;
}
public static function createFromDbOrderNo($orderNo)
{
$order = new static();
$orderId = sqlQuery('SELECT id FROM orders WHERE order_no = ? LIMIT 1', [$orderNo])->fetch()['id'] ?? null;
if (!$orderId) {
throw new InvalidArgumentException("Order [order_no={$orderNo}] does not exist.");
}
$order->createFromDB($orderId);
return $order;
}
public function createFromArray($data)
{
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
$dates = ['date_created', 'date_accept', 'date_handle', 'date_updated', 'date_delivered', 'date_due'];
foreach (array_intersect($dates, array_keys($data)) as $date) {
if ($this->$date) {
$this->$date = new DateTimeImmutable($this->$date);
}
}
if ($this->note_admin === null) {
$this->note_admin = '';
}
$this->total_price = toDecimal($this->total_price);
$this->total_price_without_vat = toDecimal($this->total_price_without_vat);
$this->status = intval($this->status);
return true;
}
public function fetchFields($fields)
{
$SQL = sqlQueryBuilder()->select($fields)
->from('orders')
->where(Operator::equals(['id' => $this->id]))
->sendToMaster()
->execute();
if ($data = $SQL->fetchAssociative()) {
return $this->createFromArray($data);
} else {
return false;
}
}
public function fetchNotes()
{
if ($this->note_user !== null) {
return true;
}
$fields = 'note_user, note_admin';
return $this->fetchFields($fields);
}
public function fetchInvoice()
{
if ($this->invoice_name !== null) {
return true;
}
$fields = 'invoice_name, invoice_surname, invoice_firm, invoice_ico, invoice_dic, invoice_street, invoice_city, invoice_zip, invoice_country, invoice_phone, invoice_email,
delivery_name, delivery_surname, delivery_firm, delivery_street, delivery_city, delivery_zip, delivery_country, delivery_type, delivery_complete, invoice_copy_email';
return $this->fetchFields($fields);
}
public function fetchDates()
{
if ($this->date_created !== null) {
return true;
}
$fields = 'date_created, date_accept, date_handle, date_updated, date_delivered, date_due';
return $this->fetchFields($fields);
}
public function getItemsOrdering()
{
return 'oi.id';
}
public function fetchItems()
{
if (!empty($this->items)) {
return $this->items;
}
$query['fields'] = 'oi.id, oi.id_product, oi.id_variation, oi.pieces, oi.pieces_reserved, oi.piece_price,
oi.total_price, oi.descr, oi.tax as vat, COALESCE(pv.ean, p.ean) ean, pv.title variation_title, oi.note, p.guarantee,
COALESCE(pv.in_store, p.in_store) in_store, p.campaign, pr.name as producer, pr.id as id_producer, oi.date, oi.discount, s.name as section_name, s.id as section_id';
$query['from'] = $this->items_table.' oi
LEFT JOIN products p ON p.id=oi.id_product
LEFT JOIN producers pr ON p.producer=pr.id
LEFT JOIN products_variations pv ON pv.id=oi.id_variation
LEFT JOIN sections s ON s.id = (
SELECT sec.id
FROM sections sec
LEFT JOIN products_in_sections ps ON sec.id = ps.id_section
WHERE ps.id_product = p.id
AND sec.priority = (SELECT MAX(s2.priority)
FROM sections s2
LEFT JOIN products_in_sections ps2 ON s2.id = ps2.id_section
WHERE ps2.id_product = p.id
)
LIMIT 1
)';
$query['order'] = $this->getItemsOrdering();
if ($this->editMode) {
$query['order'] = 'oi.id_product IS NULL, '.$query['order'];
}
if (findModule('products_variations', 'variationCode')) {
$query['fields'] .= ', COALESCE(pv.code, p.code) code';
} else {
$query['fields'] .= ', p.code code';
}
// Code of suppliers
if (findModule('stock_in')) {
$query['fields'] .= ', (SELECT group_concat(code SEPARATOR " ") FROM '.getTableName('products_of_suppliers').' pos WHERE pos.id_product = oi.id_product AND (pos.id_variation = oi.id_variation OR (pos.id_variation IS NULL AND oi.id_variation IS NULL))) as suppliers_codes';
}
if (findModule('products', 'note')) {
$query['fields'] .= ', COALESCE(pv.note, p.note) note_';
}
if (findModule(\Modules::PRODUCTS, \Modules::SUB_WEIGHT)) {
$query['fields'] .= ', COALESCE(pv.weight, p.weight) weight';
}
if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
$query['fields'] .= ', oi.price_buy';
}
$SQLItems = sqlQuery("SELECT {$query['fields']}
FROM {$query['from']}
WHERE oi.id_order='".$this->id."'
GROUP by oi.id
ORDER BY {$query['order']} ".QueryHint::ROUTE_TO_MASTER_HINT); // TODO: Přepiš mě do QB, jakmile nebude sezona
$total_price_without_vat = toDecimal(0);
$total_price_without_vat_no_rounding = toDecimal(0);
$vats = [];
if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS_FLOAT)) {
$unitsRounder = ServiceContainer::getService(\KupShop\UnitsBundle\Utils\PiecesRounder::class);
}
changeCurrency($this->currency);
foreach ($SQLItems as $item) {
$item['ean'] = formatEAN($item['ean']);
$item['piece_price'] = formatPrice($item['piece_price'], $item['vat']);
$item['total_price'] = formatPrice($item['total_price'], $item['vat']);
$item = array_merge($item, $item['piece_price']);
$item['discount'] = toDecimal($item['discount']);
$vat = (string) $item['vat'];
if (!isset($vats[$vat]['total_price_without_vat'])) {
$vats[$vat]['total_price_without_vat'] = toDecimal(0);
$vats[$vat]['total_price_with_vat'] = toDecimal(0);
}
$vats[$vat]['total_price_without_vat_no_rounding'] = $vats[$vat]['total_price_without_vat']->add($item['total_price']['value_without_vat']);
$vats[$vat]['total_price_without_vat'] = $vats[$vat]['total_price_without_vat']->add($item['total_price']['value_without_vat']->value(2));
$vats[$vat]['total_price_with_vat'] = $vats[$vat]['total_price_with_vat']->add($item['total_price']['value_with_vat']->value(2));
$total_price_without_vat_no_rounding = $total_price_without_vat_no_rounding->add($item['total_price']['value_without_vat']);
$total_price_without_vat = $total_price_without_vat->add($item['total_price']['value_without_vat']->value(2));
// Fetch item Product class
if ($item['id_product']) {
if ($item['id_variation']) {
$item['product'] = new Variation($item['id_product'], $item['id_variation']);
} else {
$item['product'] = new Product($item['id_product']);
}
$item['product']->createFromDB();
$item['product']->prepareDeliveryText();
$productListId = $item['id_product'];
if ($item['id_variation']) {
$productListId .= '/'.$item['id_variation'];
}
$this->productList->set($productListId, $item['product']);
}
if (findModule(Modules::PRODUCTS, Modules::SUB_UNITS_FLOAT)) {
if ($item['id_product']) {
$item['pieces'] = $unitsRounder->roundPieces($item['product'], $item['pieces']);
} else {
$item['pieces'] = floatval($item['pieces']);
}
}
if (empty($item['note'])) {
$item['note'] = [];
} else {
$item['note'] = json_decode($item['note'], true);
}
$this->items[$item['id']] = $item;
}
if (!empty($vats)) {
$vats = $this->getVatsDescr($vats);
}
$this->vats = $vats;
$this->total_price_array = formatPrice($this->total_price);
$this->total_price_array['value_without_vat_no_rounding'] = $total_price_without_vat_no_rounding;
$this->total_price_array['value_without_vat'] = $total_price_without_vat;
$this->total_price_array['value_vat'] = $this->total_price_array['value_with_vat']->sub($this->total_price_array['value_without_vat']);
changeCurrency();
return $this->items;
}
/**
* @return OrderItem[]
*/
public function getItems(): array
{
$items = [];
foreach ($this->fetchItems() as $key => $item) {
if (is_array($item)) {
$item = new OrderItem($item);
}
$items[$key] = $item;
}
return $items;
}
public function getTotalPrice(): TotalPrice
{
if ($this->totalPrice) {
return $this->totalPrice;
}
return $this->totalPrice = new TotalPrice(
$this->total_price,
$this->total_price_without_vat,
$this->currencyContext->getAll()[$this->getCurrency()]
);
}
public function getItemInfo($item)
{
$dispatcher = ServiceContainer::getService('event_dispatcher');
$event = $dispatcher->dispatch(new OrderItemInfoEvent($item, $this), OrderItemInfoEvent::INFO);
return $event->getInfo();
}
protected function getVatsDescr($vats)
{
$qb = sqlQueryBuilder()
->select('descr, vat')
->from('vats')
->where(Operator::inStringArray(array_keys($vats), 'vat'))
->orderBy('vat', 'DESC');
foreach ($qb->execute() as $row) {
$vatRate = (string) $row['vat'];
$vat = &$vats[$vatRate];
$vat['descr'] = $row['descr'];
$vat['tax'] = formatPrice($vat['total_price_without_vat_no_rounding'], $row['vat']);
}
// Make sure vat.tax is always defined
foreach ($vats as $vat_value => &$vat) {
if (isset($vat['tax'])) {
continue;
}
$vat['tax'] = formatPrice($vat['total_price_without_vat'], $vat_value);
}
krsort($vats, SORT_NUMERIC);
return $vats;
}
public function fetchItemsSuppliers()
{
if (!findModule(Modules::PRODUCTS_SUPPLIERS) && !findModule(Modules::ORDERS_OF_SUPPLIERS)) {
return;
}
foreach ($this->items as &$item) {
if ($item['id_product']) {
$query = 'SELECT pos.in_store, pos.code, s.order_url, s.name, pos.price_buy, v.vat
FROM '.getTableName('products_of_suppliers').' pos
LEFT JOIN '.getTableName('suppliers').' s ON pos.id_supplier = s.id
LEFT JOIN '.getTableName('products').' AS p ON pos.id_product=p.id
LEFT JOIN '.getTableName('vats')." AS v ON v.id=p.vat
WHERE pos.id_product={$item['id_product']}";
if (!empty($item['id_variation'])) {
$query .= " AND pos.id_variation={$item['id_variation']}";
}
$query .= ' ORDER BY s.id';
$item['suppliers'] = sqlFetchAll(sqlQuery($query));
}
}
}
public function fetchItemsPhoto()
{
$qb = sqlQueryBuilder()
->select('oi.id')
->from($this->items_table, 'oi')
->join('oi', 'products', 'p', 'p.id = oi.id_product')
->leftJoin('oi', 'products_variations', 'pv', 'pv.id = oi.id_variation')
->andWhere(Operator::equals(['oi.id_order' => $this->id]))
->addSelect(\Query\Product::withProductPhotoId(true));
$photos = sqlFetchAll($qb->execute(), 'id');
foreach ($this->items as &$item) {
$photo = getVal($item['id'], $photos);
if ($photo) {
$item['photo'] = getImage($photo['id_photo'], null, null, 'product_catalog', '', strtotime($photo['id_photo_update']));
}
}
}
public function changeStatus($statusId, $comment = null, $forceSendMail = null, $order_message = null, $emailType = null)
{
if (empty($comment) && $order_message) {
/** @var \KupShop\KupShopBundle\Email\OrderMessageEmail $emailService */
$emailService = ServiceContainer::getService(\KupShop\KupShopBundle\Email\OrderMessageEmail::class);
$emailService->setOrderMessage($order_message);
$comment = $emailService->getEmailTemplate($this->id_language)['body'];
}
if ($statusId == $this->status && !$forceSendMail && empty($comment) && is_null($emailType)) {
return;
}
$dbCfg = Settings::getDefault();
// Delete if edited
if ($this->isEditable() == 2) {
$this->cancelEdit();
}
$update = sqlQueryBuilder()->update('orders')
->set('status', ':status')
->set('date_updated', 'NOW()')
->setParameter('status', $statusId)
->where(Operator::equals(['id' => $this->id]));
// Store statuses and update dates
if (array_search($statusId, getStatuses('handled')) !== false) {
$update->set('date_handle', 'COALESCE(date_handle, NOW())');
if (!$this->date_handle) {
$this->date_handle = new DateTimeImmutable();
if ($this->isActive()) { // nestornovana
$this->sendOrderEvent($this, OrderEvent::ORDER_HANDLED);
}
}
}
if ($statusId > 0) {
$update->set('date_accept', 'COALESCE(date_accept, NOW())');
$this->date_accept = new DateTimeImmutable();
}
$update->execute();
$this->status_previous = $this->status;
$this->status = $statusId;
$this->sendOrderEvent($this, OrderEvent::ORDER_STATUS_CHANGED);
// Get messages
if ($this->isNewOrder()) {
$type = 'received';
} else {
$type = 'status';
}
$sendMail = $forceSendMail;
if ($sendMail === null) {
if (!empty($emailType)) {
$emailLocator = ServiceContainer::getService(EmailLocator::class);
$emailService = $emailLocator->getEmailService($emailType);
// odeslani mailu rozhodnu podle nastaveni emailu
$sendMail = $emailService->isEnabled();
} else {
// poslat email - podle Nastavení emailů
$sendMail = ($dbCfg->{"order_send_{$type}_mail"} == 'Y');
if (!$sendMail && !empty($comment)) {
// v nastaveni - neposilat email, ale mame comment, takze poslat,
// pokud to neni nova objednavka (u nove obj. comment je poznamka od zakaznika)
$sendMail = !$this->isNewOrder();
}
}
}
if ($sendMail) {
$message = $this->sendEmail($comment, $order_message, false, $emailType);
$smsService = ServiceContainer::getService(SMSSender::class);
$emailService = $this->getEmailServiceInstance($emailType, $order_message);
$emailService->setComment($comment);
$emailService->setOrder($this);
$smsService->sendSMS($this, email_service: $emailService);
} else {
// Write history
$this->logHistory($comment);
}
// Send notification to owner
if (($forceSendMail !== false) && $this->isNewOrder() && $dbCfg->order_send_to_shopkeeper == 'Y' && !empty($dbCfg->order_shopkeeper_mail)) {
$this->sendNotificationToOwner();
}
}
public function isNewOrder()
{
return $this->status_previous === -1 && $this->status === 0;
}
public function logHistory($comment, $customerNotified = false, $customData = null, ?DateTime $date = null)
{
$contextManger = ServiceContainer::getService(\KupShop\KupShopBundle\Context\ContextManager::class);
$loggerInfoHelper = ServiceContainer::getService(\KupShop\KupShopBundle\Util\Logging\LoggerInfoHelper::class);
if ($loggerInfoHelper->getInitiator()) {
$customData['initiator'] = $loggerInfoHelper->getInitiator()->value;
}
$insertedID = null;
$contextManger->activateOrder($this, function () use ($comment, $customerNotified, $customData, $date, &$insertedID) {
$insertedID = sqlGetConnection()->transactional(function () use ($comment, $customerNotified, $customData, $date) {
$admin = getAdminUser();
$this->insertSQL('orders_history', [
'id_order' => $this->id,
'id_status' => max(0, $this->status),
'date' => $date ?: new DateTime(),
'comment' => $this->replacePlaceholders($comment),
'notified' => $customerNotified ?: 0,
'admin' => !empty($admin['id']) ? $admin['id'] : null,
'custom_data' => $customData ? json_encode($customData) : null,
], [], ['date' => 'datetime']);
return sqlInsertId();
});
});
return $insertedID;
}
public function sendEmail($comment, $order_message, $force_invoice = false, $emailType = null, ?bool $sendToInvoiceEmail = null)
{
$contextManger = ServiceContainer::getService(\KupShop\KupShopBundle\Context\ContextManager::class);
return $contextManger->activateOrder($this, function () use ($comment, $order_message, $emailType, $sendToInvoiceEmail) {
// Prepare message
$dbcfg = Settings::getDefault();
$emailService = $this->getEmailServiceInstance($emailType, $order_message);
$emailService->setComment($comment);
$emailService->setOrder($this);
$emailService->setSendToInvoiceCopyEmail($sendToInvoiceEmail);
// pokud volam sendEmail, tak chci vzdycky poslat mail bez ohledu na nastaveni v administraci
$emailService->setEnabled(true);
$message = $emailService->getEmail();
// Send it
$emailService->sendEmail($message);
$emailService->setEnabled(null);
return $message;
});
}
protected function getEmailServiceInstance(?string $emailType = null, ?string $order_message = null)
{
if ($emailType) {
$emailLocator = ServiceContainer::getService(EmailLocator::class);
return $emailLocator->getEmailService($emailType);
} elseif ($order_message) {
$emailService = ServiceContainer::getService(\KupShop\KupShopBundle\Email\OrderMessageEmail::class);
$emailService->setOrderMessage($order_message);
return $emailService;
} else {
$contextManger = ServiceContainer::getService(\KupShop\KupShopBundle\Context\ContextManager::class);
return $contextManger->activateOrder($this, function () {
if ($this->isNewOrder()) {
if (findModule(Modules::B2B) && Contexts::get(UserContext::class)->isType('b2b')) {
return ServiceContainer::getService(\KupShop\KupShopBundle\Email\OrderB2BCreateEmail::class);
} else {
return ServiceContainer::getService(\KupShop\KupShopBundle\Email\OrderCreateEmail::class);
}
} else {
if ($this->status != $this->status_previous) {
return ServiceContainer::getService(\KupShop\KupShopBundle\Email\OrderChangeEmail::class);
} else {
return ServiceContainer::getService(\KupShop\KupShopBundle\Email\OrderEmail::class);
}
}
});
}
}
protected function sendNotificationToOwner($email = null)
{
$dbcfg = Settings::getDefault();
$emailService = ServiceContainer::getService(\KupShop\KupShopBundle\Email\OrderCreateAdminEmail::class);
$order = new Order();
$order->createFromDB($this->id);
$emailService->setOrder($order);
$message_admin = $emailService->getEmail();
$message_admin['to'] = $email ?: $dbcfg->order_shopkeeper_mail;
$message_admin['from'] = empty($message_admin['template']['email'])
? "\"{$order->invoice_name} {$order->invoice_surname}\" <{$order->invoice_email}>"
: $message_admin['template']['email'];
$message_admin['type'] = '';
$emailService->sendEmail($message_admin);
}
public function replacePlaceholders($text)
{
$loop = 0;
while (preg_match('/{(.+?)}/', $text) && $loop++ < 5) {
$text = replacePlaceholders($text, [], [$this, 'replacePlaceholdersItem']);
}
return $text;
}
public function replacePlaceholdersItem($placeholder)
{
if ($this->placeholders[$placeholder] ?? false) {
return $this->placeholders[$placeholder];
}
global $cfg;
switch ($placeholder) {
case 'KOD':
return $this->order_no;
case 'CISLO_FAKTURY':
return $this->getInvoiceNo();
case 'STAV':
$orderInfo = ServiceContainer::getService(OrderInfo::class);
return $orderInfo->getOrderStatus($this->status);
case 'ODKAZ':
return $this->getUrl();
case 'ODKAZ_PLATBA':
return $this->getPaymentUrl();
case 'CENA':
return printPrice($this->total_price, ['currency' => $this->currency, 'ceil' => false, 'decimal' => 'dynamic']);
case 'CENA_EUR':
$currency = $this->currencyContext->getSupported()['EUR'];
$price = $this->total_price;
if ($this->currency != 'EUR') {
$price = $this->total_price->div($currency->getRate());
}
return printPrice($price, ['currency' => $currency]);
case 'DATUM':
return date('d.\&\n\b\s\p\;m.\&\n\b\s\p\;Y');
case 'CAS':
return date('H:i:s');
case 'DATUM_OBJEDNAVKY':
return $this->date_created->format('d.\&\n\b\s\p\;m.\&\n\b\s\p\;Y');
case 'CAS_OBJEDNAVKY':
return $this->date_created->format('H:i:s');
case 'EMAIL_UZIVATELE':
return $this->invoice_email;
case 'WEB':
return $cfg['Addr']['full'];
case 'ZAPLATIT':
return printPrice($this->getRemainingPayment(), ['currency' => $this->currency]);
case 'OSLOVENI_JMENO':
return Greeting::getFromName($this->invoice_name, $this->invoice_surname);
case 'OSLOVENI_PRIJMENI':
return Greeting::getFromSurname($this->invoice_surname, $this->invoice_name);
case 'POPIS_ZPUSOBU_DORUCENI':
return $this->getDeliveryType()->description;
case 'POPIS_DOPRAVY':
return $this->getDeliveryType()->getDelivery()->description;
case 'POPIS_PLATBY':
return $this->getDeliveryType()->payment_description;
case 'ODKAZ_SLEDOVANI_BALIKU':
return $this->getDeliveryType()->getDelivery()->getTrackAndTraceLink($this->package_id, $this);
case 'OTEVIRACI_DOBA':
$smarty = createSmarty(false, true);
$delivery = $this->getDeliveryType()->getDelivery();
$openingHours = $this->getDeliveryType()->getDelivery()->getCustomData()['opening_hours'] ?? [];
// pokud mam prodejce a doprava je OdberNaProdejne, tak potrebuju vzit oteviraci dobu z prodejny
if (findModule(Modules::SELLERS) && $delivery instanceof OdberNaProdejne) {
if ($sellerId = ($delivery->data['seller_id'] ?? null)) {
$sellerUtil = ServiceContainer::getService(SellerUtil::class);
if ($seller = $sellerUtil->getSeller((int) $sellerId)) {
$openingHours = $sellerUtil->getOpeningHours($seller, new \DateTime());
}
}
}
$smarty->assign('openingHours', $openingHours);
return $smarty->fetch('email/block.opening-hours.tpl');
case 'BALIK_ID':
return $this->package_id;
case 'QR_PLATBA':
$QRPayment = ServiceContainer::getService(\KupShop\OrderingBundle\Util\Order\QRPayment::class);
$url = $QRPayment->getQRCodeImageUrl($this);
return '<a href="'.$url.'"><img src="'.$url.'" alt="QR Platba"></a>';
case 'DATUM_SPLATNOSTI':
if (!empty($this->date_due)) {
$date = $this->date_due->format('d.\&\n\b\s\p\;m.\&\n\b\s\p\;Y');
} elseif (!empty($this->date_handle)) {
$dbcfg = Settings::getDefault();
$date = clone $this->date_handle;
$dueDays = $dbcfg->shop_due_days;
$date = $date->add(new DateInterval('P'.$dueDays.'D'));
$date = $date->format('d.\&\n\b\s\p\;m.\&\n\b\s\p\;Y');
}
return $date ?? '';
case 'REKAPITULACE_OBCHODNIKOVI':
$smarty = createSmarty(true, true);
$this->fetchItems();
$smarty->assign('order', $this);
$message = $smarty->fetch('email/orderShopkeeper.tpl');
return $message;
case 'FAKTURACNI_ADRESA':
$address_array = [
1 => [$this->invoice_name, $this->invoice_surname],
2 => [$this->invoice_firm, $this->invoice_ico, $this->invoice_dic],
3 => [$this->invoice_street],
4 => [$this->invoice_custom_address],
5 => [$this->invoice_zip, $this->invoice_city],
6 => [$this->invoice_state],
7 => [$cfg['Order']['Countries'][$this->invoice_country] ?? ''],
];
return $this->printAddress($address_array);
case 'DODACI_ADRESA':
$address_array = [
1 => [$this->delivery_name, $this->delivery_surname],
2 => [$this->delivery_firm],
3 => [$this->delivery_street],
4 => [$this->delivery_custom_address],
5 => [$this->delivery_zip, $this->delivery_city],
6 => [$this->delivery_state],
7 => [$cfg['Order']['Countries'][$this->delivery_country] ?? ''],
];
return $this->printAddress($address_array);
case 'BONUS_PROGRAM_BODY':
if (findModule(Modules::BONUS_PROGRAM) && $this->id_user) {
/** @var PurchaseState $pstate */
$pstate = $this->getPurchaseState();
$bonusComputer = ServiceContainer::getService(BonusComputer::class);
$points = $bonusComputer->countBonusPoints($pstate);
return $points->printFloatValue(-4);
}
return 0;
case 'POZNAMKA_UZIVATELE':
return $this->note_user ?? '';
}
return null;
}
public function printAddress($address_array = [])
{
foreach ($address_array as &$address_line) {
$address_line = trim(join(' ', $address_line));
}
$address_array = array_filter($address_array);
$address = join('<br/>', $address_array);
return $address;
}
public function getUserEmail()
{
/*if(!empty($this->id_user))
$email = returnSQLResult("SELECT email FROM ".getTableName("users")."
WHERE id={$this->id_user}");
else
{*/
$this->fetchInvoice();
$email = $this->invoice_email;
// }
/*
if(empty($email))
logError(__FILE__, __LINE__, "Empty user ID!");
*/
return $email;
}
public function getCopyEmail()
{
return $this->invoice_copy_email;
}
public function getUrl($edit = false)
{
if (isset($this->url)) {
return $this->url;
}
return $this->url = createScriptURL([
'URL' => 'launch.php',
's' => 'orderView',
'IDo' => $this->id,
'cf' => $this->getSecurityCode(),
'ESCAPE' => 'NO',
'edit' => $edit,
'absolute' => true,
]);
}
public function getPaymentUrl()
{
$pathData = [
'id' => $this->id,
'cf' => $this->getSecurityCode(),
];
return path('payment-redirect', $pathData, \Symfony\Component\Routing\Router::ABSOLUTE_URL);
}
public function getPayments($includePending = false)
{
if (!findModule('order_payment')) {
return 0;
}
$qb = sqlQueryBuilder()->select('SUM(price)')->from('order_payments')
->where(Operator::equals(['id_order' => $this->id]))
->sendToMaster();
if (findModule('payments')) {
if ($includePending) {
$qb->andWhere(Operator::inIntArray([0, 1, 2], 'status'));
} else {
$qb->andWhere('status = 0');
}
}
$sum = doubleval($qb->execute()->fetchColumn());
return $sum;
}
public function getPaymentsArray()
{
if (!findModule('order_payment')) {
return [];
}
$qb = sqlQueryBuilder()->select('*')
->from('order_payments')
->where(Operator::equals(['id_order' => $this->id]))
->orderBy('date', 'ASC')
->sendToMaster();
$payments = [];
foreach ($qb->execute() as $payment) {
$payment['date'] = new DateTime($payment['date']);
$payment['payment_data_array'] = json_decode($payment['payment_data'] ?? '{}', true);
$payments[] = $payment;
}
return $payments;
}
public function updatePayments()
{
QueryHint::withRouteToMaster(function () {
// Check order has any items. This avoids setting orders being created/modified as paid
$hasItems = sqlQueryBuilder()->select('COUNT(*)')
->from('order_items')
->where(Operator::equals(['id_order' => $this->id]))
->execute()
->fetchOne();
if (!$hasItems) {
return;
}
$status_paid = $this->isPaid() ? self::STATUS_PAID : self::STATUS_UNPAID;
$dbcfg = Settings::getDefault();
if ($dbcfg->order_multiple_paid_status === 'Y' && $status_paid === self::STATUS_UNPAID) {
if ($this->getRemainingPaymentRaw()->lowerThan(DecimalConstants::zero())) {
$status_paid = self::STATUS_OVERPAID;
} elseif (!$this->getRemainingPaymentRaw()->equals($this->total_price) && $this->getRemainingPaymentRaw()->isPositive()) {
$status_paid = self::STATUS_UNDERPAID;
}
}
$qb = sqlQueryBuilder()->update('orders')->set('status_payed', $status_paid)
->where(Operator::equals(['id' => $this->id]));
$qb->execute();
if (($this->status_payed != $status_paid) && !$this->editMode) {
$this->status_payed = $status_paid;
if ($status_paid == self::STATUS_PAID) {
$this->sendOrderEvent($this, OrderEvent::ORDER_PAID);
} else {
$this->sendOrderEvent($this, OrderEvent::ORDER_UNPAID);
}
}
});
}
public function insertPayment($price, $note, $date = null, $allow_duplicated = false, $method = 0, ?string $payment_data = null)
{
if (!$date) {
$date = date('Y-m-d H:i:s');
}
if (findModule(\Modules::ORDER_PAYMENT)) {
$paymentId = sqlGetConnection()->transactional(function () use ($price, $note, $date, $allow_duplicated, $method, $payment_data) {
sqlQueryBuilder()->select('*')->from('orders')->where(Operator::equals(['id' => $this->id]))->forUpdate()->execute();
// Check if already exists
$qb = sqlQueryBuilder()->select('COUNT(*)')->from('order_payments')
->where(Operator::equals(['id_order' => $this->id, 'note' => $note]));
if (!$allow_duplicated && intval($qb->execute()->fetchColumn()) > 0) {
return false;
}
$admin = null;
if (($user = getAdminUser()) !== false && $user['id']) {
$admin = $user['id'];
}
$this->insertSQL('order_payments', ['price' => $price, 'date' => $date, 'note' => $note, 'id_order' => $this->id,
'method' => $method, 'admin' => $admin, 'payment_data' => $payment_data]);
$paymentId = sqlInsertId();
$this->updateSQL('orders', ['date_updated' => $date], ['id' => $this->id]);
return $paymentId;
});
$this->updatePayments();
return $paymentId;
}
return false;
}
public function sendPaymentReceipt($id_payment)
{
$payment = sqlQueryBuilder()->select('*')
->from('order_payments', 'op')
->where(\Query\Operator::equals(['op.id' => $id_payment]))->execute()->fetch();
$email = ServiceContainer::getService(\KupShop\KupShopBundle\Email\PaymentSuccessEmail::class);
$email->setOrder($this);
$email->setPayment($payment);
$message = $email->getEmail();
return $email->sendEmail($message);
}
/**
* Copy all product items from $order to this order.
*
* @param Order $order
* @param bool $onlyProducts
* @param bool $onlyPositive
*/
public function copyItems($order, $onlyProducts = true, $onlyPositive = true, $dispatchEvent = false)
{
$query = 'SELECT oi.id_product, oi.id_variation, oi.pieces, oi.note, oi.descr, oi.piece_price, oi.total_price, tax
FROM '.getTableName('order_items')." oi
WHERE oi.id_order={$order->id}";
if ($onlyProducts) {
$query .= ' AND oi.id_product IS NOT NULL';
}
if ($onlyPositive) {
$query .= ' AND oi.total_price > 0';
}
$SQL = sqlQuery($query);
/*$SQL = sqlQuery("SELECT oi.id_product, oi.id_variation, oi.pieces, oi.note, oi.descr
FROM ".getTableName("order_items")." oi
WHERE oi.id_order={$order->id} AND oi.id_product IS NOT NULL AND oi.total_price > 0");
*/
while (($row = sqlFetchAssoc($SQL)) !== false) {
if (!empty($row['id_product'])) {
$data = json_decode($row['note'], true);
if (is_array($data)) {
unset($data['priceWithoutDiscounts']);
unset($data['totalDiscount']);
unset($data['totalDiscountPerc']);
unset($data['discounts']);
}
$row['note'] = json_encode($data);
if (!$this->insertItem($row['id_product'], $row['id_variation'], $row['pieces'], $row['note'], dispatchEvent: $dispatchEvent)) {
throw new Exception("Selhal přesun produktu '{$row['descr']}' do objednávky");
}
} else {
$this->insertNonItem($this->id, $row['total_price'], $row['piece_price'], $row['pieces'], $row['tax'], $row['descr'], $row['note']);
}
}
$this->recalculate();
}
public function setEditMode($editMode = true)
{
$this->items_table = $editMode ? 'order_edit' : 'order_items';
$this->editMode = $editMode;
}
public function isEditable()
{
if (findModule('order_edit')) {
$this->fetchDates();
if (in_array($this->status, getStatuses('editable')) && $this->isActive() && !$this->date_handle) {
// Check there are products in the order and no negative pieces (returns)
$counts = sqlQueryBuilder()->select('COUNT(*) as total, SUM(oi.pieces < 0) as negative')
->from('order_items', 'oi')
->andWhere(Operator::equals(['oi.id_order' => $this->id]))
->andWhere('oi.id_product IS NOT NULL')
->execute()->fetchAssociative();
if (empty($counts['total']) || !empty($counts['negative'])) {
return 0;
}
return returnSQLResult('SELECT COUNT(*) FROM '.getTableName('order_edit')." WHERE id_order='{$this->id}'") > 0 ? 2 : 1;
}
}
return 0;
}
protected function getRemainingPaymentRaw($includePending = false): Decimal
{
if (!findModule('order_payment') && $this->status_payed == 1) {
return DecimalConstants::zero();
}
return toDecimal($this->total_price)->sub(toDecimal($this->getPayments($includePending)));
}
public function getRemainingPayment($returnDecimal = false)
{
if (!findModule('order_payment') && $this->status_payed == 1) {
return 0;
} else {
if ($this->isPaid()) {
return 0;
}
$remainingValue = toDecimal($this->total_price)->sub(toDecimal($this->getPayments()))->round(4);
if ($returnDecimal) {
return $remainingValue;
}
return $remainingValue->asFloat();
}
}
public function getRemainingPaymentInCZK($includePending = false)
{
$remaining = $this->getRemainingPaymentRaw($includePending);
if (!$remaining->isZero()) {
$currency = $this->getCurrency();
if ($currency != 'CZK') {
$currency_rate = toDecimal($this->currency_rate);
if ($currency_rate->equals(DecimalConstants::one())) {
try {
$currency_rate = CNB::getCurrency($currency);
} catch (Exception $e) {
}
}
$remaining = roundPrice($remaining->mul($currency_rate), 1, 'DB', 2);
}
}
return $remaining;
}
public function isPaid($overpaidAsPaid = false)
{
$remaining = $this->getRemainingPaymentInCZK();
if (!$overpaidAsPaid) {
$remaining = $remaining->abs();
}
return $remaining->lowerThan(DecimalConstants::one());
}
public function isActive()
{
return $this->status_storno != '1';
}
public function updateItem($itemId, $pieces, $recalculate = true)
{
sqlStartTransaction();
$SQL = sqlQuery('SELECT oi.id_product, oi.id_variation, oi.pieces
FROM '.getTableName($this->items_table)." oi
WHERE oi.id='{$itemId}' FOR UPDATE");
$item = sqlFetchAssoc($SQL);
if ($pieces == $item['pieces'] && $pieces != 0) {
sqlFinishTransaction();
return true;
}
if (!$this->editMode && $item['id_product'] > 0) {
$prod = new Product($item['id_product']);
$prod->sell($item['id_variation'], $pieces - $item['pieces']);
$dispatcher = ServiceContainer::getService('event_dispatcher');
$event = $dispatcher->dispatch(
new OrderItemEvent($prod, $item['id_variation'], null, $pieces - $item['pieces'], null, $this),
OrderItemEvent::ITEM_UPDATED
);
}
sqlQuery('UPDATE '.getTableName($this->items_table)." set pieces={$pieces}, total_price=piece_price*{$pieces}, date=NOW() WHERE id=:item_id", ['item_id' => $itemId]);
if ($recalculate) {
$this->recalculate();
}
if (!$this->editMode) {
sqlQuery('UPDATE '.getTableName('orders')."
SET date_updated=NOW()
WHERE id='{$this->id}'");
}
sqlFinishTransaction();
return true;
}
public function deleteItem($itemId, $recalculate = true)
{
sqlStartTransaction();
// Remove from store
$this->updateItem($itemId, 0, $recalculate);
$item_name = returnSQLResult("SELECT descr FROM {$this->items_table} WHERE id=:id_item", ['id_item' => $itemId]);
// Delete item
sqlQuery('DELETE FROM '.getTableName($this->items_table).' WHERE id=:id_item', ['id_item' => $itemId]);
$this->logChange("Odebrána položka \"{$item_name}\"");
sqlFinishTransaction();
return true;
}
public function insertNonItem($id_order, $total_price, $price, $pieces, $tax, $descr, $note = '', $discount = 0)
{
$this->insertSQL('order_items', [
'id_order' => $id_order,
'total_price' => $total_price,
'piece_price' => $price,
'pieces' => $pieces,
'tax' => $tax,
'descr' => $descr,
'note' => $note,
'discount' => $discount,
]);
$itemId = sqlInsertId();
$this->logChange("Přidána položka \"{$descr}\"");
return $itemId;
}
public function insertItem($productId, $variationId, $pieces, $note = '', $checkIfExists = true, ?Price $piece_price = null, bool $dispatchEvent = false)
{
$cfg = Config::get();
if (empty($variationId)) {
$variationId = null;
}
// Check if product/variation exists
$count = sqlQueryBuilder()->select('COUNT(*)')->fromProducts()
->joinVariationsOnProducts()
->where(Operator::equalsNullable(['p.id' => $productId, 'pv.id' => $variationId]))
->execute()->fetchOne();
if ($count != 1) {
$where = selectQueryCreate(['p.id' => $productId, 'pv.id' => $variationId], true);
logError(__FILE__, __LINE__, "Adding nonexistent product to order: {$where}");
throw new \DomainException('Neexistující produkt/varianta v objednávce. Zkontrolujte prosím, zda všechny produkty mají vybranou variantu produktu.');
}
if ($checkIfExists) {
// Check if item already exists
$existingItemId = sqlGetConnection()->transactional(function () use ($productId, $variationId, $pieces, $note) {
$existingItem = sqlQueryBuilder()->select('id, pieces')
->from($this->items_table)
->where(Operator::equalsNullable([
'id_order' => $this->id,
'id_product' => $productId,
'id_variation' => $variationId,
'note' => $note]))
->forUpdate()
->execute()->fetchAssociative();
if ($existingItem) {
$this->updateItem($existingItem['id'], $existingItem['pieces'] + $pieces);
return $existingItem['id'];
}
return false;
});
if ($existingItemId) {
return $existingItemId;
}
}
$product = Variation::createProductOrVariation($productId, $variationId);
$product->createFromDB($productId);
if ($piece_price) {
$pricePiece = $piece_price->getPriceWithoutVat();
} else {
$pricePiece = $product->getPrice($variationId, $product->parseNote($note), toDecimal($pieces));
$priceArray = formatCustomerPrice($pricePiece, $product->discount, $product->vat, $product->id);
$pricePiece = $priceArray['value_without_vat_no_rounding'];
}
// pridat kod zbozi na konec
$product->title = ServiceContainer::getService(ProductUtils::class)->getOrderTitle($product);
// pridat vyrobce na zacatek
if (!empty($cfg['Order']['prependProducer']) && $product->idProducer) {
$product->fetchProducer();
$product->title = $product->producer['name'].' '.$product->title;
}
$itemPiecePrice = new ProductPrice($pricePiece, $this->currencyContext->getActive(), $product->vat);
$itemTotalPrice = new ProductPrice($pricePiece->mul(toDecimal($pieces)), $this->currencyContext->getActive(), $product->vat);
$orderItem = new \KupShop\OrderingBundle\Entity\OrderItem();
$orderItem->setIdOrder($this->id);
$orderItem->setIdProduct($productId);
$orderItem->setIdVariation(empty($variationId) ? null : $variationId);
$orderItem->setPieces($pieces);
$orderItem->setPiecesReserved($pieces);
$orderItem->setPiecePrice($itemPiecePrice->getPriceWithoutVat());
$orderItem->setTotalPrice($itemTotalPrice->getPriceWithoutVat());
$orderItem->setDescr($product->title);
$orderItem->setTax($product->vat);
$orderItem->setNote(empty($note) ? '' : $note);
return sqlGetConnection()->transactional(function () use ($orderItem, $product, $dispatchEvent) {
$item_id = $this->orderItemUtil->saveOrderItem($orderItem, $this, $product, $this->editMode, $dispatchEvent);
$this->logChange("Přidána položka \"{$product->title}\"");
$this->recalculate();
return $item_id;
});
}
public function insertProductPurchaseItem(ProductPurchaseItem $purchaseItem, bool $checkIfExists = true): int
{
if ($checkIfExists) {
$item = sqlQueryBuilder()
->select('id, pieces')
->from($this->items_table)
->where(Operator::equalsNullable(
[
'id_order' => $this->id,
'id_product' => $purchaseItem->getIdProduct(),
'id_variation' => $purchaseItem->getIdVariation(),
'note' => json_encode($purchaseItem->getNote()),
]
))->execute()->fetch();
if ($item) {
$this->updateItem($item['id'], $item['pieces'] + $purchaseItem->getPieces());
$purchaseItem->setId($item['id']);
return (int) $item['id'];
}
}
if (!$purchaseItem->getProduct()) {
throw new \DomainException('Neexistující produkt/varianta v objednávce. Zkontrolujte prosím, zda všechny produkty mají vybranou variantu produktu.');
}
return sqlGetConnection()->transactional(function () use ($purchaseItem) {
$qb = sqlQueryBuilder()
->insert($this->items_table)
->directValues([
'id_order' => $this->id,
'id_product' => $purchaseItem->getIdProduct(),
'id_variation' => $purchaseItem->getIdVariation(),
'pieces' => $purchaseItem->getPieces(),
'pieces_reserved' => $purchaseItem->getPieces(),
'piece_price' => $purchaseItem->getPiecePriceWithoutVat(),
'total_price' => $purchaseItem->getPriceWithoutVat(),
'descr' => $purchaseItem->getName(),
'tax' => $purchaseItem->getPrice()->getVat(),
'note' => json_encode($purchaseItem->getNote()),
]);
$qb->execute();
$itemId = (int) sqlInsertId();
// Update sell status
if (!$this->editMode) {
$reserved = $purchaseItem->getProduct()->sell($purchaseItem->getIdVariation(), $purchaseItem->getPieces());
// Update timestamps
sqlQueryBuilder()
->update('orders')
->set('date_updated', 'NOW()')
->where(Operator::equals(['id' => $this->id]))
->execute();
sqlQueryBuilder()
->update('order_items')
->directValues(['pieces_reserved' => $reserved])
->where(Operator::equals(['id' => $itemId]))
->execute();
}
$purchaseItem->setId($itemId);
return $itemId;
});
}
public function storno($byUser = true, $comment = null, $sendMail = null)
{
$contextManager = ServiceContainer::getService(\KupShop\KupShopBundle\Context\ContextManager::class);
sqlGetConnection()->transactional(function () use ($byUser, $comment, $contextManager) {
$contextManager->activateOrder($this, function () use ($byUser, $comment) {
$this->returnItemsToStore();
// Change order status
global $cfg;
$statusStorno = findModule(Modules::ORDERS, 'status_storno', 'default');
if ($statusStorno === 'default') {
$statuses = array_filter(array_keys($cfg['Order']['Status']['global']), fn ($x) => $x < 100);
$status = end($statuses);
} elseif ($statusStorno === 'keep') {
$status = $this->status;
} else {
$status = $statusStorno;
}
if (is_null($comment)) {
$comment = $byUser ? translate_shop('user_storno', 'order_msg') : translate_shop('admin_storno', 'order_msg');
} else {
$this->setPlaceholder('DUVOD', $comment);
}
$this->status_storno = 1;
$this->changeStatus($status, 'Objednávka stornována.', false);
$this->sendOrderEvent($this, OrderEvent::ORDER_STORNO);
// stornovat objednavku
sqlQuery("UPDATE orders
SET status_storno='1', date_updated=NOW()
WHERE id=:id", ['id' => $this->id]);
sqlQuery('UPDATE orders SET date_handle=NOW() WHERE id=:id AND date_handle IS NULL', ['id' => $this->id]);
});
});
$emailType = OrderStornoEmail::getType();
// pokud se jedna o rezervaci, tak poslu storno email pro rezervace
if ($this->source === OrderInfo::ORDER_SOURCE_RESERVATION) {
$emailType = OrderReservationStornoEmail::getType();
}
if ($sendMail === null) {
$emailLocator = ServiceContainer::getService(EmailLocator::class);
$emailService = $emailLocator->getEmailService($emailType);
$sendMail = $emailService->isEnabled();
}
if ($sendMail) {
$this->sendEmail($comment, null, false, $emailType);
}
$asyncQueue = ServiceContainer::getService(AsyncQueue::class);
$asyncQueue->handleAll();
}
protected function returnItemsToStore()
{
// Return products to store
$items = sqlQuery('SELECT id, id_product, id_variation, pieces
FROM order_items
WHERE id_order=:id AND id_product<>0', ['id' => $this->id]);
foreach ($items as $row) {
$prod = new Product($row['id_product']);
$prod->sell($row['id_variation'], -$row['pieces']);
}
}
public function getPurchaseState(bool $withDiscounts = false): PurchaseState
{
if ($this->purchaseState) {
return $this->purchaseState;
}
$purchaseState = $this->purchaseUtil->createStateFromOrder($this, $withDiscounts);
return $this->purchaseState = $purchaseState;
}
/**
* @param PurchaseState|null $purchaseState
*
* @return $this
*/
public function setPurchaseState($purchaseState)
{
$this->purchaseState = $purchaseState;
return $this;
}
public function setPackageId(?string $packageId): void
{
$this->package_id = $packageId;
sqlQueryBuilder()
->update('orders')
->directValues(['package_id' => $packageId])
->andWhere(Operator::equals(['id' => $this->id]))
->execute();
}
public function recalculate($updateDB = true, bool $round = true)
{
$totalPrice = Decimal::fromString('0.0');
$totalPriceWithoutVat = Decimal::fromString('0.0');
// polozky faktury
$itemsQb = sqlQueryBuilder()
->select('id_product, pieces, piece_price, total_price, descr, tax')
->from($this->items_table)
->andWhere(Operator::equals(['id_order' => $this->id]))
->orderBy('id')
->sendToMaster();
foreach ($itemsQb->execute() as $item) {
// Round to two decimal places to remove any rounding error
$totalPrice = $totalPrice->add(toDecimal($item['total_price'])->addVat($item['tax'])->value($round ? 2 : null));
$totalPriceWithoutVat = $totalPriceWithoutVat->add(toDecimal($item['total_price']));
}
// Round total price, disable bata (only for products, delivery)
$currency = $this->currencyContext->getOrDefault($this->getCurrency());
if ($round) {
$totalPrice = roundPrice($totalPrice, $currency->getPriceRoundOrder(), decimal: 2, bata: false);
$totalPriceWithoutVat = $totalPriceWithoutVat->value(2);
}
// upravit v databazi
if (!$this->editMode && $updateDB) {
sqlQueryBuilder()
->update('orders')
->directValues(
[
'total_price' => $totalPrice,
'total_price_without_vat' => $totalPriceWithoutVat,
]
)
->where(Operator::equals(['id' => $this->id]))
->execute();
}
$this->total_price = toDecimal($totalPrice);
$this->total_price_without_vat = toDecimal($totalPriceWithoutVat);
if (findModule('order_payment') && !$this->editMode) {
$this->updatePayments();
}
$this->items = [];
return $totalPrice;
}
public function saveUsedDiscount($discountId, $itemId = null, ?array $note = null, $price = null)
{
sqlQueryBuilder()->insert('order_discounts_orders')
->directValues([
'id_order' => $this->id,
'id_order_item' => $itemId,
'id_order_discount' => $discountId,
])->onDuplicateKeyUpdate(['id_order'])
->execute();
sqlQueryBuilder()->update('order_discounts')
->set('uses_count', '(SELECT COUNT(DISTINCT id_order) FROM order_discounts_orders WHERE id_order_discount=:id_order_discount)')
->where(Operator::equals(['id' => $discountId]))
->setParameter('id_order_discount', $discountId)
->execute();
if ($note && ($generated_coupon = ($note['generated_coupon'] ?? null))) {
sqlQuery('UPDATE discounts_coupons
SET used = "Y", id_order_used = '.$this->id.'
WHERE id = '.$generated_coupon['id'].'
AND id_discount = '.$generated_coupon['id_discount'].' LIMIT 1');
if ($price && ($couponPrice = $note['coupon_price'] ?? false)) {
$couponPrice = toDecimal($couponPrice);
$couponRemainingPrice = $couponPrice->add($price);
}
sqlQueryBuilder()->insert('discounts_coupons_orders')
->directValues([
'id_discount_coupon' => $generated_coupon['id'],
'id_order' => $this->id,
'id_order_item' => $itemId,
'used_amount' => $price,
'remaining_amount' => $couponRemainingPrice ?? null,
])->execute();
}
return true;
}
/**
* @param bool $initial
* @param bool $skipStore
*
* @return bool
*/
public function recalculateFull($deliveryId, $coupon = null, $initial = false, $skipStore = false)
{
sqlStartTransaction();
// ulozit vybrane zbozi do objednavky
$SQL = sqlQuery("SELECT oi.id, oi.id_product, oi.id_variation, oi.pieces, oi.id_order, p.discount, p.vat, p.title,
COALESCE(pv.price, p.price) price, FIND_IN_SET('Z', p.campaign)>0 free_delivery, oi.note
FROM ".getTableName($this->items_table).' AS oi
LEFT JOIN '.getTableName('products').' AS p ON oi.id_product=p.id
LEFT JOIN '.getTableName('products_variations')." AS pv ON oi.id_variation=pv.id
WHERE oi.id_order='{$this->id}' FOR UPDATE");
// Traverse all ordered products
$orderTotalPrice = Decimal::fromString('0');
$freeDelivery = false;
$purchaseState = $this->getPurchaseState();
if ($purchaseState->isFreeDelivery()) {
$freeDelivery = true;
}
$purchaseStateProducts = $purchaseState->getProducts();
$divideDiscounts = [];
while (($row = sqlFetchAssoc($SQL)) !== false) {
$IDp = $row['id_product'];
$IDv = $row['id_variation'];
// Skip non-product items
if (empty($IDp)) {
continue;
}
$product = Variation::createProductOrVariation($IDp, $IDv);
$product->createFromDB($product->id);
$freeDelivery |= $row['free_delivery'] > 0;
$Pieces = $row['pieces'];
$decimal_Pieces = toDecimal($row['pieces']);
$price = $this->purchaseUtil->getItemPrice($row, $product);
$priceArray = PriceWrapper::wrap($price);
// First round final pice, then multiply, and last calc exact price without vat. This avoids rounding error.
$Rounded_piece_price_with_vat = $priceArray['value_with_vat'];
$Rounded_total_price_with_vat = $Rounded_piece_price_with_vat->mul($decimal_Pieces);
$Rounded_piece_price = $priceArray['value_without_vat'];
$Rounded_total_price = calcPrice($Rounded_total_price_with_vat, -$priceArray['vat']->asFloat());
$totalPriceArray = formatPrice($Rounded_total_price, $priceArray['vat']->asFloat());
$id_order_item = $row['id'];
$productPurchaseItem = null;
$filterProducts = array_filter($purchaseStateProducts, function ($p) use ($id_order_item) { return $p->getId() == $id_order_item; });
foreach ($filterProducts as $key => $item) {
unset($purchaseStateProducts[$key]);
$productPurchaseItem = $item;
if ($productPurchaseItem && !$decimal_Pieces->isZero()) {
// $productPurchaseItem - odpovidajici polozka v purchaseState,
// muze mit slevy, ktere byly rozpocitane k jednotlivym polozkam =>
// pridame slevy k orderItem a upravime ceny
$productDiscounts = $productPurchaseItem->getDiscounts();
$decimal_Pieces = toDecimal($productPurchaseItem->getPieces());
$priceWithoutDiscounts = $productPurchaseItem->getPrice();
$vat = $priceWithoutDiscounts->getVat();
$Rounded_total_price_with_vat = $productPurchaseItem->getPriceWithDiscountsWithVat();
$Rounded_total_price = $Rounded_total_price_with_vat->removeVat($vat);
$totalPriceArray = formatPrice($Rounded_total_price, $vat->asFloat());
$Rounded_piece_price = $Rounded_total_price->div($decimal_Pieces);
if ($productDiscounts) {
$note = $product->parseNote($row['note']);
$productPurchaseItem->addDiscountsNote();
$note = array_replace_recursive($note, $productPurchaseItem->getNote());
$row['note'] = json_encode($note);
foreach ($productDiscounts as $productDiscount) {
$name = $productDiscount['name'] ?? 'Sleva';
$key = $productDiscount['id'].'-'.$name;
if (!isset($divideDiscounts[$key])) {
$divideDiscounts[$key] = DecimalConstants::zero();
}
$discountPrice = $productDiscount['discountPrice']->mul($decimal_Pieces)->addVat($vat)->value(2);
$divideDiscounts[$key] = $divideDiscounts[$key]->add($discountPrice);
}
}
}
}
// celkova cena objednavky, od ktere se odecita sleva
$orderTotalPrice = $orderTotalPrice->add($Rounded_total_price_with_vat);
// Products list for discounts
$product['totalPrice'] = $totalPriceArray;
$product['pieces'] = $Pieces;
// #########################################################################
// pricist prodane kusy
if (!$this->editMode && !$skipStore) {
$product->sell($IDv, $Pieces);
}
// #########################################################################
// zapsat polozku do DB
$this->updateSQL($this->items_table, ['piece_price' => $Rounded_piece_price, 'total_price' => $Rounded_total_price,
'pieces' => $decimal_Pieces, 'note' => $row['note'], ], ['id' => $row['id']]);
$dispatcher = ServiceContainer::getService('event_dispatcher');
$event = $dispatcher->dispatch(new OrderItemEvent(
$product, $IDv, $orderTotalPrice, $Pieces, [
'row' => $row,
'items_table' => $this->items_table,
], $this),
OrderItemEvent::ITEM_CREATED
);
$orderTotalPrice = $event->getPrice();
}
sqlFreeResult($SQL);
// pokud v $purchaseStateProducts jeste neco zbylo - jde o slevu na nejlevnejsi produkt,
// kdyz toho nejlevnejsiho produktu bylo napr. 5 kusu,
// tak v purchaseState byly rozdeleny na 2 ProductPurchaseItem - 1 ks se slevou + 4 ks bez slevy
// 4 ks bez slevy uz byly pridany, zbyva pridat jeste ten 1 nejlevnejsi kus se slevou
foreach ($purchaseStateProducts as $productPurchaseItem) {
$productDiscounts = $productPurchaseItem->getDiscounts();
$decimal_Pieces = toDecimal($productPurchaseItem->getPieces());
$priceWithoutDiscounts = $productPurchaseItem->getPrice();
$vat = $priceWithoutDiscounts->getVat();
$Rounded_total_price_with_vat = $productPurchaseItem->getPriceWithDiscountsWithVat();
$Rounded_total_price = $Rounded_total_price_with_vat->removeVat($vat);
if ($productDiscounts) {
$productPurchaseItem->addDiscountsNote();
foreach ($productDiscounts as $productDiscount) {
$name = $productDiscount['name'] ?? 'Sleva';
$key = $productDiscount['id'].'-'.$name;
if (!isset($divideDiscounts[$key])) {
$divideDiscounts[$key] = DecimalConstants::zero();
}
$discountPrice = $productDiscount['discountPrice']->mul($decimal_Pieces)->addVat($vat)->value(2);
$divideDiscounts[$key] = $divideDiscounts[$key]->add($discountPrice);
}
}
$note = json_encode($productPurchaseItem->getNote());
$qb = sqlQueryBuilder()
->insert($this->items_table)
->directValues([
'id_order' => $this->id,
'id_product' => $productPurchaseItem->getIdProduct(),
'id_variation' => $productPurchaseItem->getIdVariation(),
'pieces' => $decimal_Pieces,
'pieces_reserved' => $decimal_Pieces,
'piece_price' => $Rounded_total_price->div($decimal_Pieces),
'total_price' => $Rounded_total_price,
'descr' => $productPurchaseItem->getName(),
'tax' => $vat,
'note' => empty($note) ? '' : $note,
]);
$qb->execute();
$orderTotalPrice = $orderTotalPrice->add($Rounded_total_price_with_vat);
}
// Remove all non-product items
if (!$initial) {
sqlQuery('DELETE FROM '.getTableName($this->items_table)." WHERE id_product IS NULL AND id_order='{$this->id}'");
}
if (!$this->editMode) {
if (findModule(Modules::PRODUCTS_CHARGES)) {
/* @var PurchaseItemInterface $charge */
foreach ($purchaseState->getCharges() as $charge) {
$insert = [
'id_order' => $this->id,
'pieces' => 1,
'piece_price' => $charge->getPrice()->getPriceWithoutVat(),
'total_price' => $charge->getPrice()->getPriceWithoutVat(),
'descr' => $charge->getName(),
'tax' => $charge->getPrice()->getVat(),
'date' => new DateTime(),
'note' => json_encode($charge->getNote()),
];
if ($charge instanceof ProductPurchaseItem) {
$insert['id_product'] = $charge->getIdProduct();
$insert['id_variation'] = $charge->getIdVariation();
}
$this->insertSQL($this->items_table, $insert, [], ['date' => 'datetime']);
$insert['id'] = sqlInsertId();
$dispatcher = ServiceContainer::getService('event_dispatcher');
if ($charge instanceof ProductPurchaseItem) {
$dispatcher->dispatch(new OrderItemEvent(
$charge->getProduct(), $charge->getIdVariation(), $orderTotalPrice, 1, [
'row' => $insert,
'items_table' => $this->items_table,
], $this), OrderItemEvent::ITEM_CREATED);
$charge->getProduct()->sell($charge->getIdVariation(), 1);
} else {
$event = $dispatcher->dispatch(new OrderItemEvent(
null, null, $orderTotalPrice, 1, [
'row' => $insert,
'items_table' => $this->items_table,
], $this),
OrderItemEvent::NON_ITEM_CREATED
);
}
$orderTotalPrice = $orderTotalPrice->add($charge->getPrice()->getPriceWithVat());
}
}
$apply_discount_on_delivery = findModule(Modules::DELIVERY_TYPES, Modules::SUB_APPLY_DISCOUNT_ON_DELIVERY);
$free_delivery_no_discount = findModule(Modules::DELIVERY_TYPES, Modules::SUB_FREE_DELIVERY_NO_DISCOUNT);
// vypocitame cenu dopravy pro pripad APPLY_DISCOUNT_ON_DELIVERY:
// pokud po odecteni slevy jdeme do minusu,
// pricist cenu dopravy (apply_discount_on_delivery),
// je urcite placena, protoze cena je nulova,
// nebo muze byt zdarma, pokud free_delivery_no_discount = true
$orderTotalPriceNoDiscounts = $purchaseState->getProductsTotalPrice()->getPriceWithVat();
$orderTotalPriceNoDiscounts = $orderTotalPriceNoDiscounts->add($purchaseState->getChargesTotalPrice()->getPriceWithVat());
$totalPriceForDelivery = ($free_delivery_no_discount ? $orderTotalPriceNoDiscounts : DecimalConstants::zero());
$totalPriceForDelivery = new Price($totalPriceForDelivery, $this->currencyContext->getActive(), 0);
$delivery_price = $this->getDeliveryTypePrice($deliveryId, $totalPriceForDelivery, $freeDelivery);
// add discounts from PurchaseState
if (findModule(Modules::ORDER_DISCOUNT)) {
$usedDiscounts = $purchaseState->getUsedDiscounts();
/** @var PurchaseItemInterface $discount */
foreach ($purchaseState->getDiscounts() as $discount) {
if (!$discount->getIdDiscount()) {
continue;
}
$key = $discount->getIdDiscount().'-'.$discount->getName();
if (array_key_exists($key, $divideDiscounts)) {
// sleva je rozpocitana k jednotlivým produktům
// priceDiscount by mela byt 0 => polozka se nepridava
$priceDiscount = $discount->getPriceWithVat()->add($divideDiscounts[$key]);
} else {
// nastaveno NERozpočítat slevu k jednotlivým produktům =>
// bude pridana polozka s priceDiscount
// Backward compatibility - priceDiscount je zaokruhlena (podle nastaveni meny)
$priceDiscount = $discount->getPrice()->getPriceWithVat();
$priceDiscount = $discount->getPriceWithVat(); // zaokrouhlena na haléře
}
$priceDiscount = $priceDiscount->additiveInverse();
$isCoupon = ($discount->getNote()['discount_type'] ?? '') == 'use_coupon';
if ($priceDiscount->isPositive()) {
if (($apply_discount_on_delivery || $isCoupon) && $orderTotalPrice->lessThan($priceDiscount)) {
// pokud jdeme do minusu, pricist cenu dopravy (apply_discount_on_delivery)
$priceDiscount = Decimal::min($priceDiscount, $orderTotalPrice->add($delivery_price->getPriceWithVat()));
} else {
$priceDiscount = Decimal::min($priceDiscount, $orderTotalPrice);
}
}
if ($priceDiscount->isZero() && !($discount instanceof ProductPurchaseItem)) {
$this->saveUsedDiscount($discount->getIdDiscount(), null, $discount->getNote(), $discount->getPriceWithVat());
if (($key = array_search($discount->getIdDiscount(), $usedDiscounts)) !== null) {
unset($usedDiscounts[$key]);
}
continue;
}
$pieces = $discount->pieces ?? 1;
// remove vat
$priceDiscountWithoutVat = $priceDiscount->removeVat($discount->getPrice()->getVat());
$priceDiscountWithoutVat = DecimalConstants::zero()->sub($priceDiscountWithoutVat);
$piecePriceDiscountWithoutVat = $priceDiscountWithoutVat->div(toDecimal($pieces));
$insert = [
'id_order' => $this->id,
'pieces' => $pieces,
'piece_price' => $piecePriceDiscountWithoutVat,
'total_price' => $priceDiscountWithoutVat,
'descr' => $discount->getName(),
'tax' => $discount->getPrice()->getVat(),
'date' => new DateTime(),
'note' => json_encode($discount->getNote()),
];
if ($discount instanceof ProductPurchaseItem) {
$insert['id_product'] = $discount->getIdProduct();
$insert['id_variation'] = $discount->getIdVariation();
$insert['total_price'] = $discount->getPrice()->getPriceWithoutVat();
$insert['piece_price'] = $insert['total_price']->div(toDecimal($discount->getPieces()));
}
$this->insertSQL($this->items_table, $insert, [], ['date' => 'datetime']);
$insert['id'] = sqlInsertId();
$this->saveUsedDiscount($discount->getIdDiscount(), $insert['id'], $discount->getNote(), $discount->getPriceWithVat());
if (($key = array_search($discount->getIdDiscount(), $usedDiscounts)) !== null) {
unset($usedDiscounts[$key]);
}
$dispatcher = ServiceContainer::getService('event_dispatcher');
if ($discount instanceof ProductPurchaseItem) {
$dispatcher->dispatch(new OrderItemEvent(
$discount->getProduct(), $discount->getIdVariation(), $orderTotalPrice, $pieces, [
'row' => $insert,
'items_table' => $this->items_table,
], $this), OrderItemEvent::ITEM_CREATED);
$discount->getProduct()->sell($discount->getIdVariation(), $pieces);
} else {
$dispatcher->dispatch(new OrderItemEvent(
null, null, $orderTotalPrice, 1, [
'row' => $insert,
'items_table' => $this->items_table,
], $this), OrderItemEvent::NON_ITEM_CREATED);
}
// sub from total price
if (($discount->getNote()['discount_type'] ?? '') != 'use_coupon') {
// odecteme pokud to neni darkovy poukaz (Akce 'Uplatnit kupón')
$orderTotalPrice = $orderTotalPrice->sub($priceDiscount);
}
}
// save non-items used discounts (like free delivery)
foreach ($usedDiscounts as $usedDiscount) {
$this->saveUsedDiscount($usedDiscount);
}
}
// Potřebuju přepočítat váhu kvůli ceně dopravy, je tam zacachovaná nula
$this->getTotalWeight(true);
// Insert delivery type to DB
$totalPriceForDelivery = ($free_delivery_no_discount ? $orderTotalPriceNoDiscounts : $orderTotalPrice);
$totalPriceForDelivery = new Price($totalPriceForDelivery, $this->currencyContext->getActive(), 0);
$this->addDeliveryType($deliveryId, $freeDelivery, $totalPriceForDelivery);
}
$this->recalculate();
sqlFinishTransaction();
return true;
}
public static function getOpenOrders()
{
if (!findModule('order_edit')) {
return [];
}
$ret = [];
$id_user = Contexts::get(UserContext::class)->getActiveId();
if (!$id_user) {
return $ret;
}
$query = 'SELECT id
FROM '.getTableName('orders').' o
WHERE o.status IN ('.join(',', getStatuses('editable')).")
AND id_user='{$id_user}'
AND status_storno!=1
ORDER BY ID DESC";
$SQL = sqlQuery($query);
while (($row = sqlFetchAssoc($SQL)) !== false) {
$ret[] = $row['id'];
}
return $ret;
}
public function getDeliveryId()
{
if (!findModule('eshop_delivery')) {
return 0;
}
return $this->id_delivery;
}
public function getDeliveryType($deliveryId = null)
{
if (!findModule('eshop_delivery')) {
return new DeliveryType();
}
if (is_null($deliveryId)) {
$deliveryId = $this->getDeliveryId();
}
$delivery = new DeliveryType();
$deliveryType = $delivery->createFromDB($deliveryId);
$contextmanager = ServiceContainer::getService(\KupShop\KupShopBundle\Context\ContextManager::class);
$contextmanager->activateOrder($this, function () use ($deliveryType) {
$deliveryType->attachToOrder($this);
});
return $deliveryType;
}
public function cancelEdit()
{
sqlQuery('DELETE FROM '.getTableName('order_edit')." WHERE id_order='{$this->id}'");
}
public function getSecurityCode()
{
$orderArr = sqlQueryBuilder()
->select('id, order_no, date_created')
->from('orders')
->where(Operator::equals(['id' => $this->id]))
->sendToMaster()
->execute()->fetchAssociative();
$code = $orderArr['id'].'*'.$orderArr['order_no'].'*'.$orderArr['date_created'];
return md5($code);
}
public function getDetailUrl($success = 0)
{
if (findModule(\Modules::COMPONENTS)) {
if ($success == 1) {
return path('kupshop_content_orders_success', ['id' => $this->id, 'cf' => $this->getSecurityCode()]);
} else {
return path('kupshop_content_orders_order', ['id' => $this->id, 'cf' => $this->getSecurityCode()]);
}
}
// always return url with security code
return createScriptURL([
'URL' => 'launch.php',
's' => 'orderView',
'IDo' => $this->id,
'status' => $success,
'cf' => $this->getSecurityCode(),
'ESCAPE' => 'NO',
]);
}
public function getStatusText($status = null)
{
global $cfg;
if (is_null($status)) {
$status = $this->status;
}
return $cfg['Order']['Status']['global'][$status];
}
/**
* @param Decimal $totalPrice
* @param null $order
* @param bool $hidden
*
* @return DeliveryType[]
*/
public static function getDeliveryTypeList($totalPrice, $freeDelivery, $order = null, $hidden = false, $purchaseState = null)
{
// kdyz sem vleze decimal tak toho udelame Price
if ($totalPrice instanceof Decimal) {
$currencyContext = ServiceContainer::getService(CurrencyContext::class);
$totalPrice = new Price($totalPrice, $currencyContext->getActive(), 0);
}
/** @var DeliveryType[] $deliveryTypes */
$deliveryTypes = DeliveryType::getAll($hidden);
$ret = [];
foreach ($deliveryTypes as $id => $deliveryType) {
if (!$deliveryType->accept($totalPrice, $freeDelivery, $purchaseState)) {
continue;
}
$ret[$id] = $deliveryType;
}
return $ret;
}
/**
* @return Price|null
*/
public function getDeliveryTypePrice($deliveryId, $orderTotalPrice, $freeDelivery)
{
if (!findModule('eshop_delivery')) {
return null;
}
$deliveryType = $this->getDeliveryType($deliveryId);
$deliveryType->accept($orderTotalPrice, $freeDelivery, $this->getPurchaseState());
if ($deliveryType->payment_class) {
$deliveryType->payment_class->setOrder($this);
}
$delivery_price = $deliveryType->getPrice();
// convert to actual active currency
$delivery_price = PriceCalculator::convert($delivery_price, $this->currencyContext->getActive());
return $delivery_price;
}
/**
* @param $orderTotalPrice Price
*/
public function addDeliveryType($deliveryId, $freeDelivery, $orderTotalPrice)
{
if (!findModule('eshop_delivery')) {
return;
}
$deliveryType = $this->getDeliveryType($deliveryId);
$price = $this->getDeliveryTypePrice($deliveryId, $orderTotalPrice, $freeDelivery);
// pridat cenu dobirky do objednavky
if (!$price->getValue()->isZero() || findModule(Modules::ORDERS, Modules::SUB_FREE_DELIVERY_ROW)) {
$this->insertDeliveryItem($price, $deliveryType);
}
$SQL = sqlQuery('UPDATE orders SET delivery_type=:delivery, id_delivery=:id_delivery WHERE id=:id', ['id' => $this->id, 'delivery' => $deliveryType['name'], 'id_delivery' => $deliveryType['id']]);
if (sqlAffectedRows($SQL) > 0) {
$this->sendOrderEvent($this, OrderEvent::ORDER_DELIVERY_CHANGED);
}
}
public function changeDeliveryType($id_delivery)
{
$old_delivery_price = $this->getDeliveryPrice();
$contextmanager = ServiceContainer::getService(\KupShop\KupShopBundle\Context\ContextManager::class);
$contextmanager->activateOrder($this, function () use ($id_delivery, &$deleted) {
// delete delivery item
$deleted = 0;
foreach (QueryHint::withRouteTomaster(fn () => $this->fetchItems()) as $item) {
if (($item['note']['item_type'] ?? false) == 'delivery') {
$SQL = sqlQuery('DELETE FROM '.getTableName($this->items_table).' WHERE id_order=:id_order AND id=:id_item', ['id_order' => $this->id, 'id_item' => $item['id']]);
$deleted += sqlNumRows($SQL);
}
}
$this->recalculate(false);
$freeDelivery = returnSQLResult('SELECT oi.id FROM '.getTableName($this->items_table).' oi
LEFT JOIN '.getTableName('products')." p ON p.id=oi.id_product
WHERE oi.id_order=:id_order AND FIND_IN_SET('Z', p.campaign) > 0", ['id_order' => $this->id]);
if (!empty($id_delivery)) {
$old_delivery_type = $this->delivery_type;
$this->addDeliveryType($id_delivery, $freeDelivery, new Price($this->total_price, $this->currencyContext->getActive(), 0));
// save id delivery to Order object
$this->id_delivery = $id_delivery;
$deliveryType = DeliveryType::get($id_delivery, true);
if ($old_delivery_type != $deliveryType->name) {
$this->logChange('Změna typu dopravy z '.$old_delivery_type.' na '.$deliveryType->name);
}
}
$this->recalculate(true);
});
return ($deleted > 0) || $old_delivery_price['value_with_vat']->isZero();
}
public function getOrderPriceLevel()
{
if (!$this->id) {
return false;
}
$priceLevelId = $this->getData('price_level')['id'] ?? null;
if (!$priceLevelId && $this->id_user) {
$priceLevelId = sqlQueryBuilder()
->select('udpl.id_price_level')
->from('orders', 'o')
->leftJoin('o', 'users_dealer_price_level', 'udpl', 'udpl.id_user=o.id_user')
->where(Operator::equals(['o.id' => $this->id]))
->execute()->fetchOne();
}
if ($priceLevelId) {
$priceLevel = ServiceContainer::getService(\KupShop\CatalogBundle\PriceLevel::class);
$priceLevel->createFromDB($priceLevelId);
if (!empty($priceLevel->name)) {
return $priceLevel;
}
}
return false;
}
public function getTotalWeight($force = false)
{
if (!findModule(Modules::PRODUCTS, Modules::SUB_WEIGHT)) {
return 0.0;
}
if ($this->total_weight !== null && !$force) {
return (float) $this->total_weight;
}
$weight = 0.;
$products = array_column($this->fetchItems(), 'product');
Product::fetchSetsMulti($products, true);
$splitType = Settings::getDefault()['products_sets']['split'] ?? 'price_vat';
foreach ($this->fetchItems() as $item) {
if ($item instanceof OrderItem) {
$item = $item->getItem();
}
$item['weight'] = $item['weight'] ?: ($item['product']['weight'] ?? null);
if (!empty($item['weight'])) {
$pieces = abs($item['pieces']);
$weight += $item['weight'] * $pieces;
if (!empty($item['product']['sets']) && ($splitType != 'no_split')) {
foreach ($item['product']['sets'] as $set_product) {
$weight -= (!empty($set_product['weight']) ? $set_product['weight'] : 0) * $set_product['set_pieces'] * $pieces;
}
}
}
}
$this->total_weight = $weight;
return $this->total_weight;
}
public function getMaxItemWeight()
{
$maxItemWeight = 0.;
$products = array_column($this->fetchItems(), 'product');
Product::fetchSetsMulti($products, true);
foreach ($this->fetchItems() as $item) {
if (!empty($item['product']['sets'])) {
foreach ($item['product']['sets'] as $set_product) {
if ($set_product['weight'] > $maxItemWeight) {
$maxItemWeight = $set_product['weight'];
}
}
} else {
if ($item['weight'] > $maxItemWeight) {
$maxItemWeight = $item['weight'];
}
}
}
return $maxItemWeight;
}
public function getCurrency()
{
return $this->currency ?? $this->currencyContext->getDefaultId();
}
public function getLanguage()
{
return $this->id_language ?? $this->languageContext->getDefaultId();
}
public function getInvoiceNo()
{
if (findModule(Modules::INVOICES) && (Settings::getDefault()['generate_invoices'] == 'Y')) {
return $this->invoice_no;
}
return $this->invoice_no = $this->order_no;
}
public function generateInvoiceNo()
{
if (is_null($this->getInvoiceNo())) {
$this->sendOrderEvent($this, OrderEvent::ORDER_HANDLED);
}
return $this->invoice_no;
}
/**
* 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
{
return isset($this->{$offset}) ? $this->{$offset} : null;
}
public function getDataAll($forced = false)
{
if ($forced || $this->note_admin === null) {
$this->note_admin = sqlQueryBuilder()
->select('note_admin')
->from('orders')
->where(Operator::equals(['id' => $this->id]))
->sendToMaster()
->execute()
->fetchOne() ?: '';
}
return json_decode($this->note_admin ?? '', true);
}
/**
* @return mixed found value or FALSE
*/
public function getData($key, $forced = true)
{
$json = $this->getDataAll($forced);
if (!empty($json[$key])) {
return $json[$key];
} else {
return false;
}
}
public function getFlags()
{
return explodeFlags($this->flags ?? '');
}
public function setDataAll($Array)
{
$json = json_encode($Array);
$this->note_admin = $json;
$this->updateSQL('orders', ['note_admin' => $json], ['id' => $this->id]);
}
public function setData($key, $value)
{
$array = $this->getDataAll(true);
$array[$key] = $value;
$this->setDataAll($array);
}
public function isClosed()
{
$dbcfg = Settings::getDefault();
if (findModule(Modules::INVOICES) && !findRight('ORDER_EDIT_INVOICE') && ($this->invoice_no ?? false)) {
return true;
}
// Nepovolím editaci objednávky, která čeká na příjem zboží, protože položky stejně budu znova vytvářet při příjmu
if (findModule(Modules::RETURNS) && $this->getData('id_return') && $this->status == 100) {
return true;
}
if (empty($dbcfg->shop_orders_finished) || empty($this->date_handle)) {
return false;
}
$date_finished = DateTime::createFromFormat('Y-m-d', $dbcfg->shop_orders_finished);
return $this->date_handle <= $date_finished;
}
public function getHistory($all = false)
{
if ($all && $this->history) {
return $this->history;
}
$SQL = sqlQueryBuilder()->select('id, id_status, date, comment, custom_data')
->from('orders_history')
->where('id_order=:id_order')->setParameter('id_order', $this->id)
->orderBy('date', 'ASC');
if (!$all) {
$SQL->andWhere('notified > 0');
}
$history = sqlFetchAll($SQL);
foreach ($history as &$h) {
if (empty($h['custom_data'])) {
$h['custom_data'] = [];
} else {
$h['custom_data'] = json_decode($h['custom_data'], true);
}
}
$orderInfo = ServiceContainer::getService(OrderInfo::class);
$history = array_map(function ($v) use ($orderInfo) {
return $v + ['status_text' => $orderInfo->getOrderStatus($v['id_status'])];
}, $history);
return $history;
}
public function getDeliveryPrice()
{
if (empty($this->items)) {
$this->fetchItems();
}
$delivery_price = formatPrice(0);
foreach ($this->items as $item) {
if (empty($item['id_product']) && (($item['note']['item_type'] ?? false) == 'delivery')) {
$delivery_price = $item['total_price'];
break;
}
}
return $delivery_price;
}
public function hasSameAddress()
{
$this->fetchInvoice();
$fields = ['name', 'surname', 'firm', 'street', 'city', 'zip', 'state', 'country', 'custom_address', 'phone'];
foreach ($fields as $field) {
if ($this->{'delivery_'.$field} != $this->{'invoice_'.$field}) {
return false;
}
}
return true;
}
public function setPlaceholder($placeholder, $value)
{
$this->placeholders[$placeholder] = $value;
}
public function logChange($comment, $customerNotified = false)
{
if ($this->status >= 0 && !$this->editMode && !$this->suspendLogging) {
$this->logHistory($comment, $customerNotified);
}
}
/**
* @param bool $suspendLogging
*
* @return OrderBase
*/
public function setSuspendLogging($suspendLogging)
{
$this->suspendLogging = $suspendLogging;
return $this;
}
protected function isPaymentMethodForEET($method): bool
{
return $method == Payment::METHOD_CASH;
}
public function assignUser($id_user = null)
{
$fields = [
'name' => 'invoice_name',
'id' => 'id_user',
'surname' => 'invoice_surname',
'firm' => 'invoice_firm',
'ico' => 'invoice_ico',
'dic' => 'invoice_dic',
'street' => 'invoice_street',
'city' => 'invoice_city',
'zip' => 'invoice_zip',
'country' => 'invoice_country',
'phone' => 'invoice_phone',
'email' => 'invoice_email',
'delivery_name' => 'delivery_name',
'delivery_surname' => 'delivery_surname',
'delivery_firm' => 'delivery_firm',
'delivery_street' => 'delivery_street',
'delivery_city' => 'delivery_city',
'delivery_zip' => 'delivery_zip',
'delivery_country' => 'delivery_country',
];
if (!$id_user) {
foreach ($fields as $key => $field) {
$data[$field] = '';
}
$data['id_user'] = null;
} else {
$SQL = sqlQueryBuilder()->select('*')->from('users')->where('id =:id')->setParameter('id', $id_user)->execute()->fetchAll();
if (count($SQL) == 1) {
$user = $SQL[0];
foreach ($user as $key => $field) {
if (!empty($field) && isset($fields[$key])) {
$data[$fields[$key]] = $field;
}
}
}
}
if (!empty($data)) {
return $this->updateSQL('orders', $data, ['id' => $this->id]);
}
return false;
}
protected function insertDeliveryItem($price, $deliveryType)
{
$row = [
'id_order' => $this->id,
'id_product' => null,
'id_variation' => null,
'pieces' => 1,
'piece_price' => $price->getPriceWithoutVat(),
'total_price' => $price->getPriceWithoutVat(),
'descr' => $deliveryType->name,
'tax' => $price->getVat(),
'date' => new DateTime(),
'note' => '{"item_type":"delivery"}',
];
$this->insertSQL($this->items_table, $row, [], ['date' => 'datetime']);
$row['id'] = sqlInsertId();
$dispatcher = ServiceContainer::getService('event_dispatcher');
$event = $dispatcher->dispatch(new OrderItemEvent(
null, null, $this->total_price, 1, [
'row' => $row,
'items_table' => $this->items_table,
], $this), OrderItemEvent::NON_ITEM_CREATED);
}
/**
* @return OrderEvent
*/
protected function sendOrderEvent($order, $eventName)
{
$eventDispatcher = ServiceContainer::getService('event_dispatcher');
$event = new OrderEvent($order);
$eventDispatcher->dispatch($event, $eventName);
return $event;
}
/** @deprecated Use OrderUtil service instead */
public function addFlag($flag)
{
return sqlQueryBuilder()->update('orders')
->set('flags', 'ADD_TO_SET(:flag, flags)')
->setParameter('flag', $flag)
->where(Operator::equals(['id' => $this->id]))
->execute();
}
public function __wakeup()
{
$this->__construct($this->id);
QueryHint::withRouteToMaster(fn () => $this->createFromDB($this->id));
}
public function __sleep()
{
return ['id'];
}
public function getUser()
{
if (!$this->id_user) {
return null;
}
return User::createFromId($this->id_user);
}
public function getStore(): ?int
{
if (!findModule(\Modules::STORES)) {
return null;
}
if (!$this->store) {
$store = sqlQueryBuilder()->select('id_store')
->from('order_stores')
->where(Operator::equals(['id_order' => $this->id]))
->execute()->fetchOne();
if ($store === false && findModule(\Modules::SELLERS)) {
$store = sqlQueryBuilder()->select('id_store')
->from('sellers', 's')
->innerJoin('s', 'order_sellers', 'os', 's.id = os.id_seller')
->where(Operator::equals(['os.id_order' => $this->id]))
->execute()->fetchOne();
}
$this->store = $store === false ? null : $store;
}
return $this->store;
}
}
if (empty($subclass)) {
class Order extends OrderBase
{
}
}