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 [
- * <store_id> => [
- * ['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
{
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
{
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 [
+ * <store_id> => [
+ * ['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'
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),
];
}
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<int, array>, // сырые данные по каждой неделе
- * weeksShare: array<int, 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) для каждой недели указанного месяца.