From ab9c71e16adb388d5714fa3b3ac4e8030115e022 Mon Sep 17 00:00:00 2001 From: Vladimir Fomichev Date: Wed, 10 Sep 2025 14:30:11 +0300 Subject: [PATCH] =?utf8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20js?= =?utf8?q?=20=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../MatrixBouquetActualityController.php | 100 ++++--- .../views/matrix-bouquet-actuality/index.php | 56 +--- .../web/js/matrix-bouquet-actuality/index.js | 255 ++++++++++++++++++ 3 files changed, 322 insertions(+), 89 deletions(-) create mode 100644 erp24/web/js/matrix-bouquet-actuality/index.js diff --git a/erp24/controllers/MatrixBouquetActualityController.php b/erp24/controllers/MatrixBouquetActualityController.php index c4788a46..1e189f9d 100644 --- a/erp24/controllers/MatrixBouquetActualityController.php +++ b/erp24/controllers/MatrixBouquetActualityController.php @@ -5,6 +5,8 @@ namespace app\controllers; use Yii; use yii\data\ActiveDataProvider; use yii\helpers\ArrayHelper; +use yii\web\BadRequestHttpException; +use yii\web\Response; use yii_app\records\BouquetComposition; use yii_app\records\BouquetCompositionMatrixTypeHistory; use yii_app\records\MatrixBouquetActuality; @@ -62,12 +64,6 @@ class MatrixBouquetActualityController extends Controller } - $dataProvider = new ActiveDataProvider([ - 'query' => BouquetComposition::find()->where('0=1'), - 'pagination' => ['pageSize' => 50], - 'sort' => ['defaultOrder' => ['name' => SORT_ASC]], - ]); - $filtersUsed = array_filter([ $filter->group_id, $filter->subgroup_id, @@ -195,44 +191,52 @@ class MatrixBouquetActualityController extends Controller }]); } - $bouquets = $query->orderBy(['bc.name' => SORT_ASC])->all(); - - $rows = []; - foreach ($bouquets as $bouquet) { - $acts = $bouquet->actualities; - $price = $bouquet->priceRel; - if ($acts) { - foreach ($acts as $act) { - $rows[] = [ - 'product' => $bouquet, - 'actuality' => $act, - 'price' => $price, - ]; - } - } else { + + } else { + $query = BouquetComposition::find()->alias('bc'); + $query->joinWith(['priceRel pr'])->addSelect(['bc.*', 'pr.price AS price']); + $query->with(['actualities' => function ($q) { + $q->orderBy(['date_from' => SORT_ASC]); + }]); + } + + $bouquets = $query->orderBy(['bc.name' => SORT_ASC])->all(); + + $rows = []; + foreach ($bouquets as $bouquet) { + $acts = $bouquet->actualities; + $price = $bouquet->priceRel; + if ($acts) { + foreach ($acts as $act) { $rows[] = [ 'product' => $bouquet, - 'actuality' => null, + 'actuality' => $act, 'price' => $price, ]; } + } else { + $rows[] = [ + 'product' => $bouquet, + 'actuality' => null, + 'price' => $price, + ]; } + } - $dataProvider = new \yii\data\ArrayDataProvider([ - 'allModels' => $rows, - 'pagination' => ['pageSize' => 1000], - 'sort' => [ - 'attributes' => [ - 'product.name', - 'product.price', - 'actuality.date_from', - 'actuality.date_to' - ], - 'defaultOrder' => ['product.name' => SORT_ASC], + $dataProvider = new \yii\data\ArrayDataProvider([ + 'allModels' => $rows, + 'pagination' => ['pageSize' => 1000], + 'sort' => [ + 'attributes' => [ + 'product.name', + 'product.price', + 'actuality.date_from', + 'actuality.date_to' ], - ]); - } + 'defaultOrder' => ['product.name' => SORT_ASC], + ], + ]); $groups = MatrixType::find() ->select(['id','name']) @@ -479,6 +483,32 @@ class MatrixBouquetActualityController extends Controller return $this->redirect(['index']); } + public function actionAjaxDelete() + { + Yii::$app->response->format = Response::FORMAT_JSON; + + $request = Yii::$app->request; + $id = $id ?? $request->post('id') ?? $request->get('id'); + + if (empty($id)) { + throw new BadRequestHttpException('Missing parameter: id'); + } + try { + $model = $this->findModel($id); + $model->delete(); + + return [ + 'success' => true, + 'message' => 'Запись успешно удалена', + ]; + } catch (\Throwable $e) { + return [ + 'success' => false, + 'message' => $e->getMessage(), + ]; + } + } + /** * Finds the MatrixBouquetActuality model based on its primary key value. * If the model is not found, a 404 HTTP exception will be thrown. diff --git a/erp24/views/matrix-bouquet-actuality/index.php b/erp24/views/matrix-bouquet-actuality/index.php index a177dad4..9f95d87f 100644 --- a/erp24/views/matrix-bouquet-actuality/index.php +++ b/erp24/views/matrix-bouquet-actuality/index.php @@ -16,7 +16,7 @@ use yii_app\records\Products1cNomenclatureActuality; $this->title = 'Каталог букетов'; $this->params['breadcrumbs'][] = $this->title; -$this->registerJsFile('/js/products1cNomenclatureActuality/index.js', ['position' => View::POS_END]); +$this->registerJsFile('/js/matrix-bouquet-actuality/index.js', ['position' => View::POS_END]); // Список месяцев-годов для выпадающих списков function monthList() { @@ -219,6 +219,7 @@ foreach ($subgroups as $sg) { if ($row['price']) { return $row['price']->price; } + return 0; }, 'format' => ['decimal', 2], ], @@ -297,58 +298,5 @@ foreach ($subgroups as $sg) { - { - btn.addEventListener('click', () => { - const targetId = btn.getAttribute('data-target'); - const el = document.getElementById(targetId); - if (!el) return; - el.value = ''; - el.dispatchEvent(new Event('change')); - }); - }); - const onlyActive = document.getElementById('onlyActiveCheckbox'); - const onlyInactive = document.getElementById('onlyInactiveCheckbox'); - if (onlyActive && onlyInactive) { - onlyActive.addEventListener('change', () => { if (onlyActive.checked) { onlyInactive.checked = false; } }); - onlyInactive.addEventListener('change', () => { if (onlyInactive.checked) { onlyActive.checked = false; } }); - } - - group.addEventListener('change', filterSubgroups); - - - filterSubgroups(); -})(); -JS; - -$this->registerJs($js); -?> - diff --git a/erp24/web/js/matrix-bouquet-actuality/index.js b/erp24/web/js/matrix-bouquet-actuality/index.js new file mode 100644 index 00000000..eb1b3ef2 --- /dev/null +++ b/erp24/web/js/matrix-bouquet-actuality/index.js @@ -0,0 +1,255 @@ +(function() { + + const monthOptions = Object.entries(window.productActualityConfig.months || {}).map( + ([k, v]) => `` + ).join(''); + + const baseMonths = getMonthsMap(); + const monthsForNewRows = extendMonthsMap({ ...baseMonths }, 0); + const monthsForExisting = baseMonths; + + let actualIdx = $('#actuality-form table tbody tr').length || 0; + $('.from-month').each(function () { + const hasActuality = $(this).data('actuality') == 1; + if (hasActuality) return; + applyFromMonthLimits($(this), monthsForExisting); + }); + $('.to-month').each(function(){ + const hasActuality = $(this).data('actuality') == 1; + if (hasActuality) return; + applyFromMonthLimits($(this), monthsForExisting); + }); + + $(document).on('change', '.from-month', function(){ + syncToWithFrom($(this)); + }); + + $('#filter-date-from').on('change', function(){ + var from = $(this).val(); + var to = $('#filter-date-to'); + + to.find('option').each(function(){ + var val = $(this).val(); + $(this).toggle(val === '' || !from || val >= from); + }); + + var cur = to.val(); + if (from && (!cur || cur < from)) { + if (to.find(`option[value="${from}"]`).length) { + to.val(from).trigger('change'); + } else { + var firstVisible = to.find('option').filter(function(){ return $(this).is(':visible') && $(this).val(); }).first().val(); + if (firstVisible) to.val(firstVisible).trigger('change'); + } + } + }); + var $onlyActiveCheckbox = $('#onlyActiveCheckbox'); + var $onlyInactiveCheckbox = $('#onlyInactiveCheckbox'); + + $onlyActiveCheckbox.change(function() { + if ($(this).is(':checked')) { + $onlyInactiveCheckbox.prop('checked', false); + $onlyInactiveCheckbox.prop('disabled', true); + } else { + $onlyInactiveCheckbox.prop('disabled', false); + } + }); + + $onlyInactiveCheckbox.change(function() { + if ($(this).is(':checked')) { + $onlyActiveCheckbox.prop('checked', false); + $onlyActiveCheckbox.prop('disabled', true); + } else { + $onlyActiveCheckbox.prop('disabled', false); + } + }); + + if ($onlyActiveCheckbox.is(':checked')) { + $onlyInactiveCheckbox.prop('disabled', true); + } else if ($onlyInactiveCheckbox.is(':checked')) { + $onlyActiveCheckbox.prop('disabled', true); + } + + + const group = document.getElementById('filter-category'); + const sub = document.getElementById('filter-subcategory'); + + function filterSubgroups() { + const gid = group.value; + const keepSelected = sub.value; + let hasVisibleSelected = false; + + + for (const opt of sub.options) { + if (!opt.value) { opt.hidden = false; continue; } + const parentId = opt.getAttribute('data-parent'); + const visible = !gid || parentId === gid; + opt.hidden = !visible; + if (visible && opt.value === keepSelected) hasVisibleSelected = true; + } + + + if (!hasVisibleSelected) { + sub.value = ''; + } + } + + + document.querySelectorAll('.clear-btn[data-target]').forEach(btn => { + btn.addEventListener('click', () => { + const targetId = btn.getAttribute('data-target'); + const el = document.getElementById(targetId); + if (!el) return; + el.value = ''; + el.dispatchEvent(new Event('change')); + }); + }); + + + const onlyActive = document.getElementById('onlyActiveCheckbox'); + const onlyInactive = document.getElementById('onlyInactiveCheckbox'); + if (onlyActive && onlyInactive) { + onlyActive.addEventListener('change', () => { if (onlyActive.checked) { onlyInactive.checked = false; } }); + onlyInactive.addEventListener('change', () => { if (onlyInactive.checked) { onlyActive.checked = false; } }); + } + + group.addEventListener('change', filterSubgroups); + + + filterSubgroups(); + + $(document).on('change', '.from-month, .to-month', function(){ + let $row = $(this).closest('tr'); + let guid = $row.find('input[type=hidden][name*="[guid]"]').val(); + checkIntervalsForGuid(guid); + }); + + function ymParse(ym) { + const [y, m] = ym.split('-').map(Number); + return { y, m }; + } + function ymFormat(y, m) { + return `${y}-${String(m).padStart(2, '0')}`; + } + function ymAdd(ym, deltaMonths) { + const { y, m } = ymParse(ym); + const d = new Date(y, m - 1 + deltaMonths, 1); + return ymFormat(d.getFullYear(), d.getMonth() + 1); + } + function getMinStartMonthByRule(today = new Date()) { + const y = today.getFullYear(); + const m = today.getMonth(); // 0..11 + const d = today.getDate(); + const shift = (d <= 10) ? 1 : 2; // правило 10/11 + const min = new Date(y, m + shift, 1); + return ymFormat(min.getFullYear(), min.getMonth() + 1); + } + + + function getMonthsMap() { + return { ...(window.productActualityConfig?.months || {}) }; + } + + + function extendMonthsMap(monthsMap, nextYears = 1) { + const keys = Object.keys(monthsMap).sort(); + if (keys.length === 0) return monthsMap; + + const last = keys[keys.length - 1]; + const totalAppend = nextYears * 12; + + let cur = last; + for (let i = 0; i < totalAppend; i++) { + cur = ymAdd(cur, 1); + if (!monthsMap[cur]) { + monthsMap[cur] = cur.replace('-', '-'); + } + } + return monthsMap; + } + + function buildMonthOptions(monthsMap, minYM) { + const keys = Object.keys(monthsMap).sort().filter(k => !minYM || k >= minYM); + return keys.map(k => ``).join(''); + } + + function applyFromMonthLimits(fromSelect, monthsMap) { + const minYM = getMinStartMonthByRule(); + + const prompt = fromSelect.find('option[value=""]').length ? '' : ''; + const options = prompt + buildMonthOptions(monthsMap, minYM); + const current = fromSelect.val(); + + fromSelect.html(options); + + if (current && current >= minYM) { + fromSelect.val(current); + } else { + fromSelect.val(''); + } + + fromSelect.trigger('change'); + } + + function syncToWithFrom(fromSelect) { + const from = fromSelect.val(); + const $to = fromSelect.closest('td').find('.to-month'); + if (!$to.length) return; + + $to.find('option').each(function(){ + const val = $(this).val(); + $(this).toggle(!val || !from || val >= from); + }); + + const curTo = $to.val(); + if (from && (!curTo || curTo < from)) { + if ($to.find(`option[value="${from}"]`).length) { + $to.val(from).trigger('change'); + } else { + const firstVisible = $to.find('option').filter(function(){ return $(this).is(':visible') && $(this).val(); }).first().val(); + if (firstVisible) $to.val(firstVisible).trigger('change'); + } + } + } + + $(document).on('click', '.clear-interval-btn', function () { + let btn = $(this); + let id = btn.data('id'); + + if (!confirm('Очистить запись интервала?')) { + return; + } + + $.ajax({ + url: '/matrix-bouquet-actuality/ajax-delete', + type: 'POST', + data: {id: id, _csrf: yii.getCsrfToken()}, + success: function (response) { + if (response.success) { + btn.closest('tr, .table-success').removeClass('table-success'); + const scope = btn.closest('td'); + const selects = scope.find('select.from-month, select.to-month'); + selects.each(function () { + const s = $(this); + + s.prop('selectedIndex', 0).val(''); + + if (s.hasClass('select2-hidden-accessible')) { + s.val(null).trigger('change.select2'); + } else { + s.trigger('change'); + } + }); + btn.remove(); + alert(response.message); + } else { + alert('Ошибка: ' + response.message); + } + }, + error: function () { + alert('Произошла ошибка при удалении'); + } + }); + }); + +})(); \ No newline at end of file -- 2.39.5