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 'QR Platba'; 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 { } }