From: marina Date: Tue, 11 Feb 2025 14:29:15 +0000 (+0300) Subject: ERP-302 Редактирование букета X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=553427e82ea1cbeb105ed5ca7329d2850c7ee930;p=erp24_rep%2Fyii-erp24%2F.git ERP-302 Редактирование букета --- diff --git a/erp24/.gitignore b/erp24/.gitignore index c25408fc..a5dd55d5 100644 --- a/erp24/.gitignore +++ b/erp24/.gitignore @@ -40,4 +40,5 @@ tests/_support/_generated /data/* /runtime/* /api2/runtime/* -/api2/json/* \ No newline at end of file +/api2/json/* +/widgets/app.log \ No newline at end of file diff --git a/erp24/controllers/BouquetController.php b/erp24/controllers/BouquetController.php index 4851bc0f..0a81ba7a 100644 --- a/erp24/controllers/BouquetController.php +++ b/erp24/controllers/BouquetController.php @@ -5,6 +5,8 @@ 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; use yii\web\UploadedFile; @@ -12,9 +14,11 @@ use yii_app\records\BouquetComposition; use yii_app\records\BouquetCompositionProducts; use yii_app\records\BouquetForecast; use yii_app\records\CityStore; +use yii_app\records\Files; use yii_app\records\Products1c; use yii_app\records\Products1cNomenclature; use yii_app\records\StoreType; +use yii_app\services\FileService; /** * Контроллер для управления букетами и их составами. @@ -28,17 +32,96 @@ class BouquetController extends Controller public function actionView($id) { + $model = BouquetComposition::findOne($id); + + if (Yii::$app->request->isPost) { + $data = Yii::$app->request->post(); + $month = $data['month']; + $year = $data['year']; + + $model->photo_bouquet = UploadedFile::getInstances($model, 'photo_bouquet'); + if ($model->photo_bouquet) { + Files::deleteAll(['file_type' => 'image', 'entity_id' => $id, 'entity' => BouquetComposition::PHOTO_BOUQUET]); + foreach ($model->photo_bouquet as $photo) { + FileService::saveUploadedFile($photo, BouquetComposition::PHOTO_BOUQUET, $model->id); + } + } + + $model->video_presentation = UploadedFile::getInstances($model, 'video_presentation'); + if ($model->video_presentation) { + Files::deleteAll(['file_type' => 'video', 'entity_id' => $id, 'entity' => BouquetComposition::VIDEO_PRESENTATION]); + FileService::saveUploadedFile($model->video_presentation, BouquetComposition::VIDEO_PRESENTATION, $model->id); + } + + $model->video_build_process = UploadedFile::getInstances($model, 'video_build_process'); + if ($model->video_build_process) { + Files::deleteAll(['file_type' => 'video', 'entity_id' => $id, 'entity' => BouquetComposition::VIDEO_BUILD_PROCESS]); + FileService::saveUploadedFile($model->video_build_process, BouquetComposition::VIDEO_BUILD_PROCESS, $model->id); + } + + + if (!empty($data['BouquetForecast']['type_sales_value'])) { + $salesData = $data['BouquetForecast']['type_sales_value']; + + if (!empty($salesData['offline'])) { + BouquetForecast::processSalesData($id, $year, $month, $salesData['offline'], BouquetForecast::OFFLINE_STORES); + } + + if (!empty($salesData['online'])) { + BouquetForecast::processSalesData($id, $year, $month, $salesData['online'], BouquetForecast::ONLINE_STORES); + } + + if (!empty($salesData['marketplace'])) { + BouquetForecast::processSalesData($id, $year, $month, $salesData['marketplace'], BouquetForecast::MARKETPLACE); + } + } + + } + + + $photoFiles = Files::find()->where(['entity_id' => $id, 'file_type' => 'image', 'entity' => BouquetComposition::PHOTO_BOUQUET])->all(); + $videoFiles = Files::find()->where(['entity_id' => $id, 'file_type' => 'image', 'entity' => BouquetComposition::VIDEO_PRESENTATION])->all(); + $processFiles = Files::find()->where(['entity_id' => $id, 'file_type' => 'image', 'entity' => BouquetComposition::VIDEO_BUILD_PROCESS])->all(); + + $photoUrls = array_map(fn($file) => Url::to([$file->url]), $photoFiles); + $videoUrls = array_map(fn($file) => Url::to([$file->url]), $videoFiles); + $processUrls = array_map(fn($file) => Url::to([$file->url]), $processFiles); + + $bouquetCompositionProducts = BouquetCompositionProducts::find()->andWhere(['bouquet_id' => $id])->all(); + $storesTypeList = BouquetForecast::getStoresList($id, BouquetForecast::OFFLINE_STORES, StoreType::class, []); + $marketplaceList = BouquetForecast::getStoresList($id, BouquetForecast::MARKETPLACE, CityStore::class, ['visible' => CityStore::IS_VISIBLE]); + $onlineStoresList = BouquetForecast::getStoresList($id, BouquetForecast::ONLINE_STORES, CityStore::class, ['visible' => CityStore::IS_VISIBLE]); - BouquetForecast::findAll(['bouquet_id' => $id]); - $storesTypeList = ArrayHelper::map(BouquetForecast::findAll(['bouquet_id' => $id]) ?? StoreType::find()->orderBy('sequence_number')->all(), 'id', 'name'); - $storesList = ArrayHelper::map(BouquetForecast::findAll(['bouquet_id' => $id]) ?? CityStore::findAll(['visible' => CityStore::IS_VISIBLE]), 'id', 'name'); return $this->render('view', [ - 'storesList' => $storesList, + 'model' => $model, + 'onlineStoresList' => $onlineStoresList, + 'bouquetCompositionProducts' => $bouquetCompositionProducts, + 'marketplaceList' => $marketplaceList, 'storesTypeList' => $storesTypeList, + 'photoUrls' => $photoUrls, + 'photoFiles' => $photoFiles, + 'videoUrls' => $videoUrls, + 'processUrls' => $processUrls, ]); } + + /** + * Сохранение записи в таблицу files + */ + private function saveFileRecord($bouquetId, $filePath, $type) + { + $file = new Files([ + 'url' => $filePath, + 'file_type' => $type, + 'created_at' => time(), + 'entity_id' => $bouquetId, + 'entity' => 'bouquet/photo', + ]); + $file->save(); + } + public function actionUpdate($id) { $model = BouquetComposition::findOne($id); @@ -99,6 +182,7 @@ class BouquetController extends Controller 'availableItems' => $availableItems, ]); } + public function actionUpload() { $model = new BouquetComposition(); diff --git a/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php b/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php index 4037f70a..e6bf5564 100644 --- a/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php +++ b/erp24/migrations/m250211_064121_create_bouquet_forecast_table.php @@ -12,14 +12,14 @@ class m250211_064121_create_bouquet_forecast_table extends Migration */ public function safeUp() { - $this->createTable('{{%bouquet_forecast}}', [ + $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('Тип продаж'), - 'store_sales_id' => $this->integer()->comment('ИД сущности типа продаж'), - 'value' => $this->float('Значение'), + '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 создателя записи'), diff --git a/erp24/records/BouquetComposition.php b/erp24/records/BouquetComposition.php index c791c6ee..6f3f1672 100644 --- a/erp24/records/BouquetComposition.php +++ b/erp24/records/BouquetComposition.php @@ -27,6 +27,14 @@ use yii_app\records\BouquetCompositionProducts; */ class BouquetComposition extends ActiveRecord { + public $photo_bouquet; + public $video_presentation; + public $video_build_process; + + public const PHOTO_BOUQUET = 'bouquet/photo_bouquet'; + public const VIDEO_PRESENTATION = 'bouquet/video_presentation'; + public const VIDEO_BUILD_PROCESS = 'bouquet/video_build_process'; + public static function tableName() { return 'erp24.bouquet_composition'; @@ -56,11 +64,9 @@ class BouquetComposition extends ActiveRecord [['name'], 'required'], [['matrix_type_id', 'video_id', 'created_by', 'updated_by'], 'integer'], [['created_at', 'updated_at'], 'safe'], - - [['photo_id'], 'file', 'extensions' => 'jpg, jpeg, png, gif'], - - [['guid', 'name'], 'string', 'max' => 255], + [['photo_bouquet'], 'file', 'extensions' => 'jpg, jpeg, png, gif', 'maxFiles' => 10], + [['video_presentation', 'video_build_process'], 'file', 'extensions' => 'mkv, mov, avi, mp4'], ]; } diff --git a/erp24/records/BouquetCompositionProducts.php b/erp24/records/BouquetCompositionProducts.php index 98d4b778..88c98581 100644 --- a/erp24/records/BouquetCompositionProducts.php +++ b/erp24/records/BouquetCompositionProducts.php @@ -78,7 +78,7 @@ class BouquetCompositionProducts extends ActiveRecord } public function getProduct() { - return $this->hasOne(Products1c::class, ['id' => 'product_guid']); // Исправил связь + return $this->hasOne(Products1c::class, ['id' => 'product_guid']); } } diff --git a/erp24/records/BouquetForecast.php b/erp24/records/BouquetForecast.php index be9c4f5b..6acef163 100644 --- a/erp24/records/BouquetForecast.php +++ b/erp24/records/BouquetForecast.php @@ -3,7 +3,10 @@ namespace yii_app\records; use Yii; +use yii\behaviors\BlameableBehavior; +use yii\behaviors\TimestampBehavior; use yii\db\ActiveRecord; +use yii\db\Expression; /** * This is the model class for table "bouquet_forecast". @@ -13,8 +16,8 @@ use yii\db\ActiveRecord; * @property int|null $year Год * @property int|null $month Месяц * @property int|null $type_sales Тип продаж - * @property int|null $store_sales_id ИД сущности типа продаж - * @property float|null $value Значение + * @property int|null $type_sales_id ИД сущности типа продаж + * @property float|null $type_sales_value Значение * @property string|null $created_at Дата создания * @property string|null $updated_at Дата обновления * @property int|null $created_by ID создателя записи @@ -32,7 +35,24 @@ class BouquetForecast extends ActiveRecord */ public static function tableName() { - return '{{%bouquet_forecast}}'; + return '{{%erp24.bouquet_forecast}}'; + } + + public function behaviors() + { + return [ + [ + 'class' => TimestampBehavior::class, + 'createdAtAttribute' => 'created_at', + 'updatedAtAttribute' => 'updated_at', + 'value' => new Expression('NOW()'), + ], + [ + 'class' => BlameableBehavior::class, + 'createdByAttribute' => 'created_by', + 'updatedByAttribute' => 'updated_by', + ], + ]; } /** @@ -41,8 +61,8 @@ class BouquetForecast extends ActiveRecord public function rules() { return [ - [['bouquet_id', 'year', 'month', 'type_sales', 'store_sales_id', 'created_by', 'updated_by'], 'integer'], - [['value'], 'number'], + [['bouquet_id', 'year', 'month', 'type_sales', 'type_sales_id', 'created_by', 'updated_by'], 'integer'], + [['type_sales_value'], 'number'], [['created_at', 'updated_at'], 'safe'], ]; } @@ -58,12 +78,66 @@ class BouquetForecast extends ActiveRecord 'year' => 'Год', 'month' => 'Месяц', 'type_sales' => 'Тип продаж', - 'store_sales_id' => 'ИД сущности типа продаж', - 'value' => 'Значение', + 'type_sales_id' => 'ИД сущности типа продаж', + 'type_sales_value' => 'Значение', 'created_at' => 'Дата создания', 'updated_at' => 'Дата обновления', 'created_by' => 'ID создателя записи', 'updated_by' => 'ID обновителя записи', ]; } -} + + public static function getStoresList($id, $typeSales, $defaultModel, $defaultCondition) + { + $joinTable = $defaultModel === CityStore::class ? 'city_store' : 'store_type'; + + $list = BouquetForecast::find() + ->andWhere(['bouquet_id' => $id, 'type_sales' => $typeSales]) + ->leftJoin("$joinTable AS df", "df.id = bouquet_forecast.type_sales_id") + ->select(["df.name AS name", 'type_sales_value AS value', 'type_sales_id AS id']) + ->orderBy('type_sales_id') + ->asArray() + ->all(); + + if (empty($list)) { + $list = $defaultModel::find() + ->andWhere($defaultCondition) + ->select([ + 'id', + 'name', + new Expression('NULL as value') + ]) + ->asArray() + ->all(); + } + + return $list; + } + + public static function processSalesData($id, $year, $month, $salesData, $salesType) + { + foreach ($salesData as $key => $value) { + $model = BouquetForecast::findOne([ + 'bouquet_id' => $id, + 'year' => $year, + 'month' => $month, + 'type_sales' => $salesType, + 'type_sales_id' => $key + ]); + + if (empty($model)) { + $model = new BouquetForecast([ + 'bouquet_id' => $id, + 'year' => $year, + 'month' => $month, + 'type_sales' => $salesType, + 'type_sales_id' => $key, + 'type_sales_value' => $value + ]); + $model->save(); + } elseif ($model->type_sales_value != $value) { + $model->updateAttributes(['type_sales_value' => $value]); + } + } + } +} \ No newline at end of file diff --git a/erp24/views/bouquet/update.php b/erp24/views/bouquet/update.php index c0f485df..cc9b3407 100644 --- a/erp24/views/bouquet/update.php +++ b/erp24/views/bouquet/update.php @@ -12,7 +12,7 @@ use yii_app\records\Products1cNomenclature; /** @var array|null $availableItems */ /** @var array|null $selectedItems */ -$this->title = 'Три гладиолуса'; +$this->title = $model->name; $this->params['breadcrumbs'][] = ['label' => 'Букеты', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; @@ -20,7 +20,6 @@ $this->registerJsFile('/js/bouquet/bouquet.js', ['position' => \yii\web\View::PO ?>
- 'h4']) ?>

title) ?>

