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 '
';
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('
', $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
{
}
}