--- /dev/null
+<?php
+
+namespace app\controllers;
+
+use Yii;
+use yii_app\records\Products1cAdditionalCharacteristics;
+use yii_app\records\Products1cNomenclature;
+use yii_app\records\Products1cNomenclatureActuality;
+use yii_app\records\Products1cNomenclatureActualitySearch;
+use yii\web\Controller;
+use yii\web\NotFoundHttpException;
+use yii\filters\VerbFilter;
+use yii_app\records\Products1cPropType;
+
+/**
+ * Products1cNomenclatureActualityController implements the CRUD actions for Products1cNomenclatureActuality model.
+ */
+class Products1cNomenclatureActualityController extends Controller
+{
+ /**
+ * @inheritDoc
+ */
+ public function behaviors()
+ {
+ return array_merge(
+ parent::behaviors(),
+ [
+ 'verbs' => [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['POST'],
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Lists all Products1cNomenclatureActuality models.
+ *
+ * @return string
+ */
+ public function actionIndex()
+ {
+ $filter = new \yii\base\DynamicModel([
+ 'category','subcategory','species',
+ 'type','color','sort','size',
+ 'date_from','date_to',
+ ]);
+ $filter->addRule([
+ 'category','subcategory','species',
+ 'type','color','sort','size',
+ 'date_from','date_to',
+ ], 'safe');
+ $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();
+ }
+
+
+ $emptyQuery = Products1cNomenclature::find()->where('0=1');
+ $dataProvider = new \yii\data\ActiveDataProvider([
+ 'query' => $emptyQuery,
+ 'pagination' => ['pageSize' => 50],
+ 'sort' => ['defaultOrder'=>['name'=>SORT_ASC]],
+ ]);
+
+
+ $filtersUsed = array_filter([
+ $filter->category,
+ $filter->subcategory,
+ $filter->species,
+ $filter->type,
+ $filter->color,
+ $filter->sort,
+ $filter->size,
+ $filter->date_from,
+ $filter->date_to,
+ ]);
+ if ($filtersUsed) {
+ $query = Products1cNomenclature::find()->alias('n');
+
+
+ foreach (['category','subcategory','species'] as $attr) {
+ if ($filter->$attr) {
+ $query->andWhere(["n.$attr" => $filter->$attr]);
+ }
+ }
+
+
+ if ($filter->date_from || $filter->date_to) {
+ $query->innerJoin(
+ Products1cNomenclatureActuality::tableName() . ' a',
+ 'a.guid = n.id AND a.active = 1'
+ );
+ if ($filter->date_from) {
+ $df = \DateTime::createFromFormat('Y-m', $filter->date_from)
+ ->format('Y-m-01 00:00:00');
+ $query->andWhere(['>=', 'a.date_from', $df]);
+ }
+ if ($filter->date_to) {
+ $dt = \DateTime::createFromFormat('Y-m', $filter->date_to);
+ $dt->modify('last day of this month')->setTime(23,59,59);
+ $query->andWhere(['<=', 'a.date_end', $dt->format('Y-m-d H:i:s')]);
+ }
+ }
+
+
+ $attrMap = [
+ 'type' => ['type','тип'],
+ 'color' => ['цвет','color'],
+ 'sort' => ['sort','сорт'],
+ 'size' => ['size','размер'],
+ ];
+ foreach (['type','color','sort','size'] as $attr) {
+ if ($filter->$attr) {
+
+ $propIds = Products1cPropType::find()
+ ->select('id')
+ ->andWhere(['name' => $attrMap[$attr]])
+ ->column();
+
+ $productIds = Products1cAdditionalCharacteristics::find()
+ ->select('product_id')
+ ->distinct()
+ ->andWhere(['property_id' => $propIds, 'value' => $filter->$attr])
+ ->column();
+
+
+ if (empty($productIds)) {
+ $query->andWhere('0=1');
+ break;
+ }
+
+ $query->andWhere(['n.id' => $productIds]);
+ }
+ }
+
+ // 4.4) Подставляем в провайдер
+ $dataProvider->query = $query;
+ }
+
+ $categories = Products1cNomenclature::find()->select('category')->distinct()->column();
+ $subcategories = Products1cNomenclature::find()->select('subcategory')->distinct();
+ $subcategories->andWhere(['not', ['subcategory' => null]]);
+ $subcategories->andWhere(['<>', 'subcategory', '']);
+ if ($filter->category) {
+ $subcategories->andWhere(['category' => $filter->category]);
+ }
+ $subcategories = $subcategories->column();
+
+ $species = Products1cNomenclature::find()
+ ->select('species')
+ ->distinct()
+ ->andWhere(['not', ['species' => null]])
+ ->andWhere(['<>', 'species', '']);
+
+ if ($filter->subcategory) {
+ $species->andWhere(['subcategory' => $filter->subcategory]);
+ }
+
+ $species = $species->column();
+
+ $lists = [];
+ $attrMap = [
+ 'type' => ['type','тип'],
+ 'color' => ['цвет','color'],
+ 'sort' => ['sort','сорт'],
+ 'size' => ['size','размер'],
+ ];
+ foreach (['type','color','sort','size'] as $attr) {
+ $propIds = Products1cPropType::find()
+ ->select('id')
+ ->andWhere(['name'=>$attrMap[$attr]])
+ ->column();
+ $lists[$attr] = Products1cAdditionalCharacteristics::find()
+ ->select('value')->distinct()
+ ->where(['property_id'=>$propIds])
+ ->column();
+ }
+
+ return $this->render('index', [
+ 'filter' => $filter,
+ 'dataProvider' => $dataProvider,
+ 'categories' => array_combine($categories,$categories),
+ 'subcategories' => array_combine($subcategories,$subcategories),
+ 'species' => array_combine($species, $species),
+ 'types' => array_combine($lists['type'], $lists['type']),
+ 'colors' => array_combine($lists['color'], $lists['color']),
+ 'sorts' => array_combine($lists['sort'], $lists['sort']),
+ 'sizes' => array_combine($lists['size'], $lists['size']),
+ ]);
+ }
+
+
+
+ /**
+ * Обработка массового сохранения диапазонов актуальности.
+ * Если из/до нет или невалидны — пропускаем.
+ * Закрываем старую запись и создаем новую при изменении диапазона.
+ * @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;
+ }
+
+ $fromDt = \DateTime::createFromFormat('Y-m', $row['from']);
+ if (!$fromDt) {
+ continue;
+ }
+ $newFrom = $fromDt->format('Y-m-01 00:00:00');
+
+ $toDt = \DateTime::createFromFormat('Y-m', $row['to']);
+ if (!$toDt) {
+ continue;
+ }
+ $toDt->modify('last day of this month')->setTime(23, 59, 59);
+ $newTo = $toDt->format('Y-m-d H:i:s');
+
+
+ $warehouseNN = !empty($row['warehouse_nn']);
+ $warehouseMS = !empty($row['warehouse_msk']);
+ $supplier = $row['supplier'] ?? null;
+ $plantation = $row['plantation'] ?? null;
+
+
+ $old = Products1cNomenclatureActuality::find()
+ ->andWhere(['guid' => $row['guid'], 'active' => 1])
+ ->one();
+
+ if ($old) {
+ if ($old->date_from === $newFrom && $old->date_end === $newTo) {
+ continue;
+ }
+
+ $old->active = 0;
+ $old->updated_by = $userId;
+ $old->updated_at = $now;
+ $old->save(false);
+ }
+
+
+ $new = new Products1cNomenclatureActuality([
+ 'guid' => $row['guid'],
+ 'date_from' => $newFrom,
+ 'date_end' => $newTo,
+ 'active' => 1,
+ 'created_at' => $now,
+ 'created_by' => $userId,
+ ]);
+ $new->save(false);
+ }
+ }
+
+ /**
+ * Displays a single Products1cNomenclatureActuality 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 Products1cNomenclatureActuality model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return string|\yii\web\Response
+ */
+ public function actionCreate()
+ {
+ $model = new Products1cNomenclatureActuality();
+
+ 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 Products1cNomenclatureActuality 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 Products1cNomenclatureActuality 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 Products1cNomenclatureActuality 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 Products1cNomenclatureActuality the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = Products1cNomenclatureActuality::findOne(['id' => $id])) !== null) {
+ return $model;
+ }
+
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+}
--- /dev/null
+<?php
+
+use yii\db\Migration;
+
+/**
+ * Handles the creation of table `{{%products_1c_nomenclature_actuality}}`.
+ */
+class m250729_101715_create_products_1c_nomenclature_actuality_table extends Migration
+{
+ const TABLE_NAME = 'erp24.products_1c_nomenclature_actuality';
+ /**
+ * {@inheritdoc}
+ */
+ public function safeUp()
+ {
+ $tableSchema = $this->db->getTableSchema(self::TABLE_NAME);
+
+ if (!isset($tableSchema)) {
+ $this->createTable(self::TABLE_NAME, [
+ 'id' => $this->primaryKey(),
+ 'guid' => $this->string()->notNull(),
+ 'date_from' => $this->dateTime()->notNull()->comment('Дата и время начала активности'),
+ 'date_end' => $this->dateTime()->null()->comment('Дата и время окончания активности'),
+ 'active' => $this->tinyInteger()->notNull()->defaultValue(1)->comment('Активность записи'),
+ 'created_at' => $this->dateTime()->notNull()->comment('Дата создания'),
+ 'updated_at' => $this->dateTime()->null()->comment('Дата обновления'),
+ 'created_by' => $this->integer()->notNull()->comment('ИД создателя'),
+ 'updated_by' => $this->integer()->null()->comment('ИД редактировавшего'),
+ ]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function safeDown()
+ {
+ $tableSchema = $this->db->getTableSchema(self::TABLE_NAME);
+ if (isset($tableSchema)) {
+ $this->dropTable(self::TABLE_NAME);
+ }
+ }
+}
namespace yii_app\records;
use Yii;
+use yii\db\ActiveQuery;
/**
* This is the model class for table "products_1c_nomenclature".
'color' => 'Цвет',
];
}
+
+ /**
+ * Relation: все записи актуальности для этой номенклатуры
+ */
+ public function getActualities(): ActiveQuery
+ {
+ return $this->hasMany(
+ Products1cNomenclatureActuality::class,
+ ['guid' => 'id']
+ );
+ }
+
+ /**
+ * Relation: единственная (первыей) активная запись актуальности
+ */
+ public function getActiveActuality(): ActiveQuery
+ {
+ return $this->hasOne(
+ Products1cNomenclatureActuality::class,
+ ['guid' => 'id']
+ )->andWhere(['active' => 1]);
+ }
+
+ /**
+ * Проверяет, есть ли для этой номенклатуры активная запись
+ * @return bool
+ */
+ public function hasActiveActuality(): bool
+ {
+ return (bool)$this->getActiveActuality()->exists();
+ }
}
--- /dev/null
+<?php
+
+namespace yii_app\records;
+
+use Yii;
+
+/**
+ * This is the model class for table "products_1c_nomenclature_actuality".
+ *
+ * @property int $id
+ * @property string $guid
+ * @property string $date_from Дата и время начала активности
+ * @property string|null $date_end Дата и время окончания активности
+ * @property int $active Активность записи
+ * @property string $created_at Дата создания
+ * @property string|null $updated_at Дата обновления
+ * @property int $created_by ИД создателя
+ * @property int|null $updated_by ИД редактировавшего
+ */
+class Products1cNomenclatureActuality extends \yii\db\ActiveRecord
+{
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function tableName()
+ {
+ return 'products_1c_nomenclature_actuality';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rules()
+ {
+ return [
+ [['date_end', 'updated_at', 'updated_by'], 'default', 'value' => null],
+ [['active'], 'default', 'value' => 1],
+ [['guid', 'date_from', 'created_at', 'created_by'], 'required'],
+ [['date_from', 'date_end', 'created_at', 'updated_at'], 'safe'],
+ [['active', 'created_by', 'updated_by'], 'default', 'value' => null],
+ [['active', 'created_by', 'updated_by'], 'integer'],
+ [['guid'], 'string', 'max' => 255],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'id' => 'ID',
+ 'guid' => 'Guid',
+ 'date_from' => 'Date From',
+ 'date_end' => 'Date End',
+ 'active' => 'Active',
+ 'created_at' => 'Created At',
+ 'updated_at' => 'Updated At',
+ 'created_by' => 'Created By',
+ 'updated_by' => 'Updated By',
+ ];
+ }
+
+}
--- /dev/null
+<?php
+
+namespace yii_app\records;
+
+use yii\base\Model;
+use yii\data\ActiveDataProvider;
+use yii\helpers\ArrayHelper;
+use yii_app\records\Products1cNomenclatureActuality;
+
+/**
+ * Products1cNomenclatureActualitySearch represents the model behind the search form of `yii_app\records\Products1cNomenclatureActuality`.
+ */
+class Products1cNomenclatureActualitySearch extends Products1cNomenclatureActuality
+{
+ public $category;
+ public $type;
+ public $color;
+ public $subcategory;
+ public $sort;
+ public $species;
+ public $size;
+
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rules()
+ {
+ return [
+ [['id', 'active', 'created_by', 'updated_by'], 'integer'],
+ [['guid', 'date_from', 'date_end', 'created_at', 'updated_at'], 'safe'],
+ [['category','type','color','subcategory','sort','species','size'], 'safe'],
+
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function scenarios()
+ {
+ // bypass scenarios() implementation in the parent class
+ return Model::scenarios();
+ }
+
+ /**
+ * Creates data provider instance with search query applied
+ *
+ * @param array $params
+ * @param string|null $formName Form name to be used into `->load()` method.
+ *
+ * @return ActiveDataProvider
+ */
+ public function search($params, $formName = null)
+ {
+ $query = Products1cNomenclatureActuality::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,
+ 'date_from' => $this->date_from,
+ 'date_end' => $this->date_end,
+ 'active' => $this->active,
+ '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;
+ }
+
+ public static function getCategoryList(): array
+ {
+ $rows = Products1cNomenclature::find()
+ ->select('category')
+ ->distinct()
+ ->where(['not', ['category'=>null]])
+ ->orderBy('category')
+ ->asArray()
+ ->all();
+
+ return ArrayHelper::map($rows, 'category', 'category');
+ }
+
+ public static function getTypeList(): array
+ {
+ $typeList = Products1cPropType::find()
+ ->select(['id'])
+ ->andWhere(['name' => ['type', 'тип']])
+ ->column();
+ $additionalCharacteristic = Products1cAdditionalCharacteristics::find()
+ ->select(['value'])->distinct()->where(['property_id' => $typeList ])->asArray()->all();
+
+ return ArrayHelper::map($additionalCharacteristic, 'value', 'value');
+ }
+
+ public static function getColorList(): array
+ {
+ $typeList = Products1cPropType::find()
+ ->select(['id'])
+ ->andWhere(['name' => ['цвет', 'color']])
+ ->column();
+ $additionalCharacteristic = Products1cAdditionalCharacteristics::find()
+ ->select(['value'])->distinct()->where(['property_id' => $typeList])->asArray()->all();
+
+ return ArrayHelper::map($additionalCharacteristic, 'value', 'value');
+ }
+
+ public static function getSubcategoryList(): array
+ {
+ $rows = Products1cNomenclature::find()
+ ->select('subcategory')
+ ->distinct()
+ ->where(['not', ['subcategory'=>null]])
+ ->orderBy('subcategory')
+ ->asArray()
+ ->all();
+
+ return ArrayHelper::map($rows, 'subcategory', 'subcategory');
+ }
+
+ public static function getSortList(): array
+ {
+ $typeList = Products1cPropType::find()
+ ->select(['id'])
+ ->andWhere(['name' => ['sort', 'сорт']])
+ ->column();
+ $additionalCharacteristic = Products1cAdditionalCharacteristics::find()
+ ->select(['value'])->distinct()->where(['property_id' => $typeList])->asArray()->all();
+
+ return ArrayHelper::map($additionalCharacteristic, 'value', 'value');
+ }
+
+ public static function getSpeciesList(): array
+ {
+ $rows = Products1cNomenclature::find()
+ ->select('species')
+ ->distinct()
+ ->where(['not', ['species'=>null]])
+ ->orderBy('species')
+ ->asArray()
+ ->all();
+
+ return ArrayHelper::map($rows, 'species', 'species');
+ }
+
+ public static function getSizeList(): array
+ {
+ $typeList = Products1cPropType::find()
+ ->select(['id'])
+ ->andWhere(['name' => ['size', 'размер']])
+ ->column();
+ $additionalCharacteristic = Products1cAdditionalCharacteristics::find()
+ ->select(['value'])->distinct()->where(['property_id' => $typeList])->asArray()->all();
+
+ return ArrayHelper::map($additionalCharacteristic, 'value', 'value');
+ }
+}
--- /dev/null
+<?php
+
+use kartik\form\ActiveForm;
+use kartik\grid\GridView;
+use yii\helpers\Html;
+
+
+/* @var $this yii\web\View */
+/* @var $filter yii\base\DynamicModel */
+/* @var $dataProvider yii\data\ActiveDataProvider */
+/* @var array $categories */
+/* @var array $subcategories */
+/* @var array $species */
+/* @var array $types */
+/* @var array $colors */
+/* @var array $sorts */
+/* @var array $sizes */
+
+$this->title = 'Актуализация номенклатуры';
+$this->params['breadcrumbs'][] = $this->title;
+
+// Список месяцев-годов для выпадающих списков
+function monthList()
+{
+ $list = [];
+ $start = new DateTime('2021-01');
+ $end = new DateTime('2025-12');
+ while ($start <= $end) {
+ $key = $start->format('Y-m');
+ $list[$key] = $start->format('Y‑m');
+ $start->modify('+1 month');
+ }
+ return $list;
+}
+
+$months = monthList();
+?>
+
+<div class="products1c-nomenclature-actuality-index p-4">
+
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <!-- Форма фильтров -->
+ <?php $formFilter = ActiveForm::begin([
+ 'method' => 'get',
+ 'action' => ['index'],
+ 'options' => ['class' => 'mb-4'],
+ ]); ?>
+
+ <div class="row">
+
+ <!-- 1-я колонка: основные фильтры -->
+ <div class="col-6 ">
+ <div class="row mb-3">
+ <div class="col">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'category', ['options' => ['class' => 'w-90']])->dropDownList(
+ $categories,
+ ['prompt' => 'Категория', 'id' => 'filter-category']
+ )->label(false) ?>
+
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-category" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="col">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'type', ['options' => ['class' => 'w-90']])->dropDownList(
+ $types,
+ ['prompt' => 'Тип', 'id' => 'filter-type']
+ )->label(false) ?>
+
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-type" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="col">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'color', ['options' => ['class' => 'w-90']])->dropDownList(
+ $colors,
+ ['prompt' => 'Цвет', 'id' => 'filter-color']
+ )->label(false) ?>
+
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-color" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-3">
+ <div class="col">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'subcategory', ['options' => ['class' => 'w-90']])->dropDownList(
+ $subcategories,
+ ['prompt' => 'Подкатегория', 'id' => 'filter-subcategory', 'class' => 'w-100']
+ )->label(false) ?>
+
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-subcategory" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="col">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'sort', ['options' => ['class' => 'w-90']])->dropDownList(
+ $sorts,
+ ['prompt' => 'Сорт', 'id' => 'filter-sort']
+ )->label(false) ?>
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-sort" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="col"></div>
+ </div>
+ <div class="row">
+ <div class="col">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'species', ['options' => ['class' => 'w-90']])->dropDownList(
+ $species,
+ ['prompt' => 'Вид', 'id' => 'filter-species']
+ )->label(false) ?>
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-species" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="col">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'size', ['options' => ['class' => 'w-90']])->dropDownList(
+ $sizes,
+ ['prompt' => 'Размер', 'id' => 'filter-size']
+ )->label(false) ?>
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-size" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="col"></div>
+ </div>
+ </div>
+
+ <!-- 2-я колонка: даты актуальности -->
+ <div class="col-3 ps-4" style="border-left: #ccc solid 1px">
+ <div class="mb-2 fw-bold">Актуальность ассортимента</div>
+ <div class="mb-3">
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'date_from', ['options' => ['class' => 'w-100']])
+ ->dropDownList($months, ['prompt' => 'Выбрать дату от', 'id' => 'filter-date-from'])
+ ->label(false) ?>
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-date-from" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class="d-flex justify-content-between">
+ <?= $formFilter->field($filter, 'date_to', ['options' => ['class' => 'w-100']])
+ ->dropDownList($months, ['prompt' => 'Выбрать дату до', 'id' => 'filter-date-to'])
+ ->label(false) ?>
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-date-to" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 3-я колонка: поставщик/плантация + кнопка Применить -->
+ <div class="col-3 ps-4" style="border-left: #ccc solid 1px">
+ <div class="mb-3">
+
+ <div class="input-group">
+ <?= Html::dropDownList('supplier', null,
+ ['Астра','Бифлористика'],
+ ['class' => 'form-select', 'id' => 'filter-supplier', 'prompt' => 'Поставщик']) ?>
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-supplier" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="mb-4">
+
+ <div class="input-group">
+ <?= Html::dropDownList('plantation', null,
+ ['Плантация1','Плантация2'],
+ ['class' => 'form-select', 'id' => 'filter-plantation', 'prompt' => 'Плантация']) ?>
+ <div class="mb-4 ms-1 d-flex justify-content-center align-items-center clear-btn" data-target="filter-plantation" >
+ <i class="fa fa-times"></i>
+ </div>
+ </div>
+ </div>
+ <div class="text-end">
+ <?= Html::submitButton('Применить', ['class' => 'btn btn-primary']) ?>
+ </div>
+ </div>
+
+ </div>
+
+ <?php ActiveForm::end(); ?>
+
+
+
+
+ <!-- Форма массового обновления актуальности -->
+ <?php $form = ActiveForm::begin(['id' => 'actuality-form']); ?>
+ <div class="form-group d-flex justify-content-end">
+ <?= Html::submitButton('Сохранить', ['class' => 'btn btn-success']) ?>
+ </div>
+ <?= GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'responsive' => false,
+ 'hover' => true,
+ 'floatHeader' => false,
+ 'tableOptions' => ['class' => 'table table-bordered'],
+ 'containerOptions' => ['style' => 'overflow:auto; max-height:500px;'],
+ 'rowOptions' => function($model) {
+ return $model->hasActiveActuality() ? ['class' => 'table-success'] : [];
+ },
+ 'columns' => [
+ [
+ 'attribute' => 'name',
+ 'label' => 'Наименование',
+ 'format' => 'raw',
+ 'contentOptions' => ['style'=>'min-width:200px;'],
+ 'value' => function ($m) {
+ return Html::encode($m->name);
+ }
+ ],
+ [
+ 'label' => 'Актуальность ассортимента',
+ 'format' => 'raw',
+ 'contentOptions' => ['style'=>'white-space:nowrap; min-width:200px;'],
+ 'value' => function ($m, $k, $i) use ($months) {
+ $active = $m->getActiveActuality()->one();
+ $from = $active ? (new \DateTime($active->date_from))->format('Y-m') : null;
+ $to = $active ? (new \DateTime($active->date_end))->format('Y-m') : null;
+ return Html::hiddenInput("actuality[$i][guid]", $m->id)
+ . Html::tag('div',
+ Html::dropDownList("actuality[$i][from]", $from, $months, [
+ 'class'=>'form-select form-select-sm me-1',
+ 'prompt'=>'от'
+ ])
+ . Html::dropDownList("actuality[$i][to]", $to, $months, [
+ 'class'=>'form-select form-select-sm',
+ 'prompt'=>'до'
+ ]),
+ ['class'=>'d-flex align-items-center']
+ );
+ }
+ ],
+ [
+ 'label' => 'Склад NN',
+ 'format' => 'raw',
+ 'contentOptions' => ['style'=>'width:60px; text-align:center;'],
+ 'value' => function ($m, $k, $i) {
+ return Html::checkbox("actuality[$i][warehouse_nn]", false);
+ }
+ ],
+ [
+ 'label' => 'Склад MSK',
+ 'format' => 'raw',
+ 'contentOptions' => ['style'=>'width:60px; text-align:center;'],
+ 'value' => function ($m, $k, $i) {
+ return Html::checkbox("actuality[$i][warehouse_msk]", false);
+ }
+ ],
+ [
+ 'label' => 'Поставщик/Плантация',
+ 'format' => 'text',
+ 'contentOptions' => ['style'=>'min-width:150px;'],
+ 'value' => function ($m) {
+ return '–';
+ }
+ ],
+ ],
+ ]); ?>
+
+
+
+ <?php ActiveForm::end(); ?>
+
+</div>
+
+<?php
+$js = <<<JS
+$('.from-month').on('change', function(){
+ var from = $(this).val(),
+ to = $(this).closest('td').find('.to-month');
+ to.find('option').each(function(){
+ $(this).toggle($(this).val() >= from);
+ });
+ if (to.val() < from) {
+ to.val(from);
+ }
+});
+$('.clear-btn').on('click', function(){
+ var target = $(this).data('target');
+ $('#' + target).val(null).trigger('change');
+});
+JS;
+$this->registerJs($js);
+?>