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;
}
- $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,
}]);
}
- $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'])
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.
$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()
{
if ($row['price']) {
return $row['price']->price;
}
+ return 0;
},
'format' => ['decimal', 2],
],
</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);
-?>
-
--- /dev/null
+(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