use Exception;
use Yii;
use yii\helpers\ArrayHelper;
-use yii\helpers\FileHelper;
use yii\helpers\Url;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
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;
{
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();
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'];
*/
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}
*/
*/
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 обновителя записи'),
+ ]);
+ }
}
/**
*/
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');
+ }
+ }
}
*/
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 обновителя записи'),
+ ]);
+ }
}
/**
->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();
*/
class PricesDynamic extends \yii\db\ActiveRecord
{
+ public const ACTIVE = 1;
/**
* {@inheritdoc}
*/
</div>
<div class="col-md-7">
<div class="col-md-3"><?= Html::label("Месяц", null, ['class' => 'font-weight-bold pt-3 h6']) ?></div>
- <div class="col-md-9"> <?= Html::dropDownList("month", $model?->forecastMonthAndYear->month ?? null, \yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, ['class' => 'form-control']) ?></div>
+ <div class="col-md-9"><?= Html::dropDownList("month", $model?->forecastMonthAndYear->month ?? null, \yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, ['class' => 'form-control']) ?></div>
</div>
</div>
<div class="row">
<?php
+use kartik\grid\GridView;
+use yii\helpers\ArrayHelper;
use yii\helpers\Html;
+use yii\helpers\Url;
+use yii\widgets\ActiveForm;
+use yii_app\records\Files;
+use yii_app\records\MatrixType;
+use yii_app\records\Products1c;
/** @var yii\web\View $this */
-$this->title = 'Составы букетов';
+$this->title = 'Содержание матрицы';
?>
-<div class="bouquet-index">
+
+<div class="bouquet-index p-4">
<h1><?= Html::encode($this->title) ?></h1>
- <p>
- <?= Html::a('Добавить букет', ['create'], ['class' => 'btn btn-success']) ?>
- </p>
+ <?php $form = ActiveForm::begin([
+ 'options' => ['class' => 'w-100'],
+ 'method' => 'get'
+ ]); ?>
+
+ <div class="row align-items-center border-bottom py-3">
+ <div class="col-md-2 text-center">
+ <?php if (!empty($matrix_type)): ?>
+ <?= Html::label("«{$matrix_type}»", null, ['class' => 'fw-bold fs-4 mb-0']) ?>
+ <?php endif; ?>
+ </div>
+
+ <div class="col-md-2">
+ <div class="form-group mb-0">
+ <?= Html::dropDownList("year", Yii::$app->request->get('year'), [2024 => 2024, 2025 => 2025], [
+ 'class' => 'form-control',
+ 'prompt' => 'Год'
+ ]) ?>
+ </div>
+ </div>
+
+ <div class="col-md-2">
+ <div class="form-group mb-0">
+ <?= Html::dropDownList("month", Yii::$app->request->get('month'), \yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, [
+ 'class' => 'form-control',
+ 'prompt' => 'Месяц'
+ ]) ?>
+ </div>
+ </div>
+
+ <div class="col-md-2 mt-5">
+ <div class="form-group mb-0">
+ <?= Html::dropDownList('matrix_type_id', Yii::$app->request->get('matrix_type_id'),
+ ArrayHelper::map(MatrixType::find()->all(), 'id', 'name'), [
+ 'class' => 'form-control',
+ 'prompt' => 'Тип матрицы'
+ ]) ?>
+ </div>
+ <div class="text-center mt-2">
+ <?= Html::a('Редактировать', Url::to('/matrix-type'), ['class' => 'text-decoration-none', 'target' => '_blank']) ?>
+ </div>
+ </div>
+
+ <div class="col-md-2 text-center">
+ <?= Html::submitButton('Применить', ['class' => 'btn btn-primary w-100']) ?>
+ </div>
+
+ <div class="col-md-2 text-center">
+ <?= Html::a('Список матриц', Url::to('/matrix-type'), ['class' => 'btn btn-info w-100 mb-2']) ?>
+ <?= Html::a('Создать букет', ['create'], ['class' => 'btn btn-success w-100']) ?>
+ </div>
+ </div>
+
+ <?php ActiveForm::end(); ?>
+
+ <div class="grid-view px-5">
+ <?= GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'layout' => "{items}\n{pager}",
+ 'tableOptions' => ['class' => 'table'],
+ 'columns' => [
+ [
+ 'attribute' => 'name',
+ 'format' => 'raw',
+ 'value' => function ($model) {
+ $compositionHtml = "<div class='row'>";
+
+ if (is_array($model->bouquetCompositionProducts)) {
+ foreach ($model->bouquetCompositionProducts as $item) {
+ $product = Products1c::findOne($item->product_guid);
+ $productName = $product ? $product->name : 'Неизвестный продукт';
+
+ $compositionHtml .= "
+ <div class='row border-bottom mb-1'>
+ <div class='col-md-9 fw-bold'>{$productName}</div>
+ <div class='col-md-3 fw-bold text-right'>{$item->count}</div>
+ </div>";
+ }
+ }
+
+ $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 .= "</div>";
+
+ return "
+ <div class='matrix-data mx-3'>
+ <div class='col-md-1'></div>
+ <div class='col-md-4'>
+ <h4>{$model->name}</h4>
+ <div class='bg-white border rounded shadow-sm'
+ style='height: 160px; padding: 10px; overflow-y: auto; overflow-x: hidden; max-width: 100%; word-wrap: break-word;'>
+ {$compositionHtml}
+ </div>
+ <div class='self-cost pt-3' style='display: flex; gap: 10px;'>
+ Себестоимость: " . $model->getSelfCost() . "<br>
+ Наценка: " . $model->getMarkUp() . "<br>
+ Цена: " . $model->getCost() . "<br>
+ </div>
+ </div>
+
+ <div class='row'>
+ <div class='col-md-5'>
+ <div class='card' style='aspect-ratio: 1/1; display: flex; overflow: hidden;'> " .
+ Html::img(Url::to($imageUrls[0]), [
+ 'style' => 'width: 100%; height: 100%; object-fit: cover;'
+ ]) . "
+ </div>
+ </div>
+
+ <div class='col-md-2 d-flex flex-column gap-2'>
+ <div class='card bg-transparent border-0' style='aspect-ratio: 1/1; width: 100%; flex: 1;'>
+ " . 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
+ ]) . "
+ <div class='text-center bg-transparent fw-bold p-0 m-0'>Презентация</div>
+ </div>
+ <div class='card' style='aspect-ratio: 1/1; width: 100%; flex: 1;'> " .
+ Html::img(Url::to($imageUrls[1]), [
+ 'style' => 'width: 100%; height: 100%; object-fit: cover;'
+ ]) . "
+ </div>
+ </div>
+
+ <div class='col-md-2 d-flex flex-column gap-2'>
+ <div class='card bg-transparent border-0' style='aspect-ratio: 1/1; width: 100%; flex: 1;'> " .
+ 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
+ ]) . "
+ <div class='text-center bg-transparent fw-bold'>Процесс сборки</div>
+ </div>
+ <div class='card' style='aspect-ratio: 1/1; width: 100%; flex: 1;'> " .
+ Html::img(is_array($imageUrls) && isset($imageUrls[2]) ? Url::to($imageUrls[2]) : null,
+ ['style' => 'width: 100%; height: 100%; object-fit: cover;']) . "
+ </div>
+ </div>
+ <div class='row mt-auto text-center'>
+ <div class='col-md-7'></div>
+ <div class='col-md-2'>
+ " . Html::a('Редактировать', Url::to(['/bouquet/view', 'id' => $model['id']]), [
+ 'class' => 'btn btn-warning'
+ ]) . "
+ </div>
+ </div>
+ </div>
+ </div>";
+ },
+ 'contentOptions' => ['class' => 'align-top'],
+ 'headerOptions' => ['style' => 'display:none'],
+ ],
+ ],
+ ]) ?>
+ </div>
</div>
-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 });