diff --git a/mirzaev/skillparts/system/commands/TestController.php b/mirzaev/skillparts/system/commands/TestController.php index 2822d39..35072ef 100644 --- a/mirzaev/skillparts/system/commands/TestController.php +++ b/mirzaev/skillparts/system/commands/TestController.php @@ -6,6 +6,9 @@ use yii\console\Controller; use yii\console\ExitCode; use app\models\Invoice; +use app\models\Product; +use app\models\ProductGroup; +use app\models\ImportEdgeSupply; class TestController extends Controller { @@ -75,4 +78,59 @@ class TestController extends Controller return ExitCode::OK; } + + + public function actionAnalogs($_id = 'product/51987159') + { + + + return ExitCode::OK; + } + + public function actionWriteAnalog($_id = 'product/51987159', $analog = 'product/12051485') + { + // Инициализация товара + $product = Product::searchById($_id); + + // Инициализация аналога + $analog = Product::searchById($analog); + + if (!$group = ProductGroup::searchByProduct($product)) { + // Не найдена группа товаров + + // Запись новой группы + $group = ProductGroup::writeEmpty(active: true); + + // Запись товара в группу + $group->writeProduct($product); + } + + if ($_group = ProductGroup::searchByProduct($analog)) { + // Найдена друга группа у товара который надо добавить в группу + + // Перенос всех участников (включая целевой товар) + return $group->transfer($_group); + } else { + // Не найдена группа у товара который надо добавить в группу + + // Запись целевого товара в группу + return $group->writeProduct($analog); + } + + return ExitCode::OK; + } + + public function actionReadAnalog($_id = 'product/51987159') + { + var_dump((ProductGroup::searchByProduct(Product::searchById($_id))->searchProducts())); + + return ExitCode::OK; + } + + public function actionEdgeMax() + { + var_dump(ImportEdgeSupply::generateVersion()); + + return ExitCode::OK; + } } diff --git a/mirzaev/skillparts/system/controllers/CartController.php b/mirzaev/skillparts/system/controllers/CartController.php index 2bb7d2e..7188fde 100644 --- a/mirzaev/skillparts/system/controllers/CartController.php +++ b/mirzaev/skillparts/system/controllers/CartController.php @@ -86,26 +86,26 @@ class CartController extends Controller $account = Account::initAccount(); // Поиск корзины (текущего заказа) - $model = Order::searchByType(); + $data = Order::searchByType()[0] ?? null; - if (empty($model)) { + if (empty($data['order'])) { // Корзина не инициализирована // Инициализация - $model = new Order(); + $data['order'] = new Order(); - if ($model->save()) { + if ($data['order']->save()) { // Удалось инициализировать заказ // Подключение заказа к аккаунту - $model->connect($account); + $data['order']->connect($account); } else { throw new Exception('Не удалось инициализировать заказ'); } } // Инициализация содержимого корзины - $connections = $model->content(10, $page); + $data['supplies'] = $data['order']->supplies(10, $page, test: true); // Инициализация данных списка для выбора терминала $delivery_to_terminal_list = $account->genListTerminalsTo(); @@ -138,14 +138,14 @@ class CartController extends Controller yii::$app->response->format = Response::FORMAT_JSON; return [ - 'main' => $this->renderPartial('index', compact('account', 'model', 'connections', 'delivery_to_terminal_list')), + 'main' => $this->renderPartial('index', compact('account', 'data', 'delivery_to_terminal_list')), 'title' => 'Корзина', 'redirect' => '/cart', '_csrf' => yii::$app->request->getCsrfToken() ]; } - return $this->render('index', compact('account', 'model', 'connections', 'delivery_to_terminal_list')); + return $this->render('index', compact('account', 'data', 'delivery_to_terminal_list')); } public function actionEditComm(string $catn, string $prod): array|string|null diff --git a/mirzaev/skillparts/system/controllers/OrderController.php b/mirzaev/skillparts/system/controllers/OrderController.php index be3e197..c19038b 100644 --- a/mirzaev/skillparts/system/controllers/OrderController.php +++ b/mirzaev/skillparts/system/controllers/OrderController.php @@ -266,11 +266,10 @@ class OrderController extends Controller } // Инициализация заказов - $orders = Order::searchByType( + $data = Order::searchByType( type: $type, limit: 10, page: 1, - select: '{account_edge_order, order}', supplies: true, from: $from, to: $to, @@ -278,7 +277,6 @@ class OrderController extends Controller ); // Фильтрация - if ( !yii::$app->user->isGuest && yii::$app->user->identity->type === 'administrator' @@ -287,20 +285,20 @@ class OrderController extends Controller // Пользователь имеет доступ // Инициализация заказов для модератора - $moderator_orders = self::genOrdersForModeration(); + $moderator_data = Order::searchByType(account: '@all', type: 'requested', limit: 10, page: 1, supplies: true); } else { // Пользователь не имеет доступ // Инициализация заглушки - $moderator_orders = null; + $moderator_data = null; } + // Инициализация аккаунта + $account ?? $account = Account::initAccount(); + if (yii::$app->request->isPost) { // POST-запрос - // Инициализация аккаунта - $account ?? $account = Account::initAccount(); - // Конвертация из UNIXTIME в формат поддерживаемый календарём по спецификации HTML $from = DateTime::createFromFormat('U', (string) $from)->format('Y-m-d'); $to = DateTime::createFromFormat('U', (string) $to)->format('Y-m-d'); @@ -309,15 +307,15 @@ class OrderController extends Controller yii::$app->response->format = Response::FORMAT_JSON; return [ - 'main' => $this->renderPartial('/orders/index', compact('orders', 'moderator_orders', 'search', 'from', 'to', 'window') - + ['panel' => $this->renderPartial('/orders/search/panel', compact('account') + ['response' => @$orders[0]['supplies']] ?? null)]), + 'main' => $this->renderPartial('/orders/index', compact('data', 'moderator_data', 'account', 'search', 'from', 'to', 'window') + + ['panel' => $this->renderPartial('/orders/search/panel', compact('account') + ['data' => $data] ?? null)]), 'title' => 'Заказы', 'redirect' => '/orders', '_csrf' => yii::$app->request->getCsrfToken() ]; } - return $this->render('/orders/index', compact('orders', 'moderator_orders')); + return $this->render('/orders/index', compact('data', 'moderator_data', 'account')); } /** @@ -394,19 +392,19 @@ class OrderController extends Controller ]; // Инициализация корзины - if (!$model = Order::searchByType($account)) { + if (!$data = Order::searchByType($account)[0]) { // Корзина не найдена (текущий заказ) // Инициализация - $model = new Order(); - $model->save() or throw new Exception('Не удалось инициализировать заказ'); + $data['order'] = new Order(); + $data['order']->save() or throw new Exception('Не удалось инициализировать заказ'); // Запись ребра: АККАУНТ -> ЗАКАЗ - AccountEdgeOrder::write($account->readId(), $model->readId(), 'current') or $model->addError('errors', 'Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ'); + AccountEdgeOrder::write($account->readId(), $data['order']->readId(), 'current') or $data['order']->addError('errors', 'Не удалось инициализировать ребро: АККАУНТ -> ЗАКАЗ'); } // Если запись не удалась, то вернуть код: 500 Internal Server Error - $model->writeSupply($supply_id, $delivery_type, (int) $amount) or yii::$app->response->statusCode = 500; + $data['order']->writeSupply($supply_id, $delivery_type, (int) $amount) or yii::$app->response->statusCode = 500; return $return; } @@ -585,105 +583,98 @@ class OrderController extends Controller */ public function actionRequest(): string|array|null { - // Инициализация - $model = Order::searchByType(supplies: true); + // Инициализация аккаунта + $account = Account::initAccount(); - if (($account_edge_order = AccountEdgeOrder::searchByVertex(yii::$app->user->id, $model->readId(), 'current')[0]) ?? false) { - // Найдено ребро: АККАУНТ -> ЗАКАЗ + if (isset($account)) { + // Найден аккаунт + if ($account->filled() === true) { + // Заполнены все необходимые поля для оформления заказа (подразумевается прохождение второго этапа регистрации) - if ($order_edge_supply = OrderEdgeSupply::searchByDirection($account_edge_order->_to, type: 'write', limit: 500)) { - // Найдены рёбра: ЗАКАЗ -> ПОСТАВКА + // Инициализация данных о заказе и поставках + $data = Order::searchByType(supplies: true)[0]; - foreach ($order_edge_supply as $edge) { - // Перебор рёбер: ЗАКАЗ -> ПОСТАВКА + if (($account_edge_order = AccountEdgeOrder::searchByVertex(yii::$app->user->id, $data['order']->readId(), 'current')[0]) ?? false) { + // Найдено ребро: АККАУНТ -> ЗАКАЗ - if ($product = Product::searchBySupplyId($edge->_to)) { - // Найден товар + // Инициализация статуса необходимости реинициализации + $reinitialization = false; - // Проверка на активность товара - if ($product['stts'] === 'active'); - else $edge->delete(); + if ($order_edge_supply = OrderEdgeSupply::searchByDirection($account_edge_order->_to, type: 'write', limit: 500)) { + // Найдены рёбра: ЗАКАЗ -> ПОСТАВКА + + foreach ($order_edge_supply as $edge) { + // Перебор рёбер: ЗАКАЗ -> ПОСТАВКА + + if ($product = Product::searchBySupplyId($edge->_to)) { + // Найден товар + + if ($product['stts'] !== 'active') { + // Не активен товар + + // Удаление из заказа + $edge->delete(); + + // Статус необходимости реинициализации + $reinitialization = true; + } + } + } + } + + // Реинициализация + if ($reinitialization) $data = Order::searchByType(supplies: true); + + // Запись + $account_edge_order->type = 'requested'; + + if ($account_edge_order->update() > 0) { + // Удалось сохранить изменения + + // Запись в журнал + $data['order']->journal('requested'); + + Invoice::generate($data['order']->_key, $this->renderPartial('/invoice/order/pattern', [ + 'buyer' => [ + 'id' => yii::$app->user->identity->_key, + 'info' => 'Неизвестно' + ], + 'data' => $data, + 'date' => $account_edge_order->date ?? time() + ])); + + // Отправка уведомлений модераторам + Notification::_write($this->renderPartial('/notification/system/orders/new', ['id' => $account_edge_order->_key]), true, '@auth', Notification::TYPE_MODERATOR_ORDER_NEW); + + // Отправка уведомления покупателю + Notification::_write($this->renderPartial('/notification/system/orders/new', ['id' => $account_edge_order->_key]), true, $account->_key, Notification::TYPE_NOTICE); } } - } - // Реиницилазация - $model = Order::searchByType(supplies: true); + return $this->actionIndex(); + } else { + // Не заполнены все необходимые поля для оформления заказа (подразумевается прохождение второго этапа регистрации) - // Запись - $account_edge_order->type = 'requested'; + var_dump($account->getErrors()); - if ($account_edge_order->update()) { - // Удалось сохранить изменения + foreach ($account->getErrors() as $parameter => $errors) { + // Перебор параметров с ошибками - // Запись в журнал - $model->journal('requested'); + foreach ($errors as $error) { + // Перебор ошибок - // Инициализация буфера поставок - $supplies = []; + // Инициализация ярлыка + $label = $account->getAttributeLabel($parameter) ?? $parameter; - foreach ($model['supplies'] as $supply) { - // Перебор поставок - - $supplies[] = [ - 'title' => $supply['supply']['catn'], - 'delivery' => 0, - 'amount' => [ - // 'value' => $supply['amount'][$supply['order_edge_supply'][]] ?? 0, - // 'value' => $supply['amount'] ?? 0, - 'value' => 0, - 'unit' => 'шт' - ], - 'cost' => [ - // 'value' => $supply['cost'] ?? 0, - 'value' => 0, - 'unit' => 'руб' - ], - 'type' => 'supply' - ]; + // Отправка уведомления покупателю + Notification::_write($error . " ($label)", account: $account->_key, type: Notification::TYPE_ERROR); + } } - Invoice::generate($model->_key, $this->renderPartial('/invoice/order/pattern', [ - 'buyer' => [ - 'id' => yii::$app->user->identity->_key, - 'info' => 'Неизвестно' - ], - 'order' => [ - 'id' => $model->_key, - 'date' => $account_edge_order->date ?? time(), - 'entries' => $supplies - ] - ])); - - // Отправка уведомлений модераторам - Notification::_write($this->renderPartial('/notification/system/orders/new', ['id' => $account_edge_order->_key]), true, '@auth', Notification::TYPE_MODERATOR_ORDER_NEW); + return null; } } - - return $this->actionIndex(); - } - - /** - * Генерация данных заказов для модераторов - * - * Включает поиск запрошенных заказов и связанных с ними поставках - * - * @return array ['order' => array, 'order_edge_account' => array, 'supplies' => array] - * - * @todo Уничтожить заменив на Order::searchByType(supplies: true) - */ - protected static function genOrdersForModeration(int $page = 1): array - { - $orders = Order::searchByType(account: '@all', type: 'requested', limit: 10, page: 1, select: '{account_edge_order, order}'); - - foreach ($orders as &$order) { - // Перебор заказов - - $order['supplies'] = Order::searchById($order['order']['_id'])->content(10, $page); - } - - return $orders; } /** diff --git a/mirzaev/skillparts/system/controllers/ProfileController.php b/mirzaev/skillparts/system/controllers/ProfileController.php index 89cd1d3..f566f53 100644 --- a/mirzaev/skillparts/system/controllers/ProfileController.php +++ b/mirzaev/skillparts/system/controllers/ProfileController.php @@ -791,6 +791,8 @@ class ProfileController extends Controller return self::syncGeolocationWithDellin($account); } + + return false; } /** @@ -1227,6 +1229,9 @@ class ProfileController extends Controller * Удалить склад * * @return array|string|null + * + * @todo + * 1. Удаление всех ImportEdgeSupply с устаревшими версиями */ public function actionWarehousesDelete(): array|string|null { @@ -1277,11 +1282,22 @@ class ProfileController extends Controller foreach (Supply::searchByImport($import->readId(), limit: 9999) as $supply) { // Перебор найденных поставок - if (ImportEdgeSupply::searchBySupply($supply, limit: 1)?->delete() === 1) { - // Удалено ребро: ИНСТАНЦИЯ ПОСТАВКИ -> ПОСТАВКА + if ($edge = ImportEdgeSupply::searchBySupply($supply)) { + // Найдено ребро: ИНСТАНЦИЯ ПОСТАВКИ -> ПОСТАВКА - // Удаление поставки - if ($supply->delete() === 1) ++$deleted; + // Инициализация версии + $version = $edge->vrsn; + + if ($edge->delete() === 1) { + // Удалено ребро: ИНСТАНЦИЯ ПОСТАВКИ -> ПОСТАВКА + + if (ImportEdgeSupply::searchMaxVersion($supply) <= $version) { + // Не найдена более новая версия поставки (обрабатываемая считается актуальной) + + // Удаление поставки + if ($supply->delete() === 1) ++$deleted; + } + } } } diff --git a/mirzaev/skillparts/system/models/Account.php b/mirzaev/skillparts/system/models/Account.php index 7c3ee36..23c093c 100644 --- a/mirzaev/skillparts/system/models/Account.php +++ b/mirzaev/skillparts/system/models/Account.php @@ -989,7 +989,7 @@ class Account extends Document implements IdentityInterface, PartnerInterface public static function isModer($account = null): bool { if ($account = self::initAccount($account)) { - // Аккаунт инициализирован + // Инициализирован аккаунт if ($account->type === 'moderator') { return true; @@ -1022,4 +1022,26 @@ class Account extends Document implements IdentityInterface, PartnerInterface default => 'Неизвестно' }; } + + public static function filled($account = null): bool|self + { + if ($account = self::initAccount($account)) { + // Инициализирован аккаунт + + // Проверка на заполненность обязательных полей + if (empty($account->name)) $account->addError('name', 'Не заполнено необходимое поле для заказа'); + if (empty($account->boss)) $account->addError('boss', 'Не заполнено необходимое поле для заказа'); + if (empty($account->simc)) $account->addError('simc', 'Не заполнено необходимое поле для заказа'); + if (empty($account->comp)) $account->addError('comp', 'Не заполнено необходимое поле для заказа'); + if (empty($account->mail)) $account->addError('mail', 'Не заполнено необходимое поле для заказа'); + if (empty($account->taxn)) $account->addError('taxn', 'Не заполнено необходимое поле для заказа'); + if (empty($account->cntg)) $account->addError('cntg', 'Не заполнено необходимое поле для заказа'); + if (empty($account->fadd)) $account->addError('fadd', 'Не заполнено необходимое поле для заказа'); + if (empty($account->ladd)) $account->addError('ladd', 'Не заполнено необходимое поле для заказа'); + + return $account->hasErrors() ? $account : true; + } + + return false; + } } diff --git a/mirzaev/skillparts/system/models/AccountEdgeOrder.php b/mirzaev/skillparts/system/models/AccountEdgeOrder.php index b971752..5d59965 100644 --- a/mirzaev/skillparts/system/models/AccountEdgeOrder.php +++ b/mirzaev/skillparts/system/models/AccountEdgeOrder.php @@ -24,10 +24,20 @@ class AccountEdgeOrder extends Edge return self::find()->where(['_to' => $order_id])->limit($limit)->all(); } - public static function convertStatusToRussian(string $status): string + /** + * Генерация ярлыка на русском языке для статуса заказа + * + * @param string $status Статус заказа + * + * @return string Ярлык статуса на русском языке + */ + public static function statusToRussian(string $status = ''): string { - return match($status) { - 'accepted' => 'Доставляется', + return match ($status) { + 'requested' => 'Запрошен', + 'accepted' => 'Ожидается отправка', + 'going' => 'Доставляется', + 'completed' => 'Завершен', default => 'Обрабатывается' }; } diff --git a/mirzaev/skillparts/system/models/Document.php b/mirzaev/skillparts/system/models/Document.php index c6528d1..8ae593a 100644 --- a/mirzaev/skillparts/system/models/Document.php +++ b/mirzaev/skillparts/system/models/Document.php @@ -150,7 +150,7 @@ abstract class Document extends ActiveRecord return static::findOne(['_id' => $_id]); } - public static function readLast(): ?static + public static function readLast(): static|null|bool { return static::find()->orderBy(['DESC'])->one(); } diff --git a/mirzaev/skillparts/system/models/Edge.php b/mirzaev/skillparts/system/models/Edge.php index 8bc59d7..faf2054 100644 --- a/mirzaev/skillparts/system/models/Edge.php +++ b/mirzaev/skillparts/system/models/Edge.php @@ -167,20 +167,44 @@ abstract class Edge extends Document /** * Поиск рёбер по направлению */ - public static function searchByDirection(string $target, string $direction = 'OUTBOUND', string $type = '', int $limit = 1): static|array|null + public static function searchByDirection(string $_from, string $direction = 'OUTBOUND', string $type = '', array $where = [], int $limit = 1): static|array|null { if (str_contains($direction, 'OUTBOUND')) { // Исходящие рёбра - $query = static::find()->where(['_from' => $target, 'type' => $type]); + if (isset($where)) { + // Получен параметр $where + + // Реинициализация + $where = array_merge($where + ['_from' => $_from]); + } else { + // Не получен параметр $where + + // Реинициализация + $where = array_merge(['_from' => $_from, 'type' => $type]); + } + + $query = static::find()->where($where); } else if (str_contains($direction, 'INBOUND')) { // Входящие рёбра - $query = static::find()->where(['_to' => $target, 'type' => $type]); + if (isset($where)) { + // Получен параметр $where + + // Реинициализация + $where = array_merge($where + ['_to' => $_from]); + } else { + // Не получен параметр $where + + // Реинициализация + $where = array_merge(['_to' => $_from, 'type' => $type]); + } + + $query = static::find()->where($where); } else if (str_contains($direction, 'ANY')) { // Исходящие и входящие рёбра - return static::searchByDirection(target: $target, direction: 'OUTBOUND', type: $type, limit: $limit) + static::searchByDirection(target: $target, direction: 'INBOUND', type: $type, limit: $limit); + return static::searchByDirection(_from: $_from, direction: 'OUTBOUND', type: $type, where: $where, limit: $limit) + static::searchByDirection(_from: $_from, direction: 'INBOUND', type: $type, where: $where, limit: $limit); } if ($limit < 2) { diff --git a/mirzaev/skillparts/system/models/ImportEdgeSupply.php b/mirzaev/skillparts/system/models/ImportEdgeSupply.php index f9f50ca..a4ef76d 100644 --- a/mirzaev/skillparts/system/models/ImportEdgeSupply.php +++ b/mirzaev/skillparts/system/models/ImportEdgeSupply.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace app\models; +use mirzaev\yii2\arangodb\Query; + /** * Связь инстанций импорта с поставками */ @@ -25,6 +27,7 @@ class ImportEdgeSupply extends Edge return array_merge( parent::attributes(), [ + 'vrsn' ] ); } @@ -37,6 +40,7 @@ class ImportEdgeSupply extends Edge return array_merge( parent::attributeLabels(), [ + 'vrsn' => 'Версия' ] ); } @@ -49,12 +53,63 @@ class ImportEdgeSupply extends Edge return array_merge( parent::rules(), [ + [ + [ + 'vrsn' + ], + 'integer', + 'message' => '{attribute} должен быть числом.' + ] ] ); } - public static function searchBySupply(Supply $supply): static + /** + * Перед сохранением + * + * @todo Подождать обновление от ебаного Yii2 и добавить + * проверку типов передаваемых параметров + */ + public function beforeSave($data): bool { - return static::find()->where(['_to' => $supply->readId()])->one(); + if (parent::beforeSave($data)) { + if ($this->isNewRecord) { + } + + return true; + } + + return false; + } + + /** + * Поиск максимальной версии + * + * Ищет максимальную версию у поставок + * + * @param Supply $supply Поставка + * + * @return int Версия, если найдена + */ + public static function searchMaxVersion(Supply $supply): ?int + { + return static::find()->execute("RETURN MAX( + FOR import_edge_supply IN import_edge_supply + FILTER (import_edge_supply._to == 'supply/$supply->_key') + LIMIT 0,999 + RETURN import_edge_supply.vrsn + )")[0] ?? null; + } + + /** + * Поиск по поставке + * + * @param Supply $supply + * + * @return static|null + */ + public static function searchBySupply(Supply $supply): ?static + { + return static::find()->where(['_to' => $supply->readId()])->one()[0] ?? null; } } diff --git a/mirzaev/skillparts/system/models/Order.php b/mirzaev/skillparts/system/models/Order.php index 6d3e7b9..2e5b44d 100644 --- a/mirzaev/skillparts/system/models/Order.php +++ b/mirzaev/skillparts/system/models/Order.php @@ -274,7 +274,7 @@ class Order extends Document implements DocumentInterface int|null $from = null, int|null $to = null, bool $count = false - ): self|int|array|null { + ): int|array|null { // Инициализация аккаунта if (empty($account) && isset(yii::$app->user->identity)) { // Данные не переданы @@ -335,7 +335,7 @@ class Order extends Document implements DocumentInterface } // Поиск заказов в базе данных - $return = self::searchByEdge( + $orders = self::searchByEdge( from: 'account', to: 'order', subquery_where: $subquery_where, @@ -346,73 +346,81 @@ class Order extends Document implements DocumentInterface sort: ['DESC'], select: $select, direction: 'INBOUND', - count: !$supplies && $count ? true : false + count: !$supplies && $count ); if (!$supplies && $count) { // Запрошен подсчет заказов - return $return; + return $orders; + } + + // Инициализация буфера возврата + $return = []; + + // Инициализация архитектуры буфера вывода + foreach ($orders as $key => $order) { + // Перебор заказов + + // Запись в буфер возврата + $return[$key]['order'] = $order; } if ($supplies) { // Запрошен поиск поставок - foreach ($return as &$container) { + foreach ($return as $key => &$container) { // Перебор заказов - if ($container instanceof Order) { - $buffer = $container; + + if ($container['order'] instanceof Order) { + // Инстанция заказа + + // Инициализация заказа + $order = $container['order']; } else { + // Массив с заказом (подразумевается) + // Инициализация настроек $config = $container['order']; unset($config['_id'], $config['_rev'], $config['_id']); - // Инициализация буфера - $buffer = new Order($config); + // Инициализация заказа + $order = new Order($config); } // Чтение полного содержания - $container['supplies'] = $buffer->content($limit, $page, $search, count: $count); + $return[$key]['supplies'] = $order->supplies($limit, $page, $search, count: $count); if ($count) { - // Запрошен подсчет поставок + // Запрошен подсчет поставок (переделать под подсчёт) - return $container['supplies']; + return $return[$key]['supplies']; } } } - return $limit === 1 ? $return[0] ?? null : $return; + return $return; } - // /** - // * Объеденить дубликаты поставок - // * - // * @return array Данные заказа - // */ - // public function mergeDuplicateSupplies(array $order): array { - // foreach ($orders as $order) { - - // } - // } - /** - * Поиск содержимого заказа + * Поиск содержимых поставок заказа * * @todo В будущем возможно заказ не только поставок реализовать * Переписать реестр и проверку на дубликаты, не понимаю как они работают */ - public function content(int $limit = 1, int $page = 1, string|null $search = null, bool $count = false): Supply|int|array|null + public function supplies(int $limit = 1, int $page = 1, string|null $search = null, bool $count = false, $test = false): Supply|int|array|null { + // Инициализация аккаунта + $account = Account::initAccount(); + // Генерация сдвига по запрашиваемым данным (пагинация) $offset = $limit * ($page - 1); if (!empty($search)) { // Передан поиск по продуктам в заказах - // Добавить ограничитель - + // Запись ограничения по максимальному значению $limit = 9999; } @@ -426,286 +434,125 @@ class Order extends Document implements DocumentInterface 'order._id' => $this->readId() ] ], - foreach: ['edge' => 'order_edge_supply'], - where: 'edge._to == supply._id', + where: 'order_edge_supply != []', limit: $limit, offset: $offset, filterStart: ['catn' => $search], direction: 'INBOUND', - select: '{supply, order_edge_supply}', + select: '{order_edge_supply}', count: $count ); if ($count) { - // Запрошен подсчет + // Подсчёт запрошен return $connections; } - // Рекурсивная фильтрации соединений - recursive_connections_filtration: - - // Инициализация статуса об изменении списка поставок - $changed = false; - - // Инициализация глобального буфера поставок - $buffer_global = []; - - // Разделение поставок по типам доставки - // foreach ($connections as &$connection) { - // // Перебор поставок - - // if (count($connection['order_edge_supply'] ?? []) > 1) { - // // Поставок более чем одна - - // // Инициализация (выбор значения для фильтрации) - // $target_dlvr_type = reset($connection['order_edge_supply'])['dlvr']['type'] ?? 'auto'; - - // // Инициализация буфера поставок - // $buffer = $connection; - // unset($buffer['order_edge_supply']); - - // foreach ($connection['order_edge_supply'] as $key => &$order_edge_supply) { - // // Перебор рёбер: ЗАКАЗ -> ПОСТАВКА - - // // if ((empty($order_edge_supply['dlvr']['type']) && $target_dlvr_type === 'auto') - // // || (isset($order_edge_supply['dlvr']['type']) && $order_edge_supply['dlvr']['type'] !== $target_dlvr_type) - // // ) { - // if ((isset($order_edge_supply['dlvr']['type']) && $order_edge_supply['dlvr']['type'] === $target_dlvr_type) - // || (empty($order_edge_supply['dlvr']['type']) && $target_dlvr_type === 'auto') - // ) { - // // Тип доставки для одной поставки отличается - - // // Запись в буфер - // $buffer['order_edge_supply'][$key] = $order_edge_supply; - - // // Удаление из списка - // unset($connection['order_edge_supply'][$key]); - - // // Перезапись статуса - // $changed = true; - // } - // } - - // // Запись из буфера в глобальный буфер - // $buffer_global[] = $buffer; - // } - // } - - // $connections = array_merge($connections, $buffer_global); - - // if ($changed) goto recursive_connections_filtration; - // Инициализация реестра дубликатов - $registry = []; + $supplies = []; - // Подсчёт и перестройка массива для очистки от дубликатов foreach ($connections as $key => &$connection) { - // Перебор поставок + // Перебор объектов для заказа - if (in_array([ - 'catn' => $connection['supply']['catn'], - 'type' => $type = reset($connection['order_edge_supply'])['dlvr']['type'] ?? 'auto' - ], $registry)) { - // Если данная поставка найдена в реестре - - // Удаление - unset($connections[$key]); - - // Пропуск итерации - continue; - } - - // Повторный перебор для поиска дубликатов - foreach ($connections as &$connection_for_check) { - if ($connection == $connection_for_check) { - // Найден дубликат - - // Запись в реестр - $registry[$key] = [ - 'catn' => $connection_for_check['supply']['catn'], - 'type' => $type - ]; - } - } - - // Инициализация счетчиков - $connection['amount'] = [ - 'auto' => 0, - 'avia' => 0 - ]; - - // Подсчет количества поставок foreach ($connection['order_edge_supply'] as $edge) { // Перебор связанных поставок - if ($edge['dlvr']['type'] === 'auto') ++$connection['amount']['auto']; - if ($edge['dlvr']['type'] === 'avia') ++$connection['amount']['avia']; + // Инициализация связанной с заказом поставки + $supply = Supply::searchById($edge['_to']); + + // Инициализация поставщика в буфере + if (empty($supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']])) $supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']] = [ + 'supply' => $supply, + 'account' => Account::searchBySupplyId($supply->readId()), + 'product' => Product::searchBySupplyId($supply->readId()), + 'currency' => 'руб', + 'amount' => 1 + ]; + else ++$supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']]['amount']; + + // Инициализация буфера с обрабатываемой поставкой + $buffer = &$supplies[$supply->prod][$supply->catn][$edge['dlvr']['type']]; + + // Запись ребра + $buffer['edge'] = $edge; + + if (empty($buffer['supply']->cost) || $buffer['supply']->cost < 1) { + // Если стоимость не найдена или равна нулю (явная ошибка) + + // Удаление из базы данных + $this->deleteSupply($buffer['supply']); + + // Инициализация стоимости товара для уведомления (чтобы там не было NULL) + $cost = $buffer['supply']->cost ?? 0; + + // Отправка уведомлений покупателю + Notification::_write("Стоимость товара $supply->catn равна $cost", account: $account->_key, type: Notification::TYPE_ERROR); + Notification::_write("Товар $supply->catn удалён", account: $account->_key, type: Notification::TYPE_ERROR); + + // Отправка уведомления поставщику + + // Отправка уведомления модератору + + // Удаление из списка + unset($connections[$key]); + + // Выход из цикла + break; + } + + try { + // Инициализация данных геолокации + try { + $from = (int) $buffer['account']['opts']['delivery_from_terminal'] ?? empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default; + } catch (Exception $e) { + $from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default; + } + + try { + $to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36; + } catch (Exception $e) { + $to = 36; + } + + if (($buffer_connection = $buffer['product']['bffr']["$from-$to-" . $edge['dlvr']['type']] ?? false) && time() < $buffer_connection['expires']) { + // Найдены данные доставки в буфере и их срок хранения не превышен, информация актуальна + + // Запись в буфер вывода + $buffer['delivery'] = $buffer_connection['data']; + } else { + // Инициализация инстанции продукта в базе данных (реинициализация под ActiveRecord) + $product = Product::searchByCatnAndProd($buffer['product']['catn'], $buffer['product']['prod']); + + // Инициализация доставки Dellin (автоматическая) + $product->bffr = ($product->bffr ?? []) + [ + "$from-$to-" . $edge['dlvr']['type'] => [ + 'data' => $buffer['delivery'] = Dellin::calcDeliveryAdvanced( + $from, + $to, + (int) ($buffer['product']['wght'] ?? 0), + (int) ($buffer['product']['dmns']['x'] ?? 0), + (int) ($buffer['product']['dmns']['y'] ?? 0), + (int) ($buffer['product']['dmns']['z'] ?? 0), + avia: $edge['dlvr']['type'] === 'avia' + ), + 'expires' => time() + 86400 + ] + ]; + + // Отправка в базу данных + $product->update(); + } + + // Запись цены (цена поставки + цена доставки + наша наценка) + $buffer['cost'] = ($supply->cost ?? $supply->onec['Цены']['Цена']['ЦенаЗаЕдиницу'] ?? throw new exception('Не найдена цена товара')) + ($buffer['delivery']['price']['all'] ?? $buffer['delivery']['price']['one'] ?? 0) + ($settings['increase'] ?? 0) ?? 0; + } catch (Exception $e) { + $buffer['delivery'] = null; + } } } - // Инициализация дополнительных данных - foreach ($connections as $key => &$connection) { - // Перебор поставок - - // Чтение стоимости - $cost = Supply::readCostById($connection['supply']['_id']); - - if (empty($cost) || $cost < 1) { - // Если стоимость не найдена или равна нулю (явная ошибка) - - // Удаление из базы данных - $this->deleteSupply($connection['supply']['_id']); - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // Добавить уведомление об ошибочном товаре - - // Удаление из списка - unset($connections[$key]); - - // Пропуск итерации - continue; - } - - // Поиск ребра до аккаунта - $connection['account'] = Account::searchBySupplyId($connection['supply']['_id']); - - // Поиск привязанного товара - $connection['product'] = Product::searchBySupplyId($connection['supply']['_id']); - - // if (empty(reset($connection['order_edge_supply'])['dlvr']['type']) || reset($connection['order_edge_supply'])['dlvr']['type'] === 'auto') { - // // Доставка автоматическая - - try { - // Инициализация данных геолокации - try { - $from = (int) $connection['account']['opts']['delivery_from_terminal'] ?? empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default; - } catch (Exception $e) { - $from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default; - } - - try { - $to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36; - } catch (Exception $e) { - $to = 36; - } - - if ($buffer_connection = $connection['product']['bffr']["$from-$to"] ?? false) { - // Найдены данные доставки в буфере - - if (time() < $buffer_connection['expires']) { - // Срок хранения не превышен, информация актуальна - - // Запись в буфер вывода - $connection['delivery']['auto'] = $buffer_connection['data']; - } - } else { - // Инициализация инстанции продукта в базе данных - $product = Product::searchByCatnAndProd($connection['product']['catn'], $connection['product']['prod']); - - // Инициализация доставки Dellin (автоматическая) - $product->bffr = ($product->bffr ?? []) + [ - "$from-$to" => [ - 'data' => $connection['delivery']['auto'] = Dellin::calcDeliveryAdvanced( - $from, - $to, - (int) ($connection['product']['wght'] ?? 0), - (int) ($connection['product']['dmns']['x'] ?? 0), - (int) ($connection['product']['dmns']['y'] ?? 0), - (int) ($connection['product']['dmns']['z'] ?? 0), - count($connection['order_edge_supply']) - ), - 'expires' => time() + 86400 - ] - ]; - - // Отправка в базу данных - $product->update(); - } - } catch (Exception $e) { - $connection['delivery']['auto']['error'] = true; - - // echo '
'; - // var_dump($e->getMessage()); - // var_dump($e->getTrace()); - // var_dump($e->getFile()); - // die; - - // var_dump(json_decode($e->getMessage(), true)['errors']); die; - } - - // Запись цены (цена поставки + цена доставки + наша наценка) - $connection['cost']['auto'] = ($cost ?? $connection['supply']->onec['Цены']['Цена']['ЦенаЗаЕдиницу']) + ($connection['delivery']['auto']['price']['all'] ?? $connection['delivery']['auto']['price']['one'] ?? 0) + ($settings['increase'] ?? 0) ?? 0; - // } else { - // Доставка самолётом - - try { - // Инициализация данных геолокации - try { - $from = (int) $connection['account']['opts']['delivery_from_terminal'] ?? empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default; - } catch (Exception $e) { - $from = empty(Settings::searchActive()->delivery_from_default) ? 36 : (int) Settings::searchActive()->delivery_from_default; - } - - try { - $to = (int) yii::$app->user->identity->opts['delivery_to_terminal'] ?? 36; - } catch (Exception $e) { - $to = 36; - } - - if ($buffer_connection = $connection['product']['bffr']["$from-$to-avia"] ?? false) { - // Найдены данные доставки в буфере - - if (time() < $buffer_connection['expires']) { - // Срок хранения не превышен, информация актуальна - - // Запись в буфер вывода - $connection['delivery']['avia'] = $buffer_connection['data']; - } - } else { - // Инициализация инстанции продукта в базе данных - $product = Product::searchByCatnAndProd($connection['product']['catn'], $connection['product']['prod']); - - // Инициализация доставки Dellin (автоматическая) - $product->bffr = ($product->bffr ?? []) + [ - "$from-$to-avia" => [ - 'data' => $connection['delivery']['avia'] = Dellin::calcDeliveryAdvanced( - $from, - $to, - (int) ($connection['product']['wght'] ?? 0), - (int) ($connection['product']['dmns']['x'] ?? 0), - (int) ($connection['product']['dmns']['y'] ?? 0), - (int) ($connection['product']['dmns']['z'] ?? 0), - count($connection['order_edge_supply']), - avia: true - ), - 'expires' => time() + 86400 - ] - ]; - - // Отправка в базу данных - $product->update(); - } - } catch (Exception $e) { - $connection['delivery']['avia']['error'] = true; - - // var_dump($e->getMessage()); - // var_dump($e->getTrace()); - // var_dump($e->getFile()); - // die; - - // var_dump(json_decode($e->getMessage(), true)['errors']); die; - } - - // Запись цены (цена поставки + цена доставки + наша наценка) - $connection['cost']['avia'] = ($cost ?? $connection['supply']->onec['Цены']['Цена']['ЦенаЗаЕдиницу']) + ($connection['delivery']['avia']['price']['all'] ?? $connection['delivery']['avia']['price']['one'] ?? 0) + ($settings['increase'] ?? 0) ?? 0; - // } - - // Запись валюты - $connection['currency'] = 'руб'; - } - - return $connections; + return $supplies; } /** diff --git a/mirzaev/skillparts/system/models/OrderEdgeSupply.php b/mirzaev/skillparts/system/models/OrderEdgeSupply.php index e8b8976..a3ad355 100644 --- a/mirzaev/skillparts/system/models/OrderEdgeSupply.php +++ b/mirzaev/skillparts/system/models/OrderEdgeSupply.php @@ -143,11 +143,22 @@ class OrderEdgeSupply extends Edge return []; } - public static function convertStatusToRussian(string|int $status): string + + /** + * Генерация ярлыка на русском языке для статуса заказа поставки + * + * @param string $status Статус заказа поставки + * + * @return string Ярлык статуса на русском языке + */ + public static function statusToRussian(string $status = ''): string { - return match($status) { - 'accepted', 1 => 'Ожидается отправка', - default => 'Запрошен' + return match ($status) { + 'requested' => 'Запрошен', + 'accepted' => 'Ожидается отправка', + 'going' => 'Доставляется', + 'completed' => 'Завершен', + default => 'Обрабатывается' }; } } diff --git a/mirzaev/skillparts/system/models/Product.php b/mirzaev/skillparts/system/models/Product.php index 542cb95..859e246 100644 --- a/mirzaev/skillparts/system/models/Product.php +++ b/mirzaev/skillparts/system/models/Product.php @@ -59,11 +59,6 @@ class Product extends Document */ public UploadedFile|string|array|null $file_image = null; - /** - * Группа в которой состоит товар - */ - public ProductGroup|null $group = null; - /** * Имя коллекции */ @@ -113,7 +108,6 @@ class Product extends Document 'stts' => 'Статус', 'file_excel' => 'Документ (file_excel)', 'file_image' => 'Изображение (file_image)', - 'group' => 'Группа (group)', 'account' => 'Аккаунт' ] ); @@ -233,7 +227,7 @@ class Product extends Document } /** - * Запись пустого продукта + * Запись пустого товара */ public static function writeEmpty(string $catn, string $prod = 'Неизвестный', bool $active = false): ?self { @@ -275,7 +269,6 @@ class Product extends Document }; } - if (!file_exists(YII_PATH_PUBLIC . $catalog_h150 = '/img/products/' . $this->_key . '/h150')) { // Директория для обложек изображений продукта не найдена @@ -462,138 +455,35 @@ class Product extends Document )[0]; } - /** - * Найти все аналоги - * - * @param string $prod Производитель - * @param string $catn Артикул - * @param int $limit Ограничение по количеству - * - * @return array|null Найденные аналоги - */ - public static function searchAnalogs(string $prod, string $catn, int $limit = 30): ?array - { - // Инициализация буфера возврата - $return = []; - - // Поиск ключей аналогов - $analogs = ProductEdgeProduct::searchConnections(self::searchByCatnAndProd($catn, $prod)?->_key, $limit); - - foreach ($analogs as $analog) { - // Перебор найденных ключей (_key) аналогов - - if ($analog = Product::searchById(self::collectionName() . "/$analog")) { - // Найден товар - - if (isset($analog->stts) && $analog->stts === 'active') { - // Пройдена проверка по статусу - - // Запись в буфер вывода - $return[] = $analog; - } - } else { - // Не найден товар - } - } - - return $return; - } - - /** - * Синхронизация аналогов - * - * Связывает с товаром и связанными с ним товарами в одну группу - * - * @param self $to Цель - * - * @return array Созданные рёбра - */ - public function synchronization(self $to): array - { - // Инициализация буфера сохранённых рёбер - $edges = []; - - // Инициализация списка товаров в группе и буфера для связей "всех со всеми" - $products = $_products = array_unique(array_merge($this->connections(), $to->connections(), [Product::collectionName() . "/$to->_key"])); - - foreach ($products as $product) { - // Перебор связей для создания связей во всех товарах - - // Удаление обрабатываемого товара из буферного списка товаров - // unset($_products[array_search($product, $_products)]); - - foreach ($_products as $_product) { - // Перебор товаров из буфера - - // if ($from = self::searchById($product)) { - // // Товар найден - // } else { - // if ($from = Product::writeEmpty($product)) { - // // Удалось записать товар - // } else { - // // Не удалось записать товар - - // continue; - // } - // } - - if ($to = self::searchById($_product)) { - // Товар найден - } else { - if ($to = Product::writeEmpty($_product)) { - // Удалось записать товар - } else { - // Не удалось записать товар - - continue; - } - } - - if ($edge = $this->connect($to)) { - // Ребро создано - - // Запись в буфер - $edges[] = $edge; - } - } - } - - return $edges; - } - /** * Подключение аналога * - * @param self $to Цель + * @param Product $target Товар который надо подключить * - * @return ProductEdgeProduct|null Ребро между товарами, если создалось + * @return ProductEdgeProductGroup|null Ребро между товаром и группой, если создалось */ - public function connect(self $to): ?ProductEdgeProduct + public function connect(Product $product): ?ProductEdgeProductGroup { - if (ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$this->_key", Product::collectionName() . "/$to->_key", type: 'analogue')) { - // Найдено ребро - } else if (ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$to->_key", Product::collectionName() . "/$this->_key", type: 'analogue')) { - // Найдено ребро (наоборот) + if (!$group = ProductGroup::searchByProduct($this)) { + // Не найдена группа товаров - // !!! Вероятно эта проверка здесь не нужна, так как мы знаем входные данные + // Запись новой группы + $group = ProductGroup::writeEmpty(active: true); + + // Запись товара в группу + $group->writeProduct($this); + } + + if ($_group = ProductGroup::searchByProduct($product)) { + // Найдена другая группа у товара который надо добавить в группу + + // Перенос всех участников (включая целевой товар) + return $group->transfer($_group); } else { - // Не найдены ребра + // Не найдена группа у товара который надо добавить в группу - if ($edge = ProductEdgeProduct::write(Product::collectionName() . "/$this->_key", Product::collectionName() . "/$to->_key", data: ['type' => 'analogue'])) { - // Ребро сохранено - - // Запись в журнал о соединении - $this->journal('connect analogue', [ - 'to' => Product::collectionName() . "/$to->_key" - ]); - - // Запись в журнал о соединении - $to->journal('connect analogue', [ - 'from' => Product::collectionName() . "/$this->_key" - ]); - - return $edge; - } + // Запись целевого товара в группу + return $group->writeProduct($product); } return null; @@ -602,67 +492,21 @@ class Product extends Document /** * Отключение аналога * - * @param self|null $to Цель (если null, то целью являются все подключенные аналоги) - * @param bool $all Удалить соединения со всеми членами группы + * @return bool Статус выполнения */ - public function disconnect(self|null $to = null, bool $all = true): bool + public function disconnect(): bool { - if (isset($to)) { - // Передана цель для удаления (из её группы) + if ($group = ProductGroup::searchByProduct($this)) { + // Найдена группа товаров - if ($edge = @ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$this->_key", Product::collectionName() . "/$to->_key", type: 'analogue')[0]) { - // Найдено ребро - } else if ($edge = @ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$to->_key", Product::collectionName() . "/$this->_key", type: 'analogue')[0]) { - // Найдено ребро (наоборот) - } else { - // Не найдены ребра - - return false; - } - } else { - // Не передана цель для удаления (из её группы) - - foreach ($this->connections() as $edge) { - // Перебор всех рёбер - - if (Product::collectionName() . "/$this->_key" !== $edge && $to = Product::searchById($edge)) { - // Найден товар (проверен на то, что не является самим собой) - - // Разъединение - $this->disconnect($to, all: false); - } - } + // Удаление из группы + $group->deleteProduct($this); return true; - } + } else { + // Не найдена группа товаров - if ($edge->delete() > 0) { - // Удалось удалить ребро (связь) - - if ($all) { - // Запрошено удаление соединений со всеми членами группы - - foreach ($to->connections() as $edge) { - // Перебор рёбер (найденных соединений с группой в которой находилась цель) - - if (Product::collectionName() . "/$this->_key" !== $edge && $to = Product::searchById($edge)) { - // Найден товар (проверен на то, что не является самим собой) - - // Разъединение - $this->disconnect($to, all: false); - } - } - } - - // Запись в журнал о разъединении - $this->journal('disconnect analogue', [ - 'to' => Product::collectionName() . "/$to->_key" - ]); - - // Запись в журнал о соединении - $to->journal('disconnect analogue', [ - 'from' => Product::collectionName() . "/$this->_key" - ]); + // Заебись return true; } @@ -670,42 +514,16 @@ class Product extends Document return false; } - /** - * Найти все связанные товары - * - * @param int $limit Ограничение по максимальному значению - */ - public function connections(int $limit = 100): array - { - // Инициализация буфера связанных товаров - $products = []; - - foreach (ProductEdgeProduct::searchByDirection(self::collectionName() . "/$this->_key", direction: 'ANY', type: 'analogue', limit: $limit) as $edge) { - // Перебор связей для создания списка товаров (вершин) - - // Добавление товаров (вершин) в буфер (подразумевается, что без дубликатов) - if (!in_array($edge->_from, $products, true)) $products[] = $edge->_from; - if (!in_array($edge->_to, $products, true)) $products[] = $edge->_to; - } - - return $products; - } - /** * Проверка на уникальность * - * @param static|null $account — Аккаунт - * - * @return bool|static true если создать новую запись, static если найден дубликат + * @return bool|static Товар, если найден * * @todo * 1. Обработка дубликатов */ - public function validateForUniqueness(Account|int|null $account = null): bool|static + public function validateForUniqueness(): bool|static { - // Инициализация аккаунта - $account = Account::initAccount($account); - if ($supplies = self::search(['catn' => $this->catn, 'prod' => $this->prod], limit: 100)) { // Найдены поставки с таким же артикулом (catn) и производителем (prod) @@ -717,9 +535,9 @@ class Product extends Document // Возврат (найден дубликат в базе данных) return $supply; - } else return true; + } - // Возврат (подразумевается ошибка) + // Возврат (подразумевается отсутствие дубликатов в базе данных) return false; } @@ -796,4 +614,30 @@ class Product extends Document return false; } + + /** + * Найти товары по группе + * + * @param string|null $_id Идентификатор группы + * + * @return array|null Товары (Product) + */ + public static function searchByProductGroup(string $_id): ?array + { + return static::searchByEdge( + from: 'product_group', + to: 'product', + edge: 'product_edge_product_group', + direction: 'INBOUND', + subquery_where: [ + [ + 'product_edge_product_group._from == "' . $_id . '"' + ] + ], + subquery_select: 'product', + where: 'product_edge_product_group[0]._id != null', + limit: 1, + select: 'product_edge_product_group[0]' + )[0]; + } } diff --git a/mirzaev/skillparts/system/models/ProductEdgeProductGroup.php b/mirzaev/skillparts/system/models/ProductEdgeProductGroup.php index 09669b8..3c9304d 100644 --- a/mirzaev/skillparts/system/models/ProductEdgeProductGroup.php +++ b/mirzaev/skillparts/system/models/ProductEdgeProductGroup.php @@ -10,4 +10,17 @@ class ProductEdgeProductGroup extends Edge { return 'product_edge_product_group'; } + + /** + * Поиск по товару + * + * @param Product $product Товар + * @param int $amount Ограничение по максимальному количеству + * + * @return null|self Ребро, если найдено + */ + public static function searchByProduct(Product $product, int $limit = 1): ?self + { + return self::find()->where(['_from' => $product->readId()])->limit($limit)->all()[0] ?? null; + } } diff --git a/mirzaev/skillparts/system/models/ProductGroup.php b/mirzaev/skillparts/system/models/ProductGroup.php index 557014b..238421d 100644 --- a/mirzaev/skillparts/system/models/ProductGroup.php +++ b/mirzaev/skillparts/system/models/ProductGroup.php @@ -4,15 +4,19 @@ declare(strict_types=1); namespace app\models; +use app\models\traits\SearchByEdge; + use carono\exchange1c\interfaces\GroupInterface; use Zenwalker\CommerceML\Model\Group; /** - * Группировка продуктов + * Группировка продуктов для соединения их в аналоги */ class ProductGroup extends Document implements GroupInterface { + use SearchByEdge; + /** * Имя коллекции */ @@ -29,7 +33,7 @@ class ProductGroup extends Document implements GroupInterface return array_merge( parent::attributes(), [ - 'name' + 'stts' ] ); } @@ -42,7 +46,7 @@ class ProductGroup extends Document implements GroupInterface return array_merge( parent::attributeLabels(), [ - 'name' => 'Название (name)' + 'stts' => 'Статус' ] ); } @@ -55,23 +59,138 @@ class ProductGroup extends Document implements GroupInterface return array_merge( parent::rules(), [ - // [ - // 'name', - // 'required', - // 'message' => 'Заполните поле: {attribute}' - // ] + [ + 'stts', + 'string', + 'length' => [4, 20], + 'message' => '{attribute} должен быть строкой от 4 до 20 символов' + ] ] ); } + /** + * Запись пустой группы + * + * @param bool $active Статус активации + * + * @return self Группа товаров, если создана + */ + public static function writeEmpty(bool $active = false): ?self + { + // Инициализация + $model = new self; + + // Настройки + $model->stts = $active ? 'active' : 'inactive'; + + // Запись + return $model->save() ? $model : null; + } + /** * Запись члена группы + * + * @deprecated */ public function writeMember(Product $member): ProductEdgeProductGroup { return ProductEdgeProductGroup::write($member->readId(), $this->readId(), 'member'); } + /** + * Запись товара в группу + * + * @param Product $product Товар + */ + public function writeProduct(Product $product): ?ProductEdgeProductGroup + { + // Запись товара в группу + $edge = ProductEdgeProductGroup::write($product->readId(), $this->readId(), data: ['type' => 'member']); + + // Запись в журнал + $this->journal('write member', [ + 'product' => $product->readId() + ]); + + return $edge; + } + /** + * Удаление товара из группы + * + * @param Product $product Товар + * + * @return void + */ + public function deleteProduct(Product $product): void + { + // Удаление товара из группы (подразумевается, что будет только одно) + foreach (ProductEdgeProductGroup::searchByVertex($product->readId(), $this->readId(), filter: ['type' => 'member']) as $edge) $edge->delete; + + // Запись в журнал + $this->journal('delete member', [ + 'product' => $product->readId() + ]); + } + + /** + * Найти рёбра до товаров + * + * @param int $limit Ограничение по максимальному количеству + */ + public function searchEdges(int $limit = 999): ?array + { + return ProductEdgeProductGroup::searchByDirection($this->readId(), 'INBOUND', where: ['type' => 'member'], limit: $limit); + } + + /** + * Прочитать связанные товары + * + * @param int $limit Ограничение по максимальному количеству + */ + public function searchProducts(int $limit = 999): ?array + { + // Инициализация буфера товаров + $products = []; + + foreach ($this->searchEdges($limit) as $edge) { + // Перебор рёбер + + $products[] = Product::searchById($edge->_from); + } + + return $products; + } + + /** + * Перенос членов группы из другой + * + * @param ProductGroup $group Группа из которой нужен перенос + * + * @return null|int Количество перенесённых товаров, если произведён перенос + */ + public function transfer(ProductGroup $group): ?int + { + // Проверка на то, что запрошен перенос "из себя в себя" + if ($this->readId() === $group->readId()) return null; + + // Инициализация счётчика записанных товаров + $transfered = 0; + + // Перенос + foreach ($group->searchProducts() as $product) if ($this->writeProduct($product)) ++$transfered; + + // Деактивация целевой группы (пустой) + $group->deactivate(); + + // Запись в журнал + $this->journal('transfer', [ + 'from' => $group->readId() + ]); + + return $transfered; + } + /** * Запись рёбер групп * @@ -171,4 +290,29 @@ class ProductGroup extends Document implements GroupInterface { return static::findOne(['onec_id' => $onec_id]); } + + /** + * Найти по идентификатору товара + * + * @param Product $product Товар + * + * @return self|null Группа (ProductGroup) + */ + public static function searchByProduct(Product $product): ?self + { + return static::searchByEdge( + from: 'product', + to: 'product_group', + edge: 'product_edge_product_group', + direction: 'INBOUND', + subquery_where: [ + [ + 'product_edge_product_group._from == "' . $product->readId() . '"' + ] + ], + subquery_select: 'product_group', + where: 'product_edge_product_group[0]._id != null', + limit: 1 + )[0] ?? null; + } } diff --git a/mirzaev/skillparts/system/models/Search.php b/mirzaev/skillparts/system/models/Search.php index 0d49de6..0e21f65 100644 --- a/mirzaev/skillparts/system/models/Search.php +++ b/mirzaev/skillparts/system/models/Search.php @@ -392,12 +392,12 @@ class Search extends Document * * @param array $row Товар сгенерированный через Search::content() * @param string|null $cover Обложка - * @param array $empties Реестр не найденных товаров + * @param array $list Реестр найденных товаров * @param bool $analogs Запрошены аналоги (не выведет пустые товары) * * @return string HTML-элемент с товаром */ - public static function generate(array &$row, string|null &$cover = null, array &$empties = [], bool $analogs = false): string + public static function generate(array &$row, string|null &$cover = null, array &$list = [], bool $analogs = false): string { foreach ($row['imgs'] ?? [] as &$img) { // Перебор изображений для обложки @@ -437,6 +437,9 @@ class Search extends Document foreach (empty($row['supplies']) || $supplies_amount === 0 ? [null] : $row['supplies'] as &$supply) { // Перебор поставок + // Запись в список найденных + $list[$row['prod']] = [$row['catn']] + (isset($list[$row['prod']]) ? $list[$row['prod']] : []); + // Инициализация модификатора класса if ($supplies_amount > $supply_iterator) { // Это не последняя строка с товаром и его поставками @@ -491,9 +494,6 @@ class Search extends Document HTML; - // Запись в список ненайденных - $empties[$row['prod']] = [$row['catn']] + (isset($empties[$row['prod']]) ? $empties[$row['prod']] : []); - // Запись блокировщика $empty_block = true; diff --git a/mirzaev/skillparts/system/models/Supply.php b/mirzaev/skillparts/system/models/Supply.php index a2088ea..dc86ce1 100644 --- a/mirzaev/skillparts/system/models/Supply.php +++ b/mirzaev/skillparts/system/models/Supply.php @@ -92,15 +92,7 @@ class Supply extends Product implements ProductInterface, OfferInterface { return array_merge( parent::rules(), - [ - // [ - // [ - // 'oemn' - // ], - // 'arrayValidator', - // 'message' => '{attribute} должен быть массивом.' - // ] - ] + [] ); } @@ -341,7 +333,7 @@ class Supply extends Product implements ProductInterface, OfferInterface // Пользователь аутентифицирован и авторизован // Инициализация п̸̨͇͑͋͠р̷̬̂́̀̊о̸̜̯̹̅͒͘͝д̴̨̨̨̟̈́̆у̴̨̭̮̠́͋̈́к̴̭͊̋̎т̵̛̣͈̔̐͆а̵̨͖͑ - $product = self::initEmpty($this->catn); + $product = self::initEmpty($this->catn, $this->prod); if (!is_array($product)) { // Создался только один товар и вернулся в виде модели @@ -356,7 +348,7 @@ class Supply extends Product implements ProductInterface, OfferInterface // Перебор артикулов из массива ОЕМ-номеров // Инициализация и запись - $product[] = self::initEmpty($oem); + $product[] = self::initEmpty($oem, $this->prod); } } @@ -456,23 +448,51 @@ class Supply extends Product implements ProductInterface, OfferInterface foreach ($data as $doc) { // Перебор полученных документов + // Проверка на пустую страницу или документ (пустой массив) + if (empty($doc)) continue; + foreach ($doc as $row) { // Перебор строк + if (!is_array($row)) { + // Строка является не массивом со значением колонок, а самим значением колонки (значит в $data и так хранился $doc - у файла всего одна страница) + + // Универсализация + $row = $doc; + } + // Поиск артикула - $article = $row['Артикул'] ?? $row['артикул'] ?? $row['Article'] ?? $row['article'] ?? $row['catn']; + $article = (string) ($row['Артикул'] ?? $row['артикул'] ?? $row['Article'] ?? $row['article'] ?? $row['catn'] ?? null); // Поиск количества товаров - $amount = $row['Количество'] ?? $row['количество'] ?? $row['Amount'] ?? $row['amount'] ?? $row['amnt'] ?? 1; + $amount = $row['Количество'] ?? $row['количество'] ?? $row['Amount'] ?? $row['amount'] ?? $row['amnt'] ?? null; + + // Поиск производителя + $prod = (string) ($row['Производитель'] ?? $row['производитель'] ?? $row['Production'] ?? $row['production'] ?? $row['prod'] ?? null); // Поиск аналогов $analogs = explode(',', (string) ($row['Аналоги'] ?? $row['аналоги'] ?? $row['Analogs'] ?? $row['analogs'] ?? $row['ОЕМ'] ?? $row['eom'] ?? ''), 50); - // Поиск производителя - $prod = $row['Производитель'] ?? $row['производитель'] ?? $row['Production'] ?? $row['production'] ?? $row['prod'] ?? 'Неизвестный'; + // Инициализация буфера аналогов + $buffer = []; + + // Дополнительная фильтрация аналогов + foreach ($analogs as $analog) foreach (explode('/', $analog, 50) as $value) $buffer[] = $value; + + // Запись аналогов из буфера + $analogs = $buffer; + + // Пропуск пустых строк (подразумевается) + if ($article === null || $amount === null || $prod === null) continue; + + // Инициализация товара для инициализации группы + if (!$product = Product::searchByCatnAndProd($article, $prod)) $product = Product::writeEmpty($article, $prod, Account::isMinimalAuthorized($account)); + + // Инициализация группы товаров + if (!$group = ProductGroup::searchByProduct($product)) $group = ProductGroup::writeEmpty(active: true); // Инициализация функции создания поставки - $create = function (string $_supply) use ($article, $row, $prod, $analogs, &$created, &$updated, &$imported, $amount, $account): bool { + $create = function (string $_supply, int|null $amount = null) use ($group, $row, $prod, $analogs, &$created, &$updated, &$imported, $account): bool { // Очистка $_supply = trim($_supply); @@ -484,7 +504,7 @@ class Supply extends Product implements ProductInterface, OfferInterface // Запись артикула (каталожного номера) в буфер $_row['catn'] = $_supply; - $_row['cost'] = (float) preg_replace('/[^\d\.]+/', '', preg_replace('/\,+/', ' ', $row['Стоимость'] ?? $row['стоимость'] ?? $row['Цена'] ?? $row['цена'] ?? $row['Cost'] ?? $row['cost'] ?? $row['Price'] ?? $row['price'])) ?? 0; + $_row['cost'] = (float) preg_replace('/[^\d\.]+/', '', preg_replace('/\,+/', ' ', (string) ($row['Стоимость'] ?? $row['стоимость'] ?? $row['Цена'] ?? $row['цена'] ?? $row['Cost'] ?? $row['cost'] ?? $row['Price'] ?? $row['price']))) ?? 0; $_row['prod'] = $prod; $_row['oemn'] = array_walk($analogs, 'trim'); @@ -496,137 +516,12 @@ class Supply extends Product implements ProductInterface, OfferInterface if ($supply->validate()) { // Проверка пройдена - if (($_supply = $supply->validateForUniqueness($account)) instanceof static) { + if ((($_supply = $supply->validateForUniqueness()) instanceof Supply)) { // Найдена поставка с такими параметрами (артикул и производитель) - if ($_supply->cost !== $_row['cost']) { - // Стоимость изменилась - - if ($product = Product::searchByCatnAndProd($supply->catn, $supply->prod)) { - // Найден товар подходящий для привязки с только что созданной поставкой (подразумевается что уже был привязан в коде выше) - - // Приведение типа (для анализатора) - if (is_array($product)) $product = $product[0]; - } else { - // Не найден товар подходящий для привязки с только что созданной поставкой - - if ($product = Product::writeEmpty($supply->catn, $supply->prod, Account::isMinimalAuthorized($account))) { - // Удалось записать новый товар (НЕАКТИВНЫЙ) - - // Отправка уведомления - // Notification::_write("Не найден товар подходящий для связи с поставкой: $supply->catn", account: '@authorized'); - } else { - // Не удалось записать новый товар - - // Отправка уведомления - Notification::_write("Не удалось создать новый товар: $supply->catn", account: '@authorized'); - - // Запись статуса об ошибке - $error = true; - } - } - - // Завершение выполнения при ошибке - if ($error) return !$error; - - if ($product = Product::searchByCatnAndProd($_supply->catn, $_supply->prod)) { - // Найден товар подходящий для привязки с этой поставкой - - for ($i = 0; $i++ < $amount;) { - // Перебор создаваемых рёбер (так работает обозначение количества товаров в наличии) - - // Поиск ребёр - $edges = SupplyEdgeProduct::searchByVertex($_supply->readId(), $product->readId(), limit: 999); - - if (count($edges) === 0) { - // Ребёр нет, но должны быть (если количество загружаемых поставок более нуля) - - for ($i = 0; $i++ < $amount;) { - // Перебор создаваемых рёбер (так работает обозначение количества товаров в наличии) - - // Запись ребра - SupplyEdgeProduct::write($_supply->readId(), $product->readId(), data: ['type' => 'connect']); - } - } else if ($amount === count($edges)) { - // Количество товаров в поставке не изменилось - - // Раз изменений нет, то обновлять ничего не нужно - continue; - } else if ($amount < count($edges)) { - // Количество товаров в поставке стало МЕНЬШЕ - - // Расчёт разницы - $delete = count($edges) - $amount; - - // Инициализация количества рёбер которые не удалось удалить - $failed = 0; - - for ($i = 0; $i < $delete; $i++) { - // Перебор рёбер на удаление (синхронизация) - - if ($edges[$i]->delete() >= 1) { - // Удалено ребро - } else { - // Не удалено ребро - - // Обновление количества рёбер которые не удалось удалить - ++$failed; - } - } - - // Отправка уведомления - Notification::_write("Не удалось удалить $failed рёбер у поставки $_supply->catn"); - } else if ($amount > count($edges)) { - // Количество товаров в поставке стало БОЛЬШЕ - - // Расчёт разницы - $write = $amount - count($edges); - - // Инициализация количества рёбер которые не удалось записать - $failed = 0; - - for ($i = 0; $i < $write; $i++) { - // Перебор рёбер на запись (синхронизация) - - if (SupplyEdgeProduct::write($_supply->readId(), $product->readId(), data: ['type' => 'connect'])) { - // Записано ребро - } else { - // Не записано ребро - - // Обновление количества рёбер которые не удалось записать - ++$failed; - } - } - - // Отправка уведомления - Notification::_write("Не удалось записать $failed рёбер у поставки $_supply->catn"); - } - } - } - } - - // Инициализация буфера с параметрами загружаемого товара - $vars = $supply->getAttributes(); - - // Удаление _key, чтобы не перезаписать его при замене параметров документа в буфере - unset($vars['_key']); - - // Перенос данных в буфер (существующий в базе данных дубликат) - $_supply->setAttributes($vars, false); - - // Перезапись существующего документа - $_supply->update(); - - // Обновление счётчика - $updated++; - - // Запись поставки в буфер - $imported[] = $_supply; - - // Запись в буфер (для универсальной обработки) $supply = $_supply; } else { - // Не найден документ с такими параметрами + // Не найдена поставка с такими параметрами if ($supply->save()) { // Поставка записана в базу данных @@ -638,6 +533,141 @@ class Supply extends Product implements ProductInterface, OfferInterface $imported[] = $supply; }; } + + if ($supply->cost !== $_row['cost']) { + // Стоимость изменилась + + if ($product = Product::searchByCatnAndProd($supply->catn, $supply->prod)) { + // Найден товар подходящий для привязки + + // Приведение типа (для анализатора) + if (is_array($product)) $product = $product[0]; + } else { + // Не найден товар подходящий для привязки с только что созданной поставкой + + if ($product = Product::writeEmpty($supply->catn, $supply->prod, Account::isMinimalAuthorized($account))) { + // Удалось записать новый товар (НЕАКТИВНЫЙ) + + // Отправка уведомления + // Notification::_write("Не найден товар подходящий для связи с поставкой: $supply->catn", account: '@authorized'); + } else { + // Не удалось записать новый товар + + // Отправка уведомления + Notification::_write("Не удалось создать новый товар: $supply->catn", account: '@authorized'); + + // Запись статуса об ошибке + $error = true; + } + } + + // Завершение выполнения при ошибке + if ($error) return false; + + if ($product = Product::searchByCatnAndProd($supply->catn, $supply->prod)) { + // Найден товар подходящий для привязки с этой поставкой + + // Поиск ребёр + $edges = SupplyEdgeProduct::searchByVertex($supply->readId(), $product->readId(), limit: 999); + + if (isset($amount)) { + // Передано количество + if (count($edges) === 0) { + // Ребёр нет, но должны быть (если количество загружаемых поставок более нуля) + + // Инициализация количества рёбер которые не удалось записать + $failed = 0; + + for ($i = 0; $i++ < $amount;) { + // Перебор создаваемых рёбер (так работает обозначение количества товаров в наличии) + + if (SupplyEdgeProduct::write($supply->readId(), $product->readId(), data: ['type' => 'connect'])) { + // Записано ребро + } else { + // Не записано ребро + + // Обновление количества рёбер которые не удалось записать + ++$failed; + } + } + + // Отправка уведомления + if ($failed > 0) Notification::_write("Не удалось записать $failed рёбер у поставки $supply->catn"); + } else if ($amount === count($edges)) { + // Количество товаров в поставке не изменилось + + // Раз изменений нет, то обновлять ничего не нужно + // continue; + } else if ($amount < count($edges)) { + // Количество товаров в поставке стало МЕНЬШЕ + + // Расчёт разницы + $delete = count($edges) - $amount; + + // Инициализация количества рёбер которые не удалось удалить + $failed = 0; + + for ($i = 0; $i < $delete; $i++) { + // Перебор рёбер на удаление (синхронизация) + + if ($edges[$i]->delete() >= 1) { + // Удалено ребро + } else { + // Не удалено ребро + + // Обновление количества рёбер которые не удалось удалить + ++$failed; + } + } + + // Отправка уведомления + Notification::_write("Не удалось удалить $failed рёбер у поставки $supply->catn"); + } else if ($amount > count($edges)) { + // Количество товаров в поставке стало БОЛЬШЕ + + // Расчёт разницы + $write = $amount - count($edges); + + // Инициализация количества рёбер которые не удалось записать + $failed = 0; + + for ($i = 0; $i < $write; $i++) { + // Перебор рёбер на запись (синхронизация) + + if (SupplyEdgeProduct::write($supply->readId(), $product->readId(), data: ['type' => 'connect'])) { + // Записано ребро + } else { + // Не записано ребро + + // Обновление количества рёбер которые не удалось записать + ++$failed; + } + } + + // Отправка уведомления + if ($failed > 0) Notification::_write("Не удалось записать $failed рёбер у поставки $supply->catn"); + } + } + } + } + + // Инициализация буфера с параметрами загружаемого товара + $vars = $supply->getAttributes(); + + // Удаление _key, чтобы не перезаписать его при замене параметров документа в буфере + unset($vars['_key']); + + // Перенос данных в буфер (существующий в базе данных дубликат) + $supply->setAttributes($vars, false); + + // Перезапись существующего документа + $supply->update(); + + // Обновление счётчика + $updated++; + + // Запись поставки в буфер + $imported[] = $supply; } else { // Проверка не пройдена @@ -654,26 +684,19 @@ class Supply extends Product implements ProductInterface, OfferInterface // Активация товара $product->activate(); - // Инициализация списка артикулов группы для добавления аналогов - $group = [$article] + $analogs; + // foreach (Product::searchByCatn($product->catn) as $target) { + // // Перебор товаров по артикулу (все производители) + // } - foreach ($group as $catn) { - // Перебор артикулов для добавления аналогов - - foreach (Product::searchByCatn((string) $catn) as $target) { - // Перебор товаров для добавления аналогов - - // Добавление в группу аналогов - if ($to = Product::searchByCatn((string) $target['catn'], 1)) $product->synchronization($to); - } - } + // Добавление в группу аналогов + $group->writeProduct($product); } return !$error; }; // Запись поставки - $create((string) $article); + $create($article, (int) $amount); foreach ($analogs as $_supply) { // Перебор аналогов (если найдены) @@ -710,7 +733,7 @@ class Supply extends Product implements ProductInterface, OfferInterface foreach ($imported as $supply) { // Перебор импортированных поставок - if (ImportEdgeSupply::write($import->collectionName() . "/$import->_key", $supply->collectionName() . "/$supply->_key", data: ['type' => 'imported'])) { + if (ImportEdgeSupply::write($import->readId(), $supply->readId(), data: ['type' => 'imported', 'vrsn' => ImportEdgeSupply::searchMaxVersion($supply) + 1])) { // Записано ребро: ИНСТАНЦИЯ ПОСТАВОК -> ПОСТАВКА // Запись в журнал инстанции импорта diff --git a/mirzaev/skillparts/system/models/traits/SearchByEdge.php b/mirzaev/skillparts/system/models/traits/SearchByEdge.php index 1909c55..fd7601e 100644 --- a/mirzaev/skillparts/system/models/traits/SearchByEdge.php +++ b/mirzaev/skillparts/system/models/traits/SearchByEdge.php @@ -32,6 +32,7 @@ trait SearchByEdge array $params = [], bool $asArray = true, bool $debug = false, + bool $aql = false, bool $count = false ): mixed { $subquery = static::find() @@ -71,11 +72,20 @@ trait SearchByEdge ->limit($limit) ->select($select ?? $to); + // Режим вывода строки запроса + if ($aql) { + // Запрошена проверка + + return (string) $request->createCommand(); + } + // Режим проверки if ($debug) { // Запрошена проверка - return (string) $request->createCommand(); + var_dump((string) $request->createCommand()); + + return null; } // Запрос diff --git a/mirzaev/skillparts/system/views/cart/index.php b/mirzaev/skillparts/system/views/cart/index.php index a28fa97..2c1cd06 100644 --- a/mirzaev/skillparts/system/views/cart/index.php +++ b/mirzaev/skillparts/system/views/cart/index.php @@ -6,7 +6,7 @@ use yii; use yii\bootstrap\ActiveForm; use app\models\connection\Dellin; - +use app\models\Supply; use DateTime; ?> @@ -21,10 +21,14 @@ use DateTime;++ Производитель +Артикул-+$list) { + // Перебор поставщиков - // Инициализация комментария - $comment = $order_edge_supply[0]['comm'] ?? 'Комментарий к заказу'; + foreach ($list as $catn => $deliveries) { + // Перебор поставок - if ($amount['auto'] > 0) { - // Найдены поставки с автоматической доставкой + foreach ($deliveries as $delivery => $supply) { + // Перебор типов доставки - // Инициализация цены - $price_auto = $price_raw['auto'] . ' ' . $currency; + // Инициализация комментария + $comment = $supply['edge']['comm'] ?? 'Комментарий к заказу'; - // Инициализация доставки - if (!isset($delivery) || (isset($delivery['auto'], $delivery['auto']['error']) || $delivery === '?')) { - // Не удалось рассчитать доставку + // Инициализация доставки + if (empty($supply['delivery'])) { + // Не удалось рассчитать доставку - // Инициализация времени - $delivery_auto = '?'; - } else { - // Удалось рассчитать доставку + // Инициализация времени + $days = '?'; + } else { + // Удалось рассчитать доставку - // Инициализация даты отправки - try { - // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель) + // Инициализация даты отправки + try { + // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель) - $delivery_auto_send_date = DateTime::createFromFormat('Y-m-d', $delivery['auto']['orderDates']['arrivalToOspSender'])->getTimestamp(); - } catch (Throwable $e) { - // Взять данные из "pickup" (Дата передачи груза на адресе отправителя) + $delivery_send_date = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['arrivalToOspSender'])->getTimestamp(); + } catch (Throwable $e) { + // Взять данные из "pickup" (Дата передачи груза на адресе отправителя) - $delivery_auto_send_date = DateTime::createFromFormat('Y-m-d', $delivery['auto']['orderDates']['pickup'])->getTimestamp(); + $delivery_send_date = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['pickup'])->getTimestamp(); + } + + // Инициализация времени доставки + try { + // Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале) + + // Оставлено на всякий случай для дальнейших разбирательств + + $delivery_converted = DateTime::createFromFormat('Y-m-d H:i:s', $supply['delivery']['orderDates']['giveoutFromOspReceiver'])->getTimestamp(); + } catch (Throwable $e) { + // Инициализация даты отправки + + // Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель) + $delivery_converted = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['arrivalToOspReceiver'])->getTimestamp(); + } + $days = ceil(($delivery_converted - ($delivery_send_date ?? 0)) / 60 / 60 / 24) + 1; } - // Инициализация времени доставки - try { - // Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале) + // Инициализация иконки + $icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck'; - // Оставлено на всякий случай для дальнейших разбирательств - - $delivery_auto_converted = DateTime::createFromFormat('Y-m-d H:i:s', $delivery['auto']['orderDates']['giveoutFromOspReceiver'])->getTimestamp(); - } catch (Throwable $e) { - // Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель) - - $delivery_auto_converted = DateTime::createFromFormat('Y-m-d', $delivery['auto']['orderDates']['arrivalToOspReceiver'])->getTimestamp(); - } - $delivery_auto = ceil(($delivery_auto_converted - ($delivery_auto_send_date ?? 0)) / 60 / 60 / 24) + 1; - } - - // Генерация HTML - echo << -+ ПоставщикКоличество @@ -37,168 +41,100 @@ use DateTime;--- HTML; } } } else { diff --git a/mirzaev/skillparts/system/views/invoice/order/pattern.php b/mirzaev/skillparts/system/views/invoice/order/pattern.php index 1b88f16..4f61725 100644 --- a/mirzaev/skillparts/system/views/invoice/order/pattern.php +++ b/mirzaev/skillparts/system/views/invoice/order/pattern.php @@ -8,7 +8,7 @@ use app\models\Settings; -- + // Генерация HTML + echo << +- HTML; - } - - if ($amount['avia'] > 0) { - // Найдены поставки с доставкой по воздуху - - // Инициализация цены - $price_avia = $price_raw['avia'] . ' ' . $currency; - - // Инициализация доставки - if (!isset($delivery) || (isset($delivery, $delivery['auto'], $delivery['avia']['error']) || $delivery === '?')) { - // Не удалось рассчитать доставку - - // Инициализация времени - $delivery_avia = '?'; - } else { - // Удалось рассчитать доставку - - // Инициализация даты отправки - try { - // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель) - - $delivery_avia_send_date = DateTime::createFromFormat('Y-m-d', $delivery['avia']['orderDates']['arrivalToOspSender'])->getTimestamp(); - } catch (Throwable $e) { - // Взять данные из "pickup" (Дата передачи груза на адресе отправителя) - - $delivery_avia_send_date = DateTime::createFromFormat('Y-m-d', $delivery['avia']['orderDates']['pickup'])->getTimestamp(); - } - - // Инициализация времени доставки - try { - // Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале) - - $delivery_avia_converted = DateTime::createFromFormat('Y-m-d H:i:s', $delivery['avia']['orderDates']['giveoutFromOspReceiver'])->getTimestamp(); - } catch (Throwable $e) { - // Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель) - - // Оставлено на всякий случай для дальнейших разбирательств - - $delivery_avia_converted = DateTime::createFromFormat('Y-m-d', $delivery['avia']['orderDates']['arrivalToOspReceiver'])->getTimestamp(); - } - $delivery_avia = ceil(($delivery_avia_converted - ($delivery_avia_send_date ?? 0)) / 60 / 60 / 24) + 1; + HTML; } - - // Генерация HTML - echo << -+- -+-+ +++ $prod +++ $catn +++ {$supply['account']['indx']} +++ ++++~$days дн
++ {$supply['cost']} {$supply['currency']} +- {$supply['catn']} ----- ----~$delivery_auto дн
-- $price_auto -----$comment
+ +++$comment
+---- -- --- {$supply['catn']} --- {$supply['dscr']} --- ----~$delivery_avia дн
-- $price_avia ------$comment
-Счёт №= $order['id'] ?> +Счёт №= $data['order']->_key ?> @@ -89,7 +89,7 @@ use app\models\Settings; preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone); $timezone = $timezone[1][0]; ?> - Счет на оплату №= $order['id'] ?> от = (new DateTime())->setTimestamp($order['date'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?> + Счет на оплату №= $data['order']->_key ?> от = (new DateTime())->setTimestamp($date)->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?> @@ -133,7 +133,9 @@ use app\models\Settings;№ -Товары (работы, услуги) +Производитель +Товар +Поставщик Количество Цена Сумма @@ -151,16 +153,21 @@ use app\models\Settings; $cost = 0; ?> - -- + $supplies) : ?> + $deliveries) : ?> + $supply) : ?> += $row++ ?> -= $entry['title'] ?> - -= $entry['amount']['value'] ?> -= $entry['cost']['value'] ?> -= $entry['cost']['unit'] ?> -= $cost += $entry['cost']['value'] * $entry['amount']['value'] ?> -+ + += $row++ ?> += $prod ?> += $catn ?> += $supply['account']['indx'] ?> += $supply['amount'] ?> += $supply['cost'] * $supply['amount'] ?> += $supply['currency'] ?> += $cost += $supply['cost'] * $supply['amount'] ?> +diff --git a/mirzaev/skillparts/system/views/notification/system/orders/new.php b/mirzaev/skillparts/system/views/notification/system/orders/new.php index c2ff066..464a228 100644 --- a/mirzaev/skillparts/system/views/notification/system/orders/new.php +++ b/mirzaev/skillparts/system/views/notification/system/orders/new.php @@ -1,3 +1,3 @@ diff --git a/mirzaev/skillparts/system/views/orders/index.php b/mirzaev/skillparts/system/views/orders/index.php index fd54dff..3b37966 100644 --- a/mirzaev/skillparts/system/views/orders/index.php +++ b/mirzaev/skillparts/system/views/orders/index.php @@ -47,16 +47,15 @@ if (empty($window)) { - - - - + + +jrnl as $entry) { // Перебор записей в журнале if ($entry['action'] === 'requested') { @@ -81,7 +80,7 @@ if (empty($window)) { ]; ?> -
#= $order['order']['_key'] ?>
+#= $moderator_data['order']->_key ?>
= $date['H:i'] ?? 'Неизвестно' ?> = $date['m.d.Y'] ?? 'Неизвестно' ?> @@ -89,96 +88,73 @@ if (empty($window)) {
-- ++ - + $list) : ?> + $deliveries) : ?> + $supply) : ?> + imgs ?? [] as $img) { + // Перебор изображений для обложки - // Инициализация обложки - $covr = null; + if ($img['covr'] ?? false) { + // Обложка найдена - foreach ($imgs ?? [] as $img) { - // Перебор изображений для обложки + $covr = $img['h150']; - if ($img['covr'] ?? false) { - // Обложка найдена + break; + } + } - $covr = $img['h150']; + if (is_null($covr)) { + // Обложка не инициализирована - break; - } - } + if (!$covr = $supply['product']->imgs[0]['h150'] ?? false) { + // Не удалось использовать первое изображение как обложку - if (is_null($covr)) { - // Обложка не инициализирована + // Запись обложки по умолчанию + $covr = '/img/covers/h150/product.png'; + } + } - if (!$covr = $imgs[0]['h150'] ?? false) { - // Не удалось использовать первое изображение как обложку + if ($supply['amount'] > 0) { + // Пройдена проверка на количество поставок в заказе - // Запись обложки по умолчанию - $covr = '/img/covers/h150/product.png'; - } - } + // if (Order::checkSuppliesStts($order_edge_supply)) { + // $status = ''; + // } else { + $status = ''; + // } - if ($amount['auto'] > 0) { - // Найдены поставки с автоматической доставкой + // Инициализация иконки + $icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck'; - if (Order::checkSuppliesStts($order_edge_supply)) { - $status = ''; - } else { - $status = ''; - } - - // Генерация HTML - echo << - --- {$product['catn']} x{$amount['auto']} - - $status -
- - HTML; - } - - if ($amount['avia'] > 0) { - // Найдены поставки с автоматической доставкой - - if (Order::checkSuppliesStts($order_edge_supply)) { - $status = ''; - } else { - $status = ''; - } - - // Генерация HTML - echo << - -- {$product['catn']} x{$amount['avia']} - - $status -
- - HTML; - } - - ?> - - - - + // Генерация HTML + echo << + ++ {$catn} x{$supply['amount']} + + $status +
+ + HTML; + } + ?> + + + + +@@ -186,30 +162,19 @@ if (empty($window)) {+-Выберите поставку
- - 'Запрошен', - 'handled' => 'Обрабатывается', - 'completed' => 'Завершен', - } - ?> - -Статус: = $status ?>
- Подтвердить ++Статус: = AccountEdgeOrder::statusToRussian(AccountEdgeOrder::searchByOrder($moderator_data['order']->readId())['stts'] ?? '') ?>
+ Подтвердить