]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Перенос js и удаление
authorVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 10 Sep 2025 11:30:11 +0000 (14:30 +0300)
committerVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 10 Sep 2025 11:30:11 +0000 (14:30 +0300)
erp24/controllers/MatrixBouquetActualityController.php
erp24/views/matrix-bouquet-actuality/index.php
erp24/web/js/matrix-bouquet-actuality/index.js [new file with mode: 0644]

index c4788a466dff1a24c3ad99e26c44b922b1a1e6f5..1e189f9deee9769380a3862f640042a7252d7baa 100644 (file)
@@ -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.
index a177dad46963796d5b9b81a1ae129da0210467cd..9f95d87f2bc90ffb2a2fc63475793477e24235bd 100644 (file)
@@ -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) {
 
 </div>
 
-<?php
-$js = <<<JS
-(function() {
-  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();
-})();
-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 (file)
index 0000000..eb1b3ef
--- /dev/null
@@ -0,0 +1,255 @@
+(function() {
+
+    const monthOptions = Object.entries(window.productActualityConfig.months || {}).map(
+        ([k, v]) => `<option value="${k}">${v}</option>`
+    ).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 => `<option value="${k}">${monthsMap[k]}</option>`).join('');
+    }
+
+    function applyFromMonthLimits(fromSelect, monthsMap) {
+        const minYM = getMinStartMonthByRule();
+
+        const prompt = fromSelect.find('option[value=""]').length ? '<option value="">от</option>' : '';
+        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