]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
ыыы feature_zozirova_erp-412_feedback_autopplannogramma
authormarina <m.zozirova@gmail.com>
Thu, 15 May 2025 13:34:56 +0000 (16:34 +0300)
committermarina <m.zozirova@gmail.com>
Thu, 15 May 2025 13:34:56 +0000 (16:34 +0300)
erp24/controllers/AutoPlannogrammaController.php
erp24/services/AutoPlannogrammaService.php
erp24/views/auto-plannogramma/4.php
erp24/views/auto-plannogramma/6.php

index 577ec8fe1c8269af16faf0833f2aabb7a9a31377..38f87910b65646a138f825133bbea0965625c4a3 100644 (file)
@@ -289,8 +289,8 @@ class AutoPlannogrammaController extends BaseController
 
             $service = new AutoPlannogrammaService();
 
-            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
-            $data = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters);
+            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+            $data = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters['type']);
 
             $flatData = array_filter($data, function ($row) use ($filters) {
                 foreach ($filters as $key => $value) {
@@ -388,11 +388,18 @@ class AutoPlannogrammaController extends BaseController
 
             $service = new AutoPlannogrammaService();
 
-            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
-            $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters);
-            $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
+            $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date']);
+            $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters);
             $data = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
 
+            if ($filters['type'] == AutoPlannogrammaService::TYPE_WRITE_OFFS) {
+                $monthCategoryWriteOffsShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+                $monthCategoryWriteOffsGoal = $service->getMonthCategoryGoal($monthCategoryWriteOffsShare, $filters['plan_date'], $filters['type']);
+                $monthSubcategoryWriteOffsShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+                $data = $service->getMonthSubcategoryGoal($monthSubcategoryWriteOffsShare, $monthCategoryWriteOffsGoal, $filters['type'], $data);
+            }
+
             $flatData = array_filter($data, function ($row) use ($filters) {
                 foreach ($filters as $key => $value) {
                     if (empty($value)) continue;
@@ -491,13 +498,22 @@ class AutoPlannogrammaController extends BaseController
             $filters['plan_date'] = $filters['year'] . '-' . str_pad($filters['month'], 2, '0', STR_PAD_LEFT) . '-01';
             $service = new AutoPlannogrammaService();
 
-            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
-            $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters);
-            $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+            $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
+            $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date']);
+            $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters);
             $monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
-            $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+            $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters);
             $data = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
 
+            if ($filters['type'] == AutoPlannogrammaService::TYPE_WRITE_OFFS) {
+                $monthCategoryWriteOffsShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+                $monthCategoryWriteOffsGoal = $service->getMonthCategoryGoal($monthCategoryWriteOffsShare, $filters['plan_date'], $filters['type']);
+                $monthSubcategoryWriteOffsShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+                $monthSubcategoryWriteOffsGoals = $service->getMonthSubcategoryGoal($monthSubcategoryWriteOffsShare, $monthCategoryWriteOffsGoal, $filters['type']);
+                $monthSpeciesWriteOffShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+                $data = $service->getMonthSpeciesGoalDirty($monthSpeciesWriteOffShare, $monthSubcategoryWriteOffsGoals, $filters['type'], $data);
+            }
+
             $flatData = array_filter($data, function ($row) use ($filters) {
                 foreach ($filters as $key => $value) {
                     if (empty($value)) continue;
@@ -510,8 +526,6 @@ class AutoPlannogrammaController extends BaseController
                 return true;
             });
 
-
-
             $dataProvider = new ArrayDataProvider([
                 'allModels' => $flatData,
                 'pagination' => ['pageSize' => 100],
index 4645a0d91e502519dae0d8b4422a4ef80d01a002..3a3bf27d631c09c50de39f01a6ba74cb4a6907ea 100644 (file)
@@ -10,8 +10,8 @@ use yii_app\records\SalesWriteOffsPlan;
 
 class AutoPlannogrammaService
 {
-    private const TYPE_SALES = 'sales'; // Тип операции: продажи
-    private const TYPE_WRITE_OFFS = 'writeOffs'; // Тип операции: списания
+    public const TYPE_SALES = 'sales'; // Тип операции: продажи
+    public const TYPE_WRITE_OFFS = 'writeOffs'; // Тип операции: списания
     private const CATEGORY_LOOKBACK_MONTHS = 3; // Период для анализа категорий (месяцы)
     private const LOOKBACK_MONTHS = 2; // Отступаемый шаг от плановой даты перед расчетами
 
@@ -24,21 +24,20 @@ class AutoPlannogrammaService
         return CityStore::findAll(['visible' => CityStore::IS_VISIBLE]);
     }
 
-    /** Корректировка процента списаний, если он превышает 10% от процента продаж.
-     * @param float $currentPercent Текущий процент списаний.
-     * @param float $salesPercent Процент продаж для того же магазина, категории и подкатегории.
-     * @return float Скорректированный процент списаний.
+    /**
+     * Корректировка цели списаний, если она превышает 10% от цели продаж.
+     * @param float|null $writeOffGoal Текущая цель списаний
+     * @param float|null $salesGoal Цель продаж для того же магазина, категории и подкатегории
+     * @return float Скорректированная цель списаний
      */
-    private function adjustWriteOffPercent(
-        float $currentPercent,
-        float $salesPercent
-    ): float {
-        if ($currentPercent > ($salesPercent * 0.1)) {
-            return $currentPercent * 0.1;
+    private function adjustWriteOffPercent(?float $writeOffGoal, ?float $salesGoal): float
+    {
+        if ($writeOffGoal === null || $salesGoal === null || $salesGoal <= 0) {
+            return $writeOffGoal ?? 0.0;
         }
-        return $currentPercent;
-    }
 
+        return ($writeOffGoal / $salesGoal >= 0.1) ? $salesGoal * 0.1 : $writeOffGoal;
+    }
 
     /**
      * Получение доли категорий или списаний за месяц
@@ -140,7 +139,8 @@ class AutoPlannogrammaService
                     ->andWhere(['>=', "$alias.date", (new \DateTime($month1 . '-01'))->format('Y-m-d')])
                     ->andWhere(['<=', "$alias.date", (new \DateTime($month3 . '-01'))->modify('last day of this month')->format('Y-m-d')])
                     ->groupBy(['ex.entity_id']),
-            ], 'main.ex_entity_id = totals.store_id');
+            ], 'main.ex_entity_id = totals.store_id')
+        ->orderBy('category');
 
         // Выполнение запроса и форматирование
         $rows = $query->all();
@@ -166,12 +166,13 @@ class AutoPlannogrammaService
      * @param array $filters Фильтры
      * @return array Массив с целями по категориям
      */
-    public function getMonthCategoryGoal(array $categoryShare, string $datePlan, array $filters): array
+    public function getMonthCategoryGoal(array $categoryShare, string $datePlan, string $type = self::TYPE_SALES): array
     {
         $timestamp = strtotime($datePlan);
         $year = date('Y', $timestamp);
         $month = date('m', $timestamp);
 
+
         $plans = SalesWriteOffsPlan::find()
             ->where(['year' => $year, 'month' => $month])
             ->asArray()
@@ -189,7 +190,7 @@ class AutoPlannogrammaService
                 $result[] = [
                     'category' => $item['category'],
                     'store_id' => $storeId,
-                    'goal' => round($item['percent'] * ($filters['type'] === self::TYPE_WRITE_OFFS ? $plan['write_offs_plan'] : $plan['total_sales_plan']), 2),
+                    'goal' => round($item['percent'] * ($type === self::TYPE_WRITE_OFFS ? $plan['write_offs_plan'] : $plan['total_sales_plan']), 2),
                 ];
             }
         }
@@ -255,7 +256,7 @@ class AutoPlannogrammaService
                     ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
                     ->leftJoin('export_import_table ex', $storeJoinCondition)
                     ->andWhere(['ex.entity_id' => $storeIds])
-                    ->andWhere(['<>', 'p1c.subcategory', ''])
+                    ->andWhere(['<>', 'p1c.category', ''])
                     ->andWhere(['or', ...$months])
                     ->groupBy(['ex.entity_id', 'p1c.category', 'p1c.subcategory']),
             ])
@@ -263,6 +264,7 @@ class AutoPlannogrammaService
                 ['totals' => (new Query())
                     ->select([
                         'store_id' => 'ex.entity_id',
+                        'category' => 'p1c.category',
                         'total' => new Expression($sumExpression),
                     ])
                     ->from($fromTable)
@@ -270,41 +272,14 @@ class AutoPlannogrammaService
                     ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
                     ->leftJoin('export_import_table ex', $storeJoinCondition)
                     ->andWhere(['ex.entity_id' => $storeIds])
-                    ->andWhere(['<>', 'p1c.subcategory', ''])
+                    ->andWhere(['<>', 'p1c.category', ''])
                     ->andWhere(['or', ...$months])
-                    ->groupBy(['ex.entity_id'])],
-                'main.ex_entity_id = totals.store_id'
-            );
-
-        $rows = $query->all();
-        $result = [];
-        $salesPercents = [];
-
-        // Сначала вычисляем проценты для продаж (они понадобятся для сравнения)
-        foreach ($rows as $row) {
-            $key = "{$row['store_id']}_{$row['category']}_{$row['subcategory']}";
-            if ($row['type'] === self::TYPE_SALES) {
-                $salesPercents[$key] = $row['percent'];
-            }
-        }
+                    ->groupBy(['ex.entity_id', 'p1c.category'])],
+                'main.ex_entity_id = totals.store_id AND main.category = totals.category'
+            )
+            ->orderBy('category, subcategory');
 
