From b7a2d0a8ba94c4f9f043f5fa2e55f5882585fe61 Mon Sep 17 00:00:00 2001 From: fomichev Date: Fri, 27 Jun 2025 18:08:47 +0300 Subject: [PATCH] =?utf8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D1=81=D1=87?= =?utf8?q?=D0=B5=D1=82=20=D0=B2=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?utf8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/commands/CronController.php | 2 +- erp24/controllers/CategoryPlanController.php | 23 ++++ erp24/jobs/RebuildAutoplannogramJob.php | 128 +++++++++++++++++++ erp24/services/AutoPlannogrammaService.php | 24 +++- 4 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 erp24/jobs/RebuildAutoplannogramJob.php diff --git a/erp24/commands/CronController.php b/erp24/commands/CronController.php index 480b3789..f8a88280 100644 --- a/erp24/commands/CronController.php +++ b/erp24/commands/CronController.php @@ -1591,7 +1591,7 @@ class CronController extends Controller public function actionAutoplannogrammaCalculate(): void { $date = new DateTime(); - $date->modify('+2 months'); + $date->modify('+2 months'); // TODO модифицировать $planDate = $date->format('Y-m-01'); $month = (int)$date->format('m'); $year = (int)$date->format('Y'); diff --git a/erp24/controllers/CategoryPlanController.php b/erp24/controllers/CategoryPlanController.php index dacf448d..7805e95d 100644 --- a/erp24/controllers/CategoryPlanController.php +++ b/erp24/controllers/CategoryPlanController.php @@ -10,6 +10,7 @@ use yii\helpers\Json; use yii\web\Controller; use yii\web\Response; use yii_app\helpers\HtmlHelper; +use yii_app\jobs\RebuildAutoplannogramJob; use yii_app\records\CategoryPlan; use yii_app\records\CityStore; use yii_app\records\CityStoreParams; @@ -498,6 +499,28 @@ class CategoryPlanController extends Controller { )); } + + if (Yii::$app->request->get('rebuild') === '1' + ) { + Yii::$app->queue->push(new RebuildAutoplannogramJob([ + 'year' => (int)$model->year, + 'month' => (int)$model->month, + 'storeId' => (int)$model->store_id, + ])); + $params = [ + 'DynamicModel' => $model->attributes, + ]; + + $params['DynamicModel'] = array_filter($params['DynamicModel'], function($v) { + return $v !== null && $v !== ''; + }); + + return $this->redirect(array_merge( + ['index'], + $params + )); + } + $currentDate = new \DateTime(date($model->year . '-' . $model->month . '-01')); $startDate = (clone $currentDate)->modify('-4 months')->modify('first day of this month'); diff --git a/erp24/jobs/RebuildAutoplannogramJob.php b/erp24/jobs/RebuildAutoplannogramJob.php new file mode 100644 index 00000000..afa66d45 --- /dev/null +++ b/erp24/jobs/RebuildAutoplannogramJob.php @@ -0,0 +1,128 @@ +setDate($this->year, $this->month, 1); + $planDate = $date->format('Y-m-01'); + + /** @var CityStore $store */ + $store = CityStore::findOne($this->storeId); + if (!$store) { + throw new \RuntimeException("Store #{$this->storeId} not found"); + } + + try { + + $forecastParams = [ + 'month' => $this->month, + 'year' => $this->year, + 'type' => AutoPlannogrammaService::TYPE_SALES, + 'store_id' => $this->storeId, + 'category' => null, + 'subcategory' => null, + 'species' => null, + 'plan_date' => $planDate, + ]; + $forecast = $service->calculateFullForecastForWeek($forecastParams); + $writeOffsForecast = $service->getWeeklyProductsWriteoffsForecast( + $this->month, $this->year, $forecast, $this->storeId + ); + $salesForecast = $service->getWeeklyBouquetProductsSalesForecast( + $this->month, $this->year, $this->storeId + ); + + $existing = Autoplannogramma::find() + ->where([ + 'store_id' => $this->storeId, + 'year' => $this->year, + 'month' => $this->month, + 'week' => array_unique(ArrayHelper::getColumn($forecast, 'week')), + ]) + ->indexBy(fn($m) => $m->week . '_' . $m->product_id) + ->all(); + + foreach ($forecast as $item) { + $key = $item['week'] . '_' . $item['product_id']; + $model = $existing[$key] ?? new Autoplannogramma(); + $quantity = (float)($item['forecast_week_pieces'] ?? 0); + $details = []; + $total = $quantity; + + if (!empty($writeOffsForecast[$item['product_id']][$item['week']]['writeOffs'])) { + $w = $writeOffsForecast[$item['product_id']][$item['week']]['writeOffs']; + $details['writeOffs']['quantity'] = $w; + $total += is_array($w) ? array_sum($w) : (float)$w; + } + + foreach (['offline','online','marketplace'] as $t) { + $block = ['share'=>0,'quantity'=>0,'groups'=>[]]; + if (!empty($salesForecast[$this->storeId][$item['product_id']][$t])) { + $share = $salesForecast[$this->storeId][$t]['share'] ?? 0; + $block['share'] = $share; + $block['quantity'] = round($quantity * $share,2); + $total += $block['quantity']; + + foreach ($salesForecast[$this->storeId][$item['product_id']][$t] as $k=>$v) { + $block['groups'][$k] = (float)$v; + $total += (float)$v; + } + } + $details[$t] = $block; + } + + $details['forecast'] = ['quantity' => $quantity]; + $total = (float) sprintf('%.2f', $total); + + $needsUpdate = $model->isNewRecord + || $model->calculate != $quantity + || ceil($model->total) != ceil($total) + || json_encode($model->details, JSON_UNESCAPED_UNICODE) + !== json_encode($details, JSON_UNESCAPED_UNICODE); + + if ($needsUpdate) { + $model->setAttributes([ + 'year' => $this->year, + 'month' => $this->month, + 'week' => $item['week'], + 'product_id' => $item['product_id'], + 'store_id' => $this->storeId, + 'is_archive' => false, + 'capacity_type' => 1, + 'details' => json_encode($details, JSON_UNESCAPED_UNICODE), + 'calculate' => $quantity, + 'modify' => ceil($total), + 'total' => ceil($total), + ]); + if (!$model->save()) { + \Yii::error( + 'Ошибка сохранения Autoplannogramma: ' + . json_encode($model->getErrors(), JSON_UNESCAPED_UNICODE), + __METHOD__ + ); + } + } + } + } catch (Throwable $e) { + \Yii::error("Job failed: " . $e->getMessage(), __METHOD__); + } + } +} \ No newline at end of file diff --git a/erp24/services/AutoPlannogrammaService.php b/erp24/services/AutoPlannogrammaService.php index 0f56cfb3..b44fc7ff 100644 --- a/erp24/services/AutoPlannogrammaService.php +++ b/erp24/services/AutoPlannogrammaService.php @@ -9,6 +9,7 @@ use yii\db\mssql\PDO; use yii\db\Query; use yii\helpers\ArrayHelper; use yii_app\records\BouquetComposition; +use yii_app\records\CategoryPlan; use yii_app\records\CityStore; use yii_app\records\PricesDynamic; use yii_app\records\Products1cNomenclature; @@ -847,9 +848,28 @@ class AutoPlannogrammaService $datePlan = $filters['plan_date']; $dateFromForCategory = (new \DateTime($datePlan))->modify('-' . (self::CATEGORY_LOOKBACK_MONTHS + self::LOOKBACK_MONTHS) . ' months')->format('Y-m-d'); - $monthCategoryShare = $this->getMonthCategoryShareOrWriteOff($dateFromForCategory, $filters); - $monthCategoryGoal = $this->getMonthCategoryGoal($monthCategoryShare, $datePlan, $filters['type']); + //$monthCategoryShare = $this->getMonthCategoryShareOrWriteOff($dateFromForCategory, $filters); + //$monthCategoryGoal = $this->getMonthCategoryGoal($monthCategoryShare, $datePlan, $filters['type']); + $monthCategoryGoal = []; + $categoryPlan = CategoryPlan::find()->where(['year' => date('Y', strtotime($datePlan)), 'month' => date('m', strtotime($datePlan)), 'store_id' => $filters['store_id']])->indexBy('category')->asArray()->all(); + foreach ($categoryPlan as $category) { + if ($category['category'] === 'Матрица') { + continue; + } + if ($filters['type'] == AutoPlannogrammaService::TYPE_SALES) { + $fullGoal = $category['offline'] + $category['internet_shop'] + $category['marketplace']; + } else { + $fullGoal = $category['write_offs']; + } + + $monthCategoryGoal[] = [ + 'category' => $category['category'], + 'store_id' => $filters['store_id'], + 'goal' => $fullGoal + ]; + + } $monthSubcategoryShare = $this->getMonthSubcategoryShareOrWriteOff($datePlan, $filters); $monthSubcategoryGoal = $this->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal); -- 2.39.5