From 34f172477b7069963fda3f4ea38828fef2c8e49b Mon Sep 17 00:00:00 2001 From: Vladimir Fomichev Date: Tue, 9 Sep 2025 15:12:23 +0300 Subject: [PATCH] =?utf8?q?=D0=90=D0=BA=D1=82=D1=83=D0=B0=D0=BB=D1=8C=D0=BD?= =?utf8?q?=D0=BE=D1=81=D1=82=D1=8C=20=D0=B1=D1=83=D0=BA=D0=B5=D1=82=D0=BE?= =?utf8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../MatrixBouquetActualityController.php | 480 ++++++++++++++++++ erp24/records/BouquetComposition.php | 17 + erp24/records/MatrixBouquetActuality.php | 68 +++ .../records/MatrixBouquetActualitySearch.php | 77 +++ .../views/matrix-bouquet-actuality/_form.php | 39 ++ .../matrix-bouquet-actuality/_search.php | 45 ++ .../views/matrix-bouquet-actuality/create.php | 20 + .../views/matrix-bouquet-actuality/index.php | 349 +++++++++++++ .../views/matrix-bouquet-actuality/update.php | 21 + erp24/views/matrix-bouquet-actuality/view.php | 45 ++ 10 files changed, 1161 insertions(+) create mode 100644 erp24/controllers/MatrixBouquetActualityController.php create mode 100644 erp24/records/MatrixBouquetActuality.php create mode 100644 erp24/records/MatrixBouquetActualitySearch.php create mode 100644 erp24/views/matrix-bouquet-actuality/_form.php create mode 100644 erp24/views/matrix-bouquet-actuality/_search.php create mode 100644 erp24/views/matrix-bouquet-actuality/create.php create mode 100644 erp24/views/matrix-bouquet-actuality/index.php create mode 100644 erp24/views/matrix-bouquet-actuality/update.php create mode 100644 erp24/views/matrix-bouquet-actuality/view.php diff --git a/erp24/controllers/MatrixBouquetActualityController.php b/erp24/controllers/MatrixBouquetActualityController.php new file mode 100644 index 00000000..4256f036 --- /dev/null +++ b/erp24/controllers/MatrixBouquetActualityController.php @@ -0,0 +1,480 @@ + [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ] + ); + } + + /** + * Lists all MatrixBouquetActuality models. + * + * @return string + */ + public function actionIndex() + { + $filter = new \yii\base\DynamicModel([ + 'group_id', 'subgroup_id', 'is_archive', + 'date_from', 'date_to', + 'onlyActive', 'onlyInactive', + ]); + $filter->addRule(['group_id', 'subgroup_id', 'is_archive', 'date_from', 'date_to'], 'safe'); + $filter->addRule(['onlyActive', 'onlyInactive'], 'boolean'); + $filter->load(Yii::$app->request->get()); + + + if (Yii::$app->request->isPost && $post = Yii::$app->request->post('actuality', [])) { + $this->processBatchActuality($post); + Yii::$app->session->setFlash('success', 'Данные по актуальности успешно сохранены.'); + return $this->refresh(); + } + + + $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, + $filter->is_archive, + $filter->date_from, + $filter->date_to, + $filter->onlyActive, + $filter->onlyInactive + ], static fn($v) => $v !== null && $v !== ''); + + + if ($filtersUsed) { + $query = BouquetComposition::find()->alias('bc'); + $query->joinWith(['priceRel pr'])->addSelect(['bc.*', 'pr.price AS price']); + + if (!empty($filter->group_id) || !empty($filter->subgroup_id)) { + $typeIds = []; + + if (!empty($filter->group_id)) { + $typeIds = $this->getMatrixTypeDescendantsIds((int)$filter->group_id); + } + + if (!empty($filter->subgroup_id)) { + if (!empty($typeIds)) { + $typeIds = array_values(array_intersect($typeIds, [(int)$filter->subgroup_id])); + } else { + $typeIds = [(int)$filter->subgroup_id]; + } + } + + if (empty($typeIds)) { + $query->andWhere('1=0'); + } else { + $bouquetIds = BouquetCompositionMatrixTypeHistory::find() + ->select('bouquet_id') + ->where(['matrix_type_id' => $typeIds, 'is_active' => true]) + ->andWhere(['is not', 'bouquet_id', null]) + ->distinct()->column(); + + if (empty($bouquetIds)) { + $query->andWhere('1=0'); + } else { + $query->andWhere(['bc.id' => $bouquetIds]); + } + } + } + + + if (!empty($filter->onlyActive)) { + $query->andWhere(['exists', + MatrixBouquetActuality::find()->alias('a1') + ->where('a1.bouquet_id = bc.id') + ->select(new \yii\db\Expression('1')) + ]); + } elseif (!empty($filter->onlyInactive)) { + $query->andWhere(['not exists', + MatrixBouquetActuality::find()->alias('a2') + ->where('a2.bouquet_id = bc.id') + ->select(new \yii\db\Expression('1')) + ]); + } + + if ($filter->date_from || $filter->date_to || $filter->is_archive !== null && $filter->is_archive !== '') { + $hasDateFrom = !empty($filter->date_from); + $hasDateTo = !empty($filter->date_to); + + $dateFrom = null; + $dateTo = null; + + if ($hasDateFrom) { + $dateFrom = (new \DateTime("{$filter->date_from}-01")) + ->setTime(0, 0, 0)->format('Y-m-d H:i:s'); + } + if ($hasDateTo) { + $dateTo = (new \DateTime("{$filter->date_to}-01")) + ->modify('last day of this month')->setTime(23, 59, 59) + ->format('Y-m-d H:i:s'); + } + + $dateExists = MatrixBouquetActuality::find() + ->alias('a') + ->where('a.bouquet_id = bc.id'); + if ($filter->is_archive !== null && $filter->is_archive !== '') { + $dateExists->andWhere(['a.is_archive' => (int)$filter->is_archive]); + } + + if ($hasDateFrom && !$hasDateTo) { + $dateExists//->andWhere(['a.date_from' => $dateFrom]) + ->andWhere(['>=', 'a.date_to', $dateFrom]);; + } elseif (!$hasDateFrom && $hasDateTo) { + $dateExists//->andWhere(['a.date_to' => $dateTo]) + ->andWhere(['<=', 'a.date_from', $dateTo]); + } else { + $dateExists + ->andWhere(['>=', 'a.date_to', $dateFrom]) + ->andWhere(['<=', 'a.date_from', $dateTo]); + } + + if (!empty($filter->onlyInactive)) { + $query->andWhere(['not exists', $dateExists->select(new \yii\db\Expression('1'))]); + } else { + $query->andWhere(['exists', $dateExists->select(new \yii\db\Expression('1'))]); + } + + $query->with(['actualities' => function ($subQuery) use ($filter, $hasDateFrom, $hasDateTo, $dateFrom, $dateTo) { + if ($filter->is_archive !== null && $filter->is_archive !== '') { + $subQuery->andWhere(['is_archive' => (int)$filter->is_archive]); + } + + if ($hasDateFrom && !$hasDateTo) { + $subQuery//->andWhere(['date_from' => $dateFrom]) + ->andWhere(['>=', 'date_to', $dateFrom]); + } elseif (!$hasDateFrom && $hasDateTo) { + $subQuery//->andWhere(['date_to' => $dateTo]) + ->andWhere(['<=', 'date_from', $dateTo]); + } else { + $subQuery->andWhere(['>=', 'date_to', $dateFrom]) + ->andWhere(['<=', 'date_from', $dateTo]); + } + $subQuery->orderBy(['date_from' => SORT_ASC]); + }]); + } else { + $query->with(['actualities' => function ($q) { + $q->orderBy(['date_from' => SORT_ASC]); + }]); + } + + + $dataProvider = new ActiveDataProvider([ + 'query' => $query->orderBy(['bc.name' => SORT_ASC]), + 'pagination' => ['pageSize' => 100], + 'sort' => [ + 'attributes' => [ + 'name' => [ + 'asc' => ['bc.name' => SORT_ASC], + 'desc' => ['bc.name' => SORT_DESC], + ], + 'price' => [ + 'asc' => ['price' => SORT_ASC], + 'desc' => ['price' => SORT_DESC], + ], + ], + 'defaultOrder' => ['name' => SORT_ASC], + ], + ]); + } + + $groups = MatrixType::find() + ->select(['id','name']) + ->where(['parent_id' => null]) + ->andWhere(['<>', 'deleted', 1]) + ->orderBy(['name' => SORT_ASC]) + ->asArray()->all(); + $groupsList = ArrayHelper::map($groups, 'id', 'name'); + + $subgroups = MatrixType::find() + ->select(['id','name','parent_id']) + ->where(['not', ['parent_id' => null]]) + ->andWhere(['<>', 'deleted', 1]) + ->orderBy(['name' => SORT_ASC]) + ->asArray()->all(); + var_dump($dataProvider);die(); + return $this->render('index', [ + 'filter' => $filter, + 'dataProvider' => $dataProvider, + 'groups' => $groupsList, + 'subgroups' => $subgroups, + ]); + } + + private function getMatrixTypeDescendantsIds(int $rootId): array + { + $all = MatrixType::find()->select(['id','parent_id']) + ->andWhere(['<>', 'deleted', 1]) + ->asArray()->all(); + + $byParent = []; + foreach ($all as $r) { + $byParent[(int)$r['parent_id']][] = (int)$r['id']; + } + + $out = []; + $stack = [$rootId]; + while ($stack) { + $id = array_pop($stack); + if (in_array($id, $out, true)) continue; + $out[] = $id; + if (!empty($byParent[$id])) { + foreach ($byParent[$id] as $cid) $stack[] = $cid; + } + } + return $out; + } + + + /** + * Обработка массового сохранения диапазонов актуальности. + * Если из/до нет или невалидны — пропускаем. + * Закрываем старую запись и создаем новую при изменении диапазона. + * @param array $post + */ + protected function processBatchActuality(array $post) + { + $userId = Yii::$app->user->id; + $now = date('Y-m-d H:i:s'); + + foreach ($post as $row) { + if (empty($row['from']) || empty($row['to'])) { + continue; + } + + $fromDate = \DateTime::createFromFormat('Y-m', $row['from']); + $toDate = \DateTime::createFromFormat('Y-m', $row['to']); + if (!$fromDate || !$toDate) { + continue; + } + $fromDate->setDate((int)$fromDate->format('Y'), (int)$fromDate->format('m'), 1) + ->setTime(0, 0, 0); + $toDate->modify('last day of this month') + ->setTime(23, 59, 59); + + $from = $fromDate->format('Y-m-d H:i:s'); + $to = $toDate->format('Y-m-d H:i:s'); + + if ($from > $to) { + Yii::warning("GUID {$row['guid']}: пропускаем — from > to"); + continue; + } + + $guid = $row['guid']; + $bouquetId = $row['bouquet_id']; + + + $fromAdj = (clone $fromDate)->modify('-1 second')->format('Y-m-d H:i:s'); + $toAdj = (clone $toDate)->modify('+1 second')->format('Y-m-d H:i:s'); + + /** @var MatrixBouquetActuality[] $hits */ + $hits = MatrixBouquetActuality::find() + ->where(['guid' => $guid]) + ->andWhere('date_to >= :fromAdj', [':fromAdj' => $fromAdj]) + ->andWhere('date_from <= :toAdj', [':toAdj' => $toAdj]) + ->orderBy(['date_from' => SORT_ASC]) + ->all(); + + if (empty($hits)) { + $new = new MatrixBouquetActuality([ + 'guid' => $guid, + 'bouquet_id' => $bouquetId, + 'date_from' => $from, + 'date_to' => $to, + 'is_archive' => 0, + 'created_at' => $now, + 'created_by' => $userId, + ]); + if (!$new->save()) { + Yii::error("Ошибка создания GUID={$guid}: " . json_encode($new->getErrors(), JSON_UNESCAPED_UNICODE)); + } + continue; + } + + $minFrom = $from; + $maxTo = $to; + foreach ($hits as $h) { + if ($h->date_from < $minFrom) { $minFrom = $h->date_from; } + if ($h->date_to > $maxTo) { $maxTo = $h->date_to; } + } + + $master = array_shift($hits); + $master->date_from = $minFrom; + $master->date_to = $maxTo; + $master->updated_at = $now; + $master->updated_by = $userId; + + if (!$master->save()) { + Yii::error("Ошибка обновления GUID={$guid}: " . json_encode($master->getErrors(), JSON_UNESCAPED_UNICODE)); + } + + foreach ($hits as $dup) { + $dup->delete(); + } + + while (true) { + $leftBound = (new \DateTime($master->date_from))->modify('-1 second')->format('Y-m-d H:i:s'); + $rightBound = (new \DateTime($master->date_to))->modify('+1 second')->format('Y-m-d H:i:s'); + + /** @var MatrixBouquetActuality[] $neighbors */ + $neighbors = MatrixBouquetActuality::find() + ->where(['guid' => $guid]) + ->andWhere(['<>', 'id', $master->id]) + ->andWhere('date_to >= :leftBound', [':leftBound' => $leftBound]) + ->andWhere('date_from <= :rightBound', [':rightBound' => $rightBound]) + ->orderBy(['date_from' => SORT_ASC]) + ->all(); + + if (empty($neighbors)) { + break; + } + + foreach ($neighbors as $nei) { + if ($nei->date_from < $master->date_from) + { + $master->date_from = $nei->date_from; + } + if ($nei->date_to > $master->date_to) + { + $master->date_to = $nei->date_to; + } + } + $master->updated_at = $now; + $master->updated_by = $userId; + + if (!$master->save()) { + Yii::error("Ошибка повторного обновления GUID={$guid}: " . json_encode($master->getErrors(), JSON_UNESCAPED_UNICODE)); + break; + } + + foreach ($neighbors as $nei) { + $nei->delete(); + } + } + } + } + /** + * Displays a single MatrixBouquetActuality model. + * @param int $id ID + * @return string + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new MatrixBouquetActuality model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return string|\yii\web\Response + */ + public function actionCreate() + { + $model = new MatrixBouquetActuality(); + + if ($this->request->isPost) { + if ($model->load($this->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } + } else { + $model->loadDefaultValues(); + } + + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing MatrixBouquetActuality model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param int $id ID + * @return string|\yii\web\Response + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($this->request->isPost && $model->load($this->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } + + return $this->render('update', [ + 'model' => $model, + ]); + } + + /** + * Deletes an existing MatrixBouquetActuality model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param int $id ID + * @return \yii\web\Response + * @throws NotFoundHttpException if the model cannot be found + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the MatrixBouquetActuality model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param int $id ID + * @return MatrixBouquetActuality the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = MatrixBouquetActuality::findOne(['id' => $id])) !== null) { + return $model; + } + + throw new NotFoundHttpException('The requested page does not exist.'); + } +} diff --git a/erp24/records/BouquetComposition.php b/erp24/records/BouquetComposition.php index f0e1bc55..05a82dee 100644 --- a/erp24/records/BouquetComposition.php +++ b/erp24/records/BouquetComposition.php @@ -6,6 +6,7 @@ use Exception; use Yii; use yii\behaviors\BlameableBehavior; use yii\behaviors\TimestampBehavior; +use yii\db\ActiveQuery; use yii\db\ActiveRecord; use yii\db\Expression; use yii\helpers\Json; @@ -592,4 +593,20 @@ class BouquetComposition extends ActiveRecord } return (int)date('d') > 10; } + + public function getPriceRel() + { + return $this->hasOne(Prices::class, ['product_id' => 'guid']); + } + + public function getActualities() + { + return $this->hasMany(MatrixBouquetActuality::class, ['bouquet_id' => 'id']) + ->orderBy(['date_from' => SORT_ASC]); + } + + public function hasActuality() : bool + { + return $this->getActualities()->exists(); + } } diff --git a/erp24/records/MatrixBouquetActuality.php b/erp24/records/MatrixBouquetActuality.php new file mode 100644 index 00000000..a9816805 --- /dev/null +++ b/erp24/records/MatrixBouquetActuality.php @@ -0,0 +1,68 @@ + null], + [['is_archive'], 'default', 'value' => 0], + [['guid', 'bouquet_id', 'date_from', 'created_at', 'created_by'], 'required'], + [['bouquet_id', 'is_archive', 'created_by', 'updated_by'], 'default', 'value' => null], + [['bouquet_id', 'is_archive', 'created_by', 'updated_by'], 'integer'], + [['date_from', 'date_to', 'created_at', 'updated_at'], 'safe'], + [['guid'], 'string', 'max' => 255], + ]; + } + + /** + * {@inheritdoc} + */ + public function attributeLabels() + { + return [ + 'id' => 'ID', + 'guid' => 'GUID товара из 1С', + 'bouquet_id' => 'ID букета из bouquet_composition', + 'date_from' => 'Дата и время начала активности', + 'date_to' => 'Дата и время окончания активности', + 'is_archive' => 'Признак архивного товара', + 'created_at' => 'Дата создания', + 'updated_at' => 'Дата обновления', + 'created_by' => 'ИД создателя', + 'updated_by' => 'ИД редактировавшего', + ]; + } + +} diff --git a/erp24/records/MatrixBouquetActualitySearch.php b/erp24/records/MatrixBouquetActualitySearch.php new file mode 100644 index 00000000..48bdcef0 --- /dev/null +++ b/erp24/records/MatrixBouquetActualitySearch.php @@ -0,0 +1,77 @@ +load()` method. + * + * @return ActiveDataProvider + */ + public function search($params, $formName = null) + { + $query = MatrixBouquetActuality::find(); + + // add conditions that should always apply here + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + ]); + + $this->load($params, $formName); + + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + // grid filtering conditions + $query->andFilterWhere([ + 'id' => $this->id, + 'bouquet_id' => $this->bouquet_id, + 'date_from' => $this->date_from, + 'date_to' => $this->date_to, + 'is_archive' => $this->is_archive, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + 'created_by' => $this->created_by, + 'updated_by' => $this->updated_by, + ]); + + $query->andFilterWhere(['ilike', 'guid', $this->guid]); + + return $dataProvider; + } +} diff --git a/erp24/views/matrix-bouquet-actuality/_form.php b/erp24/views/matrix-bouquet-actuality/_form.php new file mode 100644 index 00000000..3f27f0dd --- /dev/null +++ b/erp24/views/matrix-bouquet-actuality/_form.php @@ -0,0 +1,39 @@ + + +
+ + + + field($model, 'guid')->textInput(['maxlength' => true]) ?> + + field($model, 'bouquet_id')->textInput() ?> + + field($model, 'date_from')->textInput() ?> + + field($model, 'date_to')->textInput() ?> + + field($model, 'is_archive')->textInput() ?> + + field($model, 'created_at')->textInput() ?> + + field($model, 'updated_at')->textInput() ?> + + field($model, 'created_by')->textInput() ?> + + field($model, 'updated_by')->textInput() ?> + +
+ 'btn btn-success']) ?> +
+ + + +
diff --git a/erp24/views/matrix-bouquet-actuality/_search.php b/erp24/views/matrix-bouquet-actuality/_search.php new file mode 100644 index 00000000..84aec527 --- /dev/null +++ b/erp24/views/matrix-bouquet-actuality/_search.php @@ -0,0 +1,45 @@ + + + diff --git a/erp24/views/matrix-bouquet-actuality/create.php b/erp24/views/matrix-bouquet-actuality/create.php new file mode 100644 index 00000000..c04854e2 --- /dev/null +++ b/erp24/views/matrix-bouquet-actuality/create.php @@ -0,0 +1,20 @@ +title = 'Create Matrix Bouquet Actuality'; +$this->params['breadcrumbs'][] = ['label' => 'Matrix Bouquet Actualities', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

title) ?>

+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/erp24/views/matrix-bouquet-actuality/index.php b/erp24/views/matrix-bouquet-actuality/index.php new file mode 100644 index 00000000..db2dfacc --- /dev/null +++ b/erp24/views/matrix-bouquet-actuality/index.php @@ -0,0 +1,349 @@ +title = 'Актуализация букетов'; +$this->params['breadcrumbs'][] = $this->title; +$this->registerJsFile('/js/products1cNomenclatureActuality/index.js', ['position' => View::POS_END]); +// Список месяцев-годов для выпадающих списков +function monthList() +{ + $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) { + $key = $start->format('Y-m'); + $list[$key] = $start->format('Y‑m'); + $start->modify('+1 month'); + } + return $list; +} + +$months = monthList(); +$monthOptions = ''; +foreach ($months as $k => $v) { + $monthOptions .= ""; +} +?> + +
+ +

title) ?>

