1149 lines
43 KiB
PHP
1149 lines
43 KiB
PHP
<?php
|
|
|
|
namespace KupShop\BalikonosBundle;
|
|
|
|
use KupShop\AdminBundle\OrdersMassProcess;
|
|
use KupShop\BalikonosBundle\BalikobotAdapters\B2A\IBalikobotB2AAdapter;
|
|
use KupShop\BalikonosBundle\BalikobotAdapters\IBalikobotAdapter;
|
|
use KupShop\BalikonosBundle\Exception\BalikobotB2AValidationException;
|
|
use KupShop\BalikonosBundle\Exception\BalikonosException;
|
|
use KupShop\BalikonosBundle\Util\BalikobotAdapterUtil;
|
|
use KupShop\I18nBundle\Util\PriceConverter;
|
|
use KupShop\KupShopBundle\Config;
|
|
use KupShop\KupShopBundle\Context\ContextManager;
|
|
use KupShop\KupShopBundle\Context\CountryContext;
|
|
use KupShop\KupShopBundle\Context\CurrencyContext;
|
|
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
|
use KupShop\KupShopBundle\Util\Functional\Mapping;
|
|
use KupShop\OrderingBundle\Util\Order\OrderItemInfo;
|
|
use KupShop\ReclamationsBundle\Entity\ReclamationEntity;
|
|
use KupShop\ReturnsBundle\Deliveries\Utils\ReturnDeliveries;
|
|
use KupShop\ReturnsBundle\Entity\ReturnEntity;
|
|
use KupShop\ReturnsBundle\Util\ReturnsUtil;
|
|
use Query\Operator;
|
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
|
|
|
class Balikobot implements IBalikonos
|
|
{
|
|
/** Sloupec close na databázi: 0=nová, 1=vytištěný štítek, 2=uzavřený svoz, 3=doručeno */
|
|
use \DatabaseCommunication;
|
|
use OrdersMassProcess;
|
|
public const TYPE_ORDER = 'order';
|
|
public const TYPE_RECLAMATION = 'reclamation';
|
|
public const TYPE_CUSTOM = 'custom';
|
|
|
|
/*
|
|
* https://client.balikobot.cz
|
|
*/
|
|
protected $testCredentials = [
|
|
'user' => 'test20cztest',
|
|
'key' => 'QKCufcWs',
|
|
];
|
|
|
|
protected $request_url = 'https://api.balikobot.cz';
|
|
protected $request_url_v2 = 'https://apiv2.balikobot.cz';
|
|
|
|
public $orders = [];
|
|
|
|
public $IDs = [];
|
|
|
|
/** @var string Balikobot carrier code (eg: cp, gls) */
|
|
protected $carrierCode;
|
|
|
|
/** @var CurrencyContext */
|
|
protected $currencyContext;
|
|
|
|
/** @var PriceConverter|null */
|
|
protected $priceConverter;
|
|
|
|
/** @var OrderItemInfo */
|
|
protected $orderItemInfo;
|
|
|
|
/**
|
|
* @required
|
|
*/
|
|
public BalikobotAdapterUtil $balikobotAdapterUtil;
|
|
|
|
protected ?ReturnsUtil $returnsUtil;
|
|
|
|
protected ?ReturnDeliveries $returnDeliveriesUtil;
|
|
|
|
/**
|
|
* @var int
|
|
*/
|
|
public $response;
|
|
/**
|
|
* @var bool
|
|
*/
|
|
public $authorizationFailed;
|
|
|
|
private $credentials = [
|
|
'user' => null,
|
|
'key' => null,
|
|
];
|
|
|
|
public function __construct(CurrencyContext $currencyContext)
|
|
{
|
|
$this->currencyContext = $currencyContext;
|
|
}
|
|
|
|
/**
|
|
* @required
|
|
*/
|
|
public function setOrderItemInfo(OrderItemInfo $orderItemInfo): void
|
|
{
|
|
$this->orderItemInfo = $orderItemInfo;
|
|
}
|
|
|
|
/**
|
|
* @required
|
|
*/
|
|
public function setPriceConverter(?PriceConverter $priceConverter = null): void
|
|
{
|
|
$this->priceConverter = $priceConverter;
|
|
}
|
|
|
|
/**
|
|
* @required
|
|
*/
|
|
public function setReturnsUtil(?ReturnsUtil $returnsUtil): void
|
|
{
|
|
$this->returnsUtil = $returnsUtil;
|
|
}
|
|
|
|
/**
|
|
* @required
|
|
*/
|
|
public function setReturnDeliveriesUtil(?ReturnDeliveries $returnDeliveriesUtil): void
|
|
{
|
|
$this->returnDeliveriesUtil = $returnDeliveriesUtil;
|
|
}
|
|
|
|
public function setCredentials($user, $key)
|
|
{
|
|
$this->credentials['user'] = $user;
|
|
$this->credentials['key'] = $key;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setCredentialsByName($name)
|
|
{
|
|
if ($credentials = $this->getCredentialsByName($name)) {
|
|
$this->setCredentials($credentials['user'], $credentials['key']);
|
|
|
|
return true;
|
|
}
|
|
|
|
throw new BalikonosException('Balíky se nepodařilo odeslat. Uživatel "'.$name.'" nebyl nalezen.');
|
|
}
|
|
|
|
public function getCredentialsByName($name)
|
|
{
|
|
foreach ($this->getCollectionPlaces() as $user) {
|
|
if ($user['user'] == $name) {
|
|
return $user;
|
|
}
|
|
}
|
|
|
|
if (isDevelopment()) {
|
|
return $this->testCredentials;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getCollectionPlaces()
|
|
{
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
return $dbcfg->balikobot['users'] ?? [];
|
|
}
|
|
|
|
public function hasMultipleCollectionPlaces()
|
|
{
|
|
$users = $this->getCollectionPlaces();
|
|
if (count($users) > 1) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function getSavedBalikobotUser()
|
|
{
|
|
if ($balikobotUser = getVal('balikobot_user')) {
|
|
return $balikobotUser;
|
|
}
|
|
|
|
return $_COOKIE['balikobot_user'] ?? null;
|
|
}
|
|
|
|
public function checkCredentials()
|
|
{
|
|
if (empty($this->credentials['user']) || empty($this->credentials['key'])) {
|
|
if ($this->hasMultipleCollectionPlaces()) {
|
|
throw new BalikonosException('Nevybrali jste svozové místo!');
|
|
}
|
|
|
|
$users = $this->getCollectionPlaces();
|
|
if (empty($users)) {
|
|
throw new BalikonosException('Nemáte vyplněné žádně svozové místo v Nastavení / Nastavení Eshopu / Balíkobot ');
|
|
}
|
|
|
|
$user = reset($users);
|
|
$this->setCredentials($user['user'], $user['key']);
|
|
}
|
|
}
|
|
|
|
public function getUser(): ?string
|
|
{
|
|
$config = Config::get();
|
|
|
|
$this->checkCredentials();
|
|
|
|
return ($config['Modules']['balikonos']['test'] ?? false) ? $this->testCredentials['user'] : $this->credentials['user'];
|
|
}
|
|
|
|
public function getKey(): ?string
|
|
{
|
|
$config = Config::get();
|
|
|
|
$this->checkCredentials();
|
|
|
|
return ($config['Modules']['balikonos']['test'] ?? false) ? $this->testCredentials['key'] : $this->credentials['key'];
|
|
}
|
|
|
|
public function createCurl($encodedBody, string $apiResourceType, $v2 = false)
|
|
{
|
|
if ($apiResourceType == 'zasilkovna/services') {
|
|
$apiResourceType = 'v2/'.$apiResourceType;
|
|
}
|
|
|
|
$requestUrl = $this->request_url;
|
|
if ($v2) {
|
|
$requestUrl = $this->request_url_v2;
|
|
}
|
|
|
|
// TODO: rozlisit api verzi podle příznaku v body
|
|
$url = $requestUrl.'/'.$apiResourceType;
|
|
|
|
$curl = curl_init();
|
|
curl_setopt($curl, CURLOPT_URL, $url);
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($curl, CURLOPT_HEADER, false);
|
|
curl_setopt($curl, CURLOPT_POST, true);
|
|
|
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $encodedBody);
|
|
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
|
'Authorization: Basic '.base64_encode($this->getUser().':'.$this->getKey()),
|
|
'Content-Type: application/json',
|
|
'BB-Partner: .k.nwwKUE50aJP4a',
|
|
]);
|
|
|
|
return $curl;
|
|
}
|
|
|
|
public function curlExecute($curl)
|
|
{
|
|
return curl_exec($curl);
|
|
}
|
|
|
|
private function isResponseAuthorized($response)
|
|
{
|
|
if (strpos($response, '401 Unauthorized') !== false) {
|
|
return false;
|
|
}
|
|
|
|
if (strpos($response, '403 Forbidden') !== false) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function getOrdersData($complete = true)
|
|
{
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
foreach ($this->IDs as $ID => $data) {
|
|
$realOrder = true;
|
|
$type = $data['type'] ?? self::TYPE_ORDER;
|
|
|
|
if ($type != self::TYPE_ORDER) {
|
|
$ID = null;
|
|
$realOrder = false;
|
|
}
|
|
|
|
if ($data['order'] ?? false) {
|
|
$orderCls = $data['order'];
|
|
} else {
|
|
$orderCls = new \Order();
|
|
$orderCls->createFromDB($ID, true);
|
|
}
|
|
|
|
$balikobot = $orderCls->getDeliveryType($orderCls->getDeliveryId())->getDelivery()->getCustomData()['balikobot'] ?? null;
|
|
|
|
$info = [
|
|
'invoice_email' => $orderCls->invoice_email,
|
|
'invoice_name' => $orderCls->delivery_name ?: $orderCls->invoice_name,
|
|
'invoice_surname' => $orderCls->delivery_surname ?: $orderCls->invoice_surname,
|
|
'invoice_firm' => $orderCls->delivery_firm,
|
|
'invoice_street' => !empty($orderCls->delivery_street) ? $orderCls->delivery_street : $orderCls->invoice_street,
|
|
'invoice_custom_address' => !empty($orderCls->delivery_custom_address) ? $orderCls->delivery_custom_address : $orderCls->invoice_custom_address,
|
|
'invoice_city' => !empty($orderCls->delivery_city) ? $orderCls->delivery_city : $orderCls->invoice_city,
|
|
'invoice_zip' => !empty($orderCls->delivery_zip) ? $orderCls->delivery_zip : $orderCls->invoice_zip,
|
|
'invoice_country' => !empty($orderCls->delivery_country) ? $orderCls->delivery_country : $orderCls->delivery_country,
|
|
'invoice_phone' => !empty($orderCls->delivery_phone) ? $orderCls->delivery_phone : $orderCls->invoice_phone,
|
|
'order_no' => $orderCls->order_no,
|
|
'total_price' => $orderCls->total_price,
|
|
'currency' => $orderCls->currency ?? $this->currencyContext->getDefaultId(),
|
|
'id' => $ID,
|
|
'size' => $data['size'] ?? null,
|
|
'packages' => !empty($data['packages']) ? $data['packages'] : null,
|
|
'note' => $data['note'] ?? null,
|
|
'type' => $type,
|
|
];
|
|
|
|
if ($weight = (!empty($data['weights']) ? $data['weights'] : $orderCls->getTotalWeight())) {
|
|
$info['weight'] = $weight;
|
|
}
|
|
|
|
if ($info['invoice_country'] == 'SK') {
|
|
// SK - balikobot vrati chybu - Špatný formát PSČ příjemce. PSČ musí obsahovat 5 čísel a začínat číslicí 0 / 8 / 9.
|
|
$info['invoice_zip'] = str_pad($info['invoice_zip'], 5, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
$info = array_merge($info, array_filter($data));
|
|
$info['id_delivery'] = !empty($info['id_delivery']) ? $info['id_delivery'] : $orderCls->getDeliveryType()->id_delivery;
|
|
|
|
if (!$this->isDeliverySupported($info['id_delivery'])) {
|
|
throw new BalikonosException(
|
|
sprintf('Odesíláte objednávku s dopravou %s, která není spárována s Balíkobotem. Spárování nastavte v Nastavení Eshopu, na záložce Balíkobot', \Delivery::getAll()[$info['id_delivery']]->name)
|
|
);
|
|
}
|
|
|
|
// set proper carrierCode for current order
|
|
$info['carrier_code'] = $dbcfg['balikobot']['delivery_type'][$info['id_delivery']]['carrier'];
|
|
$this->carrierCode = $info['carrier_code'];
|
|
|
|
if ($complete) {
|
|
$orderNumber = $info['order_id'] ?? $orderCls->order_no;
|
|
|
|
$order = [
|
|
'balikobot_array' => [
|
|
// TODO: eid must be unique for each package (multiple packages for single order?)
|
|
'eid' => $orderNumber.'_'.time(),
|
|
// order_id is not order_no, but package id
|
|
// 'order_id' => $orderCls->order_no,
|
|
'real_order_id' => $orderNumber,
|
|
'vs' => $orderNumber,
|
|
'service_type' => $dbcfg['balikobot']['delivery_type'][$info['id_delivery']]['carrierService'],
|
|
'price' => \Decimal::max(toDecimal($info['total_price'] ?? 0), \DecimalConstants::one())->printFloatValue(2),
|
|
'ins_currency' => $orderCls->currency ?? $this->currencyContext->getDefaultId(),
|
|
'rec_phone' => $info['invoice_phone'],
|
|
'rec_name' => $info['invoice_name'].' '.$info['invoice_surname'],
|
|
'rec_firm' => implode(', ', array_filter([$info['invoice_firm'], $info['invoice_custom_address']])),
|
|
'rec_street' => $info['invoice_street'],
|
|
'rec_city' => $info['invoice_city'],
|
|
'rec_email' => $info['invoice_email'],
|
|
'rec_zip' => $this->getPostalCode($orderCls, $info['invoice_zip'], $info['id_delivery']),
|
|
'rec_country' => empty($info['invoice_country']) ? 'CZ' : $info['invoice_country'],
|
|
'del_evening' => false,
|
|
'size' => $info['size'],
|
|
'note' => $info['note'],
|
|
|
|
// return track URL
|
|
'return_track' => true,
|
|
|
|
// display text errors?
|
|
'return_full_errors' => true,
|
|
|
|
'reference' => $orderNumber,
|
|
],
|
|
];
|
|
|
|
if ($realOrder) {
|
|
$order['balikobot_array']['content_invoice_number'] = $orderCls->getInvoiceNo();
|
|
$order['balikobot_array']['content_issue_date'] = ($orderCls->date_handle ?? new \DateTime())->format('Y-m-d');
|
|
}
|
|
|
|
$maxPrice = $balikobot['max_order_price'] ?? null;
|
|
|
|
if (is_numeric($maxPrice) && $maxPrice < $order['balikobot_array']['price']) {
|
|
$order['balikobot_array']['price'] = $maxPrice;
|
|
}
|
|
|
|
if ((float) ($info['weight'] ?? 0) > 0) {
|
|
$order['balikobot_array']['weight'] = (float) $info['weight'];
|
|
}
|
|
|
|
if ($realOrder && (!empty($balikobot))) {
|
|
$order = $this->addCustomField($order, $balikobot, $orderCls);
|
|
}
|
|
} else {
|
|
$order = [];
|
|
}
|
|
|
|
if ($realOrder && ($remaining = $orderCls->getRemainingPayment()) > 0 && $complete) {
|
|
if (!findModule('currencies') || $orderCls->currency == 'CZK') {
|
|
$scale = 0;
|
|
} else {
|
|
$scale = 2;
|
|
}
|
|
|
|
$delivery_type = $orderCls->getDeliveryType($orderCls->getDeliveryId());
|
|
|
|
$activeCurrency = $orderCls->currency ?? $this->currencyContext->getDefaultId();
|
|
|
|
if ((($dbcfg->balikobot['cod_remaining'] ?? false) == 'Y') || (!empty($delivery_type->payment_class) && $delivery_type->payment_class->getPayMethod() == \Payment::METHOD_COD)) {
|
|
$contextManager = ServiceContainer::getService(ContextManager::class);
|
|
$contextManager->activateContexts([CurrencyContext::class => $activeCurrency], function () use (&$order, $remaining, $scale, $activeCurrency) {
|
|
$order['balikobot_array']['cod_price'] = roundPrice(toDecimal($remaining), decimal: 2, bata: false)->printFloatValue($scale);
|
|
$order['balikobot_array']['cod_currency'] = $activeCurrency;
|
|
});
|
|
}
|
|
}
|
|
|
|
if (isset($data['cod_price'])) {
|
|
$order['balikobot_array']['cod_price'] = $data['cod_price'];
|
|
$info['cod_price'] = $data['cod_price'];
|
|
|
|
$order['balikobot_array']['cod_currency'] = $orderCls->currency ?? $this->currencyContext->getDefaultId();
|
|
}
|
|
|
|
if (!empty($data)) {
|
|
$order = array_merge($order, $data);
|
|
}
|
|
|
|
$tmpData = array_merge($order, $info);
|
|
|
|
if ($complete) {
|
|
if (!in_array($tmpData['balikobot_array']['rec_country'], CountryContext::EU_COUNTRIES)) {
|
|
$tmpData['balikobot_array'] = $this->addDeliveryCosts($orderCls, $tmpData['balikobot_array']);
|
|
$tmpData['balikobot_array'] = $this->addContentData($orderCls, $tmpData['balikobot_array']);
|
|
}
|
|
$adapter = ServiceContainer::getService('kupshop.balikobot.adapter.'.$this->carrierCode, ContainerInterface::NULL_ON_INVALID_REFERENCE);
|
|
if ($adapter instanceof IBalikobotAdapter) {
|
|
$tmpData['balikobot_array'] = $adapter->transformOrderData($orderCls, $tmpData['balikobot_array'], $info);
|
|
}
|
|
}
|
|
|
|
if ($info['packages'] > 1) {
|
|
$zeroPriceForSubPackages = $tmpData['balikobot_array']['_zeroPriceForSubPackages'] ?? true;
|
|
unset($tmpData['balikobot_array']['_zeroPriceForSubPackages']);
|
|
if ($weight) {
|
|
$tmpData['balikobot_array']['weight'] = toDecimal($tmpData['balikobot_array']['weight'])->div(toDecimal($info['packages']))->asFloat();
|
|
}
|
|
$tmpData['balikobot_array']['order_number'] = 1;
|
|
$tmpPackagesData = $tmpData;
|
|
for ($i = 2; $i <= $info['packages']; $i++) {
|
|
$tmpPackagesData['balikobot_array']['order_number'] = $i;
|
|
$tmpPackagesData['balikobot_array']['cod_price'] = 0;
|
|
if ($zeroPriceForSubPackages) {
|
|
$tmpPackagesData['balikobot_array']['price'] = 0;
|
|
}
|
|
$tmpData['additional_orders'][] = $tmpPackagesData['balikobot_array'];
|
|
}
|
|
}
|
|
$this->orders[] = $tmpData;
|
|
}
|
|
|
|
return $this->orders;
|
|
}
|
|
|
|
protected function addDeliveryCosts(\Order $order, array $data): array
|
|
{
|
|
$deliveryPrice = 0;
|
|
|
|
foreach ($order->fetchItems() as $item) {
|
|
if ($this->orderItemInfo->getItemType($item) === OrderItemInfo::TYPE_DELIVERY) {
|
|
$deliveryPrice = $item['total_price']['value_with_vat']->asFloat();
|
|
break;
|
|
}
|
|
}
|
|
|
|
$data['delivery_costs'] = $deliveryPrice;
|
|
if ($this->priceConverter && ($currencyEUR = ($this->currencyContext->getAll()['EUR'] ?? null))) {
|
|
$data['delivery_costs_eur'] = $this->priceConverter->convert(
|
|
$order->getCurrency(),
|
|
$currencyEUR,
|
|
$deliveryPrice
|
|
);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
protected function addContentData(\Order $order, array $data)
|
|
{
|
|
$contentData = [];
|
|
|
|
foreach ($order->fetchItems() as $item) {
|
|
if (!empty($item['product'])) {
|
|
/** @var \Product $product */
|
|
$product = $item['product'];
|
|
$contentDataItem = [
|
|
'content_name_en' => $item['descr'],
|
|
'content_name' => $item['descr'],
|
|
'content_weight' => $product->weight,
|
|
'content_pieces' => $item['pieces'],
|
|
'content_price' => $item['total_price']['value_with_vat']->asFloat(),
|
|
'content_description' => $item['descr'],
|
|
'content_country' => 'CZ',
|
|
'content_currency' => $order->getCurrency(),
|
|
'content_customs_code' => substr($product->getCN(), 0, 8),
|
|
];
|
|
|
|
if (!empty($item['ean'])) {
|
|
$contentDataItem['content_ean'] = $item['ean'];
|
|
}
|
|
|
|
if ($this->priceConverter && ($currencyEUR = ($this->currencyContext->getAll()['EUR'] ?? null))) {
|
|
$contentDataItem['content_price_eur'] = $this->priceConverter->convert(
|
|
$order->getCurrency(),
|
|
$currencyEUR,
|
|
$item['total_price']['value_with_vat']
|
|
)->asFloat();
|
|
}
|
|
|
|
$contentData[] = $contentDataItem;
|
|
}
|
|
}
|
|
|
|
$data['ins_currency'] = $order->getCurrency();
|
|
$data['content_data'] = $contentData;
|
|
|
|
return $data;
|
|
}
|
|
|
|
private function getPostalCode($orderCls, $zip, $deliveryId)
|
|
{
|
|
$dbcfg = \Settings::getDefault();
|
|
if ($dbcfg['balikobot']['delivery_type'][$deliveryId]['carrierService'] == 'NP') {
|
|
$delivery_data = $orderCls->getData('delivery_data');
|
|
|
|
return !empty($delivery_data['psc']) ? $delivery_data['psc'] : $zip;
|
|
} else {
|
|
return $zip;
|
|
}
|
|
}
|
|
|
|
protected function sendToBalikonos()
|
|
{
|
|
foreach ($this->orders as &$order) {
|
|
$qb = sqlQueryBuilder()
|
|
->select('*')
|
|
->from('balikonos')
|
|
->where('id_order = :order_id AND user = :user AND id_delivery IS NOT NULL and close IN (0, 1)')
|
|
->setParameters(['order_id' => $order['id'], 'user' => $this->getUser()])
|
|
->sendToMaster()
|
|
->execute();
|
|
|
|
if ($qb->rowCount() > 0) {
|
|
$order['response'] = 'Chyba! Objednávka už v Balíkobotu existuje.';
|
|
// skip orders that are already in process
|
|
continue;
|
|
}
|
|
|
|
$requestBody = json_encode(array_merge([$order['balikobot_array']], $order['additional_orders'] ?? []));
|
|
|
|
logError(__FILE__, __LINE__, 'Balikobot odeslani baliku: '.print_r($requestBody, true));
|
|
|
|
$curl = $this->createCurl($requestBody, $order['carrier_code'].'/add');
|
|
$response = $this->curlExecute($curl);
|
|
|
|
logError(__FILE__, __LINE__, 'Balikobot odpoved: '.print_r($response, true).' na balik '.print_r($requestBody, true));
|
|
|
|
if ($response) {
|
|
$answr = json_decode($response, true);
|
|
$order['response'] = $answr;
|
|
} else {
|
|
$order['response'] = 'curl error: '.curl_error($curl);
|
|
}
|
|
|
|
if ($order['response']['status'] == '400') {
|
|
logError(__FILE__, __LINE__, 'Balikobot API UNDEFINED error sended_data: '.print_r($requestBody, true));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function saveToDB()
|
|
{
|
|
try {
|
|
$error = false;
|
|
foreach ($this->orders as &$order) {
|
|
if (!empty($order['response']['status'])) {
|
|
if (in_array($order['response']['status'], ['200', '208'])) {
|
|
foreach ($order['response'] as $key => $response) {
|
|
if (is_numeric($key) && is_array($response)) {
|
|
sqlQuery('INSERT INTO balikonos (user,id_order,id_delivery,id_shop_delivery, data)
|
|
VALUES (:user, :order_id, :package_id, :id_shop_delivery, :data)
|
|
ON DUPLICATE KEY UPDATE id_delivery=:package_id, id_shop_delivery=:id_shop_delivery, data=:data, close=0',
|
|
[
|
|
'user' => $this->getUser(),
|
|
'order_id' => $order['id'],
|
|
'package_id' => $response['package_id'],
|
|
'id_shop_delivery' => $order['id_delivery'],
|
|
'data' => json_encode($order),
|
|
]
|
|
);
|
|
$order['id_balikonos'] = sqlInsertId();
|
|
$order['package_id'] = $response['carrier_id'];
|
|
$order['error'] = 'OK';
|
|
}
|
|
}
|
|
$this->updateSQL('orders', ['package_id' => $order['response'][0]['carrier_id']], ['id' => $order['id']]);
|
|
} else {
|
|
$order['error'] = $this->getErrorMessage($order['response']);
|
|
$error = true;
|
|
}
|
|
} elseif ($error) {
|
|
$order['error'] = '';
|
|
} else {
|
|
$order['error'] = $order['response'];
|
|
}
|
|
}
|
|
$this->response = $error ? 3 : 4;
|
|
} catch (\Doctrine\DBAL\DBALException $e) {
|
|
echo $e->getMessage();
|
|
}
|
|
}
|
|
|
|
public function getErrorMessage($response)
|
|
{
|
|
$tmpErrors = [];
|
|
if (isset($response[0]['errors'])) {
|
|
$tmpErrors = array_map(
|
|
function ($error) {
|
|
return $error['message'] ?? ($error['type'].':'.$error['attribute']);
|
|
},
|
|
$response[0]['errors']
|
|
);
|
|
}
|
|
$msg = join(' ', $tmpErrors);
|
|
|
|
if (!empty($msg)) {
|
|
logError(__FILE__, __LINE__, "Balikobot API error: {$msg} \n Response:".print_r($response, true));
|
|
|
|
return $msg;
|
|
}
|
|
|
|
var_dump($response);
|
|
logError(__FILE__, __LINE__, 'Balikobot API UNDEFINED error: '.print_r($response, true));
|
|
|
|
return 'Nedefinovaná chyba';
|
|
}
|
|
|
|
public function sendDeliveries()
|
|
{
|
|
$this->getOrdersData();
|
|
$this->sendToBalikonos();
|
|
$this->saveToDB();
|
|
}
|
|
|
|
public function deletePackage($id)
|
|
{
|
|
$data = $this->selectSQL('balikonos', ['id' => $id])->fetch();
|
|
if ($data) {
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
$this->setCredentialsByName($data['user']);
|
|
|
|
$carrierCode = $dbcfg['balikobot']['delivery_type'][$data['id_shop_delivery']]['carrier'];
|
|
$packageId = $data['id_delivery'];
|
|
|
|
if ($data_data = json_decode($data['data'] ?? '', true)) {
|
|
$package = $data_data['response'][0]['carrier_id'] ?? '';
|
|
}
|
|
$log_message = 'Smazán balík: '.($package ?? '').' (Balíkobot ID: '.$packageId.')';
|
|
|
|
$curl = $this->createCurl(json_encode(['id' => $packageId]), $carrierCode.'/drop');
|
|
$response = $this->curlExecute($curl);
|
|
|
|
if ($response) {
|
|
$response = json_decode($response, true);
|
|
$responseStatus = $response[0]['status'] ?? ($response['status'] ?? 500);
|
|
switch ($responseStatus) {
|
|
case 200:
|
|
writeDownActivity($log_message);
|
|
$this->deleteSQL('balikonos', ['id' => $id]);
|
|
|
|
return true;
|
|
case 403:
|
|
throw new BalikonosException('Balík se nepodařilo smazat, protože je z vícekusové zásilky a není posledním balíkem.');
|
|
default:
|
|
writeDownActivity($log_message);
|
|
$this->deleteSQL('balikonos', ['id' => $id]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
throw new BalikonosException('Objednávku se nepodařilo odstranit ze systému Balíkobot a je potřeba ji z něj odstranit ručně.');
|
|
}
|
|
|
|
throw new BalikonosException('Při odebírání objednávky ze systému Balíkobot došlo k chybě.');
|
|
}
|
|
|
|
public function setIDs(array $IDs)
|
|
{
|
|
$this->IDs = $IDs;
|
|
}
|
|
|
|
public function closeDeliveries($id_deliver, $packageIDs = [])
|
|
{
|
|
if (empty($packageIDs)) {
|
|
return false;
|
|
}
|
|
|
|
$data = ['package_ids' => $packageIDs];
|
|
|
|
$dbcfg = \Settings::getDefault();
|
|
$this->carrierCode = $dbcfg['balikobot']['delivery_type'][$id_deliver]['carrier'];
|
|
|
|
$curl = $this->createCurl(json_encode($data), $this->carrierCode.'/order');
|
|
$response = $this->curlExecute($curl);
|
|
logError(__FILE__, __LINE__, 'Balikobot API ORDER close request: '.print_r($data, true).' response: '.print_r($response, true));
|
|
|
|
if ($response) {
|
|
$batchResponse = json_decode($response);
|
|
} else {
|
|
$batchResponse = 'curl error: '.curl_error($curl);
|
|
}
|
|
|
|
if (!empty($batchResponse->status) && $batchResponse->status == '200') {
|
|
$this->response = 2;
|
|
} else {
|
|
logError(__FILE__, __LINE__, 'Balikobot API ORDER close error: '.print_r($batchResponse, true));
|
|
|
|
throw new BalikonosException('Balíkobot vrátil chybu. Nepodařilo se uzavřít svoz.', $batchResponse->status ?? 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print PDF.
|
|
*
|
|
* @param int[] $deliver_ids
|
|
* @param string $printFormat
|
|
* @param int[] $orderIds
|
|
* @param bool $return
|
|
* @param int[] $balikobotIds
|
|
*/
|
|
public function printTickets($deliver_ids, $position, $printFormat = 'default', $orderIds = [], $return = false, $balikobotIds = [], $shop_delivery_ids = [])
|
|
{
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
$qbResult = sqlQueryBuilder()->select('b.id, b.id_delivery, b.close, b.id_shop_delivery, dtd.id_delivery AS delivery_type_delivery_id')
|
|
->from('balikonos', 'b')
|
|
->leftJoin('b', 'orders', 'o', 'o.id=b.id_order')
|
|
->leftJoin('o', 'delivery_type', 'dtd', 'dtd.id=o.id_delivery')
|
|
->andWhere(Operator::equals(['b.user' => $this->getUser()]));
|
|
|
|
if ($balikobotIds) {
|
|
$qbResult->andWhere(Operator::inIntArray($balikobotIds, 'b.id'));
|
|
} elseif ($orderIds) {
|
|
$qbResult->andWhere(Operator::inIntArray($orderIds, 'o.id'))
|
|
->andWhere('b.close < 2');
|
|
} elseif ($deliver_ids) {
|
|
$qbResult->andWhere(Operator::inIntArray($deliver_ids, 'b.id_delivery'))
|
|
->andWhere(Operator::isNotNull('b.id_delivery'));
|
|
} elseif ($shop_delivery_ids) {
|
|
$qbResult->andWhere(Operator::inIntArray($shop_delivery_ids, 'b.id_shop_delivery'))
|
|
->andWhere(Operator::isNotNull('b.id_delivery'))
|
|
->andWhere('b.close < 2');
|
|
}
|
|
|
|
$packageIDs = [];
|
|
$balikobotIds = [];
|
|
foreach ($qbResult->execute() as $row) {
|
|
// set carrierCode for future use in API requests
|
|
$id_delivery = !empty($row['id_shop_delivery']) ? $row['id_shop_delivery'] : $row['delivery_type_delivery_id'];
|
|
$this->carrierCode = $dbcfg['balikobot']['delivery_type'][$id_delivery]['carrier'];
|
|
$packageIDs[] = $row['id_delivery'];
|
|
$balikobotIds[] = $row['id'];
|
|
}
|
|
|
|
$curl = $this->createCurl(json_encode(['package_ids' => $packageIDs]), $this->carrierCode.'/labels');
|
|
$response = json_decode($this->curlExecute($curl));
|
|
|
|
if (empty($response->status) || $response->status != '200') {
|
|
logError(__FILE__, __LINE__, 'Balikobot API LABELS error: '.print_r($response, true));
|
|
throw new BalikonosException('Balíkobot vrátil chybu. Nelze vytisknout štítky. Chyba: '.($response->status ?? 'Balíkobot nevrátil žádnou odpověd.'));
|
|
} else {
|
|
sqlQueryBuilder()->update('balikonos')
|
|
->set('close', 'GREATEST(close, 1)')
|
|
->where(Operator::inIntArray($balikobotIds, 'id'))
|
|
->execute();
|
|
}
|
|
|
|
// set $position if specified
|
|
$url = $response->labels_url.(empty($position) ? '' : ('?p='.$position));
|
|
if ($return) {
|
|
return $url;
|
|
}
|
|
|
|
redirection($url);
|
|
}
|
|
|
|
public function getResult()
|
|
{
|
|
$result = [];
|
|
foreach ($this->orders as $key => $order) {
|
|
$result[$key] = [
|
|
'id_balikonos' => $order['id_balikonos'] ?? null,
|
|
'package_id' => $order['package_id'] ?? null,
|
|
'success' => ($order['error'] == 'OK') ? true : false,
|
|
'error_message' => $order['error'],
|
|
'package_ids' => $this->parseValueFromResult($order['response'] ?? [], 'carrier_id'),
|
|
'balikonos_ids' => $this->parseValueFromResult($order['response'] ?? [], 'package_id'),
|
|
'labels_url' => $order['response']['labels_url'],
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function parseValueFromResult(array $result, string $key): array
|
|
{
|
|
$values = array_map(function ($item) use ($key) {
|
|
return is_array($item) && isset($item[$key]) ? $item[$key] : null;
|
|
}, $result);
|
|
|
|
return array_filter($values);
|
|
}
|
|
|
|
public function getError()
|
|
{
|
|
foreach ($this->orders as $order) {
|
|
if (!empty($order['response']) && is_array($order['response'])) {
|
|
if (!in_array($order['response']['status'], ['200', '208'])) {
|
|
return $this->getErrorMessage($order['response']);
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function deleteDeliverBatch($deliver)
|
|
{
|
|
$packages = sqlQueryBuilder()
|
|
->select('b.*')
|
|
->from('balikonos', 'b')
|
|
->where('(b.id_shop_delivery = :id_delivery) AND b.id_delivery IS NOT NULL AND b.close = 1')
|
|
->andWhere(Operator::equals(['b.user' => $this->getUser()]))
|
|
->setParameter('id_delivery', $deliver)
|
|
->execute()->fetchAll();
|
|
|
|
$packageIDs = array_column($packages, 'id_delivery');
|
|
|
|
try {
|
|
$this->closeDeliveries($deliver, $packageIDs);
|
|
} catch (BalikonosException $e) {
|
|
// 208 == přeprava pod zaslaným eid již byla objednána dříve, takze objednavka uz musi v balikobotu byt
|
|
if ($e->getCode() != 208) {
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
$dbcfg = \Settings::getDefault();
|
|
|
|
$responseIsSet = false;
|
|
$notifiedOrders = [];
|
|
foreach ($packages as $package) {
|
|
// set carrierCode for future use in API requests
|
|
$this->carrierCode = $dbcfg['balikobot']['delivery_type'][$package['id_shop_delivery']]['carrier'];
|
|
|
|
$data = json_decode($package['data']);
|
|
|
|
$delivery_number = $data->response->{0}->carrier_id ?? '';
|
|
$cod = $data->balikobot_array->cod_price ?? '';
|
|
$weight = $data->balikobot_array->weight ?? '';
|
|
|
|
$changeStatus = true;
|
|
$cfg = Config::get();
|
|
if (array_key_exists('post_default_to', $cfg['Modules']['balikonos']) && $cfg['Modules']['balikonos']['post_default_to'] === null) {
|
|
$changeStatus = false;
|
|
}
|
|
|
|
if (!isset($notifiedOrders[$package['id_order']]) && $changeStatus && $package['id_order'] !== null) {
|
|
try {
|
|
$result = $this->ChangeStatus(
|
|
$package['id_order'],
|
|
findModule('balikonos', 'post_default_to', 2),
|
|
$delivery_number,
|
|
$cod,
|
|
$weight
|
|
);
|
|
if (!$result) {
|
|
$responseIsSet = true;
|
|
$this->response = 'Svoz byl odeslán, ale nepodařilo se odeslat e-mail. Chyba: Neni prirazena \'Zprava uzivatelum\'! V nastaveni eshopu priradte ke kazde doprave zpravu!';
|
|
}
|
|
$notifiedOrders[$package['id_order']] = true;
|
|
} catch (\Exception $e) {
|
|
getRaven()->captureException($e);
|
|
}
|
|
}
|
|
|
|
sqlQueryBuilder()->update('balikonos')->set('close', 2)
|
|
->andWhere(Operator::equals(['id' => $package['id']]))
|
|
->execute();
|
|
}
|
|
|
|
if (!$responseIsSet) {
|
|
$this->response = '1';
|
|
}
|
|
}
|
|
|
|
public function getCarriers(): array
|
|
{
|
|
$carriers = getCache('balikobot_carriers');
|
|
if (is_array($carriers)) {
|
|
return $carriers;
|
|
}
|
|
|
|
$carriers = [];
|
|
$enableCache = true;
|
|
try {
|
|
$curl = $this->createCurl('', 'info/carriers');
|
|
$response = $this->curlExecute($curl);
|
|
$response = json_decode_strict($response);
|
|
} catch (\JsonException|BalikonosException $e) {
|
|
// disable cache on error
|
|
$enableCache = false;
|
|
}
|
|
if (!empty($response->status) && $response->status == '200' && !empty($response->carriers)) {
|
|
$carriers = Mapping::mapKeys((array) $response->carriers, function ($index, $carrier) {
|
|
return [$carrier->slug, $carrier->name];
|
|
});
|
|
}
|
|
|
|
if ($enableCache) {
|
|
// cache for a day
|
|
setCache('balikobot_carriers', $carriers, 86400);
|
|
}
|
|
|
|
if (!empty($carriers)) {
|
|
return $carriers;
|
|
}
|
|
|
|
// Fallback to static list if fetch failed
|
|
return [
|
|
'cp' => 'Česká pošta s.p.',
|
|
'dhl' => 'DHL Express',
|
|
'dhlsk' => 'DHL Parcel Slovensko',
|
|
'dhlde' => 'DHL DE',
|
|
'dpd' => 'Direct Parcel Distribution CZ s.r.o.',
|
|
'geis' => 'Geis CZ s.r.o.',
|
|
'gls' => 'General Logistics Systems Czech Republic s.r.o.',
|
|
'intime' => 'IN TIME - WE DO',
|
|
'pbh' => 'Pošta bez hranic (Frogman s.r.o.)',
|
|
'ppl' => 'PPL + DHL Freight',
|
|
'sp' => 'Slovenská pošta a.s.',
|
|
'sps' => 'Slovak Parcel Service s.r.o.',
|
|
'tnt' => 'TNT',
|
|
'toptrans' => 'TOPTRANS EU a.s.',
|
|
'ulozenka' => 'Uloženka - WE DO',
|
|
'ups' => 'UPS',
|
|
'zasilkovna' => 'Zásilkovna s.r.o.',
|
|
'gw' => 'Gebrüder Weiss Slovensko',
|
|
'gwcz' => 'Gebrüder Weiss Česká republika',
|
|
'messenger' => 'Messenger',
|
|
'fedex' => 'Fedex',
|
|
'fofr' => 'Fofr',
|
|
'japo' => 'JAPO Transport',
|
|
'lockers' => 'Lockers',
|
|
'kurier' => '123kurier',
|
|
'raben' => 'Raben Logistics',
|
|
];
|
|
}
|
|
|
|
public function getAllCarrierServices(): array
|
|
{
|
|
$carrierServices = getCache('balikobot_carrier_services');
|
|
if (is_array($carrierServices)) {
|
|
return $carrierServices;
|
|
}
|
|
$carrierServices = [];
|
|
$enableCache = true;
|
|
foreach ($this->getCarriers() as $carrierCode => $carrierName) {
|
|
$curl = $this->createCurl('', $carrierCode.'/services');
|
|
try {
|
|
$response = $this->curlExecute($curl);
|
|
if (!$this->isResponseAuthorized($response)) {
|
|
$enableCache = false;
|
|
$this->authorizationFailed = true;
|
|
}
|
|
$response = json_decode_strict($response);
|
|
} catch (\JsonException $e) {
|
|
// disable cache on error
|
|
$enableCache = false;
|
|
}
|
|
if (!empty($response->status) && $response->status == '200' && !empty($response->service_types)) {
|
|
$carrierServices[$carrierCode] = (array) $response->service_types;
|
|
}
|
|
}
|
|
if ($enableCache) {
|
|
// cache for a day
|
|
setCache('balikobot_carrier_services', $carrierServices, 86400);
|
|
}
|
|
|
|
return $carrierServices;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $packageIDs
|
|
*
|
|
* @return array|false eg: [0 => ['status_id' => 1, 'status_text' => 'Zásilka byla doručena příjemci.']]
|
|
*/
|
|
public function trackPackages(string $carrierCode, array $packageIDs, $timeout = null)
|
|
{
|
|
$packageIDs = array_map(function ($element) {
|
|
return ['id' => $element];
|
|
}, $packageIDs);
|
|
$curl = $this->createCurl(
|
|
json_encode($packageIDs),
|
|
'v2/'.$carrierCode.'/track'
|
|
);
|
|
if (isset($timeout)) {
|
|
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
|
|
}
|
|
try {
|
|
$response = json_decode_strict($this->curlExecute($curl), true);
|
|
} catch (\JsonException $e) {
|
|
return false;
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* @param null $timeout
|
|
*
|
|
* @return array|null eg: [0 => ['date' => '2019-01-22 00:00:00', 'name' => 'Doručení', 'status_id' => 1]]
|
|
*/
|
|
public function trackSinglePackage(string $carrierCode, string $packageID, $timeout = null)
|
|
{
|
|
$packagesTrackingInfo = $this->trackPackages($carrierCode, [$packageID], $timeout);
|
|
|
|
return is_array($packagesTrackingInfo) ? array_pop($packagesTrackingInfo) : null;
|
|
}
|
|
|
|
public function isDeliverySupported($id_delivery)
|
|
{
|
|
return !empty(\Settings::getDefault()['balikobot']['delivery_type'][$id_delivery]['carrier']);
|
|
}
|
|
|
|
public function getHandoverUrl(string $carrierCode, $timeout = null)
|
|
{
|
|
$curl = $this->createCurl('', $carrierCode.'/orderview');
|
|
if (isset($timeout)) {
|
|
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
|
|
}
|
|
try {
|
|
$response = json_decode_strict($this->curlExecute($curl), true);
|
|
} catch (\JsonException $e) {
|
|
return null;
|
|
}
|
|
|
|
return $response['handover_url'] ?? null;
|
|
}
|
|
|
|
protected function addCustomField(array $order, array $balikobot, \Order $ordCls): array
|
|
{
|
|
foreach ($balikobot as $key => $value) {
|
|
$order['balikobot_array'][$key] = $value;
|
|
}
|
|
|
|
return $order;
|
|
}
|
|
|
|
/**
|
|
* @return array|false
|
|
*
|
|
* @throws BalikobotB2AValidationException
|
|
* @throws BalikonosException
|
|
*/
|
|
public function getReturnDeliveryData(ReturnEntity|ReclamationEntity $entity)
|
|
{
|
|
$returnDeliveryEntity = $this->returnDeliveriesUtil->getReturnDeliveryEntity($entity->getIdReturnDelivery());
|
|
|
|
$balikobotName = $returnDeliveryEntity->getCustomData()['balikobot_user'] ?? false;
|
|
if (!$balikobotName) {
|
|
throw new BalikonosException('Nemáte vyplněné svozové místo v nastavení dopravy pro vratky.');
|
|
}
|
|
|
|
$allCredentials = $this->getCollectionPlaces();
|
|
$selectedName = null;
|
|
foreach ($allCredentials as $credentials) {
|
|
if ($credentials['name'] == $balikobotName) {
|
|
$selectedName = $credentials['user'];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$selectedName) {
|
|
throw new BalikonosException('Neplatné svozové místo v nastavení dopravy pro vratky.');
|
|
}
|
|
|
|
if ($entity instanceof ReturnEntity) {
|
|
$orderIds = $this->returnsUtil->getOrderIdsByReturn($entity->getId());
|
|
$orderId = reset($orderIds);
|
|
$orderId = $orderId['id'];
|
|
} else {
|
|
$orderId = $entity->getIdOrder();
|
|
}
|
|
|
|
/**
|
|
* @var IBalikobotB2AAdapter $adapter
|
|
*/
|
|
$adapter = $this->balikobotAdapterUtil->getB2AClassAdapter($returnDeliveryEntity->getType());
|
|
|
|
$baseBody = $returnDeliveryEntity->getClass()->getBaseLabelData($entity);
|
|
|
|
$baseBody['eid'] = $entity->getCode();
|
|
if (isLocalDevelopment()) {
|
|
$baseBody['eid'] .= 'LD';
|
|
} elseif (isDevelopment()) {
|
|
$baseBody['eid'] .= 'D';
|
|
}
|
|
|
|
$body = $adapter->transformData($entity, $orderId, $baseBody);
|
|
|
|
$body = ['packages' => [$body]];
|
|
|
|
$carrierCode = $adapter->getCarrierCode();
|
|
|
|
$this->setCredentialsByName($selectedName);
|
|
|
|
$curl = $this->createCurl(json_encode($body), $carrierCode.'/b2a', true);
|
|
|
|
$response = json_decode($this->curlExecute($curl), true);
|
|
|
|
if (($response['status'] ?? 500) != 200 || ($response['packages'][0]['status'] ?? 500) != 200) {
|
|
$this->handleB2AErrors($response, $body);
|
|
}
|
|
|
|
return $adapter->transformResponse($response['packages'][0]);
|
|
}
|
|
|
|
protected function handleB2AErrors($response, $request)
|
|
{
|
|
$data = [
|
|
'request' => $request,
|
|
'response' => $response,
|
|
];
|
|
|
|
$reasons = array_filter(array_map(function ($error) {
|
|
return $error['message'] ?? false;
|
|
}, $response['packages'][0]['errors'] ?? []));
|
|
|
|
if (($response['packages'][0]['status'] ?? 500) == 200) {
|
|
getRaven()->captureMessage('Balikobot B2A api error', [], ['extra' => $data]);
|
|
}
|
|
|
|
throw new BalikobotB2AValidationException(translate('b2a_balikobot_error', 'Returns', isAdministration: true), data: $data, reasons: $reasons);
|
|
}
|
|
}
|