2728 lines
100 KiB
PHP
2728 lines
100 KiB
PHP
<?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í e‑mailů
|
||
$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
|
||
{
|
||
}
|
||
}
|