+ + + 'get', + 'action' => ['index'], + 'options' => ['class' => 'mb-4'], + ]); ?> + +
+ + +
+
Номенклатура
+
+
+
+ field($filter, 'category', ['options' => ['class' => 'w-90']])->dropDownList( + $categories, + ['prompt' => 'Категория', 'id' => 'filter-category'] + )->label(false) ?> + +
+ +
+
+
+
+
+ field($filter, 'type', ['options' => ['class' => 'w-90']])->dropDownList( + $types, + ['prompt' => 'Тип', 'id' => 'filter-type'] + )->label(false) ?> + +
+ +
+
+
+
+
+ field($filter, 'color', ['options' => ['class' => 'w-90']])->dropDownList( + $colors, + ['prompt' => 'Цвет', 'id' => 'filter-color'] + )->label(false) ?> + +
+ +
+
+
+
+
+
+
+ field($filter, 'subcategory', ['options' => ['class' => 'w-90']])->dropDownList( + $subcategories, + ['prompt' => 'Подкатегория', 'id' => 'filter-subcategory', 'class' => 'w-100'] + )->label(false) ?> + +
+ +
+
+
+
+
+ field($filter, 'sort', ['options' => ['class' => 'w-90']])->dropDownList( + $sorts, + ['prompt' => 'Сорт', 'id' => 'filter-sort'] + )->label(false) ?> +
+ +
+
+
+
+
+
+
+
+ field($filter, 'species', ['options' => ['class' => 'w-90']])->dropDownList( + $species, + ['prompt' => 'Вид', 'id' => 'filter-species'] + )->label(false) ?> +
+ +
+
+
+
+
+ field($filter, 'size', ['options' => ['class' => 'w-90']])->dropDownList( + $sizes, + ['prompt' => 'Размер', 'id' => 'filter-size'] + )->label(false) ?> +
+ +
+
+
+
+
+
+ + +
+
Актуальность ассортимента
+
+
+ field($filter, 'date_from', ['options' => ['class' => 'w-100']]) + ->dropDownList($months, + [ + 'prompt' => 'Выбрать дату от', + 'id' => 'filter-date-from', + 'class' => '' + ] + ) + ->label(false) ?> +
+ +
+
+
+
+
+ field($filter, 'date_to', ['options' => ['class' => 'w-100']]) + ->dropDownList($months, + [ + 'prompt' => 'Выбрать дату до', + 'id' => 'filter-date-to', + 'class' => '' + ] + ) + ->label(false) ?> +
+ +
+
+
+
+ field($filter, 'onlyActive')->checkbox([ + 'label' => 'Только активные', + 'uncheck' => 0, + 'checked' => (bool)$filter->onlyActive, + 'id' => 'onlyActiveCheckbox' + ])->label(false) ?> + + field($filter, 'onlyInactive')->checkbox([ + 'label' => 'Только неактивные', + 'uncheck' => 0, + 'checked' => (bool)$filter->onlyInactive, + 'id' => 'onlyInactiveCheckbox' + ])->label(false) ?> +
+
+ + +
+
Поставщики
+
+ +
+ 'form-select', 'id' => 'filter-supplier', 'prompt' => 'Поставщик']) ?> +
+ +
+
+
+
+ +
+ 'form-select', 'id' => 'filter-plantation', 'prompt' => 'Плантация']) ?> +
+ +
+
+
+ +
+
+ + 'btn btn-primary w-100']) ?> + +
+
+ + + + + + + + 'actuality-form']); ?> +
+ 'btn btn-success', 'id' => 'saveButton']) ?> +
+ $dataProvider, + 'responsive' => false, + 'hover' => true, + 'floatHeader' => false, + 'tableOptions' => ['class' => 'table table-bordered'], + 'containerOptions' => ['style' => 'overflow:auto; max-height:500px;'], + 'rowOptions' => function($row) { + return $row['actuality'] ? ['class'=>'table-success'] : []; + }, + 'columns' => [ + [ + 'label' => 'Наименование', + 'format' => 'raw', + 'contentOptions' => ['style'=>'min-width:150px;'], + 'value' => function ($row, $key, $index) { + $product = $row['product']; + $name = Html::encode($product->name . ' (' . $product->id . ')'); + $btn = Html::button('+ Добавить интервал', [ + 'class' => 'btn btn-xs btn-outline-primary ms-2 add-actuality-row', + 'type' => 'button', + 'title' => 'Добавить интервал', + 'data-guid' => $product->id, + 'data-name' => $product->name, + ]); + return '
' . $name . $btn . '
'; + } + ], + [ + 'label' => 'Актуальность ассортимента', + 'format' => 'raw', + 'contentOptions' => ['style'=>'white-space:nowrap; min-width:100px;'], + 'value' => function ($row, $k, $i) use ($months) { + $product = $row['product']; + $actuality = $row['actuality']; + $from = $actuality ? substr($actuality->date_from, 0, 7) : null; + $to = $actuality ? substr($actuality->date_to, 0, 7) : null; + $clearBtn = $actuality ? '
+ +
' : ''; + $inputs = '
'; + $inputs .= Html::hiddenInput("actuality[$i][guid]", $product->id); + if ($actuality) { + $inputs .= Html::hiddenInput("actuality[$i][id]", $actuality->id); + } + $inputs .= Html::tag('div', + Html::dropDownList("actuality[$i][from]", $from, $months, [ + 'class'=>'form-select from-month form-select-sm me-1', + 'prompt'=>'от', + 'data-actuality' => $actuality ? 1 : 0, + 'style' => 'width:auto;display:inline-block' + ]) . + Html::dropDownList("actuality[$i][to]", $to, $months, [ + 'class'=>'form-select to-month form-select-sm', + 'prompt'=>'до', + 'data-actuality' => $actuality ? 1 : 0, + 'style' => 'width:auto;display:inline-block' + ]), + ['class'=>'d-flex align-items-center'] + ) . $clearBtn ; + $inputs .= '
'; + return $inputs; + } + ], + [ + 'label' => 'Склад NN', + 'format' => 'raw', + 'contentOptions' => ['style'=>'width:60px; text-align:center;'], + 'value' => function ($m, $k, $i) use ($filter){ + return Html::checkbox("actuality[$i][warehouse_nn]", false, [ + + ]); + } + ], + [ + 'label' => 'Склад MSK', + 'format' => 'raw', + 'contentOptions' => ['style'=>'width:60px; text-align:center;'], + 'value' => function ($m, $k, $i) use ($filter){ + return Html::checkbox("actuality[$i][warehouse_msk]", false, [ + + ]); + } + ], + [ + 'label' => 'Поставщик/Плантация', + 'format' => 'text', + 'contentOptions' => ['style'=>'min-width:150px;'], + 'value' => function ($m) { + return '–'; + } + ], + ], + ]); ?> + + + + + + +
+ diff --git a/erp24/views/matrix-bouquet-actuality/update.php b/erp24/views/matrix-bouquet-actuality/update.php new file mode 100644 index 00000000..f4fb8c3b --- /dev/null +++ b/erp24/views/matrix-bouquet-actuality/update.php @@ -0,0 +1,21 @@ +title = 'Update Matrix Bouquet Actuality: ' . $model->id; +$this->params['breadcrumbs'][] = ['label' => 'Matrix Bouquet Actualities', 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = 'Update'; +?> +
+ +

title) ?>

+ + render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/erp24/views/matrix-bouquet-actuality/view.php b/erp24/views/matrix-bouquet-actuality/view.php new file mode 100644 index 00000000..72107cbf --- /dev/null +++ b/erp24/views/matrix-bouquet-actuality/view.php @@ -0,0 +1,45 @@ +title = $model->id; +$this->params['breadcrumbs'][] = ['label' => 'Matrix Bouquet Actualities', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +\yii\web\YiiAsset::register($this); +?> +
+ +

title) ?>

+ +

+ $model->id], ['class' => 'btn btn-primary']) ?> + $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => 'Are you sure you want to delete this item?', + 'method' => 'post', + ], + ]) ?> +

+ + $model, + 'attributes' => [ + 'id', + 'guid', + 'bouquet_id', + 'date_from', + 'date_to', + 'is_archive', + 'created_at', + 'updated_at', + 'created_by', + 'updated_by', + ], + ]) ?> + +
-- 2.39.5