]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Расчет долей
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 7 May 2025 15:05:17 +0000 (18:05 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Wed, 7 May 2025 15:05:17 +0000 (18:05 +0300)
erp24/controllers/AutoPlannogrammaController.php
erp24/services/AutoPlannogrammaService.php

index 0e45f48ce67bf0873d23d845b23b0b4d778212bf..eb10586de64bac6adb5fa63583c12eaa1f663a46 100644 (file)
@@ -353,7 +353,7 @@ class AutoPlannogrammaController extends BaseController
 
 
         }
-var_dump($monthSpeciesGoalsMap); die();
+//var_dump($monthSpeciesGoalsMap); die();
         return $this->render('control-species', [
             'model'           => $model,
             'result'          => $monthResult,
index 14623385579354d488daaa9d85c3c2b80332b0e0..a9f93d6b25096ec2224b4c13f6e7715b2b89b162 100644 (file)
@@ -89,7 +89,7 @@ var_dump($totals); die();
                 ->join('JOIN', new Expression('LATERAL jsonb_array_elements(w.items::jsonb) AS item'), 'true')
                 ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = item ->> \'product_id\'')
                 ->where(['>=', 'w.date', $dateFrom])
-                ->andWhere(['<=','w.date',$dateTo])
+                ->andWhere(['<=', 'w.date', $dateTo])
                 ->andWhere(['ex.entity_id' => $storeIds])
                 ->andWhere(['<>', 'p1c.category', ''])
                 ->groupBy(['ex.entity_id', 'p1c.category']);
@@ -102,7 +102,7 @@ var_dump($totals); die();
                 ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = sp.product_id')
                 ->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c')
                 ->where(['>=', 's.date', $dateFrom])
-                ->andWhere(['<=','w.date',$dateTo])
+                ->andWhere(['<=', 'w.date', $dateTo])
                 ->andWhere(['ex.entity_id' => $storeIds])
                 ->andWhere(['<>', 'p1c.category', ''])
                 ->groupBy(['ex.entity_id', 'p1c.category']);
@@ -135,10 +135,10 @@ var_dump($totals); die();
      * суммирует взвешенные итоги по каждой категории и выдаёт их доли
      * от общего взвешенного итога.
      *
-     * @param string      $month         Целевой месяц в формате 'YYYY-MM'
-     * @param array|null  $filters       ['store_id'=>…, …]
-     * @param array|null  $productFilter Опционально: [product_id, …]
-     * @param string      $type          'sales' или 'writeOffs'
+     * @param string $month Целевой месяц в формате 'YYYY-MM'
+     * @param array|null $filters ['store_id'=>…, …]
+     * @param array|null $productFilter Опционально: [product_id, …]
+     * @param string $type 'sales' или 'writeOffs'
      * @return array      [
      *   <store_id> => [
      *     ['category'=>string, 'total_sum'=>float, 'share_of_total'=>float],
@@ -149,12 +149,12 @@ var_dump($totals); die();
      */
     public function getMonthCategoryShareOrWriteOffWeighted(
         string $month,
-        ?array $filters       = null,
+        ?array $filters = null,
         ?array $productFilter = null,
-        string $type          = 'sales'
+        string $type = 'sales'
     ): array
     {
-        $stores   = $this->getVisibleStores();
+        $stores = $this->getVisibleStores();
         $storeIds = array_map(fn($s) => $s->id, $stores);
         if (!empty($filters['store_id'])) {
             $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
@@ -175,7 +175,7 @@ var_dump($totals); die();
         foreach ($monthOffsets as $idx => $offsetMonths) {
             $w = $monthWeights[$idx];
             $start = date('Y-m-01 00:00:00', strtotime("-{$offsetMonths} months", $baseMonth));
-            $end   = date('Y-m-t 23:59:59', strtotime($start));
+            $end = date('Y-m-t 23:59:59', strtotime($start));
 
             $monthStoreTotals = $this->getStoreTotals(
                 $storeIds, $start, $productFilter, $type, $end
@@ -183,38 +183,37 @@ var_dump($totals); die();
 
             $q = (new Query())
                 ->select([
-                    'store_id'  => 'ex.entity_id',
-                    'category'  => 'p1c.category',
-                    'month_sum' => new Expression(
-                        $type === 'writeOffs'
-                            ? "SUM(CAST(item->>'summ' AS NUMERIC))"
-                            : 'SUM(sp.summ)'
-                    )
+                    'store_id' => 'ex.entity_id',
+                    'category' => 'p1c.category',
+                    'month_sum' => new Expression('SUM(CAST(wop.summ AS NUMERIC))'),
                 ])
-                ->from($type === 'writeOffs' ? ['w'=>'write_offs'] : ['s'=>'sales']);
+                ->from(['w' => 'write_offs']);
 
             if ($type === 'writeOffs') {
-                $q->leftJoin('export_import_table ex',            'ex.export_val = w.store_id')
-                    ->leftJoin('LATERAL jsonb_array_elements(w.items::jsonb) AS item', 'TRUE')
-                    ->leftJoin('products_1c_nomenclature p1c',      'p1c.id = item->>\'product_id\'')
-                    ->andWhere(['>=','w.date',$start])
-                    ->andWhere(['<=','w.date',$end]);
+                $q->leftJoin(['ex' => 'export_import_table'], 'ex.export_val = w.store_id')
+                    ->leftJoin(['wop' => 'write_offs_products'], 'wop.write_offs_id = w.id')
+                    ->leftJoin(['p1c' => 'products_1c_nomenclature'], 'p1c.id = wop.product_id')
+                    ->andWhere(['>=', 'w.date', $start])
+                    ->andWhere(['<=', 'w.date', $end]);
+
                 if ($productFilter !== null) {
-                    $q->andWhere(["item->>'product_id'" => $productFilter]);
+                    $q->andWhere(['wop.product_id' => $productFilter]);
                 }
             } else {
-                $q->leftJoin('sales_products sp',                'sp.check_id = s.id')
-                    ->leftJoin('export_import_table ex',            'ex.export_val = s.store_id_1c')
-                    ->leftJoin('products_1c_nomenclature p1c',      'p1c.id = sp.product_id')
-                    ->andWhere(['>=','s.date',$start])
-                    ->andWhere(['<=','s.date',$end]);
+                $q->leftJoin(['sp' => 'sales_products'], 'sp.check_id = s.id')
+                    ->leftJoin(['ex' => 'export_import_table'], 'ex.export_val = s.store_id_1c')
+                    ->leftJoin(['p1c' => 'products_1c_nomenclature'], 'p1c.id = sp.product_id')
+                    ->andWhere(['>=', 's.date', $start])
+                    ->andWhere(['<=', 's.date', $end]);
+
                 if ($productFilter !== null) {
                     $q->andWhere(['sp.product_id' => $productFilter]);
                 }
             }
+
             $q->andWhere(['ex.entity_id' => $storeIds])
-                ->andWhere(['<>','p1c.category',''])
-                ->groupBy(['ex.entity_id','p1c.category']);
+               // ->andWhere(['<>', 'p1c.category', ''])
+                ->groupBy(['ex.entity_id', 'p1c.category']);
 
             $rows = $q->all();
 
@@ -236,12 +235,12 @@ var_dump($totals); die();
             $grand = array_sum($cats) ?: 1;
             foreach ($cats as $category => $weightedSum) {
                 $result[$storeId][] = [
-                    'category'  => $category,
+                    'category' => $category,
                     'total_sum_cat' => $weightedSum,
                     'total_sum_store' => $monthStoreTotalsWeighted[$storeId],
                     'query_total_sum_store' => $grand,
-                    'share_of_total'   => round($weightedSum / $grand, 4),
-                    'share_of_total_control'   => round($weightedSum / $monthStoreTotalsWeighted[$storeId], 4),
+                    'share_of_total' => round($weightedSum / $grand, 4),
+                    'share_of_total_control' => round($weightedSum / $monthStoreTotalsWeighted[$storeId], 4),
                 ];
             }
         }
@@ -346,6 +345,102 @@ var_dump($totals); die();
         return $result;
     }
 
+    public function getMonthSubcategoryShareOrWriteOffWeighted(string $dateFrom, ?array $filters = null, ?array $productFilter = null, string $type = 'sales'): array
+    {
+        try {
+            $dt = new \DateTime($dateFrom);
+        } catch (\Exception $e) {
+            // Неверный формат даты
+            return [];
+        }
+        $month = (int)$dt->format('m');
+        $year  = (int)$dt->format('Y');
+
+        $stores  = $this->getVisibleStores();
+        $storeIds = array_map(fn($s) => $s->id, $stores);
+        if (!empty($filters['store_id'])) {
+            $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
+        }
+        if (empty($storeIds)) {
+            return [];
+        }
+
+        $startYear = 2020;
+        $endYear   = $year - 1;
+        if ($endYear < $startYear) {
+            return [];
+        }
+        $years = range($startYear, $endYear);
+
+        $query = (new Query())
+            ->select([
+                'store_id'     => 'ex.entity_id',
+                'subcategory'  => 'p1c.subcategory',
+                'category'     => 'p1c.category',
+                'total_sum'    => new Expression(
+                    $type === 'writeOffs'
+                        ? 'SUM(CAST(wop.summ AS NUMERIC))'
+                        : 'SUM(sp.summ)'
+                ),
+            ]);
+
+        if ($type === 'writeOffs') {
+            $query->from(['w' => 'write_offs'])
+                ->leftJoin(['ex'  => 'export_import_table'],      'ex.export_val = w.store_id')
+                ->leftJoin(['wop'=> 'write_offs_products'],      'wop.write_offs_id = w.id')
+                ->leftJoin(['p1c'=> 'products_1c_nomenclature'], 'p1c.id = wop.product_id')
+
+                ->andWhere(['in', new Expression('EXTRACT(YEAR FROM w.date)'), $years])
+                ->andWhere(['=', new Expression('EXTRACT(MONTH FROM w.date)'), $month]);
+            if ($productFilter !== null) {
+                $query->andWhere(['wop.product_id' => $productFilter]);
+            }
+        } else {
+            $query->from(['s' => 'sales'])
+                ->leftJoin(['sp'  => 'sales_products'],         'sp.check_id = s.id')
+                ->leftJoin(['ex'  => 'export_import_table'],    'ex.export_val = s.store_id_1c')
+                ->leftJoin(['p1c' => 'products_1c_nomenclature'],'p1c.id = sp.product_id')
+                ->andWhere(['in', new Expression('EXTRACT(YEAR FROM s.date)'), $years])
+                ->andWhere(['=', new Expression('EXTRACT(MONTH FROM s.date)'), $month]);
+            if ($productFilter !== null) {
+                $query->andWhere(['sp.product_id' => $productFilter]);
+            }
+        }
+
+        $query->andWhere(['ex.entity_id' => $storeIds])
+            ->andWhere(['<>', 'p1c.subcategory', ''])
+            ->groupBy(['ex.entity_id', 'p1c.subcategory', 'p1c.category']);
+
+        $rows = $query->all();
+        if (empty($rows)) {
+            return [];
+        }
+
+        $sumByStoreCategory = [];
+        foreach ($rows as $r) {
+            $sid = $r['store_id'];
+            $cat = $r['category'];
+            $sumByStoreCategory[$sid][$cat] = ($sumByStoreCategory[$sid][$cat] ?? 0) + $r['total_sum'];
+        }
+
+
+        $result = [];
+        foreach ($rows as $r) {
+            $sid   = $r['store_id'];
+            $cat   = $r['category'];
+            $total = $sumByStoreCategory[$sid][$cat] ?: 1;
+            $result[] = [
+                'store_id'         => $sid,
+                'category'         => $cat,
+                'subcategory'      => $r['subcategory'],
+                'total_sum'        => $r['total_sum'],
+                'percent_of_month' => round($r['total_sum'] / $total, 4),
+            ];
+        }
+
+        return $result;
+    }
+
     public function getMonthSubcategoryGoal(array $subcategoryShare, array $categoryGoals): array
     {
         $indexedGoals = [];
@@ -475,14 +570,17 @@ var_dump($totals); die();
         //$monthCategoryShare = $this->getMonthCategoryShareOrWriteOff($dateFromForCategory, $filters);
         $monthCategoryShare = $this->getMonthCategoryShareOrWriteOffWeighted($datePlan, $filters, null, $filters['type']);
         $monthCategoryGoal = $this->getMonthCategoryGoal($monthCategoryShare, $datePlan, $filters);
-        var_dump($monthCategoryShare); die();
-        $monthSubcategoryShare = $this->getMonthSubcategoryShareOrWriteOff($dateFrom, $filters);
+
+       // $monthSubcategoryShare = $this->getMonthSubcategoryShareOrWriteOff($dateFrom, $filters);
+        $monthSubcategoryShare = $this->getMonthSubcategoryShareOrWriteOffWeighted($datePlan, $filters, null, $filters['type']);
         $monthSubcategoryGoal = $this->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
 
         //$monthSpeciesShare = $this->getMonthSpeciesShareOrWriteOff($dateFrom, $filters);
-        $monthSpeciesShare = $this->getMonthSpeciesShareOrWriteOffDate($dateFrom, $datePlan, $filters, null, $filters['type']);
+        //$monthSpeciesShare = $this->getMonthSpeciesShareOrWriteOffDate($dateFrom, $datePlan, $filters, null, $filters['type']);
+        $monthSpeciesShare = $this->getMonthSpeciesShareOrWriteOffWeighted($datePlan, $datePlan, $filters, null, $filters['type']);
         $monthSpeciesGoal = $this->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
-
+      // var_dump($monthSpeciesGoal);
+      // die();
         $filtered = array_filter($monthSpeciesGoal, function ($row) use ($filters) {
             foreach ($filters as $key => $value) {
                 if ($value === null || $value === '') {
@@ -513,10 +611,10 @@ var_dump($totals); die();
      * Считает для каждого магазина месячную сумму и долю
      * продаж или списаний по видам (species).
      *
-     * @param string      $dateFrom      Дата начала периода (Y-m-d)
-     * @param array|null  $filters       Фильтры (например ['store_id'=>...])
-     * @param array|null  $productFilter Опциональный фильтр по product_id
-     * @param string      $type          'sales' или 'writeOffs'
+     * @param string $dateFrom Дата начала периода (Y-m-d)
+     * @param array|null $filters Фильтры (например ['store_id'=>...])
+     * @param array|null $productFilter Опциональный фильтр по product_id
+     * @param string $type 'sales' или 'writeOffs'
      * @return array       [
      *   [
      *     'store_id'        => (int),
@@ -536,7 +634,7 @@ var_dump($totals); die();
         string $type = 'sales'
     ): array
     {
-        $stores  = $this->getVisibleStores();
+        $stores = $this->getVisibleStores();
         $storeIds = array_map(fn($s) => $s->id, $stores);
         if (!empty($filters['store_id'])) {
             $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
@@ -548,12 +646,12 @@ var_dump($totals); die();
         }
 
         $query = (new Query())->select([
-            'store_id'    => 'ex.entity_id',
-            'category'    => 'p1c.category',
+            'store_id' => 'ex.entity_id',
+            'category' => 'p1c.category',
             'subcategory' => 'p1c.subcategory',
             // Суммируем сразу по виду из колонки p1c.species
-            'species'     => 'p1c.species',
-            'total_sum'   => new Expression(
+            'species' => 'p1c.species',
+            'total_sum' => new Expression(
                 $type === 'writeOffs'
                     ? "SUM(CAST(item->>'summ' AS NUMERIC))"
                     : 'SUM(sp.summ)'
@@ -602,18 +700,124 @@ var_dump($totals); die();
         }
 
 
-        $rows   = $query->all();
+        $rows = $query->all();
         $result = [];
         foreach ($rows as $row) {
             $storeId = $row['store_id'];
-            $total   = $totals[$storeId] ?? 1;
+            $total = $totals[$storeId] ?? 1;
+            $result[] = [
+                'store_id' => $storeId,
+                'category' => $row['category'],
+                'subcategory' => $row['subcategory'],
+                'species' => $row['species'],
+                'total_sum' => (float)$row['total_sum'],
+                'percent_of_month' => round(((float)$row['total_sum'] / $total), 4),
+            ];
+        }
+
+        return $result;
+    }
+
+    public function getMonthSpeciesShareOrWriteOffWeighted(
+        string $dateFrom,
+        string $dateTo,
+        ?array $filters = null,
+        ?array $productFilter = null,
+        string $type = 'sales'
+    ): array
+    {
+        try {
+            $dt    = new \DateTime($dateFrom);
+        } catch (\Exception $e) {
+            return [];
+        }
+        $month = (int)$dt->format('m');
+        $year  = (int)$dt->format('Y');
+
+        $stores   = $this->getVisibleStores();
+        $storeIds = array_map(fn($s) => $s->id, $stores);
+        if (!empty($filters['store_id'])) {
+            $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
+        }
+        if (empty($storeIds)) {
+            return [];
+        }
+
+        $startYear = 2020;
+        $endYear   = $year - 1;
+        if ($endYear < $startYear) {
+            return [];
+        }
+        $years = range($startYear, $endYear);
+
+        $query = (new Query())->select([
+            'store_id'   => 'ex.entity_id',
+            'category'   => 'p1c.category',
+            'subcategory'=> 'p1c.subcategory',
+            'species'    => 'p1c.species',
+            'total_sum'  => new Expression(
+                $type === 'writeOffs'
+                    ? 'SUM(CAST(wop.summ AS NUMERIC))'
+                    : 'SUM(sp.summ)'
+            ),
+        ]);
+
+        if ($type === 'writeOffs') {
+            $query->from(['w' => 'write_offs'])
+                ->leftJoin(['ex'  => 'export_import_table'],       'ex.export_val = w.store_id')
+                ->leftJoin(['wop' => 'write_offs_products'],       'wop.write_offs_id = w.id')
+                ->leftJoin(['p1c' => 'products_1c_nomenclature'],  'p1c.id = wop.product_id')
+                ->andWhere(['IN', new Expression('EXTRACT(YEAR FROM w.date)'), $years])
+                ->andWhere(['=',  new Expression('EXTRACT(MONTH FROM w.date)'), $month]);
+            if ($productFilter !== null) {
+                $query->andWhere(['wop.product_id' => $productFilter]);
+            }
+        } else {
+            $query->from(['s' => 'sales'])
+                ->leftJoin(['sp'  => 'sales_products'],            'sp.check_id = s.id')
+                ->leftJoin(['ex'  => 'export_import_table'],       'ex.export_val = s.store_id_1c')
+                ->leftJoin(['p1c' => 'products_1c_nomenclature'],  'p1c.id = sp.product_id')
+                ->andWhere(['IN', new Expression('EXTRACT(YEAR FROM s.date)'), $years])
+                ->andWhere(['=',  new Expression('EXTRACT(MONTH FROM s.date)'), $month]);
+            if ($productFilter !== null) {
+                $query->andWhere(['sp.product_id' => $productFilter]);
+            }
+        }
+
+        $query->andWhere(['ex.entity_id' => $storeIds])
+            ->andWhere(['<>', 'p1c.species', ''])
+            ->groupBy([
+                'ex.entity_id',
+                'p1c.category',
+                'p1c.subcategory',
+                'p1c.species',
+            ]);
+
+        $rows = $query->all();
+        if (empty($rows)) {
+            return [];
+        }
+
+        $sumByStoreSubcategory = [];
+        foreach ($rows as $r) {
+            $sid = $r['store_id'];
+            $sub = $r['subcategory'];
+            $sumByStoreSubcategory[$sid][$sub] =
+                ($sumByStoreSubcategory[$sid][$sub] ?? 0) + $r['total_sum'];
+        }
+
+        $result = [];
+        foreach ($rows as $r) {
+            $sid   = $r['store_id'];
+            $sub   = $r['subcategory'];
+            $total = $sumByStoreSubcategory[$sid][$sub] ?: 1;
             $result[] = [
-                'store_id'        => $storeId,
-                'category'        => $row['category'],
-                'subcategory'     => $row['subcategory'],
-                'species'         => $row['species'],
-                'total_sum'       => (float)$row['total_sum'],
-                'percent_of_month'=> round(((float)$row['total_sum'] / $total) , 4),
+                'store_id'         => $sid,
+                'category'         => $r['category'],
+                'subcategory'      => $sub,
+                'species'          => $r['species'],
+                'total_sum'        => (float)$r['total_sum'],
+                'percent_of_month' => round($r['total_sum'] / $total, 4),
             ];
         }
 
@@ -625,11 +829,11 @@ var_dump($totals); die();
      * и рассчитывает для каждого вида (species) долю недельного объёма
      * от месячного итога.
      *
-     * @param string      $dateFrom       начало месяца (Y-m-d H:i:s)
-     * @param string      $dateTo         конец месяца  (Y-m-d H:i:s)
-     * @param array       $filters        ['store_id'=>..., 'category'=>..., ...]
-     * @param array|null  $productFilter  опц. фильтр по product_id
-     * @param string      $type           'sales' или 'writeOffs'
+     * @param string $dateFrom начало месяца (Y-m-d H:i:s)
+     * @param string $dateTo конец месяца  (Y-m-d H:i:s)
+     * @param array $filters ['store_id'=>..., 'category'=>..., ...]
+     * @param array|null $productFilter опц. фильтр по product_id
+     * @param string $type 'sales' или 'writeOffs'
      * @return array{
      *     weeksData: array<int, array>,      // сырые данные по каждой неделе
      *     weeksShare: array<int, array>      // доля недели от месячного итога
@@ -697,7 +901,7 @@ var_dump($totals); die();
 
         }
         return [
-            'weeksData'  => $weeksData,
+            'weeksData' => $weeksData,
             'weeksShare' => $weeksShareResult,
         ];
     }