From: fomichev Date: Thu, 22 May 2025 09:14:53 +0000 (+0300) Subject: Перенос методов X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=80491c24aaf7af786af2b890cb8fbebcb93bc98d;p=erp24_rep%2Fyii-erp24%2F.git Перенос методов --- diff --git a/erp24/services/AutoPlannogrammaService.php b/erp24/services/AutoPlannogrammaService.php index 05ed880f..f483248f 100644 --- a/erp24/services/AutoPlannogrammaService.php +++ b/erp24/services/AutoPlannogrammaService.php @@ -130,128 +130,7 @@ var_dump($totals); die(); return $result; } - /** - * Считает взвешенную долю категорий за целевой месяц: - * берёт три предыдущих к «месяцу-цели» месяца (пропуская сразу предыдущий), - * присваивает им веса 3, 2 и 1 (самый старый месяц — 3, самый свежий — 1), - * суммирует взвешенные итоги по каждой категории и выдаёт их доли - * от общего взвешенного итога. - * - * @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], - * … - * ], - * … - * ] - */ - public function getMonthCategoryShareOrWriteOffWeighted( - string $month, - ?array $filters = null, - ?array $productFilter = null, - string $type = 'sales' - ): array - { - $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 []; - } - - $baseMonth = strtotime("{$month}-01"); - - $monthOffsets = [3, 4, 5]; - $monthWeights = [3, 2, 1]; - - - $weightedSums = []; - $monthStoreTotalsWeighted = []; - - 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)); - - - - $q = (new Query()) - ->select([ - 'store_id' => 'ex.entity_id', - 'category' => 'p1c.category', - 'month_sum' => new Expression('SUM(CAST(wop.summ AS NUMERIC))'), - ]) - ->from(['w' => 'write_offs']); - - if ($type === 'writeOffs') { - $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(['wop.product_id' => $productFilter]); - } - } else { - $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']); - - $rows = $q->all(); - - foreach ($rows as $r) { - $sid = $r['store_id']; - $cat = $r['category']; - $sum = (float)$r['month_sum'] * $w; - $weightedSums[$sid][$cat] = ($weightedSums[$sid][$cat] ?? 0) + $sum; - } - - } - - $result = []; - foreach ($weightedSums as $storeId => $cats) { - $grand = array_sum($cats) ?: 1; - - $unlabeledSum = $cats[''] ?? 0; - $labeledCount = count($cats) - (isset($cats['']) ? 1 : 0); - - $distribution = $labeledCount > 0 ? $unlabeledSum / $labeledCount : 0; - - foreach ($cats as $category => $weightedSum) { - if ($category === '') { - continue; - } - - $adjustedSum = $weightedSum + $distribution; - $result[$storeId][] = [ - 'category' => $category, - 'total_sum_cat' => $weightedSum, - 'total_sum_store' => $grand, - 'share_of_total' => round($adjustedSum / $grand, 4), - ]; - } - } - return $result; - } public function getMonthCategoryGoal(array $categoryShare, $datePlan, $filters): array { @@ -350,96 +229,7 @@ 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 []; - } - - $years = [$year - 2, $year - 1]; - - $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 { @@ -663,28 +453,29 @@ var_dump($totals); die(); return array_values($filtered); } + + /** - * Считает для каждого магазина месячную сумму и долю - * продаж или списаний по видам (species). + * Считает взвешенную долю категорий за целевой месяц: + * берёт три предыдущих к «месяцу-цели» месяца (пропуская сразу предыдущий), + * присваивает им веса 3, 2 и 1 (самый старый месяц — 3, самый свежий — 1), + * суммирует взвешенные итоги по каждой категории и выдаёт их доли + * от общего взвешенного итога. * - * @param string $dateFrom Дата начала периода (Y-m-d) - * @param array|null $filters Фильтры (например ['store_id'=>...]) - * @param array|null $productFilter Опциональный фильтр по product_id + * @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' => (int), - * 'category' => (string), - * 'subcategory' => (string), - * 'species' => (string), // теперь — именно вид - * 'total_sum' => (float), - * 'percent_of_month'=> (float), // доля от общего объёма магазина - * ], … + * @return array [ + * => [ + * ['category'=>string, 'total_sum'=>float, 'share_of_total'=>float], + * … + * ], + * … * ] */ - public function getMonthSpeciesShareOrWriteOffDate( - string $dateFrom, - string $dateTo, + public function getMonthCategoryShareOrWriteOffWeighted( + string $month, ?array $filters = null, ?array $productFilter = null, string $type = 'sales' @@ -695,79 +486,183 @@ var_dump($totals); die(); if (!empty($filters['store_id'])) { $storeIds = array_intersect($storeIds, [(int)$filters['store_id']]); } + if (empty($storeIds)) { + return []; + } - $totals = $this->getStoreTotals($storeIds, $dateFrom, null, $type, $dateTo); - if (empty($totals)) { + $baseMonth = strtotime("{$month}-01"); + + $monthOffsets = [3, 4, 5]; + $monthWeights = [3, 2, 1]; + + + $weightedSums = []; + $monthStoreTotalsWeighted = []; + + 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)); + + + + $q = (new Query()) + ->select([ + 'store_id' => 'ex.entity_id', + 'category' => 'p1c.category', + 'month_sum' => new Expression('SUM(CAST(wop.summ AS NUMERIC))'), + ]) + ->from(['w' => 'write_offs']); + + if ($type === 'writeOffs') { + $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(['wop.product_id' => $productFilter]); + } + } else { + $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']); + + $rows = $q->all(); + + foreach ($rows as $r) { + $sid = $r['store_id']; + $cat = $r['category']; + $sum = (float)$r['month_sum'] * $w; + $weightedSums[$sid][$cat] = ($weightedSums[$sid][$cat] ?? 0) + $sum; + } + + } + + $result = []; + foreach ($weightedSums as $storeId => $cats) { + $grand = array_sum($cats) ?: 1; + + $unlabeledSum = $cats[''] ?? 0; + $labeledCount = count($cats) - (isset($cats['']) ? 1 : 0); + + $distribution = $labeledCount > 0 ? $unlabeledSum / $labeledCount : 0; + + foreach ($cats as $category => $weightedSum) { + if ($category === '') { + continue; + } + + $adjustedSum = $weightedSum + $distribution; + + $result[$storeId][] = [ + 'category' => $category, + 'total_sum_cat' => $weightedSum, + 'total_sum_store' => $grand, + 'share_of_total' => round($adjustedSum / $grand, 4), + ]; + } + } + 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'); - $query = (new Query())->select([ - 'store_id' => 'ex.entity_id', - 'category' => 'p1c.category', - 'subcategory' => 'p1c.subcategory', - // Суммируем сразу по виду из колонки p1c.species - 'species' => 'p1c.species', - 'total_sum' => new Expression( - $type === 'writeOffs' - ? "SUM(CAST(item->>'summ' AS NUMERIC))" - : 'SUM(sp.summ)' - ), - ]); + $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 []; + } + + $years = [$year - 2, $year - 1]; + + $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('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\'') - ->where(['>=', 'w.date', $dateFrom]) - ->andWhere(['<=', 'w.date', $dateTo]) - ->andWhere(['ex.entity_id' => $storeIds]) - ->andWhere(['<>', 'p1c.species', '']) - ->groupBy([ - 'ex.entity_id', - 'p1c.category', - 'p1c.subcategory', - 'p1c.species', - ]); + ->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(['item->>\'product_id\'' => $productFilter]); + $query->andWhere(['wop.product_id' => $productFilter]); } } else { $query->from(['s' => 'sales']) - ->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') - ->where(['>=', 's.date', $dateFrom]) - ->andWhere(['<=', 's.date', $dateTo]) - ->andWhere(['ex.entity_id' => $storeIds]) - ->andWhere(['<>', 'p1c.species', '']) - ->groupBy([ - 'ex.entity_id', - 'p1c.category', - 'p1c.subcategory', - 'p1c.species', - ]); + ->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 $row) { - $storeId = $row['store_id']; - $total = $totals[$storeId] ?? 1; + foreach ($rows as $r) { + $sid = $r['store_id']; + $cat = $r['category']; + $total = $sumByStoreCategory[$sid][$cat] ?: 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' => $cat, + 'subcategory' => $r['subcategory'], + 'total_sum' => $r['total_sum'], + 'percent_of_month' => round($r['total_sum'] / $total, 4), ]; } @@ -875,87 +770,8 @@ var_dump($totals); die(); return $result; } - /** - * Для заданного магазина/фильтров получает по 1–5 неделям внутри месяца - * и рассчитывает для каждого вида (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' - * @return array{ - * weeksData: array, // сырые данные по каждой неделе - * weeksShare: array // доля недели от месячного итога - * } - */ - public function getWeeksSpeciesShareOrWriteOff( - string $dateFrom, - string $dateTo, - array $filters, - ?array $productFilter, - string $type = 'writeOffs' - ): array - { - $monthResult = $this->getMonthSpeciesShareOrWriteOffDate( - $dateFrom, $dateTo, $filters, $productFilter, $type - ); - - $speciesMonthTotals = []; - foreach ($monthResult as $r) { - $speciesMonthTotals - [$r['store_id']] - [$r['category']] - [$r['subcategory']] - [$r['species']] = $r['total_sum']; - } + // Недельные расчеты - $weeksData = []; - $weeksShareResult = []; - foreach (range(1, 5) as $ind) { - $weekStart = date("Y-m-d 00:00:00", strtotime("+" . (($ind - 1) * 7) . ' days', strtotime($dateFrom))); - $weekEnd = date("Y-m-d 23:59:59", strtotime("+" . ($ind * 7 - 1) . ' days', strtotime($dateFrom))); - if ($weekEnd > $dateTo) { - $weekEnd = $dateTo; - } - if ($weekStart > $dateTo) { - continue; - } - $weekResult = $this->getMonthSpeciesShareOrWriteOffDate( - $weekStart, - $weekEnd, - $filters, - null, - $type - ); - $weeksData[$ind] = $weekResult; - if (!$weekResult) { - continue; - } - $weeksShareResult[$ind] = []; - foreach ($weekResult as $weekRow) { - $monthSum = $speciesMonthTotals - [$weekRow['store_id']] - [$weekRow['category']] - [$weekRow['subcategory']] - [$weekRow['species']] ?? null; - - if ($monthSum) { - $weeksShareResult[$ind] - [$weekRow['category']] - [$weekRow['subcategory']] - [$weekRow['species']] = $weekRow['total_sum'] / $monthSum; - } - - } - - } - return [ - 'weeksData' => $weeksData, - 'weeksShare' => $weeksShareResult, - ]; - } /** * Получает суммы продаж или списаний по видам (species) для каждой недели указанного месяца.