getOrderList();
$orderList->andSpec(Operator::equals(['o.id' => $id]));
$orders = $orderList->getOrders();
if (!($order = $orders->current())) {
throw new GraphQLNotFoundException('Order not found');
}
$order->productList->fetchPhotos('product_gallery', ['Y', 'N']);
return new Order($order, $orders);
}
public function getOrders(?int $offset, int $limit, ?Parameters $sort, ?Parameters $filter): OrderCollection
{
$sortValues = $sort ? array_filter($sort->getData()) : [];
$filterValues = $filter ? array_filter($filter->getData()) : [];
$orderList = clone $this->orderList;
// limit
$orderList->limit(ApiUtil::getLimit($limit), $offset);
// sorting
foreach ($sortValues as $field => $sortValue) {
$orderList->andSpec(function (QueryBuilder $qb) use ($field, $sortValue) {
$qb->addOrderBy($field, $sortValue->value);
});
}
if ($specs = $this->getOrdersFilterSpecs($filterValues)) {
$orderList->andSpec(Operator::andX($specs));
}
return (new OrderCollection($orderList->getOrders($totalCount)))
->setItemsTotalCount($totalCount ?? 0)
->setLimit($limit)
->setOffset($offset);
}
public function stornoOrder(OrderStornoInput $input): OrderMutateResponse
{
try {
$order = \Order::get($input->id);
} catch (\InvalidArgumentException $e) {
throw new GraphQLNotFoundException($e->getMessage());
}
if ($order->status_storno == 1) {
throw new GraphQLValidationException('Cannot cancel order! Order is already cancelled.');
}
$stornoHistoryMessage = '[API] Stornování objednávky.';
if ($input->message) {
$stornoHistoryMessage .= 'Důvod: '.$input->message;
}
$order->logHistory($stornoHistoryMessage);
$order->storno(false, $input->message, $input->sendMail);
return new OrderMutateResponse(true, $this->getOrder($input->id));
}
public function updateOrder(OrderUpdateInput $input): OrderMutateResponse
{
try {
$order = \Order::get($input->id);
} catch (\InvalidArgumentException $e) {
throw new GraphQLNotFoundException($e->getMessage());
}
$updateLog = [];
// aktualizace polozek objednavky
if (ObjectUtil::isPropertyInitialized($input, 'items')) {
foreach ($input->items ?: [] as $item) {
if (!ObjectUtil::isPropertyInitialized($item, 'id') || empty($item->id)) {
throw new GraphQLValidationException(
'OrderUpdateInput.items.id: Item ID is mandatory when updating order items!'
);
}
/** @var OrderItem $orderItem */
$orderItem = $order->getItems()[$item->id] ?? false;
if (!$orderItem) {
throw new GraphQLValidationException(
sprintf('OrderUpdateInput.items: Item with ID "%s" is not included in the order!', $item->id)
);
}
if (ObjectUtil::isPropertyInitialized($item, 'delete')) {
if ($item->delete === true) {
$order->deleteItem($item->id);
$updateLog[] = "Smazání položky \"{$item->id}\": {$orderItem->getDescr()}; {$orderItem->getPieces()} ks";
continue;
}
}
if (ObjectUtil::isPropertyInitialized($item, 'quantity')) {
if ($item->quantity < 0) {
throw new GraphQLValidationException(
'OrderUpdateInput.items.quantity: Quantity cannot be negative value!'
);
}
// aktualizace poctu kusu u polozky
if ($orderItem->getPieces() != $item->quantity) {
$order->updateItem($item->id, $item->quantity);
$updateLog[] = "Aktualizace položky \"{$item->id}\": {$orderItem->getDescr()}; {$orderItem->getPieces()} -> {$item->quantity} ks";
}
}
}
// smazani items cache, aby se pripadne polozky prenacetly podle aktualniho stavu
$order->items = [];
}
// aktualizace cisla baliku
if (ObjectUtil::isPropertyInitialized($input, 'packageId')) {
$order->setPackageId($input->packageId);
$updateLog[] = "Nastaveno číslo balíku: {$input->packageId}";
}
// custom faktura - TODO: odebrat az si to na sartoru zmeni
if (ObjectUtil::isPropertyInitialized($input, 'invoiceUrl') && !ObjectUtil::isPropertyInitialized($input, 'documents')) {
$document = new OrderDocument();
$document->type = OrderDocumentType::from('order_invoice');
$document->url = $input->invoiceUrl;
$input->documents = [$document];
}
// custom doklady
if (ObjectUtil::isPropertyInitialized($input, 'documents')) {
$externalDocuments = $order->getData('externalDocuments') ?: [];
foreach ($input->documents ?: [] as $document) {
$externalDocuments[$document->type->getValue()] = $document->url;
$updateLog[] = sprintf('Nastaven doklad %s: %2$s', $document->type->getValue(), $document->url);
}
$order->setData('externalDocuments', $externalDocuments);
}
if (ObjectUtil::isPropertyInitialized($input, 'userId')) {
if (!$this->checkUserExists($input->userId)) {
throw new GraphQLNotFoundException(sprintf('User with ID %s was not found!', $input->userId));
}
$order->assignUser($input->userId);
}
// zmena stavu objednavky
if (ObjectUtil::isPropertyInitialized($input, 'status') && $input->status) {
if (!(getOrderStatuses()[$input->status->id] ?? false)) {
throw new GraphQLNotFoundException(
sprintf('Status with ID "%s" was not found!', $input->status->id)
);
}
if ($input->status->comment) {
$order->logHistory($input->status->comment);
}
if ($input->status->emailType) {
try {
$this->emailLocator->getEmailService($input->status->emailType);
} catch (UnknownEmailTypeException) {
throw new GraphQLValidationException(
sprintf('Email type "%s" does not exists!', $input->status->emailType)
);
}
}
if (!$input->status->userMessage) {
$input->status->userMessage = $this->getOrderMessageByStatus($input->status->id);
}
if (ObjectUtil::isPropertyInitialized($input->status, 'sendEmail')) {
$sendEmail = $input->status->sendEmail;
}
$order->changeStatus(
$input->status->id,
null,
$sendEmail ?? null,
$input->status->userMessage,
$input->status->emailType,
);
}
// zaloguju info o zmenach, ktere byly behem API volani provedeny
if (!empty($updateLog)) {
$order->logHistory('[API] Byly provedeny následující aktualizace:
'.implode('
', array_map(fn ($x) => "- {$x}", $updateLog)));
}
return new OrderMutateResponse(true, $this->getOrder($input->id));
}
private function getOrderMessageByStatus(int $statusId): ?string
{
$email = sqlQueryBuilder()
->select('name')
->from('emails')
->where(
Operator::equals(
[
'enabled' => 'Y',
'type' => OrderMessageEmail::getType(),
'order_status' => $statusId,
]
)
)->execute()->fetchOne();
if ($email) {
return $email;
}
return null;
}
public function getOrdersFilterSpecs(array $filterValues): ?callable
{
$specs = [];
$filter = [];
if ($filterValues['id'] ?? false) {
$specs[] = Operator::inIntArray((array) $filterValues['id'], 'o.id');
}
if ($filterValues['code'] ?? false) {
$codeFilter = (array) $filterValues['code'];
if (count($codeFilter) === 1) {
$specs[] = Operator::equals(['o.order_no' => reset($codeFilter)]);
} else {
$specs[] = Operator::inIntArray((array) $filterValues['code'], 'o.order_no');
}
}
if (!empty($filterValues['dateCreated'])) {
$specs[] = ApiUtil::getDateTimeFilter($filterValues['dateCreated'], 'o.date_created');
}
if (!empty($filterValues['dateUpdated'])) {
$specs[] = ApiUtil::getDateTimeFilter($filterValues['dateUpdated'], 'o.date_updated');
}
if ($filterValues['status'] ?? false) {
$filter['statuses'] = (array) $filterValues['status'];
}
if ($filterValues['userId'] ?? false) {
$filter['reg_users'] = (array) $filterValues['userId'];
}
if ($filterValues['deliveryId'] ?? false) {
$filter['deliveries'] = (array) $filterValues['deliveryId'];
}
if ($filterValues['paymentId'] ?? false) {
$filter['payments'] = (array) $filterValues['paymentId'];
}
if ($filterValues['sellerId'] ?? false) {
$filter['sellers'] = (array) $filterValues['sellerId'];
}
if ($filterValues['source'] ?? false) {
$filter['source'] = (array) $filterValues['source'];
}
if ($filterValues['dropshipId'] ?? false) {
$filter['dropship'] = (array) $filterValues['dropshipId'];
}
// deprecated - just backward compatibility
if (($filterValues['date_created']['from'] ?? false) || ($filterValues['date_created']['to'] ?? false)) {
$from = ApiUtil::prepareDateTimeForDB($filterValues['date_created']['from'] ?? null);
$to = ApiUtil::prepareDateTimeForDB($filterValues['date_created']['to'] ?? null);
$specs[] = Operator::between(
'o.date_created',
new \Range($from, $to)
);
}
return $this->ordersFilterSpecs->getSpecs($filter, $specs);
}
private function getOrderList(): OrderList
{
return clone $this->orderList;
}
/**
* @required
*/
public function setOrderList(OrderList $orderList): void
{
$this->orderList = $orderList;
}
public function createOrder(OrderCreateInput $orderCreateInput, array $fetchOptions = []): OrderMutateResponse
{
$this->validateOrderCreateInput($orderCreateInput);
return sqlGetConnection()->transactional(function () use ($orderCreateInput, $fetchOptions) {
return $this->contextManager->activateContexts([CountryContext::class => $orderCreateInput->deliveryAddress?->country ?? $orderCreateInput->invoiceAddress->country],
function () use ($orderCreateInput, $fetchOptions) {
$purchaseCalculateInput = new PurchaseCalculateInput();
$purchaseCalculateInput->items = $orderCreateInput->items;
$purchaseCalculateInput->coupons = $orderCreateInput->coupons ?? [];
$purchaseStateInfo = $this->graphqlPurchaseUtil->calculatePurchaseState($purchaseCalculateInput);
$purchaseState = $purchaseStateInfo->getPurchaseState() ?? new PurchaseState([]);
$languageContext = Contexts::get(LanguageContext::class);
$currencyContext = Contexts::get(CurrencyContext::class);
$flags = array_intersect($orderCreateInput->flags ?? [], array_keys(Config::get()['Order']['Flags']));
$orderData = [
'source' => OrderInfo::ORDER_SOURCE_API,
'note_invoice' => $orderCreateInput->invoice->note ?? null,
'date_created' => ApiUtil::prepareDateTimeForDB($orderCreateInput->dateCreated ?: new \DateTime()),
'date_accept' => ApiUtil::prepareDateTimeForDB($orderCreateInput->dateAccept) ?: null,
'date_handle' => ApiUtil::prepareDateTimeForDB($orderCreateInput->dateHandle) ?: null,
'date_delivered' => ApiUtil::prepareDateTimeForDB($orderCreateInput->dateDelivered) ?: null,
'date_due' => ApiUtil::prepareDateTimeForDB($orderCreateInput->dateDue) ?: null,
'status' => $orderCreateInput->status ?: 0,
'id_user' => $orderCreateInput->userId ?? null,
// invoice
'invoice_email' => $orderCreateInput->invoiceAddress->email,
'invoice_name' => $orderCreateInput->invoiceAddress->name,
'invoice_surname' => $orderCreateInput->invoiceAddress->surname,
'invoice_phone' => $orderCreateInput->invoiceAddress->phone ?? '',
'invoice_firm' => $orderCreateInput->invoiceAddress->firm ?? '',
'invoice_ico' => $orderCreateInput->invoiceAddress->ico ?? '',
'invoice_dic' => $orderCreateInput->invoiceAddress->dic ?? '',
'invoice_street' => $orderCreateInput->invoiceAddress->street ?? '',
'invoice_city' => $orderCreateInput->invoiceAddress->city ?? '',
'invoice_zip' => $orderCreateInput->invoiceAddress->zip ?? '',
'invoice_custom_address' => $orderCreateInput->invoiceAddress->customAddress ?? '',
'invoice_state' => $orderCreateInput->invoiceAddress->state ?? '',
'invoice_country' => $orderCreateInput->invoiceAddress->country ?? '',
// delivery
'delivery_name' => $orderCreateInput->deliveryAddress->name ?? $orderCreateInput->invoiceAddress->name ?? '',
'delivery_surname' => $orderCreateInput->deliveryAddress->surname ?? $orderCreateInput->invoiceAddress->surname ?? '',
'delivery_firm' => $orderCreateInput->deliveryAddress->firm ?? $orderCreateInput->invoiceAddress->firm ?? '',
'delivery_street' => $orderCreateInput->deliveryAddress->street ?? $orderCreateInput->invoiceAddress->street ?? '',
'delivery_city' => $orderCreateInput->deliveryAddress->city ?? $orderCreateInput->invoiceAddress->city ?? '',
'delivery_zip' => $orderCreateInput->deliveryAddress->zip ?? $orderCreateInput->invoiceAddress->zip ?? '',
'delivery_country' => $orderCreateInput->deliveryAddress->country ?? $orderCreateInput->invoiceAddress->country ?? '',
'delivery_state' => $orderCreateInput->deliveryAddress->state ?? $orderCreateInput->invoiceAddress->state ?? '',
'delivery_custom_address' => $orderCreateInput->deliveryAddress->customAddress ?? $orderCreateInput->invoiceAddress->customAddress ?? '',
'delivery_phone' => $orderCreateInput->deliveryAddress->phone ?? $orderCreateInput->invoiceAddress->phone ?? '',
'delivery_email' => $orderCreateInput->deliveryAddress->email ?? '',
'flags' => implode(',', $flags),
'note_user' => $orderCreateInput->noteUser ?? '',
];
$customData = [];
foreach ($orderCreateInput->data ?? [] as $data) {
$customData[$data->key] = $data->value;
}
$orderData['note_admin'] = json_encode($customData);
if (findModule(\Modules::INVOICES) && $orderCreateInput->invoice && ObjectUtil::isPropertyInitialized($orderCreateInput->invoice, 'code')) {
$orderData['invoice_no'] = $orderCreateInput->invoice->code ?? null;
}
if (findModule(\Modules::CURRENCIES)) {
$orderData['id_language'] = $orderCreateInput->language ?? $languageContext->getDefaultId();
$orderData['currency'] = $orderCreateInput->currency ?? $currencyContext->getDefaultId();
$orderData['currency_rate'] = $orderCreateInput->currencyRate ?? $currencyContext->getOrDefault($orderData['currency'])->getRate();
}
$purchaseState->setSource(OrderInfo::ORDER_SOURCE_API);
if ($deliveryId = $orderCreateInput->deliveryType?->id ?? false) {
$deliveryType = \DeliveryType::get($deliveryId, true);
$purchaseState->setDeliveryType($deliveryType);
if (($deliveryPrice = $orderCreateInput->deliveryType?->deliveryPrice) !== null) {
$deliveryVat = $deliveryType->getPrice()->getVat()->asFloat();
$price = new Price(toDecimal($deliveryPrice)->removeVat($deliveryVat), $currencyContext->getOrDefault($orderData['currency']), $deliveryVat);
$purchaseState->setDeliveryPrice($price);
}
}
$purchaseState->setCustomData(
[
'order' => $orderData,
]
);
$purchaseState = $this->purchaseUtil->recalculateTotalPrices($purchaseState);
$order = $this->purchaseUtil->createOrderFromPurchaseState(
purchaseState: $purchaseState,
useDeliveryFromPurchaseState: true,
onCreatedCallback: fn (int $orderId) => $this->insertOrderDropshipment($orderId, $orderCreateInput)
);
$order->logHistory('[API] Objednávka vznikla přes API');
// log user note to order so it is visible in history
if ($orderCreateInput->noteUser) {
$order->logHistory($orderCreateInput->noteUser);
}
return new OrderMutateResponse(true, $this->getOrder($order->id, $fetchOptions));
});
});
}
protected function validateOrderCreateInput(OrderCreateInput $input): void
{
if ($this->dropshipmentUtil) {
if (!$this->dropshipmentUtil->getDropshipment($input->dropshipment->id)) {
throw new GraphQLValidationException('OrderCreateInput.dropshipment.id: Given dropshipment ID does not exist!');
}
if (empty($input->dropshipment->externalId)) {
throw new GraphQLValidationException('OrderCreateInput.dropshipment.externalId: Order externalId cannot be empty!');
}
$dropshipmentOrderExists = sqlQueryBuilder()
->select('id_order')
->from('order_dropshipment')
->where(Operator::equals(['id_external' => $input->dropshipment->externalId]))
->execute()->fetchOne();
if ($dropshipmentOrderExists) {
throw new GraphQLValidationException(
sprintf('OrderCreateInput.dropshipment.externalId: Order with this external ID already exists!')
);
}
}
if ($input->status && !$this->checkOrderStatus($input->status)) {
throw new GraphQLValidationException('Specified order status is not valid. Use configuration.orderStatuses to get available statuses.');
}
if ($input->invoice) {
if (ObjectUtil::isPropertyInitialized($input->invoice, 'code') && !empty($input->invoice->code) && $this->checkInvoiceExists($input->invoice->code)) {
throw new GraphQLValidationException(sprintf('Invoice with this code "%s" already exists', $input->invoice->code));
}
}
if ($input->language && !$this->checkLanguageValid($input->language)) {
throw new GraphQLValidationException(sprintf('Language "%s" is not supported.', $input->language));
}
if ($input->currency && !$this->checkCurrencyValid($input->currency)) {
throw new GraphQLValidationException(sprintf('Currency "%s" is not supported', $input->currency));
}
if ($input->deliveryType?->id && !$this->checkDeliveryTypeValid($input->deliveryType->id)) {
throw new GraphQLValidationException('Specified delivery type is not valid. Use configuration.deliveryTypes to get available delivery types.');
}
if ($input->userId && !$this->checkUserExists($input->userId)) {
throw new GraphQLValidationException(sprintf('User with this ID "%s" does not exist', $input->userId));
}
}
protected function insertOrderDropshipment(int $orderId, OrderCreateInput $input): void
{
$dropshipmentData = [];
foreach ($input->dropshipment->data ?? [] as $data) {
$dropshipmentData[$data->key] = $data->value;
}
$dropshipment = [
'id_order' => $orderId,
'id_dropshipment' => $input->dropshipment->id,
'id_external' => $input->dropshipment->externalId,
'data' => json_encode($dropshipmentData),
];
sqlQueryBuilder()
->insert('order_dropshipment')
->directValues($dropshipment)
->execute();
}
public function checkOrderStatus(int $orderStatus): bool
{
return in_array($orderStatus, array_keys(getOrderStatuses()));
}
public function checkInvoiceExists(?string $code): bool
{
return sqlQueryBuilder()
->select('id')
->from('orders')
->where(Operator::equals(['invoice_no' => $code]))
->execute()->fetchOne();
}
public function checkLanguageValid(string $language): bool
{
return isset(Contexts::get(LanguageContext::class)->getAll()[$language]);
}
public function checkCurrencyValid(string $currency): bool
{
$currencyContext = Contexts::get(CurrencyContext::class);
return $currencyContext->isValid($currency);
}
public function checkDeliveryTypeValid(int $deliveryId): bool
{
return \DeliveryType::get($deliveryId, true)->id > 0;
}
public function checkUserExists(int $userId): bool
{
return sqlQueryBuilder()
->select('id')
->from('users')
->where(Operator::equals(['id' => $userId]))
->execute()->fetchOne();
}
}