-        foreach ($rows as $row) {
-            $key = "{$row['store_id']}_{$row['category']}_{$row['subcategory']}";
-            $percent = $row['percent'];
-
-            if ($row['type'] === self::TYPE_WRITE_OFFS) {
-                $percent = $this->adjustWriteOffPercent($percent, $salesPercents[$key] ?? 0);
-            }
-
-            $result[] = [
-                'store_id' => $row['store_id'],
-                'category' => $row['category'],
-                'subcategory' => $row['subcategory'],
-                'total_sum' => $row['total_sum'],
-                'percent' => $percent,
-                'type' => $row['type'],
-            ];
-        }
+        $result = $query->all();
 
         return $result;
     }
@@ -313,9 +288,10 @@ class AutoPlannogrammaService
      * Получение целей по подкатегориям за месяц
      * @param array $subcategoryShare Доли подкатегорий
      * @param array $categoryGoals Цели по категориям
+     * @param string $type Тип операции
      * @return array Массив с целями по подкатегориям
      */
-    public function getMonthSubcategoryGoal(array $subcategoryShare, array $categoryGoals): array
+    public function getMonthSubcategoryGoal(array $subcategoryShare, array $categoryGoals, string $type = self::TYPE_SALES, array $salesGoals = []): array
     {
         $indexedGoals = [];
         foreach ($categoryGoals as $goal) {
@@ -334,6 +310,22 @@ class AutoPlannogrammaService
                 ];
             }
         }
