крупная обнова

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2022-06-01 14:24:48 +10:00
parent e1fa1c1f5a
commit 2c2e830f0a
24 changed files with 1161 additions and 1238 deletions

View File

@ -6,6 +6,9 @@ use yii\console\Controller;
use yii\console\ExitCode; use yii\console\ExitCode;
use app\models\Invoice; use app\models\Invoice;
use app\models\Product;
use app\models\ProductGroup;
use app\models\ImportEdgeSupply;
class TestController extends Controller class TestController extends Controller
{ {
@ -75,4 +78,59 @@ class TestController extends Controller
return ExitCode::OK; 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;
}
} }

View File

@ -86,26 +86,26 @@ class CartController extends Controller
$account = Account::initAccount(); $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 { } else {
throw new Exception('Не удалось инициализировать заказ'); throw new Exception('Не удалось инициализировать заказ');
} }
} }
// Инициализация содержимого корзины // Инициализация содержимого корзины
$connections = $model->content(10, $page); $data['supplies'] = $data['order']->supplies(10, $page, test: true);
// Инициализация данных списка для выбора терминала // Инициализация данных списка для выбора терминала
$delivery_to_terminal_list = $account->genListTerminalsTo(); $delivery_to_terminal_list = $account->genListTerminalsTo();
@ -138,14 +138,14 @@ class CartController extends Controller
yii::$app->response->format = Response::FORMAT_JSON; yii::$app->response->format = Response::FORMAT_JSON;
return [ 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' => 'Корзина', 'title' => 'Корзина',
'redirect' => '/cart', 'redirect' => '/cart',
'_csrf' => yii::$app->request->getCsrfToken() '_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 public function actionEditComm(string $catn, string $prod): array|string|null

View File

@ -266,11 +266,10 @@ class OrderController extends Controller
} }
// Инициализация заказов // Инициализация заказов
$orders = Order::searchByType( $data = Order::searchByType(
type: $type, type: $type,
limit: 10, limit: 10,
page: 1, page: 1,
select: '{account_edge_order, order}',
supplies: true, supplies: true,
from: $from, from: $from,
to: $to, to: $to,
@ -278,7 +277,6 @@ class OrderController extends Controller
); );
// Фильтрация // Фильтрация
if ( if (
!yii::$app->user->isGuest !yii::$app->user->isGuest
&& yii::$app->user->identity->type === 'administrator' && 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 { } else {
// Пользователь не имеет доступ // Пользователь не имеет доступ
// Инициализация заглушки // Инициализация заглушки
$moderator_orders = null; $moderator_data = null;
} }
// Инициализация аккаунта
$account ?? $account = Account::initAccount();
if (yii::$app->request->isPost) { if (yii::$app->request->isPost) {
// POST-запрос // POST-запрос
// Инициализация аккаунта
$account ?? $account = Account::initAccount();
// Конвертация из UNIXTIME в формат поддерживаемый календарём по спецификации HTML // Конвертация из UNIXTIME в формат поддерживаемый календарём по спецификации HTML
$from = DateTime::createFromFormat('U', (string) $from)->format('Y-m-d'); $from = DateTime::createFromFormat('U', (string) $from)->format('Y-m-d');
$to = DateTime::createFromFormat('U', (string) $to)->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; yii::$app->response->format = Response::FORMAT_JSON;
return [ return [
'main' => $this->renderPartial('/orders/index', compact('orders', 'moderator_orders', 'search', 'from', 'to', 'window') 'main' => $this->renderPartial('/orders/index', compact('data', 'moderator_data', 'account', 'search', 'from', 'to', 'window')
+ ['panel' => $this->renderPartial('/orders/search/panel', compact('account') + ['response' => @$orders[0]['supplies']] ?? null)]), + ['panel' => $this->renderPartial('/orders/search/panel', compact('account') + ['data' => $data] ?? null)]),
'title' => 'Заказы', 'title' => 'Заказы',
'redirect' => '/orders', 'redirect' => '/orders',
'_csrf' => yii::$app->request->getCsrfToken() '_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(); $data['order'] = new Order();
$model->save() or throw new Exception('Не удалось инициализировать заказ'); $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 // Если запись не удалась, то вернуть код: 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; return $return;
} }
@ -585,105 +583,98 @@ class OrderController extends Controller
*/ */
public function actionRequest(): string|array|null 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 ($order_edge_supply = OrderEdgeSupply::searchByDirection($account_edge_order->_to, type: 'write', limit: 500)) {
if ($product['stts'] === 'active'); // Найдены рёбра: ЗАКАЗ -> ПОСТАВКА
else $edge->delete();
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);
} }
} }
}
// Реиницилазация return $this->actionIndex();
$model = Order::searchByType(supplies: true); } else {
// Не заполнены все необходимые поля для оформления заказа (подразумевается прохождение второго этапа регистрации)
// Запись var_dump($account->getErrors());
$account_edge_order->type = 'requested';
if ($account_edge_order->update()) { foreach ($account->getErrors() as $parameter => $errors) {
// Удалось сохранить изменения // Перебор параметров с ошибками
// Запись в журнал foreach ($errors as $error) {
$model->journal('requested'); // Перебор ошибок
// Инициализация буфера поставок // Инициализация ярлыка
$supplies = []; $label = $account->getAttributeLabel($parameter) ?? $parameter;
foreach ($model['supplies'] as $supply) { // Отправка уведомления покупателю
// Перебор поставок Notification::_write($error . " ($label)", account: $account->_key, type: Notification::TYPE_ERROR);
}
$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'
];
} }
Invoice::generate($model->_key, $this->renderPartial('/invoice/order/pattern', [ return null;
'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 $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;
} }
/** /**

View File

@ -791,6 +791,8 @@ class ProfileController extends Controller
return self::syncGeolocationWithDellin($account); return self::syncGeolocationWithDellin($account);
} }
return false;
} }
/** /**
@ -1227,6 +1229,9 @@ class ProfileController extends Controller
* Удалить склад * Удалить склад
* *
* @return array|string|null * @return array|string|null
*
* @todo
* 1. Удаление всех ImportEdgeSupply с устаревшими версиями
*/ */
public function actionWarehousesDelete(): array|string|null public function actionWarehousesDelete(): array|string|null
{ {
@ -1277,11 +1282,22 @@ class ProfileController extends Controller
foreach (Supply::searchByImport($import->readId(), limit: 9999) as $supply) { 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;
}
}
} }
} }

View File

@ -989,7 +989,7 @@ class Account extends Document implements IdentityInterface, PartnerInterface
public static function isModer($account = null): bool public static function isModer($account = null): bool
{ {
if ($account = self::initAccount($account)) { if ($account = self::initAccount($account)) {
// Аккаунт инициализирован // Инициализирован аккаунт
if ($account->type === 'moderator') { if ($account->type === 'moderator') {
return true; return true;
@ -1022,4 +1022,26 @@ class Account extends Document implements IdentityInterface, PartnerInterface
default => 'Неизвестно' 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;
}
} }

View File

@ -24,10 +24,20 @@ class AccountEdgeOrder extends Edge
return self::find()->where(['_to' => $order_id])->limit($limit)->all(); 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) { return match ($status) {
'accepted' => 'Доставляется', 'requested' => 'Запрошен',
'accepted' => 'Ожидается отправка',
'going' => 'Доставляется',
'completed' => 'Завершен',
default => 'Обрабатывается' default => 'Обрабатывается'
}; };
} }

View File

