From c17640ad7c70c02432ec9c21ebde469fc3fc16fe Mon Sep 17 00:00:00 2001 From: marina Date: Wed, 19 Feb 2025 14:40:52 +0300 Subject: [PATCH] =?utf8?q?ERP-302=20=D0=A0=D0=B5=D0=B4=D0=B0=D0=BA=D1=82?= =?utf8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B1=D1=83?= =?utf8?q?=D0=BA=D0=B5=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/controllers/BouquetController.php | 42 ++-- .../m250203_094609_create_bouquets_tables.php | 49 ++--- ...1_064121_create_bouquet_forecast_table.php | 28 +-- ...12_drop_columns_on_bouquet_composition.php | 17 +- ...eate_bouquet_matrix_type_history_table.php | 28 +-- erp24/records/BouquetComposition.php | 104 ++++++++++ erp24/records/PricesDynamic.php | 1 + erp24/views/bouquet/_form.php | 2 +- erp24/views/bouquet/index.php | 183 +++++++++++++++++- erp24/web/js/bouquet/bouquet.js | 12 +- 10 files changed, 392 insertions(+), 74 deletions(-) diff --git a/erp24/controllers/BouquetController.php b/erp24/controllers/BouquetController.php index ac9425b1..d7015e4a 100644 --- a/erp24/controllers/BouquetController.php +++ b/erp24/controllers/BouquetController.php @@ -5,7 +5,6 @@ namespace app\controllers; use Exception; use Yii; use yii\helpers\ArrayHelper; -use yii\helpers\FileHelper; use yii\helpers\Url; use yii\web\Controller; use yii\web\NotFoundHttpException; @@ -17,6 +16,7 @@ use yii_app\records\BouquetCompositionProducts; use yii_app\records\BouquetForecast; use yii_app\records\CityStore; use yii_app\records\Files; +use yii_app\records\MatrixType; use yii_app\records\Products1c; use yii_app\records\Products1cNomenclature; use yii_app\records\StoreType; @@ -29,9 +29,35 @@ class BouquetController extends Controller { public function actionIndex() { - return $this->render('index'); + $query = BouquetComposition::find()->orderBy('id desc'); + $request = Yii::$app->request; + $year = $request->get('year'); + $month = $request->get('month'); + $matrixTypeId = $request->get('matrix_type_id'); + +// if ($year) { +// $query->andWhere(['year' => $year]); +// } +// if ($month) { +// $query->andWhere(['month' => $month]); +// } +// if ($matrixTypeId) { +// $query->andWhere(['matrix_type_id' => $matrixTypeId]); +// } + + $dataProvider = new \yii\data\ActiveDataProvider([ + 'query' => $query, + 'pagination' => ['pageSize' => 20], + ]); + + return $this->render('index', [ + 'dataProvider' => $dataProvider, + 'matrix_type' => $matrixTypeId ? MatrixType::findOne($matrixTypeId)?->name : null, + ]); } + + public function actionCreate() { $model = new BouquetComposition(); @@ -43,17 +69,7 @@ class BouquetController extends Controller BouquetCompositionMatrixTypeHistory::setData($data['matrix_type_id'], $model->id); } if ($model->save()) { -// return $this->redirect('view', [ -// 'model' => $model, -// 'onlineStoresList' => $onlineStoresList, -// 'bouquetCompositionProducts' => $bouquetCompositionProducts, -// 'marketplaceList' => $marketplaceList, -// 'storesTypeList' => $storesTypeList, -// 'photoUrls' => $photoUrls, -// 'photoFiles' => $photoFiles, -// 'videoUrls' => $videoUrls, -// 'processUrls' => $processUrls, -// ]); + return $this->redirect(['index']); } $month = $data['month']; $year = $data['year']; diff --git a/erp24/migrations/m250203_094609_create_bouquets_tables.php b/erp24/migrations/m250203_094609_create_bouquets_tables.php index 81d5b6ca..4745bcdb 100644 --- a/erp24/migrations/m250203_094609_create_bouquets_tables.php +++ b/erp24/migrations/m250203_094609_create_bouquets_tables.php @@ -12,31 +12,36 @@ class m250203_094609_create_bouquets_tables extends Migration */ public function safeUp() { - $this->createTable('erp24.bouquet_composition_products', [ - 'id' => $this->primaryKey(), - 'bouquet_id' => $this->integer()->notNull()->comment('ID букета'), - 'product_guid' => $this->string(255)->notNull()->comment('GUID продукта'), - 'count' => $this->float()->comment('Количество продукта'), - 'created_at' => $this->dateTime()->comment('Дата создания'), - 'updated_at' => $this->dateTime()->comment('Дата обновления'), - 'created_by' => $this->integer()->comment('ID создателя записи'), - 'updated_by' => $this->integer()->comment('ID обновителя записи'), - ]); + if (!$this->db->schema->getTableSchema('erp24.bouquet_composition_products', true)) { + $this->createTable('erp24.bouquet_composition_products', [ + 'id' => $this->primaryKey(), + 'bouquet_id' => $this->integer()->notNull()->comment('ID букета'), + 'product_guid' => $this->string(255)->notNull()->comment('GUID продукта'), + 'count' => $this->float()->comment('Количество продукта'), + 'created_at' => $this->dateTime()->comment('Дата создания'), + 'updated_at' => $this->dateTime()->comment('Дата обновления'), + 'created_by' => $this->integer()->comment('ID создателя записи'), + 'updated_by' => $this->integer()->comment('ID обновителя записи'), + ]); + } - $this->createTable('erp24.bouquet_composition', [ - 'id' => $this->primaryKey(), - 'guid' => $this->string(255)->comment('GUID букета'), - 'name' => $this->string(255)->notNull()->comment('Название букета'), - 'matrix_type_id' => $this->integer()->comment('ИД типа матрицы'), - 'photo_id' => $this->integer()->comment('ИД фото'), - 'video_id' => $this->integer()->comment('ИД видео'), - 'created_at' => $this->dateTime()->comment('Дата создания'), - 'updated_at' => $this->dateTime()->comment('Дата обновления'), - 'created_by' => $this->integer()->comment('ID создателя записи'), - 'updated_by' => $this->integer()->comment('ID обновителя записи'), - ]); + if (!$this->db->schema->getTableSchema('erp24.bouquet_composition', true)) { + $this->createTable('erp24.bouquet_composition', [ + 'id' => $this->primaryKey(), + 'guid' => $this->string(255)->comment('GUID букета'), + 'name' => $this->string(255)->notNull()->comment('Название букета'), + 'matrix_type_id' => $this->integer()->comment('ИД типа матрицы'), + 'photo_id' => $this->integer()->comment('ИД фото'), + 'video_id' => $this->integer()->comment('ИД видео'), + 'created_at' => $this->dateTime()->comment('Дата создания'), + 'updated_at' => $this->dateTime()->comment('Дата обновления'), + 'created_by' => $this->integer()->comment('ID создателя записи'), + 'updated_by' => $this->integer()->comment('ID обновителя записи'), + ]); + } } + /** * {@inheritdoc} */ diff --git a/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php b/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php index e6bf5564..b3a9ce13 100644 --- a/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php +++ b/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php @@ -12,19 +12,21 @@ class m250211_064121_create_bouquet_forecast_table extends Migration */ public function safeUp() { - $this->createTable('{{%erp24.bouquet_forecast}}', [ - 'id' => $this->primaryKey(), - 'bouquet_id' => $this->integer()->comment('ИД букета'), - 'year' => $this->integer()->comment('Год'), - 'month' => $this->integer()->comment('Месяц'), - 'type_sales' => $this->integer()->comment('Тип продаж'), - 'type_sales_id' => $this->integer()->comment('ИД сущности типа продаж'), - 'type_sales_value' => $this->float('Значение'), - 'created_at' => $this->dateTime()->comment('Дата создания'), - 'updated_at' => $this->dateTime()->comment('Дата обновления'), - 'created_by' => $this->integer()->comment('ID создателя записи'), - 'updated_by' => $this->integer()->comment('ID обновителя записи'), - ]); + if (!$this->db->schema->getTableSchema('{{%erp24.bouquet_forecast}}', true)) { + $this->createTable('{{%erp24.bouquet_forecast}}', [ + 'id' => $this->primaryKey(), + 'bouquet_id' => $this->integer()->comment('ИД букета'), + 'year' => $this->integer()->comment('Год'), + 'month' => $this->integer()->comment('Месяц'), + 'type_sales' => $this->integer()->comment('Тип продаж'), + 'type_sales_id' => $this->integer()->comment('ИД сущности типа продаж'), + 'type_sales_value' => $this->float('Значение'), + 'created_at' => $this->dateTime()->comment('Дата создания'), + 'updated_at' => $this->dateTime()->comment('Дата обновления'), + 'created_by' => $this->integer()->comment('ID создателя записи'), + 'updated_by' => $this->integer()->comment('ID обновителя записи'), + ]); + } } /** diff --git a/erp24/migrations/m250213_094412_drop_columns_on_bouquet_composition.php b/erp24/migrations/m250213_094412_drop_columns_on_bouquet_composition.php index 2d5cb75e..1f458d2b 100644 --- a/erp24/migrations/m250213_094412_drop_columns_on_bouquet_composition.php +++ b/erp24/migrations/m250213_094412_drop_columns_on_bouquet_composition.php @@ -12,9 +12,20 @@ class m250213_094412_drop_columns_on_bouquet_composition extends Migration */ public function safeUp() { - $this->dropColumn('bouquet_composition', 'photo_id'); - $this->dropColumn('bouquet_composition', 'video_id'); - $this->dropColumn('bouquet_composition', 'matrix_type_id'); + $table = 'bouquet_composition'; + $dbSchema = $this->db->schema->getTableSchema($table, true); + + if ($dbSchema !== null) { + if (isset($dbSchema->columns['photo_id'])) { + $this->dropColumn($table, 'photo_id'); + } + if (isset($dbSchema->columns['video_id'])) { + $this->dropColumn($table, 'video_id'); + } + if (isset($dbSchema->columns['matrix_type_id'])) { + $this->dropColumn($table, 'matrix_type_id'); + } + } } diff --git a/erp24/migrations/m250213_095313_create_bouquet_matrix_type_history_table.php b/erp24/migrations/m250213_095313_create_bouquet_matrix_type_history_table.php index 0fa29856..f20a77b1 100644 --- a/erp24/migrations/m250213_095313_create_bouquet_matrix_type_history_table.php +++ b/erp24/migrations/m250213_095313_create_bouquet_matrix_type_history_table.php @@ -12,18 +12,22 @@ class m250213_095313_create_bouquet_matrix_type_history_table extends Migration */ public function safeUp() { - $this->createTable('{{%erp24.bouquet_composition_matrix_type_history}}', [ - 'id' => $this->primaryKey(), - 'bouquet_id' => $this->integer()->comment('Букет ИД'), - 'matrix_type_id' => $this->integer()->comment('Тип матрицы ИД'), - 'date_from' => $this->dateTime()->comment('Дата установки'), - 'date_to' => $this->dateTime()->comment('Дата изменения'), - 'is_active' => $this->boolean()->defaultValue(true)->comment('Активна ли запись'), - 'created_at' => $this->dateTime()->comment('Дата создания'), - 'updated_at' => $this->dateTime()->comment('Дата обновления'), - 'created_by' => $this->integer()->comment('ID создателя записи'), - 'updated_by' => $this->integer()->comment('ID обновителя записи'), - ]); + $table = '{{%erp24.bouquet_composition_matrix_type_history}}'; + + if ($this->db->schema->getTableSchema($table, true) === null) { + $this->createTable($table, [ + 'id' => $this->primaryKey(), + 'bouquet_id' => $this->integer()->comment('Букет ИД'), + 'matrix_type_id' => $this->integer()->comment('Тип матрицы ИД'), + 'date_from' => $this->dateTime()->comment('Дата установки'), + 'date_to' => $this->dateTime()->comment('Дата изменения'), + 'is_active' => $this->boolean()->defaultValue(true)->comment('Активна ли запись'), + 'created_at' => $this->dateTime()->comment('Дата создания'), + 'updated_at' => $this->dateTime()->comment('Дата обновления'), + 'created_by' => $this->integer()->comment('ID создателя записи'), + 'updated_by' => $this->integer()->comment('ID обновителя записи'), + ]); + } } /** diff --git a/erp24/records/BouquetComposition.php b/erp24/records/BouquetComposition.php index 83cddaf6..d153b405 100644 --- a/erp24/records/BouquetComposition.php +++ b/erp24/records/BouquetComposition.php @@ -91,6 +91,110 @@ class BouquetComposition extends ActiveRecord ->andWhere(['is_active' => BouquetCompositionMatrixTypeHistory::IS_ACTIVE]); } + public function getPresentation() + { + return $this->hasOne(Files::class, ['entity_id' => 'id']) + ->andWhere(['entity' => self::VIDEO_PRESENTATION]); + } + + public function getBuildProcess() + { + return $this->hasOne(Files::class, ['entity_id' => 'id']) + ->andWhere(['entity' => self::VIDEO_BUILD_PROCESS]); + } + + public function getCost() + { + $cost = 0; + + $compositionProducts = $this->bouquetCompositionProducts; + + if (!$compositionProducts) { + return $cost; + } + + foreach ($compositionProducts as $item) { + if (!$item || !isset($item->product_guid)) { + continue; + } + + $priceModel = PricesDynamic::find() + ->where(['product_id' => $item->product_guid]) + ->andWhere(['=', 'active', PricesDynamic::ACTIVE]) + ->one(); + + if ($priceModel) { + $cost += $priceModel->price * $item->count; + } + } + + return $cost; + } + + public function getMarkUp() + { + if ($this->getSelfCost() == 0 || $this->getCost() == 0) + return 0; + + return sprintf("+%.2f%% / +%.2f", + (self::getCost() / self::getSelfCost() - 1) * 100, + self::getCost() - self::getSelfCost() + ); + } + + + public function getSelfCost() + { + $selfCost = 0; + $compositionProducts = $this->bouquetCompositionProducts; + + if (!$compositionProducts) { + return $selfCost; + } + + $productGuids = array_filter(array_column($compositionProducts, 'product_guid')); + + if (empty($productGuids)) { + return $selfCost; + } + + $pricesData = SelfCostProduct::find() + ->where(['product_guid' => $productGuids]) + ->andWhere(['>=', 'date', date('Y-m-d', strtotime('-14 days'))]) + ->orderBy('price') + ->select(['product_guid', 'price']) + ->asArray() + ->all(); + + $pricesByProduct = []; + foreach ($pricesData as $row) { + $pricesByProduct[$row['product_guid']][] = $row['price']; + } + + foreach ($compositionProducts as $item) { + if (!$item || !isset($item->product_guid)) { + continue; + } + + $prices = $pricesByProduct[$item->product_guid] ?? []; + + $count = count($prices); + if ($count === 0) { + continue; + } + + sort($prices); + $middle = (int) ($count / 2); + $median = ($count % 2 === 0) + ? ($prices[$middle - 1] + $prices[$middle]) / 2 + : $prices[$middle]; + + $selfCost += $median * $item->count; + } + + return $selfCost; + } + public function getForecastMonthAndYear() { // var_dump($this->hasOne(BouquetForecast::class, ['bouquet_id' => 'id'])->createCommand()->getRawSql());die(); diff --git a/erp24/records/PricesDynamic.php b/erp24/records/PricesDynamic.php index 45874daa..40234e9a 100755 --- a/erp24/records/PricesDynamic.php +++ b/erp24/records/PricesDynamic.php @@ -16,6 +16,7 @@ use Yii; */ class PricesDynamic extends \yii\db\ActiveRecord { + public const ACTIVE = 1; /** * {@inheritdoc} */ diff --git a/erp24/views/bouquet/_form.php b/erp24/views/bouquet/_form.php index 8d16ddf1..41b21a8d 100644 --- a/erp24/views/bouquet/_form.php +++ b/erp24/views/bouquet/_form.php @@ -189,7 +189,7 @@ $form = ActiveForm::begin([
'font-weight-bold pt-3 h6']) ?>
-
forecastMonthAndYear->month ?? null, \yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, ['class' => 'form-control']) ?>
+
forecastMonthAndYear->month ?? null, \yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, ['class' => 'form-control']) ?>
diff --git a/erp24/views/bouquet/index.php b/erp24/views/bouquet/index.php index 2a3f0916..d995b97f 100644 --- a/erp24/views/bouquet/index.php +++ b/erp24/views/bouquet/index.php @@ -1,16 +1,189 @@ title = 'Составы букетов'; +$this->title = 'Содержание матрицы'; ?> -
+ +