+
+        if ($type == self::TYPE_WRITE_OFFS) {
+            foreach ($result as &$row) {
+                foreach ($salesGoals as $salesGoal) {
+                    if ($row['category'] === $salesGoal['category']
+                        && $row['subcategory'] === $salesGoal['subcategory']
+                        && $row['store_id'] === $salesGoal['store_id']) {
+                        $row['old_value'] = $row['goal'];
+                        $row['sales_goal'] = $salesGoal['goal'];
+                        $row['goal'] = $this->adjustWriteOffPercent($row['goal'], $salesGoal['goal']);
+                    }
+                }
+            }
+            unset($row);
+        }
+
         return $result;
     }
 
@@ -406,18 +398,23 @@ class AutoPlannogrammaService
                 ['totals' => (new Query())
                     ->select([
                         'store_id' => 'ex.entity_id',
+                        'category' => 'p1c.category',
+                        'subcategory' => 'p1c.subcategory',
                         'total' => new Expression($sumExpression),
                     ])
                     ->from($fromTable)
                     ->leftJoin($productTableJoin, $productTableJoinCondition)
                     ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
                     ->leftJoin('export_import_table ex', $storeJoinCondition)
-                    ->andWhere(['or', ...$months])
                     ->andWhere(['ex.entity_id' => $storeIds])
                     ->andWhere(['<>', 'p1c.species', ''])
-                    ->groupBy(['ex.entity_id'])],
-                'main.ex_entity_id = totals.store_id'
-            );
+                    ->andWhere(['or', ...$months])
+                    ->groupBy(['ex.entity_id', 'p1c.category', 'p1c.subcategory'])],
+                'main.ex_entity_id = totals.store_id 
+         AND main.category = totals.category 
+         AND main.subcategory = totals.subcategory'
+            )
+            ->orderBy('category, subcategory, species');
 
         $rows = $query->all();
         $result = [];
@@ -456,9 +453,10 @@ class AutoPlannogrammaService
      * Получение целей по видам за месяц
      * @param array $speciesShare Доли видов
      * @param array $subcategoryGoals Цели по подкатегориям
+     * @param string $type Тип операции
      * @return array Массив с целями по видам
      */