diff --git a/erp24/views/bouquet/view.php b/erp24/views/bouquet/view.php index 59f528d9..0ca81748 100644 --- a/erp24/views/bouquet/view.php +++ b/erp24/views/bouquet/view.php @@ -1,28 +1,36 @@ title = $model->name; -$this->title = 'Три гладиолуса'; -//$this->title = $model->name; +$this->title = $model->name; $this->params['breadcrumbs'][] = ['label' => 'Букеты', 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; ?> [ + 'caption' => $file->url, + 'key' => $file->id, + 'url' => Url::to(['file/delete', 'id' => $file->id]), +], $photoFiles); +?> +registerCss(" .file-caption { max-width: 62% !important; @@ -31,7 +39,7 @@ $this->registerCss(" ?>
['enctype' => 'multipart/form-data'], // Это необходимо для загрузки файлов + 'options' => ['enctype' => 'multipart/form-data'], ]); ?>
@@ -63,19 +71,21 @@ $this->registerCss("
+
-
Гладиолусы краш
-
3.0
+
product->name ?>
+
count ?>
10%
30%
10%
3.2%
+
- 'btn btn-warning w-100']) ?> + id"), ['class' => 'btn btn-warning w-100']) ?>
@@ -83,20 +93,23 @@ $this->registerCss("
'text-center font-weight-bold pt-5 h5']) ?>
- field($model, 'photo_id[]')->widget(FileInput::class, [ + field($model, 'photo_bouquet[]')->widget(FileInput::class, [ 'options' => [ 'id' => 'bouquet-file-upload', 'multiple' => true, // Поддержка выбора нескольких файлов + 'accept' => 'image/png, image/jpeg, image/jpg', ], 'language' => 'ru', 'pluginOptions' => [ + 'initialPreview' => $photoUrls, + 'initialPreviewConfig' => $photoConfig, 'showPreview' => true, 'showUpload' => false, 'showCancel' => false, 'mainClass' => 'input-group-lg', - 'initialPreview' => [], // Задайте начальный список для предварительного просмотра (если есть) +// 'initialPreview' => [], // Задайте начальный список для предварительного просмотра (если есть) 'maxFileSize' => 2800, // Максимальный размер файла (в килобайтах) - 'dropZoneTitle' => 'Выберите файл', // Текст на зоне для перетаскивания + 'dropZoneTitle' => 'Выберите изображение', // Текст на зоне для перетаскивания 'browseOnZoneClick' => true, // Разрешить клик по зоне перетаскивания 'fileActionSettings' => [ 'showZoom' => false, // Убираем иконку для увеличения @@ -107,10 +120,11 @@ $this->registerCss("
'text-center font-weight-bold pt-5 h5']) ?>
- field($model, 'photo_id[]')->widget(FileInput::class, [ + field($model, 'video_presentation')->widget(FileInput::class, [ 'options' => [ 'id' => 'video-file-upload', - 'multiple' => false, // Поддержка выбора нескольких файлов + 'multiple' => false, + 'accept' => 'video/mp4, video/avi, video/mov, video/mkv', ], 'language' => 'ru', 'pluginOptions' => [ @@ -118,22 +132,23 @@ $this->registerCss(" 'showUpload' => false, 'showCancel' => false, 'mainClass' => 'input-group-lg', - 'initialPreview' => [], // Задайте начальный список для предварительного просмотра (если есть) - 'maxFileSize' => 2800, // Максимальный размер файла (в килобайтах) - 'dropZoneTitle' => 'Выберите файл', // Текст на зоне для перетаскивания + 'initialPreview' => $videoUrls, + 'maxFileSize' => 100000, // Максимальный размер файла (в килобайтах) + 'dropZoneTitle' => 'Выберите видеофайл', // Текст на зоне для перетаскивания 'browseOnZoneClick' => true, // Разрешить клик по зоне перетаскивания 'fileActionSettings' => [ - 'showZoom' => false, // Убираем иконку для увеличения + 'showZoom' => false, ], ], ])->label(false) ?>
'text-center font-weight-bold pt-5 h5']) ?>
- field($model, 'photo_id[]')->widget(FileInput::class, [ + field($model, 'video_build_process')->widget(FileInput::class, [ 'options' => [ 'id' => 'presentation-file-upload', - 'multiple' => false, // Поддержка выбора нескольких файлов + 'multiple' => false, + 'accept' => 'video/mp4, video/avi, video/mov, video/mkv', ], 'language' => 'ru', 'pluginOptions' => [ @@ -141,9 +156,9 @@ $this->registerCss(" 'showUpload' => false, 'showCancel' => false, 'mainClass' => 'input-group-lg', - 'initialPreview' => [], // Задайте начальный список для предварительного просмотра (если есть) - 'maxFileSize' => 2800, // Максимальный размер файла (в килобайтах) - 'dropZoneTitle' => 'Выберите файл', // Текст на зоне для перетаскивания + 'initialPreview' => $processUrls, + 'maxFileSize' => 100000, // Максимальный размер файла (в килобайтах) + 'dropZoneTitle' => 'Выберите видеофайл', 'browseOnZoneClick' => true, // Разрешить клик по зоне перетаскивания 'fileActionSettings' => [ 'showZoom' => false, // Убираем иконку для увеличения @@ -172,50 +187,52 @@ $this->registerCss("
-
- - -
-
- -
'; - } ?> -
+ +
+
+ "col-form-label"]); ?> +
+
+ 'form-control']) ?> +
+
+
+
'text-center font-weight-bold pt-3 h6']) ?>
-
- - -
-
- -
'; - } ?> -
+ +
+
+ "col-form-label"]); ?> +
+
+ 'form-control']) ?> +
+
+
+
'text-center font-weight-bold pt-3 h6']) ?>
-
- - -
-
- -
'; - } ?> -
+ +
+
+ "col-form-label"]); ?> +
+
+ 'form-control']) ?> +
+
+
diff --git a/erp24/widgets/FileUploadWidget.php b/erp24/widgets/FileUploadWidget.php deleted file mode 100644 index b4e60fd4..00000000 --- a/erp24/widgets/FileUploadWidget.php +++ /dev/null @@ -1,129 +0,0 @@ -registerAssets(); - - return $this->renderContent(); - } - - // Рендеринг HTML-контента для виджета - protected function renderContent() - { - return Html::tag('div', - Html::tag('label', - Html::tag('input', '', ['type' => 'file', 'id' => $this->inputId, 'multiple' => true]) . - Html::tag('span', '+', ['class' => 'plus-icon']), - ['for' => $this->inputId, 'class' => 'upload-label'] - ), - ['id' => $this->containerId, 'class' => 'file-container'] - ); - } - - // Встраивание CSS и JS непосредственно в HTML - protected function registerAssets() - { - $view = $this->getView(); - - $css = <<inputId', function(event) { - const files = event.target.files; - - for (const file of files) { - const reader = new FileReader(); - reader.onload = function(e) { - const fileContainer = \$(
); - - const img = \$(); - const removeButton = \$(); - - removeButton.on('click', function() { - fileContainer.remove(); - }); - - fileContainer.append(img).append(removeButton); - \$('#$this->containerId').prepend(fileContainer); - }; - - reader.readAsDataURL(file); - } - }); - })(jQuery); - JS; - - // Вставляем CSS и JS в страницу - $view->registerCss($css); - $view->registerJs($js); - } -}