1270 lines
40 KiB
PHP
1270 lines
40 KiB
PHP
<?php
|
|
|
|
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
|
use KupShop\AdminBundle\Util\ActivityLog;
|
|
use KupShop\CatalogBundle\Section\SectionTree;
|
|
use KupShop\KupShopBundle\Config;
|
|
use KupShop\KupShopBundle\Context\ContextManager;
|
|
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
|
|
use KupShop\KupShopBundle\Util\Database\QueryHint;
|
|
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
|
|
use Psr\Log\LoggerInterface;
|
|
use Query\QueryBuilder;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
|
|
mb_internal_encoding('utf-8');
|
|
|
|
set_time_limit(999999);
|
|
|
|
ini_set('memory_limit', '2G');
|
|
|
|
QueryHint::routeToMaster();
|
|
|
|
getRaven()->setChannel(SentryLogger::CHANNEL_SYNC);
|
|
|
|
class AbraBase
|
|
{
|
|
use DatabaseCommunication;
|
|
|
|
protected $client;
|
|
|
|
protected $batch_size = 1000;
|
|
protected $separator = '~|~';
|
|
protected $settings = [];
|
|
protected $abraNull = '0000000000';
|
|
protected $paginateByCode = false;
|
|
|
|
public static $ABRA_UIDS;
|
|
public static $PRODUCT_FIELDS;
|
|
public static $SECTION_FIELDS;
|
|
public static $PRODUCER_FIELDS;
|
|
|
|
/** @var LoggerInterface */
|
|
protected $logger;
|
|
|
|
protected ContextManager $contextManager;
|
|
|
|
public function __construct($settings = null)
|
|
{
|
|
if (is_null($settings)) {
|
|
$settings = Config::get()['Modules']['abra'];
|
|
}
|
|
|
|
$this->settings = array_merge($this->settings, $settings);
|
|
|
|
// In development/review use test Abra server
|
|
if (isDevelopment()) {
|
|
if (isset($this->settings['test_server'])) {
|
|
$this->settings['server'] = $this->settings['test_server'];
|
|
}
|
|
}
|
|
|
|
ini_set('default_socket_timeout', 300);
|
|
|
|
$this->client = $this->getSoapClient();
|
|
|
|
$this->logger = ServiceContainer::getService('logger');
|
|
$this->contextManager = ServiceContainer::getService(ContextManager::class);
|
|
}
|
|
|
|
public function getSoapClient($server = 'server')
|
|
{
|
|
// Allow incorrect SSL certificate
|
|
$context = stream_context_create([
|
|
'ssl' => [
|
|
// set some SSL/TLS specific options
|
|
'verify_peer' => false,
|
|
'verify_peer_name' => false,
|
|
'allow_self_signed' => true,
|
|
],
|
|
]);
|
|
|
|
$options = [
|
|
'trace' => 1,
|
|
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
|
|
'connection_timeout' => 300,
|
|
'location' => str_replace('/wsdl/', '/soap/', $this->settings[$server]),
|
|
'stream_context' => $context,
|
|
];
|
|
if (!empty($this->settings['basic_auth_login'])) {
|
|
$options['login'] = $this->settings['basic_auth_login'];
|
|
}
|
|
if (!empty($this->settings['basic_auth_password'])) {
|
|
$options['password'] = $this->settings['basic_auth_password'];
|
|
}
|
|
|
|
return new SoapClient($this->settings[$server], $options);
|
|
}
|
|
|
|
public function prettyXml($string)
|
|
{
|
|
if (!$string) {
|
|
return;
|
|
}
|
|
|
|
$domxml = new DOMDocument('1.0');
|
|
$domxml->preserveWhiteSpace = false;
|
|
$domxml->formatOutput = true;
|
|
/* @var $xml SimpleXMLElement */
|
|
$domxml->loadXML($string);
|
|
|
|
return $domxml->saveXML();
|
|
}
|
|
|
|
public function printTrace()
|
|
{
|
|
$requestHeaders = $this->client->__getLastRequestHeaders();
|
|
$request = $this->prettyXml($this->client->__getLastRequest());
|
|
$responseHeaders = $this->client->__getLastResponseHeaders();
|
|
$response = $this->prettyXml($this->client->__getLastResponse());
|
|
|
|
echo '<code>'.nl2br(htmlspecialchars($requestHeaders, true)).'</code>';
|
|
echo highlight_string($request, true)."<br/>\n";
|
|
|
|
echo '<code>'.nl2br(htmlspecialchars($responseHeaders, true)).'</code>'."<br/>\n";
|
|
echo highlight_string($response, true)."<br/>\n";
|
|
}
|
|
|
|
public function logTrace($type)
|
|
{
|
|
$requestHeaders = $this->client->__getLastRequestHeaders();
|
|
$request = $this->prettyXml($this->client->__getLastRequest());
|
|
$responseHeaders = $this->client->__getLastResponseHeaders();
|
|
$response = $this->prettyXml($this->client->__getLastResponse());
|
|
|
|
$this->log(["Trace send {$type}", $requestHeaders, $request]);
|
|
$this->log(["Trace receive {$type}", $responseHeaders, $response]);
|
|
}
|
|
|
|
public static $GET_ALL_TYPES = [
|
|
'42P1E2VUANDL342Q01C0CX3FCC' => [
|
|
'batch' => 100,
|
|
],
|
|
'C3V5QDVZ5BDL342M01C0CX3FCC' => [
|
|
'batch' => 100,
|
|
],
|
|
'GAWVAN4GFNDL342T01C0CX3FCC' => [
|
|
'batch' => 100,
|
|
],
|
|
'GHYLVQXQ3FE13DQC01C0CX3F40' => [
|
|
'batch' => 100,
|
|
],
|
|
'05CPMINJW3DL342X01C0CX3FCC' => [
|
|
'batch' => 100,
|
|
],
|
|
'G2WVAN4GFNDL342T01C0CX3FCC' => [
|
|
'batch' => 100,
|
|
],
|
|
'4K3EXM5PQBCL35CH000ILPWJF4' => [
|
|
'batch' => 100,
|
|
],
|
|
'G5YLVQXQ3FE13DQC01C0CX3F40' => [
|
|
'batch' => 100,
|
|
],
|
|
];
|
|
|
|
protected $type;
|
|
|
|
public function getAllData($type = null, $params = [])
|
|
{
|
|
if (!empty($params['transaction'])) {
|
|
sqlStartTransaction();
|
|
}
|
|
|
|
while (ob_get_level()) {
|
|
ob_end_flush();
|
|
}
|
|
|
|
$defaults = [
|
|
'start' => $this->abraNull,
|
|
'batch' => null,
|
|
'count' => null,
|
|
];
|
|
|
|
$types = static::$GET_ALL_TYPES;
|
|
|
|
if ($type) {
|
|
if (substr($type, -1) == '+' || substr($type, -1) == ' ') {
|
|
$type = substr($type, 0, -1);
|
|
$types = array_slice($types, array_search($type, array_keys($types)));
|
|
} else {
|
|
$types = [$type => ($types[$type] ?? [])];
|
|
}
|
|
}
|
|
|
|
foreach ($types as $type => $options) {
|
|
$this->type = $type;
|
|
$options = array_merge($defaults, $options, $params);
|
|
|
|
echo "<br>Starting {$type}<br>";
|
|
flush();
|
|
|
|
if (empty(static::$ABRA_UIDS[$type]) || static::$ABRA_UIDS[$type] == 'ignore') {
|
|
echo "ignored.\n";
|
|
continue;
|
|
}
|
|
|
|
$index = $options['start'];
|
|
$options['start'] = $this->abraNull;
|
|
$old_index = null;
|
|
$retry_count = 3;
|
|
$total_count = $options['count'];
|
|
$count = 0;
|
|
|
|
do {
|
|
$old_index = $index;
|
|
|
|
echo "<br>Fetching {$index} ...";
|
|
logError(__FILE__, __LINE__, "Fetching {$type} : {$index} ...");
|
|
flush();
|
|
|
|
$timestamp = getScriptTime();
|
|
|
|
try {
|
|
$items = $this->getOneDataBlock($type, $index, $options['batch']);
|
|
} catch (Exception $e) {
|
|
$this->printTrace();
|
|
|
|
if (--$retry_count > 0) {
|
|
$old_index = null;
|
|
continue;
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
|
|
if (!empty($params['test'])) {
|
|
$this->printTrace();
|
|
}
|
|
$delay = number_format(getScriptTime() - $timestamp, 2);
|
|
$timestamp = getScriptTime();
|
|
|
|
echo "Took {$delay} s. <br>Processing ....";
|
|
flush();
|
|
|
|
foreach ($items as $change) {
|
|
$index = $this->processChange(join($this->separator, [0, substr($change, 4)]), $index);
|
|
}
|
|
|
|
$count += count($items);
|
|
$delay = number_format(getScriptTime() - $timestamp, 2);
|
|
$timestamp = getScriptTime();
|
|
echo "Took {$delay} s. <br>Updating delayed ....";
|
|
|
|
try {
|
|
$this->updateScheduledProducts();
|
|
} catch (Exception $e) {
|
|
var_dump(['updateScheduledProducts exception', $e->getMessage()]);
|
|
}
|
|
|
|
$delay = number_format(getScriptTime() - $timestamp, 2);
|
|
echo "Took {$delay} s";
|
|
$retry_count = 3;
|
|
} while ($old_index != $index && (is_null($total_count) || $total_count > $count));
|
|
|
|
echo "<br>Ended {$type}<br>";
|
|
flush();
|
|
}
|
|
|
|
$this->finalizeSync();
|
|
}
|
|
|
|
public function getOneDataBlock($type, $index, $batch)
|
|
{
|
|
// detect CLSID
|
|
if (strlen($type) == 26) {
|
|
return $this->client->weGetAll($type, $index, $batch);
|
|
}
|
|
|
|
$selection = null;
|
|
$filter = null;
|
|
if (isset($_GET['filter']) && !empty($_GET['filter'])) {
|
|
$filter = explode(',', $_GET['filter']);
|
|
$filter = array_map(function ($value) { return "'".$value."'"; }, $filter);
|
|
$filter = join(',', $filter);
|
|
$selection = 'SELECTION';
|
|
}
|
|
|
|
$this->paginateByCode = true;
|
|
|
|
return $this->client->weGetObjectType($type, $index, $batch, $selection, $filter);
|
|
}
|
|
|
|
public function updateScheduledProducts()
|
|
{
|
|
}
|
|
|
|
public function sync()
|
|
{
|
|
$this->syncOrders();
|
|
|
|
$this->syncProducts();
|
|
|
|
$this->finalizeSync();
|
|
}
|
|
|
|
// Utility functions
|
|
public function getLastChangeID()
|
|
{
|
|
return intval(sqlFetchAssoc($this->selectSQL('abra_settings', ['name' => 'last_change'], ['value']))['value'] ?? 0);
|
|
}
|
|
|
|
public function updateLastChangeID($newID)
|
|
{
|
|
$this->updateSQL('abra_settings', ['value' => $newID], ['name' => 'last_change']);
|
|
}
|
|
|
|
public function updateOrderStatuses()
|
|
{
|
|
}
|
|
|
|
public function authorizeOrders($spec, $message)
|
|
{
|
|
$orders = sqlQueryBuilder()->select('o.id')->from('orders', 'o')
|
|
->join('o', 'abra_orders', 'ao', 'ao.id_order = o.id')
|
|
->join('o', 'delivery_type', 'dt', 'dt.id = o.id_delivery')
|
|
// ->andWhere(\Query\Operator::not(\Query\Operator::findInSet(['D'], 'o.flags')))
|
|
->andWhere($spec)->execute();
|
|
foreach ($orders as $order) {
|
|
$this->changeOrderStatus($order['id'], 1, $message);
|
|
}
|
|
}
|
|
|
|
public function changeOrderStatus($id_order, $status, $comment, $service_note = null, $emailType = null)
|
|
{
|
|
try {
|
|
$order = new Order();
|
|
$order->createFromDB($id_order);
|
|
|
|
$this->log(['Change order status', 'id' => $id_order, 'code' => $order->order_no, 'status' => $status, 'comment' => $comment]);
|
|
|
|
$ret = $this->client->__call('weSetReceivedOrderSimpleUpdate', [$this->getWebSource($order), $this->getOrderNumber($order), ["Status~|~{$status}"]]);
|
|
$this->printTrace();
|
|
|
|
if ($service_note) {
|
|
$service_note = $this->prepareFieldLengthForOrder($this->prepareTextForOrder($service_note), 200);
|
|
$this->client->__call('weSetReceivedOrderSimpleUpdate', [$this->getWebSource($order), $this->getOrderNumber($order), ["CustomerServiceNote~|~{$service_note}"]]);
|
|
}
|
|
|
|
if ($ret[0] != 'True') {
|
|
throw new Exception("{$ret[1]} - {$ret[2]}");
|
|
}
|
|
|
|
$order->changeStatus($status, $comment, isset($emailType), null, $emailType);
|
|
} catch (Exception $e) {
|
|
$this->log(['Change order status', 'id' => $id_order, 'exception' => $e->getMessage()]);
|
|
|
|
$this->logException($e);
|
|
}
|
|
}
|
|
|
|
protected $mappingCache = ['id_abra' => null, 'id' => null];
|
|
|
|
public function getMappingFromAbra($type, $code)
|
|
{
|
|
if ($this->mappingCache['id_abra'] == $type.$code) {
|
|
return $this->mappingCache['id'];
|
|
}
|
|
|
|
$id = returnSQLResult("SELECT id_{$type} FROM abra_{$type}s WHERE id_abra=:code", ['code' => $code]);
|
|
if ($id) {
|
|
$this->mappingCache = ['id_abra' => $type.$code, 'id' => $id];
|
|
}
|
|
|
|
return $id;
|
|
}
|
|
|
|
public function getMappingFromAbraMultiple($type, $code)
|
|
{
|
|
$ids = sqlFetchAll(sqlQuery("SELECT id_{$type} as id FROM abra_{$type}s WHERE id_abra=:code ORDER BY id_{$type}", ['code' => $code]), ['id' => 'id']);
|
|
|
|
return array_values($ids);
|
|
}
|
|
|
|
public function setMappingFromAbra($type, $id_kupshop, $code_abra)
|
|
{
|
|
sqlQuery("INSERT INTO abra_{$type}s (id_abra, id_{$type}) VALUES (:code, :id)", ['code' => $code_abra, 'id' => $id_kupshop]);
|
|
}
|
|
|
|
public function getMappingToAbra($type, $id)
|
|
{
|
|
$table = "abra_{$type}s";
|
|
if ($type == 'delivery') {
|
|
$table = 'abra_deliveries';
|
|
}
|
|
|
|
return returnSQLResult("SELECT id_abra FROM {$table} WHERE id_{$type}=:id", ['id' => $id]);
|
|
}
|
|
|
|
public function preparePriceToAbra($price)
|
|
{
|
|
return str_replace('.', ',', $price);
|
|
}
|
|
|
|
public function preparePriceFromAbra($price)
|
|
{
|
|
$value = str_replace(',', '.', $price);
|
|
|
|
if ($value[0] === '.') {
|
|
$value = '0'.$value;
|
|
}
|
|
|
|
if ($value[-1] === '.') {
|
|
$value .= '0';
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function parseBool($value)
|
|
{
|
|
return $value == 'Ano';
|
|
}
|
|
|
|
public function parseNullable($value)
|
|
{
|
|
if ($value == '' || ((string) $value) === $this->abraNull) {
|
|
return null;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
public function updateProductFlag($data, $enable)
|
|
{
|
|
if ($enable) {
|
|
sqlQuery('UPDATE products SET campaign = ADD_TO_SET(:flag, campaign) WHERE id=:id_product', $data);
|
|
} else {
|
|
sqlQuery('UPDATE products SET campaign = REMOVE_FROM_SET(:flag, campaign) WHERE id=:id_product', $data);
|
|
}
|
|
}
|
|
|
|
public function updateVariationFlag($data, $enable)
|
|
{
|
|
if ($enable) {
|
|
sqlQuery('UPDATE products_variations SET campaign = ADD_TO_SET(:flag, campaign) WHERE id=:id_variation', $data);
|
|
} else {
|
|
sqlQuery('UPDATE products_variations SET campaign = REMOVE_FROM_SET(:flag, campaign) WHERE id=:id_variation', $data);
|
|
}
|
|
}
|
|
|
|
// Find functions
|
|
protected $listProducer;
|
|
|
|
public function findProducer($name)
|
|
{
|
|
if ($name == '') {
|
|
return null;
|
|
}
|
|
|
|
if (is_null($this->listProducer)) {
|
|
$this->listProducer = sqlFetchAll(sqlQuery('SELECT id, LOWER(name) AS name FROM producers'), ['id' => 'name']);
|
|
}
|
|
|
|
if (($index = array_search(mb_strtolower($name), $this->listProducer)) !== false) {
|
|
return $index;
|
|
}
|
|
|
|
$max_position = returnSQLResult('SELECT MAX(position) FROM producers');
|
|
$this->insertSQL('producers', ['name' => $name, 'position' => $max_position + 1]);
|
|
$index = sqlInsertId();
|
|
|
|
$this->listProducer[$index] = mb_strtolower($name);
|
|
|
|
return $index;
|
|
}
|
|
|
|
protected $listVAT;
|
|
protected $listVATDefault = 1;
|
|
|
|
public function findVAT($percent)
|
|
{
|
|
if (is_null($this->listVAT)) {
|
|
foreach (sqlQuery('SELECT id, vat, is_default FROM vats') as $row) {
|
|
$this->listVAT[$row['id']] = $row['vat'];
|
|
if ($row['is_default'] == 'Y') {
|
|
$this->listVATDefault = $row['id'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($index = array_search($percent, $this->listVAT)) {
|
|
return $index;
|
|
}
|
|
|
|
$this->insertSQL('vats', ['descr' => "Daň {$percent}%", 'vat' => $percent]);
|
|
$index = sqlInsertId();
|
|
|
|
$this->listVAT[$index] = $percent;
|
|
|
|
return $index;
|
|
}
|
|
|
|
protected $listParameter = [];
|
|
|
|
public function findParameterValue($parameter_id, $value)
|
|
{
|
|
if (!isset($this->listParameter[$parameter_id])) {
|
|
$this->listParameter[$parameter_id] = sqlFetchAll(
|
|
sqlQuery('SELECT id, LOWER(value) AS value
|
|
FROM parameters_list WHERE id_parameter=:id_parameter',
|
|
['id_parameter' => $parameter_id]),
|
|
['id' => 'value']);
|
|
}
|
|
|
|
$params = &$this->listParameter[$parameter_id];
|
|
|
|
if ($index = array_search(mb_strtolower($value), $params)) {
|
|
return $index;
|
|
}
|
|
|
|
try {
|
|
$this->insertSQL('parameters_list', ['value' => $value, 'id_parameter' => $parameter_id]);
|
|
$index = sqlInsertId();
|
|
} catch (Exception $e) {
|
|
var_dump($e->getMessage());
|
|
$index = sqlFetchAssoc($this->selectSQL('parameters_list', ['value' => $value, 'id_parameter' => $parameter_id], ['id']))['id'];
|
|
}
|
|
|
|
$params[$index] = mb_strtolower($value);
|
|
|
|
return $index;
|
|
}
|
|
|
|
public function findCategory(&$categories, &$parentCat = null)
|
|
{
|
|
// Get next category part
|
|
$categoryName = array_shift($categories);
|
|
if ($categoryName === null) { // Terminate recurse
|
|
return $parentCat['id'];
|
|
}
|
|
|
|
if (empty($categoryName)) {
|
|
return $this->findCategory($categories, $parentCat);
|
|
}
|
|
|
|
// Get parent ID and menu
|
|
$parentId = null;
|
|
if ($parentCat) {
|
|
$menu = &$parentCat['submenu'];
|
|
$parentId = $parentCat['id'];
|
|
} else {
|
|
$menu = ServiceContainer::getService(SectionTree::class)->getTree();
|
|
}
|
|
|
|
// Find matching section
|
|
$found = null;
|
|
|
|
if (!empty($menu)) {
|
|
foreach ($menu as &$category) {
|
|
if (mb_strtolower($category['title']) == mb_strtolower($categoryName)) {
|
|
$found = &$category;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not found, create one
|
|
if (empty($found)) {
|
|
$this->insertSQL('sections', ['name' => $categoryName, 'lead_figure' => 'N', 'behaviour' => 2]);
|
|
|
|
$catId = sqlInsertId();
|
|
|
|
$this->insertSQL('sections_relation', ['id_section' => $catId, 'id_topsection' => $parentId, 'position' => 99]);
|
|
|
|
$found = [
|
|
'id' => $catId,
|
|
'title' => mb_strtolower($categoryName),
|
|
];
|
|
|
|
$menu[] = &$found;
|
|
}
|
|
|
|
return $this->findCategory($categories, $found);
|
|
}
|
|
|
|
// Sync functions
|
|
public function getOrdersToSync(): QueryBuilder
|
|
{
|
|
return sqlQueryBuilder()
|
|
->select('o.id')
|
|
->from('orders', 'o')
|
|
->leftJoin('o', 'abra_orders', 'ao', 'o.id=ao.id_order')
|
|
->where('ao.id_abra IS NULL AND o.date_created < DATE_SUB(NOW(), INTERVAL 30 SECOND)');
|
|
}
|
|
|
|
public function syncOrders()
|
|
{
|
|
$orders = $this->getOrdersToSync()->setMaxResults(10)->execute();
|
|
|
|
foreach ($orders as $order) {
|
|
$this->syncOrder($order['id']);
|
|
}
|
|
|
|
$this->updateOrderStatuses();
|
|
}
|
|
|
|
public function syncProducts()
|
|
{
|
|
$last_change = $this->getLastChangeID();
|
|
echo "<br>last_change start: {$last_change}<br>";
|
|
|
|
$startTime = getScriptTime();
|
|
|
|
do {
|
|
$this->logger->notice("Abra fetching: start:{$last_change}, size:{$this->batch_size}");
|
|
$changes = $this->client->weGetChanges($last_change, $this->batch_size);
|
|
$this->logger->notice('Abra fetched from:'.reset($changes).' to:'.end($changes));
|
|
|
|
if (getVal('test')) {
|
|
$this->printTrace();
|
|
}
|
|
|
|
if (empty($changes)) {
|
|
return false;
|
|
}
|
|
|
|
if ($last_change > 0) {
|
|
$first_change = explode($this->separator, reset($changes))[0];
|
|
if ($first_change != $last_change + 1) {
|
|
$this->log(['Přeskočená synchronizace', $first_change, $last_change]);
|
|
}
|
|
}
|
|
|
|
// logError(__FILE__, __LINE__, 'Abra sync: '.print_r($changes, true));
|
|
foreach ($changes as $change) {
|
|
$this->logger->notice("Abra sync: {$change}", ['change' => $change]);
|
|
$this->processChange($change);
|
|
}
|
|
|
|
$last_change = explode($this->separator, end($changes))[0];
|
|
|
|
echo "<br>last_change stop: {$last_change}<br>";
|
|
$this->logger->notice('Abra update last change: '.$last_change);
|
|
$this->updateLastChangeID($last_change);
|
|
|
|
// Clear changes
|
|
if (!isLocalDevelopment()) {
|
|
var_dump($this->client->weClearChanges($last_change));
|
|
}
|
|
} while (count($changes) == $this->batch_size && (getScriptTime() - $startTime) < (3 * 60));
|
|
|
|
return true;
|
|
}
|
|
|
|
public function processChange($change, $index = null)
|
|
{
|
|
$change = explode($this->separator, $change);
|
|
$type = static::$ABRA_UIDS[$change[1]];
|
|
$method = "sync_{$type}";
|
|
|
|
$last_change = $change[0];
|
|
if (!$last_change) {
|
|
if ($this->paginateByCode) {
|
|
$last_change = $change[3];
|
|
} else {
|
|
// Index by position
|
|
static $last_id = null;
|
|
$last_change = $index;
|
|
if ($last_id != $change[3]) {
|
|
$last_change++;
|
|
}
|
|
$last_id = $change[3];
|
|
}
|
|
}
|
|
|
|
if ($change[2] !== 'LastID') {
|
|
$this->$method($change[3], $change[2], $change[4], $change[5] ?? null);
|
|
}
|
|
|
|
return $last_change;
|
|
}
|
|
|
|
public function sync_product($code, $field, $value, $value2 = null)
|
|
{
|
|
$id = $this->getMappingFromAbra('product', $code);
|
|
|
|
if (!$id) {
|
|
// Create Product
|
|
$vat_id = null;
|
|
getVat($vat_id);
|
|
$this->insertSQL('products', ['code' => $code, 'vat' => $vat_id, 'figure' => 'N']);
|
|
$id = sqlInsertId();
|
|
|
|
$this->setMappingFromAbra('product', $id, $code);
|
|
}
|
|
|
|
$column = static::$PRODUCT_FIELDS[$field];
|
|
if (!$column) {
|
|
throw new Exception("Nedefinovaná položka synchronizace: {$field}");
|
|
}
|
|
|
|
$this->sync_products_switch($code, $column, $value, $value2, $id);
|
|
}
|
|
|
|
public function sync_products_switch($code, $column, $value, $value2, $id)
|
|
{
|
|
switch ($column) {
|
|
case 'id_category':
|
|
$id_section = $this->getMappingFromAbra('section', $value);
|
|
if (!$id_section) {
|
|
if ($value > 0) {
|
|
echo 'Unknown section ID: '.$value;
|
|
}
|
|
// throw new Exception('Unknown section ID: '.$value);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->deleteSQL('products_in_sections', ['id_product' => $id]);
|
|
|
|
sqlQuery('REPLACE INTO '.getTableName('products_in_sections').' (id_product, id_section)
|
|
VALUES (:id_product, :id_section)', ['id_product' => $id, 'id_section' => $id_section]);
|
|
|
|
return;
|
|
|
|
case 'unit':
|
|
$data = ['id_product' => $id, 'id_parameter' => 89];
|
|
$this->deleteSQL('parameters_products', $data);
|
|
if ($value != 'ks') {
|
|
$data['value_list'] = $this->findParameterValue(89, $value);
|
|
$this->insertSQL('parameters_products', $data);
|
|
}
|
|
|
|
return;
|
|
|
|
case 'price':
|
|
$value = $this->preparePriceFromAbra($value) / ((100 + getVat()) / 100);
|
|
break;
|
|
|
|
case 'price_common':
|
|
case 'priceWithoutVat':
|
|
$value = $this->preparePriceFromAbra($value);
|
|
break;
|
|
|
|
case 'figure':
|
|
$value = ($this->parseBool($value) || $value > 0) ? 'Y' : 'O';
|
|
break;
|
|
|
|
case 'weight':
|
|
case 'width':
|
|
case 'height':
|
|
case 'depth':
|
|
$value = $this->preparePriceFromAbra($value) ?: null;
|
|
break;
|
|
|
|
case 'main_unit_code':
|
|
$id_parameter_value = returnSQLResult('SELECT id FROM parameters_list WHERE id_parameter=89 AND value=:value', ['value' => $value]);
|
|
|
|
if (!$value) {
|
|
$this->insertSQL('parameters_list', ['value' => $value, 'id_parameter' => 89]);
|
|
$id_parameter_value = sqlInsertId();
|
|
}
|
|
|
|
$this->deleteSQL('parameters_products', ['id_product' => $id, 'id_parameter' => 89]);
|
|
|
|
$this->insertSQL('parameters_products', ['id_product' => $id, 'id_parameter' => 89, 'value_list' => $id_parameter_value]);
|
|
|
|
return;
|
|
|
|
case 'code':
|
|
try {
|
|
$this->updateSQL('products', [$column => $value], ['id' => $id]);
|
|
} catch (UniqueConstraintViolationException $e) {
|
|
echo '<br/>ERROR duplicate code: '.$value.'<br/>';
|
|
}
|
|
|
|
return;
|
|
|
|
case 'vat':
|
|
$value = $this->findVAT($value);
|
|
break;
|
|
|
|
case 'suppliers_code':
|
|
// Parametr "Objednávkové číslo"
|
|
$this->updateParameterValue($id, $value, 131);
|
|
|
|
return;
|
|
|
|
case 'delete':
|
|
$this->updateSQL('products', ['figure' => 'O'], ['id' => $id]);
|
|
|
|
return;
|
|
|
|
case 'measure_unit':
|
|
$unitId = $this->selectSQL('products_units', ['short_name_admin' => $value], ['id'])->fetchOne();
|
|
$value = $unitId ?: null;
|
|
break;
|
|
|
|
case 'measure_quantity':
|
|
$value = $this->preparePriceFromAbra($value);
|
|
break;
|
|
|
|
case 'ignore':
|
|
return;
|
|
}
|
|
|
|
$this->updateSQL('products', [$column => $value], ['id' => $id]);
|
|
}
|
|
|
|
public function sync_section($code, $field, $value)
|
|
{
|
|
$id = $this->getMappingFromAbra('section', $code);
|
|
|
|
if (!$id) {
|
|
// Create Section
|
|
$this->insertSQL('sections', ['name' => $value]);
|
|
$id = sqlInsertId();
|
|
|
|
$this->insertSQL('sections_relation', ['id_section' => $id, 'id_topsection' => null, 'position' => 999]);
|
|
|
|
$this->setMappingFromAbra('section', $id, $code);
|
|
}
|
|
|
|
$column = static::$SECTION_FIELDS[$field];
|
|
|
|
switch ($column) {
|
|
case 'parent_id':
|
|
$parent_id = $this->getMappingFromAbra('section', $value);
|
|
if (!$parent_id) {
|
|
if ($value > 0) {
|
|
echo 'Unknown section ID: '.$value;
|
|
}
|
|
// throw new Exception('Unknown section ID: '.$value);
|
|
|
|
break;
|
|
}
|
|
|
|
sqlQuery('DELETE FROM sections_relation WHERE id_section=:id', ['id' => $id]);
|
|
|
|
sqlQuery('INSERT INTO sections_relation (id_section, id_topsection)
|
|
VALUES (:id, :parent_id)', ['id' => $id, 'parent_id' => $parent_id, 'position' => 999]);
|
|
|
|
MenuSectionTree::invalidateCache();
|
|
break;
|
|
|
|
case 'position':
|
|
sqlQuery('UPDATE '.getTableName('sections_relation').'
|
|
SET position=:position
|
|
WHERE id_section=:id', ['id' => $id, 'position' => $value]);
|
|
break;
|
|
|
|
case 'description':
|
|
$section = $this->selectSQL('sections', ['id' => $id])->fetch();
|
|
if (isset($section['id_block'])) {
|
|
$block = sqlQueryBuilder()->select('id')->from('blocks')
|
|
->where('id_root=:id_block AND id_parent=:id_block')
|
|
->setParameter('id_block', $section['id_block'])
|
|
->orderBy('position', 'ASC')->setMaxResults(1)
|
|
->execute()->fetch();
|
|
$this->updateSQL('blocks', ['content' => $value], ['id' => $block['id']]);
|
|
} else {
|
|
$this->insertSQL('blocks', []);
|
|
$rootBlockID = sqlInsertId();
|
|
$this->insertSQL('blocks', [
|
|
'id_root' => $rootBlockID,
|
|
'id_parent' => $rootBlockID,
|
|
'position' => 1,
|
|
'name' => $section['name'],
|
|
'content' => $value,
|
|
]);
|
|
$this->updateSQL('sections', ['id_block' => $rootBlockID], ['id' => $id]);
|
|
}
|
|
break;
|
|
|
|
case 'figure':
|
|
$value = $this->parseBool($value);
|
|
$value = $value ? 'Y' : 'N';
|
|
$this->updateSQL('sections', [$column => $value], ['id' => $id]);
|
|
break;
|
|
|
|
case 'delete':
|
|
$this->deleteSQL('sections', ['id' => $id]);
|
|
|
|
return;
|
|
|
|
case 'name_first':
|
|
// Aktualizovat název sekce jen když je název roven Abra ID - aktualizovat jen poprvé, pak už ne
|
|
$this->updateSQL('sections', ['name' => $value], ['id' => $id, 'name' => $code]);
|
|
|
|
return;
|
|
|
|
case 'ignore':
|
|
break;
|
|
|
|
default:
|
|
$this->updateSQL('sections', [$column => $value], ['id' => $id]);
|
|
}
|
|
}
|
|
|
|
public function sync_producer($code, $field, $value)
|
|
{
|
|
$id = $this->getMappingFromAbra('producer', $code);
|
|
|
|
if (!$id) {
|
|
// Create Product
|
|
$vat_id = null;
|
|
getVat($vat_id);
|
|
$this->insertSQL('producers', ['name' => $value]);
|
|
$id = sqlInsertId();
|
|
|
|
$this->setMappingFromAbra('producer', $id, $code);
|
|
}
|
|
|
|
$column = static::$PRODUCER_FIELDS[$field];
|
|
|
|
switch ($column) {
|
|
case 'ignore':
|
|
break;
|
|
|
|
case 'position':
|
|
sqlQuery('UPDATE producers
|
|
SET position=:position
|
|
WHERE id=:id', ['id' => $id, 'position' => $value]);
|
|
break;
|
|
|
|
default:
|
|
$this->updateSQL('producers', [$column => $value], ['id' => $id]);
|
|
}
|
|
}
|
|
|
|
public function sync_ignore($code, $field, $value)
|
|
{
|
|
// Ignore
|
|
}
|
|
|
|
public function syncOrder($id)
|
|
{
|
|
// sqlQuery('SELECT COUNT(*) FROM abra_orders FOR UPDATE'); // Protected by flock now
|
|
|
|
$sync_order = $this->getOrder($id);
|
|
$code = getVal('DocumentNumber', $sync_order);
|
|
|
|
echo "<h2>synchronizuji objednavku {$code}</h2>";
|
|
|
|
try {
|
|
$ret = $this->client->__call('weSetReceivedOrder', $sync_order);
|
|
} catch (Exception $e) {
|
|
$this->printTrace();
|
|
throw $e;
|
|
}
|
|
|
|
$this->printTrace();
|
|
|
|
$this->logTrace("order {$id} - {$code}");
|
|
|
|
if ($ret[0] == 'True') {
|
|
$this->setMappingFromAbra('order', $id, $ret[2]);
|
|
|
|
$order = new Order();
|
|
$order->createFromDB($id);
|
|
$order->logHistory(translate('abra_order_send', 'abra'));
|
|
} else {
|
|
$error = $ret[1];
|
|
if ($error == 'Objednávka již v ABŘE existuje' || $error == 'ERROR:WEB009') {
|
|
$this->setMappingFromAbra('order', $id, $ret[2]);
|
|
} else {
|
|
throw new RuntimeException("Synchronizace objednávky {$code} selhala: ".join(', ', $ret));
|
|
}
|
|
}
|
|
}
|
|
|
|
public function prepareTextForOrder($text)
|
|
{
|
|
return str_replace(["\r\n", "\n"], '', $text);
|
|
}
|
|
|
|
public function prepareFieldLengthForOrder($text, $size)
|
|
{
|
|
if (is_null($text)) {
|
|
return $text;
|
|
}
|
|
|
|
$text = mb_substr($text, 0, $size);
|
|
|
|
if ($text === false) {
|
|
return '';
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
public function getOrder($id)
|
|
{
|
|
$order = new Order();
|
|
$order->createFromDB($id, true, true, true);
|
|
|
|
return $this->contextManager->activateOrder($order, function () use ($order) {
|
|
$delivery = $order->getDeliveryType($order->getDeliveryId());
|
|
|
|
$sync_order = [
|
|
// Header
|
|
'roID_WPJ' => $order->id,
|
|
'roDocumentNumber' => $order->order_no,
|
|
'roWEBnote' => str_replace(["\r\n", "\n"], '', $order->note_user),
|
|
'roCurrency' => findModule(Modules::CURRENCIES) ? $order->currency : 'CZK',
|
|
'roPaymentCode' => $this->getMappingToAbra('payment', $delivery->id_payment),
|
|
'roTransportCode' => $this->getMappingToAbra('delivery', $delivery->id_delivery),
|
|
|
|
// Delivery address
|
|
'adID_WPJ' => $order->id_user,
|
|
'adNameCompany' => $order->delivery_firm,
|
|
'adFirstName' => $order->delivery_name,
|
|
'adLastName' => $order->delivery_surname,
|
|
'adOrgIdentNumber' => null,
|
|
'adVATIdentNumber' => null,
|
|
'adStreet' => $order->delivery_street,
|
|
'adCity' => $order->delivery_city,
|
|
'adPostCode' => $order->delivery_zip,
|
|
'adCountryCode' => $order->delivery_country,
|
|
'adPhoneNumber1' => $order->delivery_phone,
|
|
'adPhoneNumber2' => null,
|
|
'adEmail' => null,
|
|
|
|
// Invoicing address
|
|
'afNameCompany' => $order->invoice_firm,
|
|
'afFirstName' => $order->invoice_name,
|
|
'afLastName' => $order->invoice_surname,
|
|
'afOrgIdentNumber' => $order->invoice_ico,
|
|
'afVATIdentNumber' => $order->invoice_dic,
|
|
'afStreet' => $order->invoice_street,
|
|
'afCity' => $order->invoice_city,
|
|
'afPostCode' => $order->invoice_zip,
|
|
'afCountryCode' => $order->invoice_country,
|
|
'afPhoneNumber1' => $order->invoice_phone,
|
|
'afPhoneNumber2' => null,
|
|
'afEmail' => $order->invoice_email,
|
|
'roRows' => $this->getOrderRows($order),
|
|
];
|
|
|
|
return $sync_order;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param $order Order
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getOrderRows($order)
|
|
{
|
|
$order->fetchItems();
|
|
|
|
$rows = [];
|
|
$rows_text = [];
|
|
|
|
foreach ($order->items as $item) {
|
|
if ($item['id_product']) {
|
|
$code = $this->getMappingToAbra('product', $item['id_product']);
|
|
} else {
|
|
$code = '';
|
|
}
|
|
|
|
if ($code) {
|
|
$type = 3;
|
|
$name = '';
|
|
$price = $this->preparePriceToAbra($item['piece_price']['value_without_vat_no_rounding']);
|
|
$quantity = $item['pieces'];
|
|
} else {
|
|
$type = 1;
|
|
$name = $item['descr'];
|
|
$price = $this->preparePriceToAbra($item['total_price']['value_without_vat_no_rounding']);
|
|
$quantity = 1;
|
|
}
|
|
|
|
// "3~|~002002~|~1~|~168,5~|~",
|
|
$row = join($this->separator, [
|
|
$type,
|
|
$code,
|
|
$quantity,
|
|
$price,
|
|
$name,
|
|
]);
|
|
|
|
if ($code) {
|
|
$rows[] = $row;
|
|
} else {
|
|
$rows_text[] = $row;
|
|
}
|
|
}
|
|
|
|
return array_merge($rows, $rows_text);
|
|
}
|
|
|
|
public function finalizeSync()
|
|
{
|
|
}
|
|
|
|
public function getClient(): SoapClient
|
|
{
|
|
return $this->client;
|
|
}
|
|
|
|
public function convertUnits($column, $unit, int $value): int
|
|
{
|
|
if ($column == 'weight') {
|
|
switch ($unit) {
|
|
case 'g':
|
|
$value /= 1000;
|
|
break;
|
|
case 't':
|
|
$value *= 1000;
|
|
}
|
|
} else {
|
|
switch ($unit) {
|
|
case 'm':
|
|
$value *= 100;
|
|
break;
|
|
case 'dm':
|
|
$value *= 10;
|
|
break;
|
|
case 'mm':
|
|
$value /= 10;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
protected function createAbraDataObject($name, $value, $type)
|
|
{
|
|
$data = [
|
|
'FieldValueAsString' => '',
|
|
'FieldValueAsFloat' => '',
|
|
'FieldValueAsDateTime' => '',
|
|
'FieldValueAsBoolean' => '',
|
|
'FieldValueAsInteger' => '',
|
|
'FieldDataKind' => 'adkData',
|
|
'BusinessObject' => '',
|
|
'Collection' => '',
|
|
];
|
|
|
|
return array_merge($data, [
|
|
'FieldName' => $name,
|
|
'FieldValueAs'.$type => $value,
|
|
'FieldDataType' => 'dt'.$type,
|
|
]);
|
|
}
|
|
|
|
public function log($data)
|
|
{
|
|
// Do not log in development
|
|
if (isLocalDevelopment()) {
|
|
return;
|
|
}
|
|
|
|
/** @var Symfony\Bridge\Monolog\Logger $logger */
|
|
$logger = ServiceContainer::getService('logger');
|
|
$logger->notice('Abra Log: '.print_r($data, true));
|
|
}
|
|
|
|
public function logToActivityLog($message, $data = [])
|
|
{
|
|
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_SYNC, 'Abra: '.$message, $data);
|
|
}
|
|
|
|
public function logException(Exception $exception)
|
|
{
|
|
$sentry = getRaven();
|
|
$sentry->captureException($exception);
|
|
}
|
|
|
|
public function getWebSource(Order $order)
|
|
{
|
|
return $this->settings['web'];
|
|
}
|
|
|
|
public function getOrderNumber(Order $order)
|
|
{
|
|
return $order->order_no;
|
|
}
|
|
|
|
public function getInvoice(Order $order)
|
|
{
|
|
$result = $this->client->weGetInvoice($this->getWebSource($order), $this->getOrderNumber($order));
|
|
|
|
if ($result[0] === 'True') {
|
|
return base64_decode($result[2]);
|
|
} else {
|
|
if (isDevelopment()) {
|
|
$this->printTrace();
|
|
}
|
|
throw new NotFoundHttpException('Order does not have invoice: '.print_r($result, true));
|
|
}
|
|
}
|
|
|
|
public function loopSychronization($timeout, $endOnEmptyQueue = false)
|
|
{
|
|
$startTime = getScriptTime();
|
|
|
|
$flock_file = fopen('data/tmp/abra_sync.lock', 'wb');
|
|
|
|
while (!flock($flock_file, LOCK_EX | LOCK_NB)) {
|
|
if ((getScriptTime() - $startTime) > $timeout) {
|
|
exit('Cannot get lock');
|
|
}
|
|
var_dump('Waiting for lock');
|
|
flush();
|
|
sleep(1);
|
|
}
|
|
|
|
// Sync only for 10 seconds to loop more often
|
|
$this->syncTimeout = 10;
|
|
|
|
do {
|
|
try {
|
|
var_dump('Loop....');
|
|
flush();
|
|
|
|
$tmp = (getScriptTime() - $startTime); // For Sentry
|
|
|
|
$this->syncOrders();
|
|
$moreMessages = $this->syncProducts();
|
|
// Update scheduled products only if no more messages waiting
|
|
// Try to deplete queue and send pending orders ASAP
|
|
if ($moreMessages !== true) {
|
|
$this->updateScheduledProducts();
|
|
|
|
if ($endOnEmptyQueue) {
|
|
break;
|
|
}
|
|
|
|
if ((getScriptTime() - $startTime) < $timeout) {
|
|
sleep(max(1, min(30, $timeout - (getScriptTime() - $startTime) + 1)));
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
exception_handler($e);
|
|
sleep(max(1, min(60, $timeout - (getScriptTime() - $startTime) + 1)));
|
|
}
|
|
} while ((getScriptTime() - $startTime) < $timeout);
|
|
|
|
$this->finalizeSync();
|
|
|
|
flock($flock_file, LOCK_UN);
|
|
fclose($flock_file);
|
|
}
|
|
|
|
public function updateParameterValue($productId, $value, $id_parameter, $column = 'value_char')
|
|
{
|
|
$data = ['id_product' => $productId, 'id_parameter' => $id_parameter];
|
|
$this->deleteSQL('parameters_products', $data);
|
|
if ($value) {
|
|
$data[$column] = $value;
|
|
$this->insertSQL('parameters_products', $data);
|
|
}
|
|
}
|
|
|
|
public function process_message_abra($body)
|
|
{
|
|
$this->processChange($body->data);
|
|
}
|
|
}
|
|
|
|
if (empty($subclass)) {
|
|
class Abra extends AbraBase
|
|
{
|
|
}
|
|
}
|