-    public function getMonthSpeciesGoalDirty(array $speciesShare, array $subcategoryGoals): array
+    public function getMonthSpeciesGoalDirty(array $speciesShare, array $subcategoryGoals, $type = self::TYPE_SALES, $salesGoals = []): array
     {
         $indexedGoals = [];
         foreach ($subcategoryGoals as $goal) {
@@ -479,6 +477,22 @@ class AutoPlannogrammaService
             }
         }
 
+        if ($type == self::TYPE_WRITE_OFFS) {
+            foreach ($result as &$row) {
+                foreach ($salesGoals as $salesGoal) {
+                    if ($row['category'] === $salesGoal['category']
+                        && $row['subcategory'] === $salesGoal['subcategory']
+                        && $row['species'] === $salesGoal['species']
+                        && $row['store_id'] === $salesGoal['store_id']) {
+                        $row['old_value'] = $row['goal'];
+                        $row['sales_goal'] = $salesGoal['goal'];
+                        $row['goal'] = $this->adjustWriteOffPercent($row['goal'], $salesGoal['goal']);
+                    }
+                }
+            }
+            unset($row);
+        }
+
         return $result;
     }
 
index 4ab49e21a06b03fdd3f6ecaeae51f2246ac95d54..a5d0d6e4327165fe4d82d476f5fc16244e573c6f 100644 (file)
     <?php ActiveForm::end(); ?>
 </div>
 
+<?php
+$columns = [
+    ['attribute' => 'store_id', 'label' => 'Магазин', 'value' => function ($data) {
+        return CityStore::findOne($data['store_id'])->name ?? null;
+    }],
+    ['attribute' => 'category', 'label' => 'Категория'],
+    ['attribute' => 'subcategory', 'label' => 'Подкатегория'],
+    ['attribute' => 'goal', 'label' => 'Сумма План', 'format' => ['decimal', 2]],
+];
+
+if ($filters['type'] == 'writeOffs') {
+    $columns = array_merge($columns, [
+        ['attribute' => 'old_value', 'label' => 'Сумма до сверки', 'format' => ['decimal', 2]],
+        ['attribute' => 'sales_goal', 'label' => 'Сумма продаж', 'format' => ['decimal', 2]],
+    ]);
+}
+?>
+
 <?= GridView::widget([
     'dataProvider' => $dataProvider,
-    'columns' => [
-        ['attribute' => 'store_id', 'label' => 'Магазин', 'value' => function ($data) {
-            return CityStore::findOne($data['store_id'])->name ?? null;
-        }],
-        ['attribute' => 'category', 'label' => 'Категория'],
-        ['attribute' => 'subcategory', 'label' => 'Подкатегория'],
-        ['attribute' => 'goal', 'label' => 'Сумма План', 'format' => ['decimal', 2]],
-    ],
+    'columns' => $columns,
 ]); ?>
 
index c49498d1718204717e41b1b628cfc162adc3844c..b35136211d02a9222d6dbacd98a0ac378c85264b 100644 (file)
     <?php ActiveForm::end(); ?>
 </div>
 
+
+
+<?php
+$columns = [
+    ['attribute' => 'store_id', 'label' => 'Магазин', 'value' => function ($data) {
+        return CityStore::findOne($data['store_id'])->name ?? null;
+    }],
+    ['attribute' => 'category', 'label' => 'Категория'],
+    ['attribute' => 'subcategory', 'label' => 'Подкатегория'],
+    ['attribute' => 'species', 'label' => 'Тип'],
+    ['attribute' => 'goal', 'label' => 'Сумма План', 'format' => ['decimal', 2]],
+];
+
+if ($filters['type'] == 'writeOffs') {
+    $columns = array_merge($columns, [
+        ['attribute' => 'old_value', 'label' => 'Сумма до сверки', 'format' => ['decimal', 2]],
+        ['attribute' => 'sales_goal', 'label' => 'Сумма продаж', 'format' => ['decimal', 2]],
+    ]);
+}
+?>
 <?= GridView::widget([
     'dataProvider' => $dataProvider,
-    'columns' => [
-        ['attribute' => 'store_id', 'label' => 'Магазин', 'value' => function ($data) {
-            return CityStore::findOne($data['store_id'])->name ?? null;
-        }],
-        ['attribute' => 'category', 'label' => 'Категория'],
-        ['attribute' => 'subcategory', 'label' => 'Подкатегория'],
-        ['attribute' => 'species', 'label' => 'Тип'],
-        ['attribute' => 'goal', 'label' => 'Сумма План', 'format' => ['decimal', 2]],
-    ],
+    'columns' => $columns,
 ]); ?>