title) ?>

-

- 'btn btn-success']) ?> -

+ ['class' => 'w-100'], + 'method' => 'get' + ]); ?> + +
+
+ + 'fw-bold fs-4 mb-0']) ?> + +
+ +
+
+ request->get('year'), [2024 => 2024, 2025 => 2025], [ + 'class' => 'form-control', + 'prompt' => 'Год' + ]) ?> +
+
+ +
+
+ request->get('month'), \yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, [ + 'class' => 'form-control', + 'prompt' => 'Месяц' + ]) ?> +
+
+ +
+
+ request->get('matrix_type_id'), + ArrayHelper::map(MatrixType::find()->all(), 'id', 'name'), [ + 'class' => 'form-control', + 'prompt' => 'Тип матрицы' + ]) ?> +
+
+ 'text-decoration-none', 'target' => '_blank']) ?> +
+
+ +
+ 'btn btn-primary w-100']) ?> +
+ +
+ 'btn btn-info w-100 mb-2']) ?> + 'btn btn-success w-100']) ?> +
+
+ + + +
+ $dataProvider, + 'layout' => "{items}\n{pager}", + 'tableOptions' => ['class' => 'table'], + 'columns' => [ + [ + 'attribute' => 'name', + 'format' => 'raw', + 'value' => function ($model) { + $compositionHtml = "
"; + + if (is_array($model->bouquetCompositionProducts)) { + foreach ($model->bouquetCompositionProducts as $item) { + $product = Products1c::findOne($item->product_guid); + $productName = $product ? $product->name : 'Неизвестный продукт'; + + $compositionHtml .= " +
+
{$productName}
+
{$item->count}
+
"; + } + } + + $images = Files::find() + ->where(['entity_id' => $model->id, 'entity' => \yii_app\records\BouquetComposition::PHOTO_BOUQUET]) + ->limit(3) + ->all(); + $imageUrls = array_map(fn($file) => Url::to($file->url), $images); + $imageUrls += array_fill(0, 3 - count($imageUrls), null); + $compositionHtml .= "
"; + + return " +
+
+
+

{$model->name}

+
+ {$compositionHtml} +
+
+ Себестоимость: " . $model->getSelfCost() . "
+ Наценка: " . $model->getMarkUp() . "
+ Цена: " . $model->getCost() . "
+
+
+ +
+
+
" . + Html::img(Url::to($imageUrls[0]), [ + 'style' => 'width: 100%; height: 100%; object-fit: cover;' + ]) . " +
+
+ +
+
+ " . Html::tag('video', '', [ + 'class' => 'video-preview', + 'src' => Url::to($model->presentation->url ?? null), + 'style' => 'width: 100%; height: 100%; object-fit: cover;', + 'autoplay' => true, + 'muted' => true, + 'loop' => true, + 'playsinline' => true + ]) . " +
Презентация
+
+
" . + Html::img(Url::to($imageUrls[1]), [ + 'style' => 'width: 100%; height: 100%; object-fit: cover;' + ]) . " +
+
+ +
+
" . + Html::tag('video', '', [ + 'class' => 'video-preview', + 'src' => Url::to($model->buildProcess->url ?? null), + 'style' => 'width: 100%; height: 100%; object-fit: cover;', + 'autoplay' => true, + 'muted' => true, + 'loop' => true, + 'playsinline' => true + ]) . " +
Процесс сборки
+
+
" . + Html::img(is_array($imageUrls) && isset($imageUrls[2]) ? Url::to($imageUrls[2]) : null, + ['style' => 'width: 100%; height: 100%; object-fit: cover;']) . " +
+
+
+
+
+ " . Html::a('Редактировать', Url::to(['/bouquet/view', 'id' => $model['id']]), [ + 'class' => 'btn btn-warning' + ]) . " +
+
+
+
"; + }, + 'contentOptions' => ['class' => 'align-top'], + 'headerOptions' => ['style' => 'display:none'], + ], + ], + ]) ?> +
diff --git a/erp24/web/js/bouquet/bouquet.js b/erp24/web/js/bouquet/bouquet.js index 390004eb..14d871ab 100644 --- a/erp24/web/js/bouquet/bouquet.js +++ b/erp24/web/js/bouquet/bouquet.js @@ -1,7 +1,9 @@ -const observer = new MutationObserver(() => { - $('.btn-group.buttons').each(function() { - $(this).remove(); +document.addEventListener('DOMContentLoaded', function() { + document.querySelectorAll('.video-preview').forEach(video => { + video.addEventListener('click', function() { + this.controls = true; + this.muted = false; + this.play(); + }); }); }); - -observer.observe(document.body, { childList: true, subtree: true }); -- 2.39.5