use Exception;
use Yii;
-use yii\helpers\ArrayHelper;
use yii\helpers\Url;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\web\Response;
-use yii\web\UploadedFile;
use yii_app\helpers\DataHelper;
-use yii_app\records\BouquetComposition;
-use yii_app\records\BouquetCompositionMatrixTypeHistory;
-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;
-use yii_app\services\FileService;
+use yii_app\records\{BouquetComposition,
+ BouquetCompositionProducts,
+ BouquetForecast,
+ CityStore,
+ Files,
+ MatrixType,
+ StoreType};
-/**
- * Контроллер для управления букетами и их составами.
- */
class BouquetController extends Controller
{
public function actionIndex()
{
- $query = BouquetComposition::find()->orderBy('id desc');
$request = Yii::$app->request;
- $year = $request->get('year');
- $month = $request->get('month');
- $matrixTypeId = $request->get('matrix_type_id');
+ $query = BouquetComposition::find()
+ ->orderBy(['id' => SORT_DESC])
+ ->groupBy('bouquet_composition.id');
- if ($year || $month) {
- $query->joinWith('bouquetForecast as bf');
- }
- if ($year) {
- $query->andWhere(['bf.year' => $year]);
- }
- if ($month) {
- $query->andWhere(['bf.month' => $month]);
- }
-
- if ($matrixTypeId) {
- $query->leftJoin('bouquet_composition_matrix_type_history mth', 'mth.bouquet_id = bouquet_composition.id');
- $query->andWhere(['mth.matrix_type_id' => $matrixTypeId, 'mth.is_active' => BouquetCompositionMatrixTypeHistory::IS_ACTIVE]);
- }
-
- $query->groupBy('bouquet_composition.id')
- ->orderBy('bouquet_composition.id desc');
+ BouquetComposition::applyFilters($query, $request);
$dataProvider = new \yii\data\ActiveDataProvider([
'query' => $query,
return $this->render('index', [
'dataProvider' => $dataProvider,
- 'matrix_type' => $matrixTypeId ? MatrixType::findOne($matrixTypeId)?->name : null,
+ 'matrix_type' => $request->get('matrix_type_id')
+ ? MatrixType::findOne($request->get('matrix_type_id'))?->name
+ : null,
]);
}
public function actionCreate()
{
- $model = new BouquetComposition();
- $errors = [];
- $flash = null;
-
- if (Yii::$app->request->isPost) {
- $data = Yii::$app->request->post();
- $model->guid = DataHelper::createGuidMy('07');
- $model->load($data);
- if ($model->save()) {
- $month = $data['month'];
- $year = $data['year'];
-
- if ($data['matrix_type_id']) {
- if (!empty(BouquetCompositionMatrixTypeHistory::setData($data['matrix_type_id'], $model->id))) {
- $errors = array_merge($errors, BouquetCompositionMatrixTypeHistory::setData($data['matrix_type_id'], $model->id));
- }
- }
-
- $model->photo_bouquet = UploadedFile::getInstances($model, 'photo_bouquet');
- if (!empty($model->photo_bouquet)) {
- Files::deleteAll(['file_type' => 'image', 'entity_id' => $model->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 (!empty($model->video_presentation)) {
- Files::deleteAll(['file_type' => 'video', 'entity_id' => $model->id, 'entity' => BouquetComposition::VIDEO_PRESENTATION]);
- foreach ($model->video_presentation as $video) {
- FileService::saveUploadedFile($video, BouquetComposition::VIDEO_PRESENTATION, $model->id);
- }
- }
-
- $model->video_build_process = UploadedFile::getInstances($model, 'video_build_process');
- if (!empty($model->video_build_process)) {
- Files::deleteAll(['file_type' => 'video', 'entity_id' => $model->id, 'entity' => BouquetComposition::VIDEO_BUILD_PROCESS]);
- foreach ($model->video_build_process as $video) {
- FileService::saveUploadedFile($video, BouquetComposition::VIDEO_BUILD_PROCESS, $model->id);
- }
- }
- if (!empty($data['BouquetForecast']['type_sales_value'])) {
- $salesData = $data['BouquetForecast']['type_sales_value'];
- $errors = [];
-
- if (!empty($salesData['offline'])) {
- $offlineErrors = BouquetForecast::processSalesData($model->id, $year, $month, $salesData['offline'], BouquetForecast::OFFLINE_STORES);
- if (!empty($offlineErrors)) {
- $errors = array_merge($errors, $offlineErrors);
- }
- }
-
- if (!empty($salesData['online'])) {
- $onlineErrors = BouquetForecast::processSalesData($model->id, $year, $month, $salesData['online'], BouquetForecast::ONLINE_STORES);
- if (!empty($onlineErrors)) {
- $errors = array_merge($errors, $onlineErrors);
- }
- }
-
- if (!empty($salesData['marketplace'])) {
- $marketplaceErrors = BouquetForecast::processSalesData($model->id, $year, $month, $salesData['marketplace'], BouquetForecast::MARKETPLACE);
- if (!empty($marketplaceErrors)) {
- $errors = array_merge($errors, $marketplaceErrors);
- }
- }
-
- if (!empty($errors)) {
- Yii::$app->session->setFlash('danger', implode(' ', array_map('json_encode', $errors)));
- }
- }
-
- if (array_key_exists('products_quantity', $data)) {
- BouquetCompositionProducts::deleteAll(['bouquet_id' => $model->id]);
- $bouquetProducts = Yii::$app->request->post('products_quantity');
- foreach ($bouquetProducts as $key => $value) {
- $product = new BouquetCompositionProducts([
- 'bouquet_id' => $model->id,
- 'product_guid' => $key,
- 'count' => $value
- ]);
- if (!$product->save()) {
- $errors = array_merge($errors, $product->getErrors());
- }
- }
- }
- Yii::$app->session->setFlash('success', 'Данные успешно сохранены');
- return $this->redirect(['view', 'id' => $model->id]);
- } else {
- $errors = $model->getErrors();
- }
- }
-
- $availableItems = ArrayHelper::map(
- Products1c::find()
- ->where([
- 'view' => Products1c::IS_VISIBLE,
- 'tip' => Products1c::TYPE_PRODUCTS
- ])
- ->all(),
- 'id',
- 'name'
- );
- $storesTypeList = BouquetForecast::getStoresList(null, BouquetForecast::OFFLINE_STORES, StoreType::class, []);
- $marketplaceList = BouquetForecast::getStoresList(null, BouquetForecast::MARKETPLACE, CityStore::class, ['visible' => CityStore::IS_VISIBLE]);
- $onlineStoresList = BouquetForecast::getStoresList(null, BouquetForecast::ONLINE_STORES, CityStore::class, ['visible' => CityStore::IS_VISIBLE]);
-
- if (!empty($errors)) {
- $flatErrors = array_merge([], ...array_values($errors));
- $flashMessage = !empty($flatErrors) ? implode(' ', $flatErrors) : 'Произошла ошибка.';
- Yii::$app->session->setFlash('danger', $flashMessage);
- }
-
- return $this->render('create', [
- 'onlineStoresList' => $onlineStoresList,
- 'marketplaceList' => $marketplaceList,
- 'storesTypeList' => $storesTypeList,
- 'availableItems' => $availableItems,
- 'flash' => $flash
- ]);
-
+ return $this->handleCreateOrUpdate(new BouquetComposition());
}
public function actionView($id)
{
- $model = BouquetComposition::findOne($id);
- $errors = [];
- $flash = null;
-
+ $model = BouquetComposition::findModel($id);
if (Yii::$app->request->isPost) {
- $data = Yii::$app->request->post();
- $model->load($data);
- if ($model->save()) {
-
- $month = $data['month'];
- $year = $data['year'];
-
- if ($data['matrix_type_id']) {
- if (!empty(BouquetCompositionMatrixTypeHistory::setData($data['matrix_type_id'], $model->id))) {
- $errors = array_merge($errors, BouquetCompositionMatrixTypeHistory::setData($data['matrix_type_id'], $model->id));
- }
- }
-
- $model->photo_bouquet = UploadedFile::getInstances($model, 'photo_bouquet');
- if (!empty($model->photo_bouquet)) {
- Files::deleteAll(['file_type' => 'image', 'entity_id' => $model->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 (!empty($model->video_presentation)) {
- Files::deleteAll(['file_type' => 'video', 'entity_id' => $model->id, 'entity' => BouquetComposition::VIDEO_PRESENTATION]);
- foreach ($model->video_presentation as $video) {
- FileService::saveUploadedFile($video, BouquetComposition::VIDEO_PRESENTATION, $model->id);
- }
- }
-
- $model->video_build_process = UploadedFile::getInstances($model, 'video_build_process');
- if (!empty($model->video_build_process)) {
- Files::deleteAll(['file_type' => 'video', 'entity_id' => $model->id, 'entity' => BouquetComposition::VIDEO_BUILD_PROCESS]);
- foreach ($model->video_build_process as $video) {
- FileService::saveUploadedFile($video, BouquetComposition::VIDEO_BUILD_PROCESS, $model->id);
- }
- }
-
-
- if (!empty($data['BouquetForecast']['type_sales_value'])) {
- $salesData = $data['BouquetForecast']['type_sales_value'];
- $errors = [];
-
- if (!empty($salesData['offline'])) {
- $offlineErrors = BouquetForecast::processSalesData($model->id, $year, $month, $salesData['offline'], BouquetForecast::OFFLINE_STORES);
- if (!empty($offlineErrors)) {
- $errors = array_merge($errors, $offlineErrors);
- }
- }
-
- if (!empty($salesData['online'])) {
- $onlineErrors = BouquetForecast::processSalesData($model->id, $year, $month, $salesData['online'], BouquetForecast::ONLINE_STORES);
- if (!empty($onlineErrors)) {
- $errors = array_merge($errors, $onlineErrors);
- }
- }
-
- if (!empty($salesData['marketplace'])) {
- $marketplaceErrors = BouquetForecast::processSalesData($model->id, $year, $month, $salesData['marketplace'], BouquetForecast::MARKETPLACE);
- if (!empty($marketplaceErrors)) {
- $errors = array_merge($errors, $marketplaceErrors);
- }
- }
-
- if (!empty($errors)) {
- Yii::$app->session->setFlash('danger', implode(' ', array_map('json_encode', $errors)));
- }
- }
-
- Yii::$app->session->setFlash('success', 'Данные успешно сохранены');
- } else {
- $errors = $model->getErrors();
- }
+ return $this->handleCreateOrUpdate($model);
}
+ return $this->renderView($model);
+ }
- $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], true), $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]);
-
+ public function actionUpdate($id)
+ {
+ $model = BouquetComposition::findModel($id);
- if (!empty($errors)) {
- $flatErrors = array_merge([], ...array_values($errors));
- $flashMessage = !empty($flatErrors) ? implode(' ', $flatErrors) : 'Произошла ошибка.';
- Yii::$app->session->setFlash('danger', $flashMessage);
+ if (Yii::$app->request->isPost && $products = Yii::$app->request->post('products_quantity')) {
+ BouquetCompositionProducts::updateProducts($model->id, $products);
+ return $this->redirect(['view', 'id' => $id]);
}
-
- return $this->render('view', [
+ return $this->render('update', [
'model' => $model,
- 'onlineStoresList' => $onlineStoresList,
- 'bouquetCompositionProducts' => $bouquetCompositionProducts,
- 'marketplaceList' => $marketplaceList,
- 'storesTypeList' => $storesTypeList,
- 'photoUrls' => $photoUrls,
- 'photoFiles' => $photoFiles,
- 'videoUrls' => $videoUrls,
- 'processUrls' => $processUrls,
- 'flash' => $flash
- ]);
- }
-
-
- /**
- * Сохранение записи в таблицу files
- */
- private function saveFileRecord($bouquetId, $filePath, $type)
- {
- $file = new Files([
- 'url' => $filePath,
- 'file_type' => $type,
- 'created_at' => time(),
- 'entity_id' => $bouquetId,
- 'entity' => 'bouquet/photo',
+ 'selectedItems' => BouquetCompositionProducts::getSelectedItems($model->id),
+ 'availableItems' => BouquetCompositionProducts::getAvailableItems($model->isNewRecord, $model->id),
]);
- $file->save();
}
- public function actionUpdate($id)
+ private function handleCreateOrUpdate(BouquetComposition $model)
{
- $model = BouquetComposition::findOne($id);
-
- if (!$model) {
- throw new NotFoundHttpException('Букет не найден.');
+ $request = Yii::$app->request;
+ if (!$request->isPost) {
+ return $this->renderCreate($model);
}
- if (Yii::$app->request->isPost) {
- try {
- if (Yii::$app->request->post('products_quantity')) {
- BouquetCompositionProducts::deleteAll(['bouquet_id' => $model->id]);
- $bouquetProducts = Yii::$app->request->post('products_quantity');
- foreach ($bouquetProducts as $key => $value) {
- $product = new BouquetCompositionProducts([
- 'bouquet_id' => $id,
- 'product_guid' => $key,
- 'count' => $value
- ]);
- $product->save();
- }
+ $data = $request->post();
+ $isNew = $model->isNewRecord;
- return $this->redirect(['view', 'id' => $id]);
- }
- } catch (Exception $exception) {
- throw new NotFoundHttpException($exception->getMessage());
- }
+ if ($isNew) {
+ $model->guid = DataHelper::createGuidMy('07');
}
- $products = BouquetCompositionProducts::find()
- ->where(['bouquet_id' => $model->id])
- ->with('product')
- ->all();
+ if (isset($data['matrix_type_id'])) {
+ $model->matrix_type_id = (int) $data['matrix_type_id'];
+ }
- $selectedItems = array_map(fn($product) => [
- 'id' => $product->product_guid,
- 'count' => $product->count,
- 'text' => $product->product->name ?? ''
- ], $products);
+ $transaction = Yii::$app->db->beginTransaction();
+ try {
+ if (!$model->load($data) || !$model->validate() || !$model->save()) {
+ throw new Exception('Ошибка при сохранении букета');
+ }
- $selectedProductIds = array_column($selectedItems, 'id');
+ $model->processRelations($data);
+ $transaction->commit();
+ Yii::$app->session->setFlash('success', 'Данные успешно сохранены');
+ return $this->redirect(['view', 'id' => $model->id]);
+ } catch (Exception $e) {
+ $transaction->rollBack();
+ $model->setErrorFlash($model->getErrors() ?: [$e->getMessage()]);
+ return $isNew ? $this->renderCreate($model) : $this->renderView($model);
+ }
+ }
- $availableItems = ArrayHelper::map(
- Products1c::find()
- ->where([
- 'view' => Products1c::IS_VISIBLE,
- 'tip' => Products1c::TYPE_PRODUCTS
- ])
- ->andWhere(['not in', 'id', $selectedProductIds])
- ->all(),
- 'id',
- 'name'
- );
- return $this->render('update', [
- 'model' => $model,
- 'selectedItems' => $selectedItems,
- 'availableItems' => $availableItems,
+ private function renderCreate(BouquetComposition $model)
+ {
+ return $this->render('create', [
+ 'onlineStoresList' => BouquetForecast::getStoresList(null, BouquetForecast::ONLINE_STORES, CityStore::class, ['visible' => CityStore::IS_VISIBLE]),
+ 'marketplaceList' => BouquetForecast::getStoresList(null, BouquetForecast::MARKETPLACE, CityStore::class, ['visible' => CityStore::IS_VISIBLE]),
+ 'storesTypeList' => BouquetForecast::getStoresList(null, BouquetForecast::OFFLINE_STORES, StoreType::class, []),
+ 'availableItems' => BouquetCompositionProducts::getAvailableItems($model->isNewRecord, $model->id),
]);
}
- public function actionUpload()
+ private function renderView(BouquetComposition $model)
{
- $model = new BouquetComposition();
+ $files = $model->getFiles();
- if (Yii::$app->request->isPost) {
- $model->photo_id = UploadedFile::getInstance($model, 'photo_id');
- if ($model->validate() && $model->upload()) {
- // Логика для сохранения
- }
- }
-
- return $this->render('upload', ['model' => $model]);
+ return $this->render('view', [
+ 'model' => $model,
+ 'onlineStoresList' => BouquetForecast::getStoresList($model->id, BouquetForecast::ONLINE_STORES, CityStore::class, ['visible' => CityStore::IS_VISIBLE]),
+ 'marketplaceList' => BouquetForecast::getStoresList($model->id, BouquetForecast::MARKETPLACE, CityStore::class, ['visible' => CityStore::IS_VISIBLE]),
+ 'storesTypeList' => BouquetForecast::getStoresList($model->id, BouquetForecast::OFFLINE_STORES, StoreType::class, []),
+ 'bouquetCompositionProducts' => BouquetCompositionProducts::getCompositionProducts($model->id),
+ 'photoUrls' => array_map(fn($file) => Url::to([$file->url], true), $files['photo']),
+ 'photoFiles' => $files['photo'],
+ 'videoUrls' => array_map(fn($file) => Url::to([$file->url]), $files['video']),
+ 'processUrls' => array_map(fn($file) => Url::to([$file->url]), $files['process']),
+ ]);
}
- public function actionGetList()
+ public function actionGetCalculates()
{
- \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
-
- $request = Yii::$app->request->post();
-
- $conditions = array_filter([
- 'type-num' => $request['type-num'] ?? null,
- 'color' => $request['color'] ?? null,
- 'species' => $request['species'] ?? null,
- 'category' => $request['category'] ?? null,
- 'size' => $request['size'] ?? null,
- ]);
-
- $subqueries = [];
- foreach ($conditions as $field => $value) {
- $subqueries[] = Products1cNomenclature::find()->select('id')->where([$field => $value]);
- }
-
- $query = Products1c::find()->where([
- 'tip' => Products1c::TYPE_PRODUCTS,
- 'view' => Products1c::IS_VISIBLE,
- ]);
-
- if (!empty($subqueries)) {
- foreach ($subqueries as $subquery) {
- $query->andWhere(['in', 'id', $subquery]);
- }
- }
+ Yii::$app->response->format = Response::FORMAT_JSON;
+ $data = json_decode(Yii::$app->request->getRawBody(), true);
+ $model = new BouquetComposition();
- return ArrayHelper::map($query->asArray()->all(), 'id', 'name');
+ return [
+ 'selfcost' => round($model->getSelfCost($data), 2),
+ 'cost' => round($model->getCost($data), 2),
+ 'markup' => $model->getMarkUp($data),
+ ];
}
public function actionGetSalesData()
];
}
- public function actionGetCalculates()
- {
- Yii::$app->response->format = Response::FORMAT_JSON;
- $data = json_decode(Yii::$app->request->getRawBody(), true);
- $model = new BouquetComposition();
-
- return [
- 'selfcost' => round($model->getSelfCost($data), 2),
- 'cost' => round($model->getCost($data), 2),
- 'markup' => $model->getMarkUp($data),
- ];
- }
-
-}
+}
\ No newline at end of file
namespace yii_app\records;
+use Exception;
use Yii;
use yii\behaviors\BlameableBehavior;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\db\Expression;
-use yii_app\records\BouquetCompositionProducts;
+use yii\web\UploadedFile;
+use yii_app\services\FileService;
/**
* This is the model class for table "erp24.bouquet_composition".
* @property int|null $updated_by
*
* @property BouquetCompositionProducts[] $bouquetCompositionProducts
+ * @property BouquetCompositionMatrixTypeHistory $matrixType
+ * @property Files $presentation
+ * @property Files $buildProcess
+ * @property BouquetForecast[] $bouquetForecast
+ *
+ * @property UploadedFile[] $photo_bouquet
+ * @property UploadedFile[] $video_presentation
+ * @property UploadedFile[] $video_build_process
*/
class BouquetComposition extends ActiveRecord
{
- public $photo_bouquet;
- public $video_presentation;
- public $video_build_process;
-
+ public const PHOTO_TYPE = 'image';
+ public const VIDEO_TYPE = 'video';
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()
+ public $photo_bouquet;
+ public $matrix_type_id;
+ public $video_presentation;
+ public $video_build_process;
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function tableName(): string
{
return 'erp24.bouquet_composition';
}
- public function behaviors()
+ /**
+ * {@inheritdoc}
+ */
+ public function behaviors(): array
{
return [
[
];
}
-
- public function rules()
+ /**
+ * {@inheritdoc}
+ */
+ public function rules(): array
{
return [
- [['name'], 'required', 'message' => 'Поле "Название" не может быть пустым.'],
- [['created_by', 'updated_by'], 'integer'],
+ [['matrix_type_id'], 'required', 'message' => 'Поле "Тип Матрицы" не может быть пустым'],
+ [['name'], 'required', 'message' => 'Поле "Название" не может быть пустым'],
+ [['created_by', 'updated_by'], 'integer', 'message' => '{attribute} должен быть целым числом'],
[['created_at', 'updated_at'], 'safe'],
- [['guid', 'name'], 'string', 'max' => 255],
-
+ [['guid', 'name'], 'string', 'max' => 255, 'tooLong' => '{attribute} не должен превышать 255 символов'],
[['photo_bouquet'], 'file',
'extensions' => 'jpg, jpeg, png, gif',
'maxFiles' => 10,
- 'message' => 'Допустимые форматы: jpg, jpeg, png, gif. Максимум 10 файлов.'
+ 'message' => 'Допустимые форматы: jpg, jpeg, png, gif. Максимум 10 файлов',
],
-
[['video_presentation', 'video_build_process'], 'file',
'extensions' => 'mkv, mov, avi, mp4',
- 'message' => 'Допустимые форматы видео: mkv, mov, avi, mp4.'
+ 'message' => 'Допустимые форматы видео: mkv, mov, avi, mp4',
],
];
}
- public function attributeLabels()
+ /**
+ * {@inheritdoc}
+ */
+ public function attributeLabels(): array
{
return [
'id' => 'ID',
];
}
+ /**
+ * Поиск модели по ID.
+ *
+ * @param int $id ID букета
+ * @return self
+ * @throws \yii\web\NotFoundHttpException Если букет не найден
+ */
+ public static function findModel(int $id): self
+ {
+ if ($model = static::findOne($id)) {
+ return $model;
+ }
+ throw new \yii\web\NotFoundHttpException('Букет не найден.');
+ }
+
+ /**
+ * Применяет фильтры к запросу букетов.
+ *
+ * @param \yii\db\ActiveQuery $query Запрос для фильтрации
+ * @param \yii\web\Request $request HTTP-запрос с параметрами фильтрации
+ */
+ public static function applyFilters($query, $request): void
+ {
+ $year = $request->get('year');
+ $month = $request->get('month');
+
+ if ($year || $month) {
+ $query->joinWith('bouquetForecast as bf');
+ if ($year) {
+ $query->andWhere(['bf.year' => $year]);
+ }
+ if ($month) {
+ $query->andWhere(['bf.month' => $month]);
+ }
+ }
+
+ if ($matrixTypeId = $request->get('matrix_type_id')) {
+ $query->leftJoin(
+ 'erp24.bouquet_composition_matrix_type_history mth', // Добавили схему
+ 'mth.bouquet_id = bouquet_composition.id'
+ )->andWhere([
+ 'mth.matrix_type_id' => $matrixTypeId,
+ 'mth.is_active' => true // Используем true без константы
+ ]);
+ }
+ }
+
+ /**
+ * Обрабатывает связанные данные букета (матрицы, файлы, продажи, продукты).
+ *
+ * @param array $data Данные из формы
+ */
+ public function processRelations(array $data): void
+ {
+ if (!$this->id) {
+ throw new Exception("ID букета не установлен при обработке связей.");
+ }
+
+ if (!isset($data['matrix_type_id'])) {
+ throw new Exception('Тип Матрицы обязателен.');
+ }
+
+ BouquetCompositionMatrixTypeHistory::updateMatrixType(
+ $this->id,
+ isset($data['matrix_type_id']) ? (int) $data['matrix_type_id'] : null
+ );
+
+ $this->processFiles();
+
+ // Проверяем, передаётся ли id букета
+ if (!empty($data)) {
+ BouquetForecast::processSalesData($this->id, $data);
+ }
+
+ if (!empty($data['products_quantity'])) {
+ BouquetCompositionProducts::updateProducts($this->id, $data['products_quantity']);
+ }
+ }
+
+ /**
+ * Обрабатывает загрузку файлов (фото и видео).
+ */
+ public function processFiles(): void
+ {
+ $fileTypes = [
+ 'photo_bouquet' => [self::PHOTO_TYPE, self::PHOTO_BOUQUET],
+ 'video_presentation' => [self::VIDEO_TYPE, self::VIDEO_PRESENTATION],
+ 'video_build_process' => [self::VIDEO_TYPE, self::VIDEO_BUILD_PROCESS],
+ ];
+
+ foreach ($fileTypes as $attribute => [$type, $entity]) {
+ $files = UploadedFile::getInstances($this, $attribute);
+ if ($files) {
+ Files::deleteAll(['file_type' => $type, 'entity_id' => $this->id, 'entity' => $entity]);
+ foreach ($files as $file) {
+ FileService::saveUploadedFile($file, $entity, $this->id);
+ }
+ }
+ }
+ }
+
+ /**
+ * Возвращает связанные файлы букета.
+ *
+ * @return array Ассоциативный массив файлов (photo, video, process)
+ */
+ public function getFiles(): array
+ {
+ return [
+ 'photo' => Files::find()->where(['entity_id' => $this->id, 'file_type' => self::PHOTO_TYPE, 'entity' => self::PHOTO_BOUQUET])->all(),
+ 'video' => Files::find()->where(['entity_id' => $this->id, 'file_type' => self::VIDEO_TYPE, 'entity' => self::VIDEO_PRESENTATION])->all(),
+ 'process' => Files::find()->where(['entity_id' => $this->id, 'file_type' => self::VIDEO_TYPE, 'entity' => self::VIDEO_BUILD_PROCESS])->all(),
+ ];
+ }
+
+ /**
+ * Устанавливает сообщение об ошибке во флэш.
+ *
+ * @param array $errors Массив ошибок
+ */
+ public function setErrorFlash(array $errors): void
+ {
+ $message = 'Произошла ошибка.';
+
+ if (!empty($errors)) {
+ $flattenedErrors = [];
+
+ foreach ($errors as $error) {
+ if (is_array($error)) {
+ $flattenedErrors = array_merge($flattenedErrors, $error);
+ } elseif (is_string($error)) {
+ $flattenedErrors[] = $error;
+ }
+ }
+
+ $message = implode(' ', $flattenedErrors);
+ }
+
+ Yii::$app->session->setFlash('danger', $message);
+ }
+
+
+ /**
+ * Возвращает связанные продукты букета.
+ *
+ * @return \yii\db\ActiveQuery
+ */
public function getBouquetCompositionProducts()
{
return $this->hasMany(BouquetCompositionProducts::class, ['bouquet_id' => 'id']);
}
+ /**
+ * Возвращает активную запись истории типа матрицы.
+ *
+ * @return \yii\db\ActiveQuery
+ */
public function getMatrixType()
{
return $this->hasOne(BouquetCompositionMatrixTypeHistory::class, ['bouquet_id' => 'id'])
->andWhere(['is_active' => BouquetCompositionMatrixTypeHistory::IS_ACTIVE]);
}
+ /**
+ * Возвращает видео-презентацию букета.
+ *
+ * @return \yii\db\ActiveQuery
+ */
public function getPresentation()
{
return $this->hasOne(Files::class, ['entity_id' => 'id'])
->andWhere(['entity' => self::VIDEO_PRESENTATION]);
}
+ /**
+ * Возвращает видео процесса сборки букета.
+ *
+ * @return \yii\db\ActiveQuery
+ */
public function getBuildProcess()
{
return $this->hasOne(Files::class, ['entity_id' => 'id'])
- ->andWhere(['entity' => self::VIDEO_BUILD_PROCESS]);
+ ->andWhere(['entity' => self::VIDEO_BUILD_PROCESS]);
}
- public function getCost($data = null)
+ /**
+ * Возвращает прогнозы продаж букета.
+ *
+ * @return \yii\db\ActiveQuery
+ */
+ public function getBouquetForecast()
{
- $cost = 0;
+ return $this->hasMany(BouquetForecast::class, ['bouquet_id' => 'id']);
+ }
+ /**
+ * Рассчитывает стоимость букета.
+ *
+ * @param array|null $data Данные продуктов (опционально)
+ * @return float Стоимость
+ */
+ public function getCost(?array $data = null): float
+ {
+ $cost = 0;
$compositionProducts = $this->bouquetCompositionProducts;
if (!$compositionProducts) {
return $cost;
}
- public function getMarkUp($data = null)
+ /**
+ * Рассчитывает наценку на букет.
+ *
+ * @param array|null $data Данные продуктов (опционально)
+ * @return string Наценка в формате "+X.XX% / +X.XX"
+ */
+ public function getMarkUp(?array $data = null): string
{
- if ($this->getSelfCost($data) == 0 || $this->getCost($data) == 0)
- return 0;
+ $selfCost = $this->getSelfCost($data);
+ $cost = $this->getCost($data);
+
+ if ($selfCost == 0 || $cost == 0) {
+ return '0';
+ }
return sprintf("+%.2f%% / +%.2f",
- (self::getCost($data) / self::getSelfCost($data) - 1) * 100,
- self::getCost($data) - self::getSelfCost($data)
+ ($cost / $selfCost - 1) * 100,
+ $cost - $selfCost
);
}
-
- public function getSelfCost($data = null)
+ /**
+ * Рассчитывает себестоимость букета.
+ *
+ * @param array|null $data Данные продуктов (опционально)
+ * @return float Себестоимость
+ */
+ public function getSelfCost(?array $data = null): float
{
$selfCost = 0;
$compositionProducts = $this->bouquetCompositionProducts;
return $selfCost;
}
}
-
- $productGuids = array_filter(array_column($compositionProducts, 'product_guid'));
-
+
+ $productGuids = array_filter(array_column($compositionProducts, 'product_guid'));
if (empty($productGuids)) {
return $selfCost;
}
}
$prices = $pricesByProduct[$item['product_guid']] ?? [];
-
$count = count($prices);
if ($count == 0) {
return $selfCost;
}
- public function getBouquetForecast()
+ /**
+ * Возвращает список доступных годов для выбора.
+ *
+ * @return array Массив годов в формате [год => год]
+ */
+ public static function getYears(): array
{
- return $this->hasMany(BouquetForecast::class, ['bouquet_id' => 'id']);
- }
-
- public static function getYears()
- {
- $currentYear = date('Y');
+ $currentYear = (int)date('Y');
$years = range($currentYear - 5, $currentYear + 5);
return array_combine($years, $years);
}
- public static function disabledButtons($isCreate = false)
+ /**
+ * Проверяет, должны ли кнопки быть отключены.
+ *
+ * @param bool $isCreate Флаг создания нового букета
+ * @return bool True, если кнопки должны быть отключены
+ */
+ public static function disabledButtons(bool $isCreate = false): bool
{
+ return false;
if ($isCreate) {
return false;
}
-
- return date('d') > 10;
+ return (int)date('d') > 10;
}
-
-}
+}
\ No newline at end of file
<?php
+
namespace yii_app\records;
+use Exception;
use Yii;
use yii\behaviors\BlameableBehavior;
use yii\behaviors\TimestampBehavior;
* @property int|null $matrix_type_id Тип матрицы ИД
* @property int|null $date_from Дата установки
* @property int|null $date_to Дата изменения
- * @property boolean|true $is_active Активна ли запись
+ * @property bool $is_active Активна ли запись
* @property string|null $created_at Дата создания
* @property string|null $updated_at Дата обновления
* @property int|null $created_by ID создателя записи
class BouquetCompositionMatrixTypeHistory extends ActiveRecord
{
public const IS_ACTIVE = true;
+ public const IS_INACTIVE = false;
- public function behaviors()
+ /**
+ * {@inheritdoc}
+ */
+ public static function tableName(): string
+ {
+ return '{{%erp24.bouquet_composition_matrix_type_history}}';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function behaviors(): array
{
return [
[
];
}
-
/**
* {@inheritdoc}
*/
- public static function tableName()
- {
- return '{{%erp24.bouquet_composition_matrix_type_history}}';
- }
-
- /**
- * {@inheritdoc}
- */
- public function rules()
+ public function rules(): array
{
return [
- [['bouquet_id', 'matrix_type_id', 'created_by', 'updated_by'], 'integer'],
- [['created_at', 'updated_at','date_to', 'date_from'], 'safe'],
- [['is_active'], 'boolean']
+ [['bouquet_id', 'matrix_type_id', 'created_by', 'updated_by'], 'integer', 'message' => '{attribute} должен быть целым числом'],
+ [['date_from', 'date_to', 'created_at', 'updated_at'], 'safe'],
+ [['is_active'], 'boolean', 'message' => '{attribute} должен быть логическим значением'],
+ [['bouquet_id', 'matrix_type_id'], 'required', 'message' => '{attribute} обязателен для заполнения'],
];
}
/**
* {@inheritdoc}
*/
- public function attributeLabels()
+ public function attributeLabels(): array
{
return [
'id' => 'ID',
];
}
- public static function setData($value, $bouquetId) {
- if (self::findOne(['bouquet_id' => $bouquetId])) {
- BouquetCompositionMatrixTypeHistory::updateAll(['date_to' => date('Y-m-d H:i:s'), 'bouquet_id' => $bouquetId, 'date_from' => null, 'is_active' => self::IS_ACTIVE]);
+ /**
+ * Обновляет или создает запись истории типа матрицы для букета.
+ *
+ * @param int $bouquetId ID букета
+ * @param int|null $matrixTypeId ID типа матрицы
+ * @throws Exception Если сохранение не удалось
+ */
+ public static function updateMatrixType(int $bouquetId, ?int $matrixTypeId): void
+ {
+ if (!$matrixTypeId) {
+ return; // Если тип матрицы не указан, ничего не делаем
}
- $matrixHistoryType = new BouquetCompositionMatrixTypeHistory([
- 'bouquet_id' => $bouquetId,
- 'matrix_type_id' => $value,
- 'date_from' => new Expression('now()'),
- ]);
- if (!$matrixHistoryType->save()) {
- return $matrixHistoryType->errors();
+ $transaction = Yii::$app->db->beginTransaction();
+ try {
+ // Деактивируем текущую активную запись
+ static::updateAll(
+ [
+ 'date_to' => new Expression('NOW()'),
+ 'is_active' => self::IS_INACTIVE,
+ ],
+ [
+ 'bouquet_id' => $bouquetId,
+ 'is_active' => self::IS_ACTIVE,
+ ]
+ );
+
+ // Создаем новую запись
+ $matrixHistoryType = new self([
+ 'bouquet_id' => $bouquetId,
+ 'matrix_type_id' => $matrixTypeId,
+ 'date_from' => new Expression('NOW()'),
+ 'is_active' => self::IS_ACTIVE,
+ ]);
+
+ if (!$matrixHistoryType->save()) {
+ throw new Exception(implode(' ', $matrixHistoryType->getFirstErrors()));
+ }
+
+ $transaction->commit();
+ } catch (Exception $e) {
+ $transaction->rollBack();
+ throw $e;
}
}
/**
- * Отношение к букету
+ * Отношение к букету.
+ *
+ * @return \yii\db\ActiveQuery
*/
public function getBouquet()
{
}
/**
- * Отношение к типу матрицы
+ * Отношение к типу матрицы.
+ *
+ * @return \yii\db\ActiveQuery
*/
public function getMatrixType()
{
return $this->hasOne(MatrixType::class, ['id' => 'matrix_type_id']);
}
-}
+}
\ No newline at end of file
<?php
+
namespace yii_app\records;
use Yii;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\db\Expression;
+use yii\helpers\ArrayHelper;
/**
* This is the model class for table "erp24.bouquet_composition_products".
*
* @property int $id
- * @property int $bouquet_id
- * @property string $product_guid
- * @property float|null $count
- * @property string|null $created_at
- * @property string|null $updated_at
- * @property int|null $created_by
- * @property int|null $updated_by
+ * @property int $bouquet_id ID букета
+ * @property string $product_guid GUID продукта
+ * @property float|null $count Количество продукта
+ * @property string|null $created_at Дата создания
+ * @property string|null $updated_at Дата обновления
+ * @property int|null $created_by ID создателя записи
+ * @property int|null $updated_by ID обновителя записи
*
* @property BouquetComposition $bouquet
+ * @property Products1c $product
*/
class BouquetCompositionProducts extends ActiveRecord
{
- public static function tableName()
+ /**
+ * {@inheritdoc}
+ */
+ public static function tableName(): string
{
return 'erp24.bouquet_composition_products';
}
- public function behaviors()
+ /**
+ * {@inheritdoc}
+ */
+ public function behaviors(): array
{
return [
[
];
}
-
- public function rules()
+ /**
+ * {@inheritdoc}
+ */
+ public function rules(): array
{
return [
- [['bouquet_id', 'product_guid'], 'required'],
- [['bouquet_id', 'created_by', 'updated_by'], 'integer'],
- [['count'], 'number'],
+ [['bouquet_id', 'product_guid'], 'required', 'message' => '{attribute} обязателен для заполнения'],
+ [['bouquet_id', 'created_by', 'updated_by'], 'integer', 'message' => '{attribute} должен быть целым числом'],
+ [['count'], 'number', 'message' => '{attribute} должен быть числом'],
[['created_at', 'updated_at'], 'safe'],
- [['product_guid'], 'string', 'max' => 255],
- [['bouquet_id'], 'exist', 'skipOnError' => true, 'targetClass' => BouquetComposition::class, 'targetAttribute' => ['bouquet_id' => 'id']],
+ [['product_guid'], 'string', 'max' => 255, 'tooLong' => '{attribute} не должен превышать 255 символов'],
+ [['bouquet_id'], 'exist', 'skipOnError' => true, 'targetClass' => BouquetComposition::class, 'targetAttribute' => ['bouquet_id' => 'id'], 'message' => 'Указанный {attribute} не существует'],
];
}
- public function attributeLabels()
+ /**
+ * {@inheritdoc}
+ */
+ public function attributeLabels(): array
{
return [
'id' => 'ID',
];
}
+ /**
+ * Обновляет продукты для указанного букета.
+ *
+ * @param int $bouquetId ID букета
+ * @param array $products Массив продуктов в формате [product_guid => count]
+ */
+ public static function updateProducts(int $bouquetId, array $products): void
+ {
+ self::deleteAll(['bouquet_id' => $bouquetId]);
+ foreach ($products as $guid => $count) {
+ $product = new self([
+ 'bouquet_id' => $bouquetId,
+ 'product_guid' => $guid,
+ 'count' => $count,
+ ]);
+ $product->save();
+ }
+ }
+
+ /**
+ * Возвращает список доступных продуктов для выбора.
+ *
+ * @param bool $isNewRecord Является ли букет новым
+ * @param int|null $bouquetId ID букета (если не новый)
+ * @return array Массив доступных продуктов в формате [id => name]
+ */
+ public static function getAvailableItems(bool $isNewRecord, ?int $bouquetId = null): array
+ {
+ $query = Products1c::find()
+ ->where(['view' => Products1c::IS_VISIBLE, 'tip' => Products1c::TYPE_PRODUCTS]);
+
+ if (!$isNewRecord && $bouquetId) {
+ $selectedIds = self::find()
+ ->select('product_guid')
+ ->where(['bouquet_id' => $bouquetId])
+ ->column();
+ $query->andWhere(['not in', 'id', $selectedIds]);
+ }
+
+ return ArrayHelper::map($query->all(), 'id', 'name');
+ }
+
+ /**
+ * Возвращает список выбранных продуктов для букета.
+ *
+ * @param int $bouquetId ID букета
+ * @return array Массив выбранных продуктов в формате [['id' => ..., 'count' => ..., 'text' => ...], ...]
+ */
+ public static function getSelectedItems(int $bouquetId): array
+ {
+ return array_map(
+ fn($product) => [
+ 'id' => $product->product_guid,
+ 'count' => $product->count,
+ 'text' => $product->product->name ?? ''
+ ],
+ self::find()->where(['bouquet_id' => $bouquetId])->with('product')->all()
+ );
+ }
+
+ /**
+ * Возвращает все продукты, связанные с букетом.
+ *
+ * @param int $bouquetId ID букета
+ * @return static[] Массив объектов продуктов
+ */
+ public static function getCompositionProducts(int $bouquetId): array
+ {
+ return self::find()->where(['bouquet_id' => $bouquetId])->all();
+ }
+
+ /**
+ * Отношение к букету.
+ *
+ * @return \yii\db\ActiveQuery
+ */
public function getBouquet()
{
return $this->hasOne(BouquetComposition::class, ['id' => 'bouquet_id']);
}
- public function getProduct() {
+ /**
+ * Отношение к продукту.
+ *
+ * @return \yii\db\ActiveQuery
+ */
+ public function getProduct()
+ {
return $this->hasOne(Products1c::class, ['id' => 'product_guid']);
}
-
-}
+}
\ No newline at end of file
use yii\db\Expression;
/**
- * This is the model class for table "bouquet_forecast".
+ * This is the model class for table "erp24.bouquet_forecast".
*
* @property int $id
* @property int|null $bouquet_id ИД букета
*/
class BouquetForecast extends ActiveRecord
{
-
public const OFFLINE_STORES = 1;
public const ONLINE_STORES = 2;
public const MARKETPLACE = 3;
/**
* {@inheritdoc}
*/
- public static function tableName()
+ public static function tableName(): string
{
return '{{%erp24.bouquet_forecast}}';
}
- public function behaviors()
+ /**
+ * {@inheritdoc}
+ */
+ public function behaviors(): array
{
return [
[
/**
* {@inheritdoc}
*/
- public function rules()
+ public function rules(): array
{
return [
- [['bouquet_id', 'year', 'month', 'type_sales', 'type_sales_id', 'created_by', 'updated_by'], 'integer'],
- [['type_sales_value'], 'number'],
+ [['bouquet_id', 'year', 'month', 'type_sales', 'type_sales_id', 'created_by', 'updated_by'], 'integer', 'message' => '{attribute} должен быть целым числом'],
+ [['type_sales_value'], 'number', 'message' => '{attribute} должен быть числом'],
[['created_at', 'updated_at'], 'safe'],
+ [['bouquet_id', 'year', 'month', 'type_sales', 'type_sales_id'], 'required', 'message' => '{attribute} обязателен для заполнения'],
];
}
/**
* {@inheritdoc}
*/
- public function attributeLabels()
+ public function attributeLabels(): array
{
return [
'id' => 'ID',
];
}
- public static function getStoresList($id, $typeSales, $defaultModel, $defaultCondition, $month = null, $year = null)
+ /**
+ * Возвращает список магазинов с данными о продажах или без них.
+ *
+ * @param int|null $bouquetId ID букета
+ * @param int $typeSales Тип продаж (OFFLINE_STORES, ONLINE_STORES, MARKETPLACE)
+ * @param string $defaultModel Класс модели (CityStore или StoreType)
+ * @param array $defaultCondition Условия для выборки магазинов
+ * @param int|null $month Месяц (по умолчанию текущий)
+ * @param int|null $year Год (по умолчанию текущий)
+ * @return array Список магазинов с данными о продажах
+ */
+ public static function getStoresList(?int $bouquetId, int $typeSales, string $defaultModel, array $defaultCondition, ?int $month = null, ?int $year = null): array
{
$joinTable = $defaultModel === CityStore::class ? 'city_store' : 'store_type';
+ $month = $month ?? (int)date('m');
+ $year = $year ?? (int)date('Y');
-
- $list = BouquetForecast::find()
- ->andWhere(['bouquet_id' => $id])
+ $list = self::find()
+ ->andWhere(['bouquet_id' => $bouquetId])
->andWhere(['type_sales' => $typeSales])
- ->andWhere(['month' => $month ?? date('m')])
- ->andWhere([ 'year' => $year ?? date('Y')])
+ ->andWhere(['month' => $month])
+ ->andWhere(['year' => $year])
->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')
return $list;
}
- public static function processSalesData($id, $year, $month, $salesData, $salesType)
+ /**
+ * Обрабатывает данные о продажах для букета.
+ *
+ * @param int $bouquetId ID букета
+ * @param array $data Данные формы, содержащие BouquetForecast[type_sales_value]
+ */
+ public static function processSalesData(int $bouquetId, array $data): void
+ {
+ if (empty($data['BouquetForecast']['type_sales_value'])) {
+ return;
+ }
+
+ $salesData = $data['BouquetForecast']['type_sales_value'];
+ $year = $data['year'];
+ $month = $data['month'];
+ $errors = [];
+
+ $types = [
+ 'offline' => self::OFFLINE_STORES,
+ 'online' => self::ONLINE_STORES,
+ 'marketplace' => self::MARKETPLACE,
+ ];
+
+ foreach ($types as $key => $type) {
+ if (empty($salesData[$key])) {
+ continue;
+ }
+
+ $typeErrors = self::updateSalesData($bouquetId, $year, $month, $salesData[$key], $type);
+ $errors = array_merge($errors, $typeErrors);
+ }
+
+ if ($errors) {
+ Yii::$app->session->setFlash('danger', implode(' ', array_map('json_encode', $errors)));
+ }
+ }
+
+ /**
+ * Обновляет или создает записи о продажах для конкретного типа.
+ *
+ * @param int $bouquetId ID букета
+ * @param int $year Год
+ * @param int $month Месяц
+ * @param array $salesData Данные о продажах в формате [type_sales_id => value]
+ * @param int $salesType Тип продаж
+ * @return array Массив ошибок (пустой, если ошибок нет)
+ */
+ private static function updateSalesData(int $bouquetId, int $year, int $month, array $salesData, int $salesType): array
{
$errors = [];
- foreach ($salesData as $key => $value) {
- $model = BouquetForecast::findOne([
- 'bouquet_id' => $id,
+ foreach ($salesData as $typeSalesId => $value) {
+ $model = self::findOne([
+ 'bouquet_id' => $bouquetId,
'year' => $year,
'month' => $month,
'type_sales' => $salesType,
- 'type_sales_id' => $key
+ 'type_sales_id' => $typeSalesId,
]);
- if (empty($model)) {
- $model = new BouquetForecast([
- 'bouquet_id' => $id,
+ if (!$model) {
+ $model = new self([
+ 'bouquet_id' => $bouquetId,
'year' => $year,
'month' => $month,
'type_sales' => $salesType,
- 'type_sales_id' => $key,
- 'type_sales_value' => $value
+ 'type_sales_id' => $typeSalesId,
+ 'type_sales_value' => $value,
]);
-
- if (!$model->save()) {
- $errors[] = $model->errors;
- }
} elseif ($model->type_sales_value != $value) {
- if (!$model->updateAttributes(['type_sales_value' => $value])) {
- $errors[] = $model->errors;
- }
+ $model->type_sales_value = $value;
+ } else {
+ continue; // Нет изменений, пропускаем
+ }
+
+ if (!$model->save()) {
+ $errors[] = $model->getFirstErrors();
}
}
'processUrls' => [],
'availableItems' => $availableItems,
'model' => null,
- 'flash' => $flash,
'id' => null
]); ?>
</div>
}
});
-$(document).on('click', '.calculate-btn, #w2-add, #w2-remove', function () {
+$(document).on('click', '.calculate-btn, .btn-add-item, .btn-remove-item', function () {
$('.cost-value, .selfcost-value, .markup-value').text('');
});
</select>
</div>
<div class='controls'>
- <button id='{$id}-add' class='btn btn-primary' type='button'>></button>
- <button id='{$id}-remove' class='btn btn-primary' type='button'><</button>
+ <button id='{$id}-add' class='btn btn-add-item btn-primary' type='button'>></button>
+ <button id='{$id}-remove' class='btn btn-remova-item btn-primary' type='button'><</button>
</div>
<div class='list-container'>
<div class='section-label'>{$this->selectedLabel}</div>