@ -150,7 +150,7 @@ abstract class Document extends ActiveRecord
return static::findOne(['_id' => $_id]); return static::findOne(['_id' => $_id]);
} }
public static function readLast(): ?static public static function readLast(): static|null|bool
{ {
return static::find()->orderBy(['DESC'])->one(); return static::find()->orderBy(['DESC'])->one();
} }

View File

@ -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')) { 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')) { } 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')) { } 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) { if ($limit < 2) {

View File

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace app\models; namespace app\models;
use mirzaev\yii2\arangodb\Query;
/** /**
* Связь инстанций импорта с поставками * Связь инстанций импорта с поставками
*/ */
@ -25,6 +27,7 @@ class ImportEdgeSupply extends Edge
return array_merge( return array_merge(
parent::attributes(), parent::attributes(),
[ [
'vrsn'
] ]
); );
} }
@ -37,6 +40,7 @@ class ImportEdgeSupply extends Edge
return array_merge( return array_merge(
parent::attributeLabels(), parent::attributeLabels(),
[ [
'vrsn' => 'Версия'
] ]
); );
} }
@ -49,12 +53,63 @@ class ImportEdgeSupply extends Edge
return array_merge( return array_merge(
parent::rules(), 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;
} }
} }

View File

@ -274,7 +274,7 @@ class Order extends Document implements DocumentInterface
int|null $from = null, int|null $from = null,
int|null $to = null, int|null $to = null,
bool $count = false bool $count = false
): self|int|array|null { ): int|array|null {
// Инициализация аккаунта // Инициализация аккаунта
if (empty($account) && isset(yii::$app->user->identity)) { 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', from: 'account',
to: 'order', to: 'order',
subquery_where: $subquery_where, subquery_where: $subquery_where,
@ -346,73 +346,81 @@ class Order extends Document implements DocumentInterface
sort: ['DESC'], sort: ['DESC'],
select: $select, select: $select,
direction: 'INBOUND', direction: 'INBOUND',
count: !$supplies && $count ? true : false count: !$supplies && $count
); );
if (!$supplies && $count) { if (!$supplies && $count) {
// Запрошен подсчет заказов // Запрошен подсчет заказов
return $return; return $orders;
}
// Инициализация буфера возврата
$return = [];
// Инициализация архитектуры буфера вывода
foreach ($orders as $key => $order) {
// Перебор заказов
// Запись в буфер возврата
$return[$key]['order'] = $order;
} }
if ($supplies) { 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 { } else {
// Массив с заказом (подразумевается)
// Инициализация настроек // Инициализация настроек
$config = $container['order']; $config = $container['order'];
unset($config['_id'], $config['_rev'], $config['_id']); 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) { 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 В будущем возможно заказ не только поставок реализовать * @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); $offset = $limit * ($page - 1);
if (!empty($search)) { if (!empty($search)) {
// Передан поиск по продуктам в заказах // Передан поиск по продуктам в заказах
// Добавить ограничитель // Запись ограничения по максимальному значению
$limit = 9999; $limit = 9999;
} }
@ -426,286 +434,125 @@ class Order extends Document implements DocumentInterface
'order._id' => $this->readId() 'order._id' => $this->readId()
] ]
], ],
foreach: ['edge' => 'order_edge_supply'], where: 'order_edge_supply != []',
where: 'edge._to == supply._id',
limit: $limit, limit: $limit,
offset: $offset, offset: $offset,
filterStart: ['catn' => $search], filterStart: ['catn' => $search],
direction: 'INBOUND', direction: 'INBOUND',
select: '{supply, order_edge_supply}', select: '{order_edge_supply}',
count: $count count: $count
); );
if ($count) { if ($count) {
// Запрошен подсчет // Подсчёт запрошен
return $connections; 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) { 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) { 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;
}
} }
} }
// Инициализация дополнительных данных return $supplies;
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 '<pre>';
// 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;
} }
/** /**

View File

@ -143,11 +143,22 @@ class OrderEdgeSupply extends Edge
return []; return [];
} }
public static function convertStatusToRussian(string|int $status): string
/**
* Генерация ярлыка на русском языке для статуса заказа поставки
*
* @param string $status Статус заказа поставки
*
* @return string Ярлык статуса на русском языке
*/
public static function statusToRussian(string $status = ''): string
{ {
return match($status) { return match ($status) {
'accepted', 1 => 'Ожидается отправка', 'requested' => 'Запрошен',
default => 'Запрошен' 'accepted' => 'Ожидается отправка',
'going' => 'Доставляется',
'completed' => 'Завершен',
default => 'Обрабатывается'
}; };
} }
} }

View File

@ -59,11 +59,6 @@ class Product extends Document
*/ */
public UploadedFile|string|array|null $file_image = null; public UploadedFile|string|array|null $file_image = null;
/**
* Группа в которой состоит товар
*/
public ProductGroup|null $group = null;
/** /**
* Имя коллекции * Имя коллекции
*/ */
@ -113,7 +108,6 @@ class Product extends Document
'stts' => 'Статус', 'stts' => 'Статус',
'file_excel' => 'Документ (file_excel)', 'file_excel' => 'Документ (file_excel)',
'file_image' => 'Изображение (file_image)', 'file_image' => 'Изображение (file_image)',
'group' => 'Группа (group)',
'account' => 'Аккаунт' 'account' => 'Аккаунт'
] ]
); );
@ -233,7 +227,7 @@ class Product extends Document
} }
/** /**
* Запись пустого продукта * Запись пустого товара
*/ */
public static function writeEmpty(string $catn, string $prod = 'Неизвестный', bool $active = false): ?self 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')) { if (!file_exists(YII_PATH_PUBLIC . $catalog_h150 = '/img/products/' . $this->_key . '/h150')) {
// Директория для обложек изображений продукта не найдена // Директория для обложек изображений продукта не найдена
@ -462,138 +455,35 @@ class Product extends Document
)[0]; )[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')) { if (!$group = ProductGroup::searchByProduct($this)) {
// Найдено ребро // Не найдена группа товаров
} else if (ProductEdgeProduct::searchByVertex(Product::collectionName() . "/$to->_key", Product::collectionName() . "/$this->_key", type: 'analogue')) {
// Найдено ребро (наоборот)
// !!! Вероятно эта проверка здесь не нужна, так как мы знаем входные данные // Запись новой группы
$group = ProductGroup::writeEmpty(active: true);
// Запись товара в группу
$group->writeProduct($this);
}
if ($_group = ProductGroup::searchByProduct($product)) {
// Найдена другая группа у товара который надо добавить в группу
// Перенос всех участников (включая целевой товар)
return $group->transfer($_group);
} else { } else {
// Не найдены ребра // Не найдена группа у товара который надо добавить в группу
if ($edge = ProductEdgeProduct::write(Product::collectionName() . "/$this->_key", Product::collectionName() . "/$to->_key", data: ['type' => 'analogue'])) { // Запись целевого товара в группу
// Ребро сохранено return $group->writeProduct($product);
// Запись в журнал о соединении
$this->journal('connect analogue', [
'to' => Product::collectionName() . "/$to->_key"
]);
// Запись в журнал о соединении
$to->journal('connect analogue', [
'from' => Product::collectionName() . "/$this->_key"
]);
return $edge;
}
} }
return null; return null;
@ -602,67 +492,21 @@ class Product extends Document
/** /**
* Отключение аналога * Отключение аналога
* *
* @param self|null $to Цель (если null, то целью являются все подключенные аналоги) * @return bool Статус выполнения
* @param bool $all Удалить соединения со всеми членами группы
*/ */
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]) { // Удаление из группы
// Найдено ребро $group->deleteProduct($this);
} 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);
}
}
return true; 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; return true;
} }
@ -670,42 +514,16 @@ class Product extends Document
return false; 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 Товар, если найден
*
* @return bool|static true если создать новую запись, static если найден дубликат
* *
* @todo * @todo
* 1. Обработка дубликатов * 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)) { if ($supplies = self::search(['catn' => $this->catn, 'prod' => $this->prod], limit: 100)) {
// Найдены поставки с таким же артикулом (catn) и производителем (prod) // Найдены поставки с таким же артикулом (catn) и производителем (prod)
@ -717,9 +535,9 @@ class Product extends Document
// Возврат (найден дубликат в базе данных) // Возврат (найден дубликат в базе данных)
return $supply; return $supply;
} else return true; }
// Возврат (подразумевается ошибка) // Возврат (подразумевается отсутствие дубликатов в базе данных)
return false; return false;
} }
@ -796,4 +614,30 @@ class Product extends Document
return false; 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];
}
} }

