From 8c8ca5f80fca4b10257d8cdf13358c1dc5efca24 Mon Sep 17 00:00:00 2001 From: fomichev Date: Wed, 22 Apr 2026 16:07:20 +0300 Subject: [PATCH] =?utf8?q?=D0=9A=D0=B0=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=20?= =?utf8?q?=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D0=B9=D0=BD?= =?utf8?q?=D0=BE=D0=B3=D0=BE=20=D0=BC=D0=B5=D0=BD=D0=B5=D0=B4=D0=B6=D0=B5?= =?utf8?q?=D1=80=D0=B0=20-=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5?= =?utf8?q?=D0=B9=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- ...ducts1cNomenclatureActualityController.php | 32 +- erp24/views/assortment-label/index.php | 82 +- .../index.php | 851 +++++++++--------- .../products1c-nomenclature-markup/index.php | 66 +- .../products1cNomenclatureActuality/index.js | 154 +++- 5 files changed, 684 insertions(+), 501 deletions(-) diff --git a/erp24/controllers/Products1cNomenclatureActualityController.php b/erp24/controllers/Products1cNomenclatureActualityController.php index 2fd7bd61..a1517ad3 100644 --- a/erp24/controllers/Products1cNomenclatureActualityController.php +++ b/erp24/controllers/Products1cNomenclatureActualityController.php @@ -329,19 +329,27 @@ class Products1cNomenclatureActualityController extends Controller ->column(); } + $pendingMarkupCount = null; + if (Yii::$app->db->getTableSchema('products_1c_nomenclature') !== null) { + $pendingMarkupCount = Products1cNomenclature::find() + ->where(['classification_status' => 'pending']) + ->count(); + } + return $this->render('index', [ - 'filter' => $filter, - 'dataProvider' => $dataProvider, - 'categories' => array_combine($categories, $categories), - 'subcategories' => array_combine($subcategories, $subcategories), - 'species' => array_combine($species, $species), - 'types' => array_combine($lists['type'], $lists['type']), - 'colors' => array_combine($lists['color'], $lists['color']), - 'sorts' => array_combine($lists['sort'], $lists['sort']), - 'sizes' => array_combine($lists['size'], $lists['size']), - 'counters' => $counters, - 'pageSize' => $pageSize, - 'sortBy' => $sortBy, + 'filter' => $filter, + 'dataProvider' => $dataProvider, + 'categories' => array_combine($categories, $categories), + 'subcategories' => array_combine($subcategories, $subcategories), + 'species' => array_combine($species, $species), + 'types' => array_combine($lists['type'], $lists['type']), + 'colors' => array_combine($lists['color'], $lists['color']), + 'sorts' => array_combine($lists['sort'], $lists['sort']), + 'sizes' => array_combine($lists['size'], $lists['size']), + 'counters' => $counters, + 'pageSize' => $pageSize, + 'sortBy' => $sortBy, + 'pendingMarkupCount' => $pendingMarkupCount, ]); } diff --git a/erp24/views/assortment-label/index.php b/erp24/views/assortment-label/index.php index 1fd00206..0a07475e 100644 --- a/erp24/views/assortment-label/index.php +++ b/erp24/views/assortment-label/index.php @@ -8,39 +8,83 @@ use yii\web\View; /* @var int $withLabel */ /* @var float $coverage */ -$this->title = 'Справочник лейблов ассортиментной матрицы'; +$this->title = 'Ассортиментная матрица'; $this->params['breadcrumbs'][] = $this->title; $this->registerJsFile('/js/assortmentLabel/index.js', ['position' => View::POS_END]); +$this->registerCss(<<<'CSS' +:root { --ao: #1e3a5f; --cat: #6f42c1; } +.cm-tabs { border-bottom: 2px solid #dee2e6; margin-bottom: 20px; } +.cm-tabs .nav-link { font-size: 14px; color: #495057; border: none; padding: 10px 20px; font-weight: 500; + border-bottom: 3px solid transparent; margin-bottom: -2px; background: none; } +.cm-tabs .nav-link.active { color: var(--cat); font-weight: 700; border-bottom-color: var(--cat); } +.cm-tabs .nav-link:hover:not(.active) { color: var(--ao); } +.cm-tabs .tc { font-size: 10px; font-weight: 700; margin-left: 6px; padding: 1px 6px; + border-radius: 10px; background: #e9ecef; color: #495057; display: inline-block; } +.cm-tabs .tc-danger { background: #dc3545 !important; color: #fff !important; } +CSS); ?>
-
-

title) ?>

-
+ +
+ + Лейблы ассортиментной матрицы определяют вхождение товара в ассортимент по каналам. Товар может входить в несколько лейблов. +
+ -
-
-
-
%
-
покрытие матрицей
+
+ Покрытие (всего товаров: ) +
+
+
+
+ С лейблом матрицы +
-
-
-
-
-
товаров с лейблом
+
+
+
%
-
-
-
-
всего товаров
+
+
+ Без матрицы + +
+
+
+
%
diff --git a/erp24/views/products1c-nomenclature-actuality/index.php b/erp24/views/products1c-nomenclature-actuality/index.php index 8b1c9c19..c0c49297 100644 --- a/erp24/views/products1c-nomenclature-actuality/index.php +++ b/erp24/views/products1c-nomenclature-actuality/index.php @@ -1,10 +1,10 @@ title = 'Актуализация номенклатуры'; +$this->title = 'Актуальность ассортимента'; $this->params['breadcrumbs'][] = $this->title; $this->registerJsFile('/js/products1cNomenclatureActuality/index.js', ['position' => View::POS_END]); -$this->registerCss('.concept-chip{display:inline-block;font-size:10px;padding:2px 7px;border-radius:4px;font-weight:500;background:#f3e8ff;color:#7c3aed;border:1px dashed #d8b4fe;}'); +$this->registerCss(<<<'CSS' +:root { --ao: #1e3a5f; --cat: #6f42c1; } + +/* ── Tab nav ── */ +.cm-tabs { border-bottom: 2px solid #dee2e6; margin-bottom: 20px; } +.cm-tabs .nav-link { font-size: 14px; color: #495057; border: none; padding: 10px 20px; font-weight: 500; + border-bottom: 3px solid transparent; margin-bottom: -2px; background: none; } +.cm-tabs .nav-link.active { color: var(--cat); font-weight: 700; border-bottom-color: var(--cat); } +.cm-tabs .nav-link:hover:not(.active) { color: var(--ao); } +.cm-tabs .tc { font-size: 10px; font-weight: 700; margin-left: 6px; padding: 1px 6px; border-radius: 10px; + background: #e9ecef; color: #495057; display: inline-block; } +.cm-tabs .nav-link.active .tc { background: var(--cat); color: #fff; } +.cm-tabs .tc-danger { background: #dc3545 !important; color: #fff !important; } + +/* ── Stat cards ── */ +.sc { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; } +.sc-card { background: #fff; border-radius: 8px; padding: 14px 18px; flex: 1; min-width: 130px; + border: 1px solid #dee2e6; } +.sc-card .lb { font-size: 11px; color: #6c757d; text-transform: uppercase; letter-spacing: .5px; } +.sc-card .vl { font-size: 24px; font-weight: 700; } +.sc-card .su { font-size: 11px; color: #6c757d; } + +/* ── Filter bar ── */ +.fb { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; + padding: 12px 16px; margin-bottom: 12px; display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; } +.fb .fg { display: flex; flex-direction: column; gap: 2px; } +.fb .fg > label { font-size: 11px; color: #6c757d; font-weight: 500; margin: 0; } +.fb .form-select, .fb .form-control { font-size: 12px; padding: 4px 8px; height: 30px; } +.fb-sep { width: 1px; height: 30px; background: #dee2e6; align-self: flex-end; flex-shrink: 0; } + +/* ── Bulk bar ── */ +.bulk-bar { background: var(--ao); color: #fff; border-radius: 8px; padding: 10px 16px; + margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; font-size: 13px; } +.bulk-bar .btn { font-size: 11px; } + +/* ── Product card ── */ +.pr { background: #fff; border: 1px solid #dee2e6; border-radius: 8px; margin-bottom: 10px; + padding: 14px 16px; transition: box-shadow .15s; } +.pr:hover { box-shadow: 0 2px 8px rgba(0,0,0,.06); } +.pr-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 10px; } +.pr-left { display: flex; align-items: flex-start; gap: 10px; flex-shrink: 0; max-width: 300px; } +.pr-name { font-size: 15px; font-weight: 600; color: var(--ao); margin: 2px 0; line-height: 1.3; } +.pr-sub { font-size: 11px; color: #6c757d; word-break: break-all; } +.pr-center { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; } +.pr-intervals { display: flex; gap: 8px; flex-wrap: wrap; } +.pr-int { font-size: 11px; padding: 3px 10px; border-radius: 4px; border: 1px solid #dee2e6; + color: #495057; background: #f8f9fa; white-space: nowrap; } +.pr-int.active { border-color: #198754; color: #198754; background: #e8f5e9; font-weight: 600; } +.pr-int.future { border-color: #0d6efd; color: #0d6efd; background: #e7f1ff; } +.pr-int.past { border-color: #dee2e6; color: #adb5bd; background: #f8f9fa; text-decoration: line-through; } +.pr-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; } +.score-inline { display: flex; align-items: center; gap: 5px; font-size: 12px; white-space: nowrap; } +.sc-stars { display: inline-flex; gap: 1px; font-size: 12px; } +.sc-stars i { color: #dee2e6; } +.sc-stars i.on { color: #ffc107; } + +/* ── Label chips ── */ +.ch { display: inline-block; font-size: 10px; padding: 2px 8px; border-radius: 4px; font-weight: 600; + margin: 1px 2px; border: 1px solid transparent; position: relative; } +.ch-off { background: #e8f5e9; color: #2e7d32; border-color: #c8e6c9; } +.ch-site { background: #e3f2fd; color: #1565c0; border-color: #bbdefb; } +.ch-mp { background: #fce4ec; color: #c62828; border-color: #f8bbd0; } +.ch-1p { background: #fff8e1; color: #f57f17; border-color: #ffecb3; } +.ch.ed { cursor: default; } +.ch-rm { display: none; position: absolute; top: -4px; right: -4px; width: 13px; height: 13px; + border-radius: 50%; background: #dc3545; color: #fff; border: none; font-size: 8px; + line-height: 13px; text-align: center; cursor: pointer; padding: 0; } +.ch.ed:hover .ch-rm { display: block; } + +/* ── Concept chips ── */ +.concept { display: inline-block; font-size: 10px; padding: 2px 8px; border-radius: 4px; font-weight: 500; + margin: 1px 2px; background: #f3e8ff; color: #7c3aed; border: 1px dashed #d8b4fe; } + +/* ── Bottom of card ── */ +.lbl-title { font-size: 10px; color: #6c757d; text-transform: uppercase; letter-spacing: .3px; + display: block; margin-bottom: 3px; } +.btn-pr-edit { font-size: 11px; padding: 4px 14px; color: #6c757d; border: 1px solid #dee2e6; + background: #fff; border-radius: 4px; cursor: pointer; white-space: nowrap; flex-shrink: 0; } +.btn-pr-edit:hover { color: #495057; border-color: #adb5bd; background: #f8f9fa; } + +/* ── Score collapse block ── */ +.scb { margin-top: 8px; border-top: 1px solid #f0f0f0; } +.scb-toggle { font-size: 11px; color: var(--cat); cursor: pointer; padding: 5px 0; + display: flex; align-items: center; gap: 6px; user-select: none; } +.scb-toggle:hover { color: var(--ao); } +.scb-toggle .scb-chevron { font-size: 9px; transition: transform .15s; } +.scb-toggle.open .scb-chevron { transform: rotate(90deg); } +.scb-content { display: none; padding: 4px 0; } +.scb-row { display: flex; justify-content: space-between; align-items: center; padding: 3px 0; + font-size: 11px; border-bottom: 1px solid #f5f5f5; } +.scb-row:last-child { border-bottom: none; } +.scb-name { color: var(--ao); font-weight: 600; font-size: 11px; } +.scb-comment { font-size: 10px; color: #6c757d; font-style: italic; padding: 1px 0 3px; } +CSS); function monthList(): array { - $list = []; - $tz = new DateTimeZone('Europe/Moscow'); - $now = new DateTime('now', $tz); + $list = []; + $tz = new DateTimeZone('Europe/Moscow'); + $now = new DateTime('now', $tz); $start = (clone $now)->modify('first day of january last year'); $end = (clone $now)->modify('last day of december next year'); while ($start <= $end) { @@ -39,93 +133,86 @@ function monthList(): array } return $list; } - $months = monthList(); -function renderActualityChips(array $actualities): string +function actGetStatus(array $actualities): string { - if (empty($actualities)) { - return ''; - } - - $statusClasses = [ - 'active' => 'bg-success', - 'future' => 'bg-primary', - 'past' => 'bg-secondary', - ]; - - $chips = []; + if (empty($actualities)) { return 'none'; } foreach ($actualities as $act) { - $status = $act->getStatus(); - $cls = $statusClasses[$status] ?? 'bg-secondary'; - $style = $status === 'past' ? ' style="text-decoration:line-through"' : ''; - $chips[] = '' . Html::encode($act->getLabel()) . ''; + if ($act->getStatus() === 'active') { return 'active'; } } - - $visible = array_slice($chips, 0, 3); - $hidden = count($chips) - count($visible); - $html = implode('', $visible); - if ($hidden > 0) { - $html .= '+' . $hidden . ' ещё'; + foreach ($actualities as $act) { + if ($act->getStatus() === 'future') { return 'future'; } } - return $html; + return 'inactive'; } -function renderConceptChips(array $conceptNames): string +function actIntervalChips(array $actualities): string { - if (empty($conceptNames)) { - return ''; + if (empty($actualities)) { + return 'Не запланирован ни один интервал актуальности'; } - $chips = ''; - foreach ($conceptNames as $name) { - $chips .= '' - . '' - . Html::encode($name) - . ''; + $out = ''; + foreach ($actualities as $act) { + $s = $act->getStatus(); + $out .= '' . Html::encode($act->getLabel()) . ''; } - return '' . $chips . ''; + return $out; } -function renderLabelChips(array $labels, string $productGuid): string +function actLabelChips(array $labels, string $guid): string { if (empty($labels)) { return '—'; } - - $html = ''; + static $map = [ + 'offline' => ['ch-off', 'fa-store'], + 'online' => ['ch-site', 'fa-globe'], + 'marketplace' => ['ch-mp', 'fa-shopping-bag'], + ]; + $out = ''; foreach ($labels as $label) { - $color = Html::encode($label->color ?? '#6c757d'); - $name = Html::encode($label->name); - $labelId = (int)$label->id; - $html .= '' - . $name - . '' + [$cls, $ico] = $map[$label->channel_type ?? ''] ?? ['ch-1p', 'fa-tag']; + $out .= '' + . '' + . Html::encode($label->name) + . '' . ''; } - return '' . $html . ''; + return $out; +} + +function actConceptChips(array $names): string +{ + $out = ''; + foreach ($names as $name) { + $out .= '' + . Html::encode($name) . ''; + } + return $out; } ?>
-

title) ?>

- - -