From: fomichev Date: Wed, 7 May 2025 15:05:17 +0000 (+0300) Subject: Расчет долей X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=0608ac9ff4c146567dbb4709df048f4235c51cc4;p=erp24_rep%2Fyii-erp24%2F.git Расчет долей --- diff --git a/erp24/controllers/AutoPlannogrammaController.php b/erp24/controllers/AutoPlannogrammaController.php index 0e45f48c..eb10586d 100644 --- a/erp24/controllers/AutoPlannogrammaController.php +++ b/erp24/controllers/AutoPlannogrammaController.php @@ -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, diff --git a/erp24/services/AutoPlannogrammaService.php b/erp24/services/AutoPlannogrammaService.php index 14623385..a9f93d6b 100644 --- a/erp24/services/AutoPlannogrammaService.php +++ b/erp24/services/AutoPlannogrammaService.php @@ -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 [ * => [ * ['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, // сырые данные по каждой неделе * weeksShare: array // доля недели от месячного итога @@ -697,7 +901,7 @@ var_dump($totals); die(); } return [ - 'weeksData' => $weeksData, + 'weeksData' => $weeksData, 'weeksShare' => $weeksShareResult, ]; }