View File

@ -10,4 +10,17 @@ class ProductEdgeProductGroup extends Edge
{ {
return 'product_edge_product_group'; 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;
}
} }

View File

@ -4,15 +4,19 @@ declare(strict_types=1);
namespace app\models; namespace app\models;
use app\models\traits\SearchByEdge;
use carono\exchange1c\interfaces\GroupInterface; use carono\exchange1c\interfaces\GroupInterface;
use Zenwalker\CommerceML\Model\Group; use Zenwalker\CommerceML\Model\Group;
/** /**
* Группировка продуктов * Группировка продуктов для соединения их в аналоги
*/ */
class ProductGroup extends Document implements GroupInterface class ProductGroup extends Document implements GroupInterface
{ {
use SearchByEdge;
/** /**
* Имя коллекции * Имя коллекции
*/ */
@ -29,7 +33,7 @@ class ProductGroup extends Document implements GroupInterface
return array_merge( return array_merge(
parent::attributes(), parent::attributes(),
[ [
'name' 'stts'
] ]
); );
} }
@ -42,7 +46,7 @@ class ProductGroup extends Document implements GroupInterface
return array_merge( return array_merge(
parent::attributeLabels(), parent::attributeLabels(),
[ [
'name' => 'Название (name)' 'stts' => 'Статус'
] ]
); );
} }
@ -55,23 +59,138 @@ class ProductGroup extends Document implements GroupInterface
return array_merge( return array_merge(
parent::rules(), parent::rules(),
[ [
// [ [
// 'name', 'stts',
// 'required', 'string',
// 'message' => 'Заполните поле: {attribute}' '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 public function writeMember(Product $member): ProductEdgeProductGroup
{ {
return ProductEdgeProductGroup::write($member->readId(), $this->readId(), 'member'); 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]); 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;
}
} }

View File

@ -392,12 +392,12 @@ class Search extends Document
* *
* @param array $row Товар сгенерированный через Search::content() * @param array $row Товар сгенерированный через Search::content()
* @param string|null $cover Обложка * @param string|null $cover Обложка
* @param array $empties Реестр не найденных товаров * @param array $list Реестр найденных товаров
* @param bool $analogs Запрошены аналоги (не выведет пустые товары) * @param bool $analogs Запрошены аналоги (не выведет пустые товары)
* *
* @return string HTML-элемент с товаром * @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) { 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) { 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) { if ($supplies_amount > $supply_iterator) {
// Это не последняя строка с товаром и его поставками // Это не последняя строка с товаром и его поставками
@ -491,9 +494,6 @@ class Search extends Document
</div> </div>
HTML; HTML;
// Запись в список ненайденных
$empties[$row['prod']] = [$row['catn']] + (isset($empties[$row['prod']]) ? $empties[$row['prod']] : []);
// Запись блокировщика // Запись блокировщика
$empty_block = true; $empty_block = true;

View File

@ -92,15 +92,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
{ {
return array_merge( return array_merge(
parent::rules(), 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)) { 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) { foreach ($data as $doc) {
// Перебор полученных документов // Перебор полученных документов
// Проверка на пустую страницу или документ (пустой массив)
if (empty($doc)) continue;
foreach ($doc as $row) { 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); $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); $_supply = trim($_supply);
@ -484,7 +504,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Запись артикула (каталожного номера) в буфер // Запись артикула (каталожного номера) в буфер
$_row['catn'] = $_supply; $_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['prod'] = $prod;
$_row['oemn'] = array_walk($analogs, 'trim'); $_row['oemn'] = array_walk($analogs, 'trim');
@ -496,137 +516,12 @@ class Supply extends Product implements ProductInterface, OfferInterface
if ($supply->validate()) { 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; $supply = $_supply;
} else { } else {
// Не найден документ с такими параметрами // Не найдена поставка с такими параметрами
if ($supply->save()) { if ($supply->save()) {
// Поставка записана в базу данных // Поставка записана в базу данных
@ -638,6 +533,141 @@ class Supply extends Product implements ProductInterface, OfferInterface
$imported[] = $supply; $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 { } else {
// Проверка не пройдена // Проверка не пройдена
@ -654,26 +684,19 @@ class Supply extends Product implements ProductInterface, OfferInterface
// Активация товара // Активация товара
$product->activate(); $product->activate();
// Инициализация списка артикулов группы для добавления аналогов // foreach (Product::searchByCatn($product->catn) as $target) {
$group = [$article] + $analogs; // // Перебор товаров по артикулу (все производители)
// }
foreach ($group as $catn) { // Добавление в группу аналогов
// Перебор артикулов для добавления аналогов $group->writeProduct($product);
foreach (Product::searchByCatn((string) $catn) as $target) {
// Перебор товаров для добавления аналогов
// Добавление в группу аналогов
if ($to = Product::searchByCatn((string) $target['catn'], 1)) $product->synchronization($to);
}
}
} }
return !$error; return !$error;
}; };
// Запись поставки // Запись поставки
$create((string) $article); $create($article, (int) $amount);
foreach ($analogs as $_supply) { foreach ($analogs as $_supply) {
// Перебор аналогов (если найдены) // Перебор аналогов (если найдены)
@ -710,7 +733,7 @@ class Supply extends Product implements ProductInterface, OfferInterface
foreach ($imported as $supply) { 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])) {
// Записано ребро: ИНСТАНЦИЯ ПОСТАВОК -> ПОСТАВКА // Записано ребро: ИНСТАНЦИЯ ПОСТАВОК -> ПОСТАВКА
// Запись в журнал инстанции импорта // Запись в журнал инстанции импорта

View File

@ -32,6 +32,7 @@ trait SearchByEdge
array $params = [], array $params = [],
bool $asArray = true, bool $asArray = true,
bool $debug = false, bool $debug = false,
bool $aql = false,
bool $count = false bool $count = false
): mixed { ): mixed {
$subquery = static::find() $subquery = static::find()
@ -71,11 +72,20 @@ trait SearchByEdge
->limit($limit) ->limit($limit)
->select($select ?? $to); ->select($select ?? $to);
// Режим вывода строки запроса
if ($aql) {
// Запрошена проверка
return (string) $request->createCommand();
}
// Режим проверки // Режим проверки
if ($debug) { if ($debug) {
// Запрошена проверка // Запрошена проверка
return (string) $request->createCommand(); var_dump((string) $request->createCommand());
return null;
} }
// Запрос // Запрос

View File

@ -6,7 +6,7 @@ use yii;
use yii\bootstrap\ActiveForm; use yii\bootstrap\ActiveForm;
use app\models\connection\Dellin; use app\models\connection\Dellin;
use app\models\Supply;
use DateTime; use DateTime;
?> ?>
@ -21,10 +21,14 @@ use DateTime;
<div class="pl-3 mr-1"> <div class="pl-3 mr-1">
<input id="checkbox_cart_all" type="checkbox" onchange="return cart_list_checkbox(this);" /> <input id="checkbox_cart_all" type="checkbox" onchange="return cart_list_checkbox(this);" />
</div> </div>
<div class="col-2">
<span>Производитель</span>
</div>
<div class="col-2"> <div class="col-2">
<span>Артикул</span> <span>Артикул</span>
</div> </div>
<div class="col-4"> <div class="col-2">
<span>Поставщик</span>
</div> </div>
<div class="col-1 ml-auto px-0 text-center"> <div class="col-1 ml-auto px-0 text-center">
<span>Количество</span> <span>Количество</span>
@ -37,168 +41,100 @@ use DateTime;
</div> </div>
</div> </div>
<?php <?php
if (isset($connections) && !empty($connections)) { if (!empty($data['supplies'])) {
foreach ($connections as $connection) { // Найдены цели для заказа
// Перебор поставок
// Инициализация переменных // Инициализация списка поставок
extract($connection); $targets = [];
// Инициализация цены foreach ($data['supplies'] as $prod => $list) {
$price_raw = $cost; // Перебор поставщиков
// Инициализация комментария foreach ($list as $catn => $deliveries) {
$comment = $order_edge_supply[0]['comm'] ?? 'Комментарий к заказу'; // Перебор поставок
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 = '?'; $days = '?';
} else { } else {
// Удалось рассчитать доставку // Удалось рассчитать доставку
// Инициализация даты отправки // Инициализация даты отправки
try { try {
// Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель) // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)
$delivery_auto_send_date = DateTime::createFromFormat('Y-m-d', $delivery['auto']['orderDates']['arrivalToOspSender'])->getTimestamp(); $delivery_send_date = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['arrivalToOspSender'])->getTimestamp();
} catch (Throwable $e) { } catch (Throwable $e) {
// Взять данные из "pickup" (Дата передачи груза на адресе отправителя) // Взять данные из "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 { $icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck';
// Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале)
// Оставлено на всякий случай для дальнейших разбирательств // Генерация HTML
echo <<<HTML
$delivery_auto_converted = DateTime::createFromFormat('Y-m-d H:i:s', $delivery['auto']['orderDates']['giveoutFromOspReceiver'])->getTimestamp(); <div class="row py-2 cart_list_target">
} catch (Throwable $e) { <div class="col">
// Автоматическая доставка (подразумевается), данные из "arrivalToOspReceiver" (Дата прибытия натерминал-получатель) <div class="row">
<div class="pl-3 my-auto mr-1">
$delivery_auto_converted = DateTime::createFromFormat('Y-m-d', $delivery['auto']['orderDates']['arrivalToOspReceiver'])->getTimestamp(); <input id="cart_list_checkbox_{$prod}_{$catn}_auto" type="checkbox" onchange="return cart_list_checkbox(this);"/>
} </div>
$delivery_auto = ceil(($delivery_auto_converted - ($delivery_auto_send_date ?? 0)) / 60 / 60 / 24) + 1; <div class="col-2 my-auto">
} $prod
</div>
// Генерация HTML <div class="col-2 my-auto">
echo <<<HTML $catn
<div class="row py-2 cart_list_target"> </div>
<div class="col"> <div class="col-2 my-auto">
<div class="row"> {$supply['account']['indx']}
<div class="pl-3 my-auto mr-1"> </div>
<input id="cart_list_checkbox_{$supply['catn']}_auto" type="checkbox" onchange="return cart_list_checkbox(this);"/> <div class="col-1 my-auto ml-auto">
<input id="cart_list_amnt_{$prod}_{$catn}_auto" class="form-control text-center" type="text" value="{$supply['amount']}" onchange="return cart_list_amount_update('$prod', '$catn', 'auto', this)" aria-invalid="false">
</div>
<div class="col-2 my-auto text-right">
<p title="Ориентировочно"><i class="mr-1 fas $icon"></i> <b>~</b>$days дн</p>
</div>
<div class="col-2 my-auto mr-3 text-right">
{$supply['cost']} {$supply['currency']}
</div>
</div> </div>
<div class="col-2 my-auto"> <div class="dropdown-divider"></div>
{$supply['catn']} <div class="row mb-1">
</div> <div class="col-12">
<div class="col-4 my-auto"> <p id="cart_list_comment_{$prod}_{$catn}_auto" class="mt-0 ml-0 text-break pointer-event" role="button" onclick="return cart_list_comment_edit('$prod', '$catn', 'auto', this);">$comment</p>
</div> </div>
<div class="col-1 my-auto ml-auto">
<input id="cart_list_amnt_{$supply['catn']}_auto" class="form-control text-center" type="text" value="{$amount['auto']}" onchange="return cart_list_amount_update('{$supply['catn']}', 'auto', this)" aria-invalid="false">
</div>
<div class="col-2 my-auto text-right">
<p title="Ориентировочно"><i class="mr-1 fas fa-truck"></i> <b>~</b>$delivery_auto дн</p>
</div>
<div class="col-2 my-auto mr-3 text-right">
$price_auto
</div>
</div>
<div class="dropdown-divider"></div>
<div class="row mb-1">
<div class="col-12">
<p id="cart_list_comment_{$supply['catn']}_auto" class="mt-0 ml-0 text-break pointer-event" role="button" onclick="return cart_list_comment_edit('{$supply['catn']}', 'auto', this);">$comment</p>
</div> </div>
</div> </div>
</div> </div>
</div> HTML;
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
echo <<<HTML
<div class="row py-2 cart_list_target">
<div class="col">
<div class="row">
<div class="pl-3 my-auto mr-1">
<input id="cart_list_checkbox_{$supply['catn']}_avia" type="checkbox" onchange="return cart_list_checkbox(this);"/>
</div>
<div class="col-2 my-auto">
{$supply['catn']}
</div>
<div class="col-4 my-auto">
{$supply['dscr']}
</div>
<div class="col-1 my-auto ml-auto">
<input id="cart_list_amnt_{$supply['catn']}_avia" class="form-control text-center" type="text" value="{$amount['avia']}" onchange="return cart_list_amount_update('{$supply['catn']}', 'avia', this)" aria-invalid="false">
</div>
<div class="col-2 my-auto text-right">
<p title="Ориентировочно"><i class="mr-1 fas fa-plane"></i> <b>~</b>$delivery_avia дн</p>
</div>
<div class="col-2 my-auto mr-3 text-right">
$price_avia
</div>
</div>
<div class="dropdown-divider"></div>
<div class="row mb-1">
<div class="col-12">
<p id="cart_list_comment_{$supply['catn']}_avia" class="mt-0 ml-0 text-break pointer-event" role="button" onclick="return cart_list_comment_edit('{$supply['catn']}', 'avia', this);">$comment</p>
</div>
</div>
</div>
</div>
HTML;
} }
} }
} else { } else {

View File

@ -8,7 +8,7 @@ use app\models\Settings;
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Счёт <?= $order['id'] ?></title> <title>Счёт <?= $data['order']->_key ?></title>
</head> </head>
<body> <body>
@ -89,7 +89,7 @@ use app\models\Settings;
preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone); preg_match_all('/UTC([\+\-0-9:]*)/', $account->zone ?? Settings::searchActive()['timezone_default'] ?? 'UTC+3', $timezone);
$timezone = $timezone[1][0]; $timezone = $timezone[1][0];
?> ?>
<b>Счет на оплату <?= $order['id'] ?> от <?= (new DateTime())->setTimestamp($order['date'])->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?></b> <b>Счет на оплату <?= $data['order']->_key ?> от <?= (new DateTime())->setTimestamp($date)->setTimezone(new DateTimeZone($timezone))->format('d.m.Y') ?></b>
</td> </td>
</tr> </tr>
@ -133,7 +133,9 @@ use app\models\Settings;
<tr> <tr>
<td style="text-align: center; border: solid; border-top: thick; border-left: thick;" colspan="1" rowspan="2" valign="center"><b></b></td> <td style="text-align: center; border: solid; border-top: thick; border-left: thick;" colspan="1" rowspan="2" valign="center"><b></b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="6" rowspan="2" valign="center"><b>Товары (работы, услуги)</b></td> <td style="text-align: center; border: solid; border-top: thick;" colspan="3" rowspan="2" valign="center"><b>Производитель</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="3" rowspan="2" valign="center"><b>Товар</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Поставщик</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Количество</b></td> <td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Количество</b></td>
<td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Цена</b></td> <td style="text-align: center; border: solid; border-top: thick;" colspan="2" rowspan="2" valign="center"><b>Цена</b></td>
<td style="text-align: center; border: solid; border-top: thick; border-right: thick;" colspan="2" rowspan="2" valign="center"><b>Сумма</b></td> <td style="text-align: center; border: solid; border-top: thick; border-right: thick;" colspan="2" rowspan="2" valign="center"><b>Сумма</b></td>
@ -151,16 +153,21 @@ use app\models\Settings;
$cost = 0; $cost = 0;
?> ?>
<?php foreach ($order['entries'] as $entry) : ?> <?php foreach ($data['supplies'] as $prod => $supplies) : ?>
<tr> <?php foreach ($supplies as $catn => $deliveries) : ?>
<td style="text-align: center; border: solid; border-left: thick;" colspan="1" valign="center"><?= $row++ ?></td> <?php foreach ($deliveries as $delivery => $supply) : ?>
<td style="text-align: left; border: solid;" colspan="6" valign="center"><?= $entry['title'] ?></td> <tr>
<td style="text-align: center; border: solid; border-left: thick;" colspan="1" valign="center"><?= $row++ ?></td>
<td style="text-align: center; border: solid;" colspan="2" valign="center"><?= $entry['amount']['value'] ?></td> <td style="text-align: left; border: solid;" colspan="3" valign="center"><?= $prod ?></td>
<td style="text-align: center; border: solid;" valign="center"><?= $entry['cost']['value'] ?></td> <td style="text-align: left; border: solid;" colspan="3" valign="center"><?= $catn ?></td>
<td style="text-align: center; border: solid;" valign="center"><?= $entry['cost']['unit'] ?></td> <td style="text-align: left; border: solid;" colspan="2" valign="center"><?= $supply['account']['indx'] ?></td>
<td style="text-align: center; border: solid; border-right: thick;" colspan="2" valign="center"><?= $cost += $entry['cost']['value'] * $entry['amount']['value'] ?></td> <td style="text-align: center; border: solid;" colspan="2" valign="center"><?= $supply['amount'] ?></td>
</tr> <td style="text-align: center; border: solid;" valign="center"><?= $supply['cost'] * $supply['amount'] ?></td>
<td style="text-align: center; border: solid;" valign="center"><?= $supply['currency'] ?></td>
<td style="text-align: center; border: solid; border-right: thick;" colspan="2" valign="center"><?= $cost += $supply['cost'] * $supply['amount'] ?></td>
</tr>
<?php endforeach ?>
<?php endforeach ?>
<?php endforeach ?> <?php endforeach ?>
<tr> <tr>

View File

@ -1,3 +1,3 @@
<div class="p-3 h-100"> <div class="p-3 h-100">
<p class="text-center my-auto">Новый заказ: <a href="/orders#<?= $id ?>">#<?= $id ?></a></p> <p class="text-center my-auto">Заказ запрошен: <a href="/orders#<?= $id ?>">#<?= $id ?></a></p>
</div> </div>

View File

@ -47,16 +47,15 @@ if (empty($window)) {
<label class="btn btn-sm button_white mb-0 mr-2" for="orders_panel_moderation_handled">Обрабатываются</label> <label class="btn btn-sm button_white mb-0 mr-2" for="orders_panel_moderation_handled">Обрабатываются</label>
<label class="btn btn-sm button_white mb-0" for="orders_panel_moderation_completed">Завершены</label> <label class="btn btn-sm button_white mb-0" for="orders_panel_moderation_completed">Завершены</label>
</div> </div>
<?php if (!empty($moderator_orders)) : ?> <?php if (!empty($moderator_data)) : ?>
<?php foreach ($moderator_data as $moderator_data) : ?>
<?php foreach ($moderator_orders as $order) : ?> <div id="<?= $moderator_data['order']->_key ?>_panel" class="page_order_panel mb-3 py-3 px-4 rounded">
<div id="<?= $order['order']['_key'] ?>_panel" class="page_order_panel mb-3 py-3 px-4 rounded">
<h5 class="row mt-1 mb-3"> <h5 class="row mt-1 mb-3">
<?php <?php
// Инициализация времени отправки заказа // Инициализация времени отправки заказа
$date = null; $date = null;
foreach ($order['order']['jrnl'] as $entry) { foreach ($moderator_data['order']->jrnl as $entry) {
// Перебор записей в журнале // Перебор записей в журнале
if ($entry['action'] === 'requested') { if ($entry['action'] === 'requested') {
@ -81,7 +80,7 @@ if (empty($window)) {
]; ];
?> ?>
<p class="col-auto ml-1 font-weight-bold">#<?= $order['order']['_key'] ?></p> <p class="col-auto ml-1 font-weight-bold">#<?= $moderator_data['order']->_key ?></p>
<p class="col-auto mr-1 font-weight-bold"> <p class="col-auto mr-1 font-weight-bold">
<span class="mr-2"><?= $date['H:i'] ?? 'Неизвестно' ?></span> <span class="mr-2"><?= $date['H:i'] ?? 'Неизвестно' ?></span>
<span><?= $date['m.d.Y'] ?? 'Неизвестно' ?></span> <span><?= $date['m.d.Y'] ?? 'Неизвестно' ?></span>
@ -89,96 +88,73 @@ if (empty($window)) {
</h5> </h5>
<div class="dropdown-divider mb-3"></div> <div class="dropdown-divider mb-3"></div>
<div class="row px-2"> <div class="row px-2">
<div id="orders_panel_supplies_<?= $order['order']['_key'] ?>" class="col-3 unselectable"> <div id="orders_panel_supplies_<?= $moderator_data['order']->_key ?>" class="col-3 unselectable">
<?php if (!empty($order['supplies'])) : ?> <?php if (!empty($moderator_data['supplies'])) : ?>
<?php <?php
// Инициализация счетчика поставок для отрисовки горизонтального разделителя // Инициализация счетчика поставок для отрисовки горизонтального разделителя
$count = 1; $count = 1;
?> ?>
<?php foreach ($order['supplies'] as $supply) : <?php foreach ($moderator_data['supplies'] as $prod => $list) : ?>
// Перебор поставок <?php foreach ($list as $catn => $deliveries) : ?>
?> <?php foreach ($deliveries as $delivery => $supply) : ?>
<?php
// Инициализация обложки
$covr = null;
<?php foreach ($supply['product']->imgs ?? [] as $img) {
// Инициализация окружения // Перебор изображений для обложки
extract($supply);
// Инициализация обложки if ($img['covr'] ?? false) {
$covr = null; // Обложка найдена
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) {
// Не удалось использовать первое изображение как обложку // Пройдена проверка на количество поставок в заказе
// Запись обложки по умолчанию // if (Order::checkSuppliesStts($order_edge_supply)) {
$covr = '/img/covers/h150/product.png'; // $status = '<span id="' . $supply['catn'] . '_auto_supply_stts_indicator_icon" class="ml-auto my-auto fas fa-check"></span>';
} // } else {
} $status = '';
// }
if ($amount['auto'] > 0) { // Инициализация иконки
// Найдены поставки с автоматической доставкой $icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck';
if (Order::checkSuppliesStts($order_edge_supply)) { // Генерация HTML
$status = '<span id="' . $supply['catn'] . '_auto_supply_stts_indicator_icon" class="ml-auto my-auto fas fa-check"></span>'; echo <<<HTML
} else { <a id="{$prod}_{$catn}_{$delivery}_supply" class="row mb-2 p-2 px-0 rounded row_supply" type="button" onclick="return orders_supply_edit('{$prod}_{$catn}_{$delivery}', {$moderator_data['order']->_key});">
$status = ''; <img class="col-auto px-0 h-100 img-fluid rounded" src="$covr" />
} <p id="{$prod}_{$catn}_{$delivery}_supply_stts_indicator" class="col d-flex text-dark">
{$catn} x{$supply['amount']}
// Генерация HTML <small class="ml-2 my-auto fas $icon"></small>
echo <<<HTML $status
<a id="{$supply['catn']}_auto_supply" class="row mb-2 p-2 px-0 rounded row_supply" type="button" onclick="return orders_supply_edit('{$supply['catn']}_auto', {$order['order']['_key']});"> </p>
<img class="col-auto px-0 h-100 img-fluid rounded" src="$covr" /> </a>
<p id="{$supply['catn']}_auto_supply_stts_indicator" class="col d-flex text-dark"> HTML;
{$product['catn']} x{$amount['auto']} }
<small class="ml-2 my-auto fas fa-truck"></small> ?>
$status <?php if ($count++ < count($moderator_data['supplies'])) : ?>
</p> <div class="dropdown-divider mb-2"></div>
</a> <?php endif ?>
HTML; <?php endforeach ?>
} <?php endforeach ?>
if ($amount['avia'] > 0) {
// Найдены поставки с автоматической доставкой
if (Order::checkSuppliesStts($order_edge_supply)) {
$status = '<span id="' . $supply['catn'] . '_avia_supply_stts_indicator_icon" class="ml-auto my-auto fas fa-check"></span>';
} else {
$status = '';
}
// Генерация HTML
echo <<<HTML
<a id="{$supply['catn']}_avia_supply" class="row mb-2 p-2 px-0 rounded row_supply" type="button" onclick="return orders_supply_edit('{$supply['catn']}_avia', {$order['order']['_key']});">
<img class="col-auto px-0 h-100 img-fluid rounded" src="$covr" />
<p id="{$supply['catn']}_avia_supply_stts_indicator" class="col d-flex text-dark">
{$product['catn']} x{$amount['avia']}
<small class="ml-2 my-auto fas fa-plane"></small>
$status
</p>
</a>
HTML;
}
?>
<?php if ($count++ < count($order['supplies'])) : ?>
<div class="dropdown-divider mb-2"></div>
<?php endif ?>
<?php endforeach ?> <?php endforeach ?>
<?php else : ?> <?php else : ?>
<div class="row"> <div class="row">
@ -186,30 +162,19 @@ if (empty($window)) {
</div> </div>
<?php endif ?> <?php endif ?>
</div> </div>
<div id="orders_panel_edit_<?= $order['order']['_key'] ?>" class="px-4 col-6 d-flex flex-column"> <div id="orders_panel_edit_<?= $moderator_data['order']->_key ?>" class="px-4 col-6 d-flex flex-column">
<p class="my-auto">Выберите поставку</p> <p class="my-auto">Выберите поставку</p>
</div> </div>
<div id="orders_panel_info_<?= $order['order']['_key'] ?>" class="col-3 d-flex flex-column"> <div id="orders_panel_info_<?= $moderator_data['order']->_key ?>" class="col-3 d-flex flex-column">
<p class="row mt-0 mb-3 px-2"><b>Статус:</b> <span class="ml-auto"><?= AccountEdgeOrder::statusToRussian(AccountEdgeOrder::searchByOrder($moderator_data['order']->readId())['stts'] ?? '') ?></span></p>
<?php <a id="<?= $moderator_data['order']->_key ?>_button" class="row mt-auto mb-0 text-center text-white btn button_blue button_clean disabled" type="button" onclick="return order_accept('<?= $moderator_data['order']->_key ?>');">Подтвердить</a>
// Конвертация статуса заказа
$status = match ($order['account_edge_order'][0]['type']) {
'requested' => 'Запрошен',
'handled' => 'Обрабатывается',
'completed' => 'Завершен',
}
?>
<p class="row mt-0 mb-3 px-2"><b>Статус:</b> <span class="ml-auto"><?= $status ?></span></p>
<a id="<?= $order['order']['_key'] ?>_button" class="row mt-auto mb-0 text-center text-white btn button_blue button_clean disabled" type="button" onclick="return order_accept('<?= $order['order']['_key'] ?>');">Подтвердить</a>
</div> </div>
</div> </div>
<script defer> <script defer>
document.addEventListener( document.addEventListener(
'DOMContentLoaded', 'DOMContentLoaded',
function() { function() {
order_init('<?= $moderator_data['order']->_key ?>');
order_init('<?= $order['order']['_key'] ?>');
}, },
false false
); );
@ -273,27 +238,26 @@ if (empty($window)) {
</div> </div>
<?php <?php
if (isset($orders) && !empty($orders)) { if (!empty($data)) {
foreach ($orders as $order) { // Найдена информация о заказах
foreach ($data as $data) {
// Перебор заказов // Перебор заказов
// Инициализация // Инициализация ребра: АККАУНТ -> ЗАКАЗ
extract($order); $edge = AccountEdgeOrder::searchByOrder($moderator_data['order']->readId());
if (isset($order['stts']) && $order['stts'] === 'reserved') { if (isset($edge['stts']) && ($order['stts'] === 'reserved' || $edge['stts'] === 'current')) {
// Заказ был резервирован (отменён) // Заказ был резервирован (отменён) или это активный заказ (несформированный, в корзине)
continue; continue;
} }
// Пропуск активного заказа (несформированного, корзины)
if ($account_edge_order[0]['type'] === 'current') continue;
// Инициализация времени подтверждения заказа // Инициализация времени подтверждения заказа
if (isset($order['jrnl'])) { if (isset($data['order']->jrnl)) {
// Журнал найден // Найден журнал
foreach ($order['jrnl'] as $entry) { foreach ($data['order']->jrnl as $entry) {
// Перебор записей в журнале // Перебор записей в журнале
if ($entry['action'] === 'accepted') { if ($entry['action'] === 'accepted') {
@ -318,170 +282,112 @@ if (empty($window)) {
// Инициализация общей стоимости поставок // Инициализация общей стоимости поставок
$sum = 0; $sum = 0;
if (isset($supplies)) { if (isset($data['supplies'])) {
// Найдены поставки // Найдены поставки
// Инициализация максимального срока доставки // Инициализация максимального срока доставки
$delivery_max = 0; $delivery_max = 0;
// Инициализация поставок // Инициализация поставок
foreach ($supplies as $supply) { foreach ($data['supplies'] as $prod => $list) {
// Перебор поставок // Перебор поставок
foreach ($list as $catn => $deliveries) {
// Перебор доставок
foreach ($deliveries as $delivery => $supply) {
// Перебор заказов
// Инициализация переменных // Инициализация ребра: ЗАКАЗ -> ПОСТАВКА
extract($supply); $edge = OrderEdgeSupply::searchBySupplyCatnAndProd((string) $catn, $prod);
// Инициализация цены // Инициализация цены
$price_raw = $cost; $price_raw = $supply['cost'];
// Инициализация комментария // Инициализация комментария
$comment = $order_edge_supply['comm'] ?? 'Комментарий к заказу'; $comment = $edge['comm'] ?? 'Комментарий к заказу';
if ($amount['auto'] > 0) { if ($supply['amount'] > 0) {
// Найдены поставки с автоматической доставкой // Пройдена проверка на количество поставок в заказе
// Инициализация цены // Инициализация цены
$price_auto = $price_raw['auto'] . ' ' . $currency; $price = $price_raw * $supply['amount'] . ' ' . $supply['currency'];
// Инициализация доставки // Инициализация доставки
if (!isset($delivery) || (isset($delivery['auto'], $delivery['auto']['error']) || $delivery === '?')) { if (!isset($supply['delivery']) || (isset($supply['delivery'], $supply['delivery']['error']) || $supply['delivery'] === '?')) {
// Не удалось рассчитать доставку // Не удалось рассчитать доставку
// Инициализация времени // Инициализация времени
$delivery_auto = '?'; $days = '?';
} else { } else {
// Удалось рассчитать доставку // Удалось рассчитать доставку
// Инициализация даты отправки // Инициализация даты отправки
try { try {
// Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель) // Взять данные из "arrivalToOspSender" (Дата прибытия на терминал-отправитель)
$delivery_auto_send_date = DateTime::createFromFormat('Y-m-d', $delivery['auto']['orderDates']['arrivalToOspSender'])->getTimestamp(); $delivery_send_date = DateTime::createFromFormat('Y-m-d', $supply['delivery']['orderDates']['arrivalToOspSender'])->getTimestamp();
} catch (Throwable $e) { } catch (Throwable $e) {
// Взять данные из "pickup" (Дата передачи груза на адресе отправителя) // Взять данные из "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;
}
// Инициализация статуса связи поставки
$status = OrderEdgeSupply::statusToRussian($edge['stts'] ?? '');
// Инициализация класса для поставки (если необходимо)
$css = match ($edge['stts'] ?? '') {
'accepted' => ' supply_accepted',
default => ''
};
// Реинициализация максимальной даты доставки
if ($delivery_max !== '?' && $delivery_max < $days) $delivery_max = $days;
else if ($days === '?') $delivery_max = '?';
// Инициализация иконки
$icon = $delivery === 'avia' ? 'fa-plane' : 'fa-truck';
// Генерация HTML (пробела между supply и $css не должно быть)
$supplies_html .= <<<HTML
<div class="row py-2 supply$css text-center">
<div class="m-auto col-2">{$catn}</div>
<small class="m-auto col-3">$status</small>
<div class="m-auto col-3">$days <small class="mr-1 fas $icon"></small></div>
<div class="m-auto col-2">{$supply['amount']}</div>
<div class="m-auto col-2">$price</div>
</div>
HTML;
// Инициализация общей цены
$sum += $price_raw * $supply['amount'];
} }
// Инициализация времени доставки
try {
// Доставка по воздуху (подразумевается), данные из "giveoutFromOspReceiver" (Дата и время, с которого груз готов к выдаче на терминале)
// Оставлено на всякий случай для дальнейших разбирательств
$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;
} }
// Инициализация статуса связи поставки
$status = OrderEdgeSupply::convertStatusToRussian($part['stts'] ?? '');
// Инициализация класса для поставки (если необходимо)
$css = match ($part['stts'] ?? '') {
'accepted' => ' supply_accepted',
default => ''
};
// Реинициализация максимальной даты доставки
if ($delivery_max !== '?' && $delivery_max < $delivery_auto) $delivery_max = $delivery_auto;
else if ($delivery_auto === '?') $delivery_max = '?';
// Генерация HTML
// Пробела между supply и $css не должно быть
$supplies_html .= <<<HTML
<div class="row py-2 supply$css text-center">
<div class="m-auto col-2">{$supply['catn']}</div>
<small class="m-auto col-3">$status</small>
<div class="m-auto col-3">$delivery_auto <small class="mr-1 fas fa-truck"></small></div>
<div class="m-auto col-2">{$amount['auto']}</div>
<div class="m-auto col-2">$price_auto</div>
</div>
HTML;
} }
if ($amount['avia'] > 0) {
// Найдены поставки с доставкой по воздуху
// Инициализация цены
$price_avia = $price_raw['avia'] . ' ' . $currency;
// Инициализация доставки
if (!isset($delivery) || (isset($delivery['avia'], $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;
}
// Инициализация статуса связи поставки
$status = OrderEdgeSupply::convertStatusToRussian($part['stts'] ?? '');
// Инициализация класса для поставки (если необходимо)
$css = match ($part['stts'] ?? '') {
'accepted' => ' supply_accepted',
default => ''
};
// Реинициализация максимальной даты доставки
if ($delivery_max !== '?' && $delivery_max < $delivery_avia) $delivery_max = $delivery_avia;
else if ($delivery_avia === '?') $delivery_max = '?';
// Генерация HTML
// Пробела между supply и $css не должно быть
$supplies_html .= <<<HTML
<div class="row py-2 supply$css text-center">
<div class="m-auto col-2">{$supply['catn']}</div>
<small class="m-auto col-3">$status</small>
<div class="m-auto col-3">$delivery_avia <small class="mr-1 fas fa-plane"></small></div>
<div class="m-auto col-2">{$amount['avia']}</div>
<div class="m-auto col-2">$price_avia</div>
</div>
HTML;
}
// Инициализация общей цены
$sum = $price_raw['avia'] * $amount['avia'] + $price_raw['auto'] * $amount['auto'];
} }
} }
// Инициализация статуса заказа // Инициализация статуса заказа
$status = AccountEdgeOrder::convertStatusToRussian($account_edge_order[0]['type']); $status = AccountEdgeOrder::statusToRussian(AccountEdgeOrder::searchByOrder($moderator_data['order']->readId())['stts'] ?? '');
// Инициализация счета для скачивания // Инициализация счета для скачивания
$invoice = <<<HTML $invoice = <<<HTML
<a class="text-dark" href="/invoices/{$order['_key']}/download"><i class="fas fa-file-invoice-dollar"></i></a> <a class="text-dark" href="/invoices/{$data['order']->_key}/download"><i class="fas fa-file-invoice-dollar"></i></a>
HTML; HTML;
echo <<<HTML echo <<<HTML

View File

@ -40,7 +40,7 @@ use app\models\SupplyEdgeProduct;
} else { } else {
echo <<<HTML echo <<<HTML
<dt> <dt>
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Панель управления сайтом" href="$targetUrl" role="button" onclick="return page_profile_panel_settings();"><i class="fas fa-user-shield my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Панель управления</span></a> <a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Панель управления сайтом" href="/profile/panel" role="button" onclick="return page_profile_panel_settings();"><i class="fas fa-user-shield my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Панель управления</span></a>
</dt> </dt>
HTML; HTML;
} }
@ -60,7 +60,7 @@ use app\models\SupplyEdgeProduct;
HTML; HTML;
} else { } else {
echo <<<HTML echo <<<HTML
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Управление поставками" href="$targetUrl" role="button" onclick="return page_profile_supplies();"><i class="fas fa-truck my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Поставки</span></a> <a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Управление поставками" href="/profile/supplies" role="button" onclick="return page_profile_supplies();"><i class="fas fa-truck my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Поставки</span></a>
HTML; HTML;
} }
} }
@ -75,12 +75,12 @@ use app\models\SupplyEdgeProduct;
// Запрошена та же страница от которой послан запрос (текущая) // Запрошена та же страница от которой послан запрос (текущая)
echo <<<HTML echo <<<HTML
<a class="row text-dark button_white hover px-3 py-3 py-lg-2 font-weight-normal" title="Мониторинг и журналирование" href="$targetUrl" role="button" onclick="return false;"><i class="fas fa-eye my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Мониторинг</span></a> <a class="row text-dark button_white hover px-3 py-3 py-lg-2 font-weight-normal" title="Мониторинг и журналирование" href="$targetUrl" role="button" onclick="return false;"><i class="fas fa-eye my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Мониторинг</span></a>
HTML; HTML;
} else { } else {
echo <<<HTML echo <<<HTML
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Мониторинг и журналирование" href="$targetUrl" role="button" onclick="return page_profile_monitoring();"><i class="fas fa-eye my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Мониторинг</span></a> <a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Мониторинг и журналирование" href="/profile/monitoring" role="button" onclick="return page_profile_monitoring();"><i class="fas fa-eye my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Мониторинг</span></a>
HTML; HTML;
} }
?> ?>
</dt> </dt>
@ -93,12 +93,12 @@ use app\models\SupplyEdgeProduct;
// Запрошена та же страница от которой послан запрос (текущая) // Запрошена та же страница от которой послан запрос (текущая)
echo <<<HTML echo <<<HTML
<a class="row text-dark button_white hover px-3 py-3 py-lg-2 font-weight-normal" title="Настройки аккаунта" href="$targetUrl" role="button" onclick="return false;"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Настройки</span></a> <a class="row text-dark button_white hover px-3 py-3 py-lg-2 font-weight-normal" title="Настройки аккаунта" href="$targetUrl" role="button" onclick="return false;"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Настройки</span></a>
HTML; HTML;
} else { } else {
echo <<<HTML echo <<<HTML
<a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Настройки аккаунта" href="$targetUrl" role="button" onclick="return page_profile_settings();"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Настройки</span></a> <a class="row text-dark button_white px-3 py-3 py-lg-2 font-weight-normal" title="Настройки аккаунта" href="/profile" role="button" onclick="return page_profile_settings();"><i class="fas fa-sliders-h my-auto mx-auto mx-lg-0 col-1 pl-0 mr-lg-2"></i><span>Настройки</span></a>
HTML; HTML;
} }
?> ?>
</dt> </dt>

View File

@ -3,6 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
use app\models\Product; use app\models\Product;
use app\models\ProductGroup;
use app\models\Search; use app\models\Search;
?> ?>
@ -29,7 +30,7 @@ use app\models\Search;
<?php if (isset($response) && is_array($response) && $response) : ?> <?php if (isset($response) && is_array($response) && $response) : ?>
<?php <?php
// Инициализация реестра пустышек (товаров без поставок или с ошибками) // Инициализация реестра пустышек (товаров без поставок или с ошибками)
$empties = []; $list = [];
?> ?>
<section class="mb-4 col-auto"> <section class="mb-4 col-auto">
@ -43,7 +44,7 @@ use app\models\Search;
$catn = $row['catn'] ?? 'Неизвестно'; $catn = $row['catn'] ?? 'Неизвестно';
// Генерация списка товаров // Генерация списка товаров
$supplies_html = Search::generate($row, $covr, $empties); $supplies_html = Search::generate($row, $covr, $list);
?> ?>
<div class="col mb-2"> <div class="col mb-2">
<div class="row p-2 rounded"> <div class="row p-2 rounded">
@ -62,37 +63,42 @@ use app\models\Search;
<?php endforeach ?> <?php endforeach ?>
</section> </section>
<?php if (!empty($empties)) : ?> <section class="col">
<section class="col"> <?php
<?php // Инициализация буфера аналогов
// Инициализация буфера аналогов $analogs = [];
$analogs = [];
// Инициализация буфера записанных аналогов // Инициализация буфера записанных аналогов
$writed = []; $writed = [];
foreach ($empties as $prod => $products) {
// Перебор производителей не найденных товаров
foreach ($products as $catn) { foreach ($list as $prod => $products) {
// Перебор не найденных товаров товаров производителя // Перебор производителей найденных товаров
// Чтение и запись аналогов // Инициализация производителя
$analogs[$catn] = Search::content(products: Product::searchAnalogs($prod, $catn)); $analogs[$prod] = $analogs[$prod] ?? [];
// Исключение из вывода в списке аналогов (проверка на дубликат) foreach ($products as $catn) {
$writed[$catn] = true; // Перебор найденных товаров товаров производителя
}
// Чтение и запись аналогов
$analogs[$prod][$catn] = Search::content(products: ProductGroup::searchByProduct(Product::searchByCatnAndProd($catn, $prod))->searchProducts());
// Исключение из вывода в списке аналогов (проверка на дубликат)
$writed[$prod][$catn] = true;
} }
}
// Инициализация блокировщика отрисовки заголовка // Инициализация блокировщика отрисовки заголовка
$block_title = false; $block_title = false;
?> ?>
<?php foreach ($analogs as $products) : ?>
<?php foreach ($analogs as $prod => $catns) : ?>
<?php foreach ($catns as $catn => $products) : ?>
<?php foreach ($products as $product) : ?> <?php foreach ($products as $product) : ?>
<?php <?php
// Проверка на дубликат // Проверка на дубликат
if (array_key_exists($product['catn'], $writed)) continue; if (array_key_exists($product['catn'], $writed[$prod])) continue;
// Инициализация данных товара // Инициализация данных товара
$covr = null; $covr = null;
@ -131,8 +137,8 @@ use app\models\Search;
?> ?>
<?php endforeach ?> <?php endforeach ?>
<?php endforeach ?> <?php endforeach ?>
</section> <?php endforeach ?>
<?php endif ?> </section>
<?php else : ?> <?php else : ?>
<?php if ($advanced ?? false) : ?> <?php if ($advanced ?? false) : ?>

View File

@ -526,7 +526,7 @@ function cart_registration_entity_generate(account) {
// Инициализация ярлыка "COMP" // Инициализация ярлыка "COMP"
let label_comp = document.createElement('label'); let label_comp = document.createElement('label');
label_comp.setAttribute('class', 'control-label'); label_comp.setAttribute('class', 'control-label');
label_comp.innerText = 'Организация'; label_comp.innerText = 'Компания';
// Инициализация поля "COMP" // Инициализация поля "COMP"
let input_comp = document.createElement('input'); let input_comp = document.createElement('input');