From 2841e7ed53f0ae506bdb63f6b4cf4689af33ad32 Mon Sep 17 00:00:00 2001 From: marina Date: Mon, 9 Jun 2025 22:33:56 +0300 Subject: [PATCH] =?utf8?q?ERP-360=20=D0=A1=D0=B1=D0=BE=D1=80=D0=BA=D0=B0?= =?utf8?q?=20=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B8=D1=86=D1=8B=20=D0=B0?= =?utf8?q?=D0=B2=D1=82=D0=BE=D0=BF=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/commands/CronController.php | 116 ++++++ .../AutoPlannogrammaController.php | 116 +++++- ...5_201027_create_autoplannagramma_table.php | 4 +- erp24/records/Autoplannogramma.php | 24 +- erp24/views/auto-plannogramma/index.php | 84 ++-- .../js/autoplannogramma/autoplannogramma.js | 383 +++++++++--------- 6 files changed, 478 insertions(+), 249 deletions(-) diff --git a/erp24/commands/CronController.php b/erp24/commands/CronController.php index 10f7199c..510c6ea4 100644 --- a/erp24/commands/CronController.php +++ b/erp24/commands/CronController.php @@ -1556,5 +1556,121 @@ class CronController extends Controller return ExitCode::OK; } +/** + public function actionMarinaAutoplannogrammaTest() + { + $month = date('m'); + $year = date('Y'); + + $plan_date = $year . '-' . str_pad($month, 2, '0', STR_PAD_LEFT) . '-01'; + $service = new AutoPlannogrammaService(); + $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($plan_date); + $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $plan_date); + $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($plan_date); + $monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal); + $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($plan_date); + $goals = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal); + + $result = StorePlanService::calculateHistoricalShare( + $filters['store_id'], + $filters['month'], + $filters['year'], + $filters['category'], + $filters['subcategory'], + $filters['species'] + ); + + $noHistoryProductData = $service->calculateSpeciesForecastForProductsWithoutHistory($plan_date); + + $productSalesShare = StorePlanService::calculateProductSalesShareProductsWithHistory( + $filters['store_id'], + $filters['month'], + $result['with_history'] + ); + + $productSalesForecast = $service->calculateProductForecastInPiecesProductsWithHistory( + $filters['store_id'], + $filters['month'], + $productSalesShare, + $goals, + $filters['subcategory'], + $filters['category'], + $filters['species'] + ); + + $matrixForecast = MatrixBouquetForecast::find() + ->where(['year' => $year, 'month' => $month]) + ->asArray() + ->all(); + $matrixGroups = array_unique(ArrayHelper::getColumn($matrixForecast, 'group')); + $bouquetForecast = StorePlanService::getBouquetSpiecesMonthGoalFromForecast($filters['month'], $filters['year'], $filters['store_id'], $matrixGroups); + $speciesData = $bouquetForecast['final']; + foreach ($speciesData as $store_id => $categoryData) { + foreach ($categoryData as $category => $subcategoryData) { + foreach ($subcategoryData as $subcategory => $species) { + foreach ($species as $speciesInd => $row) { + $bouquetSpeciesForecast[] = [ + 'category' => $category, + 'subcategory' => $subcategory, + 'store_id' => $store_id, + 'species' => $speciesInd, + 'goal' => $row + ]; + } + } + } + + } + $cleanedSpeciesGoals = $service->subtractSpeciesGoals($goals, $bouquetSpeciesForecast, $noHistoryProductData); + + + $salesProductForecastShare = $service->calculateProductForecastShare($noHistoryProductData, $productSalesForecast); + + $productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals); + + + $weeklySales = $service->getHistoricalSpeciesShareByWeek($plan_date); + + $weeklySalesForecast = $service->calculateWeeklyProductForecastPieces($productForecastSpecies, $weeklySales); + + + $weeklySalesForecastFormated = $service->pivotWeeklyForecast($weeklySalesForecast); + + $flatData = array_filter($weeklySalesForecastFormated, function ($row) use ($filters) { + foreach ($filters as $key => $value) { + if (empty($value)) continue; + if (!isset($row[$key])) continue; + + if (stripos((string)$row[$key], (string)$value) === false) { + return false; + } + } + return true; + }); + + foreach ($flatData as $item) { + foreach ($weeks as $weekKey => $weekNumber) { + $model = new Autoplannogramma(); + $model->week = $weekNumber; + $model->month = $month; + $model->year = $year; + $model->product_id = $item['product_id']; + $model->store_id = $item['store_id']; + $model->quantity = round($item[$weekKey]); + $model->quantity_forecast = round($item[$weekKey]); + $model->is_archive = false; + $model->auto_forecast = true; + + if (!$model->save()) { + Yii::error("Ошибка сохранения: " . json_encode($model->errors), __METHOD__); + } else { + Yii::error("Ошибка сохранения: " . json_encode($model->errors), __METHOD__); + } + } + } + + + } + **/ } diff --git a/erp24/controllers/AutoPlannogrammaController.php b/erp24/controllers/AutoPlannogrammaController.php index b684aafb..51aa1ef6 100644 --- a/erp24/controllers/AutoPlannogrammaController.php +++ b/erp24/controllers/AutoPlannogrammaController.php @@ -77,44 +77,116 @@ class AutoPlannogrammaController extends BaseController ]); } - public function actionGetProducts(string $category, string $subcategory, array $filters): array + public function actionGetProducts(): array { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; - $models = Autoplannogramma::find() - ->joinWith('products p') - ->where(['category' => $category]) - ->andWhere(['subcategory' => $subcategory]) - ->andFilterWhere($filters) - ->select(['p.id', 'p.name', 'store_id', 'quantity']) + $request = Yii::$app->request; + $category = $request->get('category'); + $subcategory = $request->get('subcategory'); + $filters = $request->get(); + + $query = Autoplannogramma::find() + ->alias('a') + ->leftJoin('products_1c_nomenclature p1n', 'p1n.id = a.product_id') + ->leftJoin('city_store_params cp', 'cp.store_id = a.store_id') + ->where(['p1n.category' => $category]) + ->andWhere(['p1n.subcategory' => $subcategory]) + ->andFilterWhere(['=', 'a.year', $filters['year']]) + ->andFilterWhere(['=', 'a.week', $filters['week']]) + ->andFilterWhere(['=', 'cp.address_city', $filters['city']]) + ->andFilterWhere(['=', 'cp.address_region', $filters['region']]) + ->andFilterWhere(['=', 'cp.address_district', $filters['district']]) + ->andFilterWhere(['=', 'cp.store_type', $filters['store_type']]) + ->andFilterWhere(['=', 'a.capacity_type', $filters['capacity_type']]); + + if (!empty($filters['territorial_manager'])) { + $territorialManagerStoreIds = StoreDynamic::find() + ->select('store_id') + ->where(['category' => 3, 'active' => 1, 'value_int' => $filters['territorial_manager']]) + ->column(); + $query->andWhere(['in', 'a.store_id', $territorialManagerStoreIds ?: [-1]]); + } + + if (!empty($filters['bush_chef_florist'])) { + $bushChefFloristStoreIds = StoreDynamic::find() + ->select('store_id') + ->where(['category' => 2, 'active' => 1, 'value_int' => $filters['bush_chef_florist']]) + ->column(); + $query->andWhere(['in', 'a.store_id', $bushChefFloristStoreIds ?: [-1]]); + } + + $models = $query + ->select([ + 'a.id AS plan_id', + 'p1n.id AS product_id', + 'p1n.name AS product_name', + 'a.store_id', + 'a.quantity', + 'a.month' + ]) ->asArray() ->all(); $result = []; foreach ($models as $model) { - $guid = $model['id']; - $name = $model['name']; - $storeId = $model['store_id']; - $quantity = $model['quantity']; - - if (!isset($result[$guid])) { - $result[$guid] = [ - 'name' => $name, - 'guid' => $guid, + $productId = $model['product_id']; + $productName = $model['product_name']; + + if (!isset($result[$productId])) { + $result[$productId] = [ + 'product_id' => $productId, + 'name' => $productName, 'values' => [], ]; } - $result[$guid]['values'][] = [ - 'count' => (int) $quantity, - 'store_id' => (int) $storeId, + $result[$productId]['values'][] = [ + 'id' => $model['plan_id'], + 'quantity' => (int)$model['quantity'], + 'store_id' => (int)$model['store_id'], ]; } return array_values($result); } + public function actionUpdateValues() + { + Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; + + $data = json_decode(Yii::$app->request->getRawBody(), true); + + if (!is_array($data)) { + return ['success' => false, 'message' => 'Неверный формат данных']; + } + + foreach ($data as $item) { + if (isset($item['id'])) { + $model = Autoplannogramma::findOne($item['id']); + if ($model !== null) { + $model->quantity = $item['value']; + $model->auto_forecast = false; + $model->save(false); + } + } else { + $model = new Autoplannogramma(); + $model->product_id = $item['product_id'] ?? null; + $model->store_id = $item['store_id'] ?? null; + $model->year = $item['year'] ?? null; + $model->month = $item['month'] ?? null; + $model->week = $item['week'] ?? null; + $model->quantity = $item['value']; + $model->quantity_forecast = 0; + $model->auto_forecast = false; + $model->save(false); + } + } + + return ['success' => true]; + } + public function actionGetVisibleStores() { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; @@ -148,7 +220,7 @@ class AutoPlannogrammaController extends BaseController } } - return ['store_ids' => $q->column()]; + return array_values($q->column()); } public function action1() @@ -1319,6 +1391,10 @@ class AutoPlannogrammaController extends BaseController return true; }); + + echo '
';
+            var_dump($flatData);
+            echo '
';die(); $dataProvider = new ArrayDataProvider([ 'allModels' => $flatData, 'pagination' => ['pageSize' => 100], diff --git a/erp24/migrations/m250605_201027_create_autoplannagramma_table.php b/erp24/migrations/m250605_201027_create_autoplannagramma_table.php index 64bc54a7..27ec0253 100644 --- a/erp24/migrations/m250605_201027_create_autoplannagramma_table.php +++ b/erp24/migrations/m250605_201027_create_autoplannagramma_table.php @@ -12,7 +12,7 @@ class m250605_201027_create_autoplannagramma_table extends Migration */ public function safeUp() { - $this->createTable('{{%autoplannogramma}}', [ + $this->createTable('{{%erp24.autoplannogramma}}', [ 'id' => $this->primaryKey(), 'week' => $this->integer()->comment('Неделя'), 'month' => $this->integer()->comment('Месяц'), @@ -23,7 +23,7 @@ class m250605_201027_create_autoplannagramma_table extends Migration 'quantity' => $this->integer()->comment('Количество'), 'quantity_forecast' => $this->integer()->comment('Количество рассчитанное'), 'is_archive' => $this->boolean()->comment('Архивная ли запись?'), - 'auto_forecast' => $this->boolean()->comment('Значение спрогнозировано?'), + 'auto_forecast' => $this->boolean()->defaultValue(true)->comment('Значение спрогнозировано?'), 'created_at' => $this->dateTime()->comment('Дата создания'), 'updated_at' => $this->dateTime()->comment('Дата обновления'), 'created_by' => $this->integer()->comment('Автор создания'), diff --git a/erp24/records/Autoplannogramma.php b/erp24/records/Autoplannogramma.php index f6828288..bc399356 100644 --- a/erp24/records/Autoplannogramma.php +++ b/erp24/records/Autoplannogramma.php @@ -4,6 +4,9 @@ namespace yii_app\records; use Product; use Yii; +use yii\behaviors\BlameableBehavior; +use yii\behaviors\TimestampBehavior; +use yii\db\Expression; /** * This is the model class for table "autoplannogramma". @@ -31,7 +34,7 @@ class Autoplannogramma extends \yii\db\ActiveRecord */ public static function tableName() { - return '{{%autoplannogramma}}'; + return 'autoplannogramma'; } /** @@ -47,6 +50,23 @@ class Autoplannogramma extends \yii\db\ActiveRecord ]; } + public function behaviors() + { + return [ + [ + 'class' => TimestampBehavior::class, + 'createdAtAttribute' => 'created_at', + 'updatedAtAttribute' => 'updated_at', + 'value' => new Expression('NOW()') + ], + [ + 'class' => BlameableBehavior::class, + 'createdByAttribute' => 'created_by', + 'updatedByAttribute' => 'updated_by', + ], + ]; + } + /** * {@inheritdoc} */ @@ -72,6 +92,6 @@ class Autoplannogramma extends \yii\db\ActiveRecord } public function getProducts() { - return $this->hasOne(Products1cNomenclature::className(), ['id' => 'product_id']); + return $this->hasMany(Products1cNomenclature::class, ['id' => 'product_id']); } } diff --git a/erp24/views/auto-plannogramma/index.php b/erp24/views/auto-plannogramma/index.php index 00931e17..45c5a3bd 100644 --- a/erp24/views/auto-plannogramma/index.php +++ b/erp24/views/auto-plannogramma/index.php @@ -65,7 +65,7 @@ $this->registerJsFile('/js/autoplannogramma/autoplannogramma.js', ['position' => 'id', 'name', ), - 'options' => ['placeholder' => 'Тер. управляющий', 'id' => 'territorial-manger'], + 'options' => ['placeholder' => 'Тер. управляющий', 'id' => 'territorial-manager'], 'pluginOptions' => ['allowClear' => true], ]) ?> @@ -123,7 +123,7 @@ $this->registerJsFile('/js/autoplannogramma/autoplannogramma.js', ['position' =>
- + 'month-label']) ?>
registerJsFile('/js/autoplannogramma/autoplannogramma.js', ['position' => - $storeName): ?> - @@ -192,7 +191,7 @@ $this->registerJsFile('/js/autoplannogramma/autoplannogramma.js', ['position' => $subcategories): ?> - + - @@ -224,31 +221,10 @@ $this->registerJsFile('/js/autoplannogramma/autoplannogramma.js', ['position' => \ No newline at end of file diff --git a/erp24/web/js/autoplannogramma/autoplannogramma.js b/erp24/web/js/autoplannogramma/autoplannogramma.js index 79bfe1dc..50705171 100644 --- a/erp24/web/js/autoplannogramma/autoplannogramma.js +++ b/erp24/web/js/autoplannogramma/autoplannogramma.js @@ -1,48 +1,42 @@ -document.addEventListener("DOMContentLoaded", function () { - // Изначально скрываем все строки, кроме .category и .head - document.querySelectorAll("tr").forEach(row => { +document.addEventListener("DOMContentLoaded", () => { + const rows = document.querySelectorAll("tr"); + rows.forEach(row => { if (!row.querySelector(".category") && !row.classList.contains("head")) { row.style.display = "none"; } }); - // Обработчик кликов для категорий document.querySelectorAll(".category").forEach(category => { category.addEventListener("click", function () { - let nextRow = this.parentElement.nextElementSibling; - let isVisible = nextRow?.style.display === "table-row"; + let row = this.parentElement.nextElementSibling; + let isOpen = row?.style.display === "table-row"; - // Закрываем все элементы внутри категории - while (nextRow && !nextRow.querySelector(".category")) { - nextRow.style.display = "none"; - nextRow = nextRow.nextElementSibling; + while (row && !row.querySelector(".category")) { + row.style.display = "none"; + row = row.nextElementSibling; } - // Если категория была закрыта — открываем подкатегории - if (!isVisible) { - nextRow = this.parentElement.nextElementSibling; - while (nextRow && !nextRow.querySelector(".category")) { - if (nextRow.querySelector(".subcategory")) { - nextRow.style.display = "table-row"; + if (!isOpen) { + row = this.parentElement.nextElementSibling; + while (row && !row.querySelector(".category")) { + if (row.querySelector(".subcategory")) { + row.style.display = "table-row"; } - nextRow = nextRow.nextElementSibling; + row = row.nextElementSibling; } } }); }); - // Обработчик кликов для подкатегорий - document.querySelectorAll(".subcategory").forEach(subcategory => { - subcategory.addEventListener("click", function (event) { - event.stopPropagation(); // Чтобы клик по подкатегории не закрывал категорию + document.querySelectorAll(".subcategory").forEach(sub => { + sub.addEventListener("click", function (e) { + e.stopPropagation(); + let row = this.parentElement.nextElementSibling; + let isOpen = row?.style.display === "table-row"; - let nextRow = this.parentElement.nextElementSibling; - let isVisible = nextRow?.style.display === "table-row"; - - // Переключаем видимость только айтемов внутри этой подкатегории - while (nextRow && !nextRow.querySelector(".subcategory") && !nextRow.querySelector(".category")) { - nextRow.style.display = isVisible ? "none" : "table-row"; - nextRow = nextRow.nextElementSibling; + while (row && !row.querySelector(".subcategory") && !row.querySelector(".category")) { + row.style.display = isOpen ? "none" : "table-row"; + row = row.nextElementSibling; } }); }); @@ -50,125 +44,97 @@ document.addEventListener("DOMContentLoaded", function () { $('.subcategory-link').on('click', function (e) { e.preventDefault(); + window.getSelection()?.removeAllRanges(); + this.blur(); + + if (!$('#week').val() || !$('#year').val()) { + alert('Необходимо выбрать год и неделю для отображения планограммы'); + return; + } const $link = $(this); const category = $link.data('category'); const subcategory = $link.data('subcategory'); const $row = $link.closest('tr'); - - $.ajax({ - url: '/auto-plannogramma/get-products', - type: 'GET', - data: { - category: category, - subcategory: subcategory - }, - success: function (response) { - // Удаляем предыдущие строки - $row.nextAll('tr.inserted-row').remove(); - - response.forEach(item => { - const tr = $(''); - - // Подкатегория - const subcategoryTd = $(` - - `); - tr.append(subcategoryTd); - - // Карта store_id => count - const valuesMap = {}; - item.values.forEach(val => { - valuesMap[val.store_id] = { - count: val.count, - guid: item.guid - }; - }); - - // Берём колонку из текущей строки, чтобы соблюсти порядок - $row.find('td[data-store-id]').each(function () { - const td = $(this); - const storeId = td.data('store-id'); - const val = valuesMap[storeId] ?? {count: '', guid: ''}; - - const newTd = $(` - '); + + const subcategoryTd = $(` + `); + tr.append(subcategoryTd); - tr.append(newTd); - }); - - $row.after(tr); + const valuesMap = Object.fromEntries(item.values.map(val => [ + val.store_id, + { + quantity: val.quantity, + id: val.id + } + ])); + + $('table thead th').each(function (index) { + const $th = $(this); + const storeId = $th.data('store-id'); + + if (storeId === undefined) return; + + const isVisible = $(`table tbody tr:first td:eq(${index})`).is(':visible'); + if (!isVisible) return; + + const val = valuesMap[storeId] || { quantity: '', id: '' }; + + const td = $(` + + `); + + tr.append(td); }); - }, - error: function (xhr) { - alert('Ошибка: ' + xhr.responseText); - } - }); + + $row.after(tr); + }); + }).fail(xhr => alert('Ошибка: ' + xhr.responseText)); }); $('#autoplannogramma').on('input', '.input', function () { - const $input = $(this); - const newValue = $input.val(); - const $td = $input.closest('td'); - const $svg = $td.find('svg'); - - $svg.find('path').attr('fill', 'red'); + $(this).closest('td').find('path').attr('fill', 'red'); }); $('#autoplannogramma').on('click', '.reject-btn', function () { - const $button = $(this); - const $td = $button.closest('td'); - const $input = $td.find('input.input'); - const $svg = $td.find('svg'); - - const originalValue = $input.data('original-value'); - - if (originalValue !== undefined) { - $input.val(originalValue); - $svg.find('path').attr('fill', 'grey'); - } + const $input = $(this).closest('td').find('input.input'); + $input.val($input.data('original-value')); + $(this).closest('td').find('path').attr('fill', 'grey'); }); -// Получение значений фильтров -function getFilterData() { - return { - year: $('#year').val(), - city: $('#city').val(), - storeType: $('#store-type').val(), - territorialManager: $('#territorial-manger').val(), - polnogrammaType: $('#polnogramma-type').val(), - week: $('#week').val(), - region: $('#region').val(), - bushChefFlorist: $('#bush_chef_florist').val(), - district: $('#district').val(), - }; -} - function getFilterData() { return { year: $('#year').val(), @@ -183,74 +149,125 @@ function getFilterData() { }; } +function getSelectedText(selector) { + const val = $(selector).val(); + return val ? $(selector).find(`option[value="${val}"]`).text() : ''; +} + +function updateFilterLabels(data) { + $('.label-year-week').text(`год: ${data.year || ''} неделя: ${data.week || ''}`); + $('.label-month-range').text(`Месяц: ${data.month || ''}`); + $('.label-capacity-type').text('Тип п-ма: ' + getSelectedText('#polnogramma-type')); + $('.label-city').text('Город: ' + getSelectedText('#city')); + $('.label-region').text('Регион: ' + getSelectedText('#region')); + $('.label-district').text('Район: ' + getSelectedText('#district')); + $('.label-store-type').text('Тип магазина: ' + getSelectedText('#store-type')); + $('.label-tu').text('Тер. Уп.: ' + getSelectedText('#territorial-manager')); + $('.label-kshf').text('КШФ: ' + getSelectedText('#bush_chef_florist')); +} + function applyStoreFilter() { const data = getFilterData(); - $.get('/auto-plannogramma/get-visible-stores', data, function (response) { - const allowedStoreIds = (response.store_ids || []).map(String); + $.get('/auto-plannogramma/get-visible-stores', data, response => { + const allowedStoreIds = (response || []).map(String); - $('td[data-store-id], th[data-store-id]').each(function () { - const storeId = String($(this).data('store-id')); - $(this).toggle(allowedStoreIds.includes(storeId)); - }); - - // Обновляем значения рядом с лейблами, выводим текст, а не id - $('.label-year-week').text(`год: ${data.year || '—'} неделя: ${data.week || '—'}`); - $('.label-month-range').text(getMonthRangeByWeek(data.week)); + $('[data-store-id]').each(function () { + const el = $(this); + const storeId = el.data('store-id').toString(); - $('.label-capacity-type').text('Тип п-ма: ' + getSelectedText('#polnogramma-type')); - $('.label-city').text('Город: ' + getSelectedText('#city')); - $('.label-region').text('Регион: ' + getSelectedText('#region')); - $('.label-district').text('Район: ' + getSelectedText('#district')); - $('.label-store-type').text('Тип магазина: ' + getSelectedText('#store-type')); - $('.label-tu').text('Тер. Уп.: ' + getSelectedText('#territorial-manager')); - $('.label-kshf').text('КШФ: ' + getSelectedText('#bush_chef_florist')); + const shouldShow = allowedStoreIds.includes(storeId); + el.toggle(shouldShow); + }); - }).fail(function (xhr) { + updateFilterLabels(data); + }).fail(xhr => { console.error('Ошибка при фильтрации магазинов:', xhr.responseText); }); } -function getSelectedText(selector) { - const select = $(selector); - const val = select.val(); - const text = select.find(`option[value="${val}"]`).text(); - return text; -} +$('.btn-save').on('click', function () { + const changedValues = []; + let hasErrors = false; -function resetStoreFilter() { - // Сброс обычных инпутов - $('#year, #week').val(''); + $('input.input:visible').each(function () { + const $input = $(this); + const id = $input.data('id'); + const value = $input.val(); + const $arrow = $input.closest('div').find('svg path'); - // Сброс всех Select2 - $('#city, #store-type, #territorial-manager, #polnogramma-type, #region, #bush_chef_florist, #district') - .val(null) - .trigger('change'); + if ($arrow.attr('fill') !== 'red') { + return; + } - // Показ всех ячеек магазинов - $('td[data-store-id], th[data-store-id]').show(); + if (id) { + changedValues.push({ + id: id, + value: value + }); + } else { + const product_id = $input.data('guid'); + const store_id = $input.data('store_id'); + const year = $('#year').val(); + const month = $('.month-label').val(); + const week = $('#week').val(); + if (!product_id || !store_id || !year || !week) { + alert('Для новых записей обязательны: product_id, store_id, year, week.'); + hasErrors = true; + return false; + } - // Очистка лейблов - $('.label-year-week').text(''); - $('.label-month-range').text(''); - $('.label-city, .label-store-type, .label-tu, .label-capacity-type, .label-region, .label-district, .label-kshf') - .text('—'); -} + changedValues.push({ + product_id: product_id, + store_id: store_id, + year: year, + month: month, + week: week, + value: value + }); + } + }); -// Привязка событий -$(document).on('click', '.btn-apply', applyStoreFilter); -$(document).on('click', '.btn-reset', resetStoreFilter); - -// Дополнительная функция: получить диапазон месяцев по номеру недели (примерная логика) -function getMonthRangeByWeek(week) { - if (!week) return '—'; - const w = parseInt(week, 10); - if (w <= 4) return 'январь'; - if (w <= 8) return 'январь - февраль'; - if (w <= 13) return 'февраль - март'; - // ... можно доработать - return '—'; -} + if (hasErrors) return; + if (changedValues.length === 0) { + alert('Нет изменений для сохранения'); + return; + } + $.ajax({ + url: '/auto-plannogramma/update-values', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(changedValues), + success: function () { + alert('Изменения успешно сохранены'); + $('input.input:visible').each(function () { + const $input = $(this); + const $arrow = $input.closest('div').find('svg path'); + if ($arrow.attr('fill') === 'red') { + $arrow.attr('fill', 'grey'); + } + }); + }, + error: function (xhr) { + alert('Ошибка при сохранении: ' + xhr.responseText); + } + }); +}); + +function resetStoreFilter() { + $('#year, #week').val(''); + $('#city, #store-type, #territorial-manager, #polnogramma-type, #region, #bush_chef_florist, #district') + .val(null).trigger('change'); + + $('[data-store-id]').show(); + updateFilterLabels({}); +} + +$('.btn-apply').on('click', function () { + applyStoreFilter(); + $('tr.inserted-row').remove(); +}); +$('.btn-reset').on('click', resetStoreFilter); -- 2.39.5
- 'label-year-week']) ?>
- 'label-month-range']) ?>
+
+ 'label-year-week']) ?>
+ 'label-month-range']) ?>
'label-capacity-type']) ?>
'label-city']) ?>
'label-region']) ?>
@@ -173,10 +173,9 @@ $this->registerJsFile('/js/autoplannogramma/autoplannogramma.js', ['position' =>
+ 'text-align: center; white-space: nowrap; - font-weight: bold; transform-origin: left bottom; padding-right: 7%;' + 'style' => 'display: block; white-space: nowrap; margin: auto; font-weight: bold; text-align: center; writing-mode: sideways-lr;' ]) ?>
+ ▲ @@ -203,12 +202,10 @@ $this->registerJsFile('/js/autoplannogramma/autoplannogramma.js', ['position' =>
- + + ▶
- - ${item.name} - - -
- - -
+ const filters = getFilterData(); + filters.category = category; + filters.subcategory = subcategory; + + $.get('/auto-plannogramma/get-products', filters, response => { + $row.nextAll('tr.inserted-row').remove(); + + response.forEach(item => { + const tr = $('
+ + ${item.name} + +
+ + +
+