class AutoPlannogrammaService
{
+ private const TYPE_SALES = 'sales'; // Тип операции: продажи
+ private const TYPE_WRITE_OFFS = 'writeOffs'; // Тип операции: списания
+ private const OPERATION_SALE = 'Продажа'; // Операция продажи
+ private const DATE_FORMAT = 'Y-m-d'; // Формат даты
+ private const CATEGORY_LOOKBACK_MONTHS = 3; // Период для анализа категорий (месяцы)
+ private const SUBCATEGORY_LOOKBACK_MONTHS = 24; // Период для анализа подкатегорий (месяцы)
+ private const LOOKBACK_MONTHS = 2; // Отступаемый шаг от плановой даты перед расчетами
+
+ /**
+ * Получение списка видимых магазинов
+ * @return CityStore[] Массив моделей магазинов
+ */
private function getVisibleStores(): array
{
return CityStore::findAll(['visible' => CityStore::IS_VISIBLE]);
}
- public function getStoreTotals(array $storeIds, string $dateFrom, ?array $productFilter = null, string $type = 'sales', ?string $dateTo = null): array
+ /**
+ * Создание базового запроса для подсчета итогов
+ * @param string $type Тип операции (продажи или списания)
+ * @return Query Объект запроса
+ */
+ private function buildBaseTotalsQuery(string $type): Query
{
- $query = (new Query())
+ // Определяем SQL-выражение для SUM
+ $sumExpression = $type === self::TYPE_WRITE_OFFS
+ ? 'SUM(wp.summ)'
+ : 'SUM(CASE WHEN s.operation = :operation THEN sp.summ ELSE 0 END)';
+
+ // Определяем таблицу и псевдоним
+ $fromTable = $type === self::TYPE_WRITE_OFFS
+ ? ['w' => 'write_offs']
+ : ['s' => 'sales'];
+
+ return (new Query())
->select([
'store_id' => 'ex.entity_id',
- 'total_sum' => new Expression(
- $type === 'writeOffs'
- ? 'SUM(wp.summ)'
- : "SUM(CASE WHEN s.operation = 'Продажа' THEN sp.summ ELSE 0 END)"
- )
+ 'total_sum' => new Expression($sumExpression, ['operation' => self::OPERATION_SALE])
])
- ->from(['s' => $type === 'writeOffs' ? 'write_offs' : 'sales']);
+ ->from($fromTable);
+ }
- if ($type == 'writeOffs') {
- $query->leftJoin('export_import_table ex', 'ex.export_val = w.store_id')
+ /**
+ * Применение соединений таблиц в зависимости от типа операции
+ * @param Query $query Объект запроса
+ * @param string $type Тип операции
+ * @return Query Обновленный запрос
+ */
+ private function applyJoins(Query $query, string $type): Query
+ {
+ if ($type === self::TYPE_WRITE_OFFS) {
+ $query->leftJoin('export_import_table ex', 'ex.export_val = w.store_id')
->leftJoin('write_offs_products wp', 'wp.write_offs_id = w.id');
-
} else {
$query->leftJoin('sales_products sp', 'sp.check_id = s.id')
->leftJoin('export_import_table ex', 'ex.export_val = s.store_id_1c');
}
+ return $query;
+ }
+
+ /**
+ * Получение итогов по магазинам
+ * @param array $storeIds Список ID магазинов
+ * @param string $dateFrom Дата начала периода
+ * @param array|null $productFilter Фильтр по продуктам
+ * @param string $type Тип операции
+ * @param string|null $dateTo Дата окончания периода
+ * @return array Ассоциативный массив с итогами
+ * @throws \InvalidArgumentException
+ */
+ public function getStoreTotals(array $storeIds, string $dateFrom, ?array $productFilter = null, string $type = self::TYPE_SALES, ?string $dateTo = null): array
+ {
+ if (empty($storeIds)) {
+ return [];
+ }
+
+ if (!in_array($type, [self::TYPE_SALES, self::TYPE_WRITE_OFFS], true)) {
+ throw new \InvalidArgumentException("Недопустимый тип операции: $type");
+ }
+
+ $query = $this->buildBaseTotalsQuery($type);
+ $query = $this->applyJoins($query, $type);
$query->where(['>=', 'date', $dateFrom])
- ->andWhere(['ex.entity_id' => $storeIds]);
+ ->andWhere(['ex.entity_id' => $storeIds])
+ ->groupBy('ex.entity_id');
if ($productFilter !== null) {
- if ($type === 'writeOffs') {
- $query->andWhere(['w.product_id'=> $productFilter]);
- } else {
- $query->andWhere(['sp.product_id' => $productFilter]);
- }
+ $query->andWhere([$type === self::TYPE_WRITE_OFFS ? 'wp.product_id' : 'sp.product_id' => $productFilter]);
}
if ($dateTo !== null) {
- $query->andWhere(['<=', 'w.date', $dateTo]);
+ $query->andWhere(['<=', 'date', $dateTo]);
}
- $query->groupBy('ex.entity_id');
-
- $rows = $query->all();
-
- return ArrayHelper::map($rows, 'store_id', 'total_sum');
+ return ArrayHelper::map($query->all(), 'store_id', 'total_sum');
}
- public function getMonthCategoryShareOrWriteOff(string $dateFrom, ?array $filters = null, ?array $productFilter = null, string $type = 'sales'): array
+ /**
+ * Получение доли категорий или списаний за месяц
+ * @param string $dateFrom Дата начала периода
+ * @param array|null $filters Дополнительные фильтры
+ * @param array|null $productFilter Фильтр по продуктам
+ * @param string $type Тип операции
+ * @return array Массив с долями категорий
+ * @throws \InvalidArgumentException
+ */
+ public function getMonthCategoryShareOrWriteOff(string $dateFrom, ?array $filters = null, string $type = self::TYPE_SALES): array
{
- $stores = $this->getVisibleStores();
- $storeIds = array_map(fn($s) => $s->id, $stores);
+ // Проверка типа операции
+ if (!in_array($type, [self::TYPE_SALES, self::TYPE_WRITE_OFFS], true)) {
+ throw new \InvalidArgumentException("Недопустимый тип операции: $type");
+ }
- if (!empty($filters) && array_key_exists('store_id', $filters) && $filters['store_id'] !== '') {
+ // Получение ID видимых магазинов
+ $storeIds = array_map(fn($store) => $store->id, $this->getVisibleStores());
+
+ // Применение фильтра по магазину, если указан
+ if (!empty($filters['store_id'])) {
$storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
}
- $totals = $this->getStoreTotals($storeIds, $dateFrom, null, $type);
-
- if (empty($totals)) return [];
+ // Формирование компонентов запроса
+ $sumExpression = $type === self::TYPE_WRITE_OFFS ? 'SUM(wp.summ)' : 'SUM(sp.summ)';
+ $fromTable = $type === self::TYPE_WRITE_OFFS ? ['w' => 'write_offs'] : ['s' => 'sales'];
+ $productJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'wp.product_id' : 'sp.product_id';
+ $storeJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'ex.export_val = w.store_id' : 'ex.export_val = s.store_id_1c';
+ $productTableJoin = $type === self::TYPE_WRITE_OFFS ? ['wp' => 'write_offs_products'] : ['sp' => 'sales_products'];
+ $productTableJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'wp.write_offs_id = w.id' : 'sp.check_id = s.id';
+ // Создание запроса с CTE для итоговых сумм
$query = (new Query())
->select([
- 'store_id' => 'ex.entity_id',
- 'category' => 'p1c.category',
- 'total_sum' => new Expression(
- $type === 'writeOffs' ? 'SUM(wp.summ)' : 'SUM(sp.summ)'
- ),
+ 'store_id' => 'main.ex_entity_id',
+ 'category' => 'main.category',
+ 'total_sum' => 'main.total_sum',
+ 'percent' => new Expression('ROUND(CAST(main.total_sum AS DECIMAL) / NULLIF(totals.total, 0), 4)'),
+ 'type' => new Expression(':type', ['type' => $type]),
])
- ->from($type === 'writeOffs' ? ['w' => 'write_offs'] : ['s' => 'sales']);
-
- if ($type === 'writeOffs') {
- $query->join('LEFT JOIN', 'export_import_table ex', 'ex.export_val = w.store_id')
- ->leftJoin('write_offs_products wp', 'wp.write_offs_id = w.id')
- ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = wp.product_id')
- ->where(['>=', 'w.date', $dateFrom])
- ->andWhere(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.category', ''])
- ->groupBy(['ex.entity_id', 'p1c.category']);
-
- if ($productFilter !== null) {
- $query->andWhere(['wp.product_id' => $productFilter]);
- }
- } else {
- $query->leftJoin('sales_products sp', 'sp.check_id = s.id')
- ->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(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.category', ''])
- ->groupBy(['ex.entity_id', 'p1c.category']);
-
- if ($productFilter !== null) {
- $query->andWhere(['sp.product_id' => $productFilter]);
- }
- }
+ ->from([
+ 'main' => (new Query())
+ ->select([
+ 'ex_entity_id' => 'ex.entity_id',
+ 'category' => 'p1c.category',
+ 'total_sum' => new Expression($sumExpression),
+ ])
+ ->from($fromTable)
+ ->leftJoin($productTableJoin, $productTableJoinCondition)
+ ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
+ ->leftJoin('export_import_table ex', $storeJoinCondition)
+ ->where(['<=', key($fromTable) . '.date', $dateFrom])
+ ->andWhere(['>=', key($fromTable) . '.date',(new \DateTime($dateFrom))->modify('-' . self::CATEGORY_LOOKBACK_MONTHS . ' months')->format('Y-m-d')])
+ ->andWhere(['ex.entity_id' => $storeIds])
+ ->andWhere(['<>', 'p1c.category', ''])
+ ->groupBy(['ex.entity_id', 'p1c.category']),
+ ])
+ ->innerJoin(
+ ['totals' => (new Query())
+ ->select([
+ 'store_id' => 'ex.entity_id',
+ 'total' => new Expression($sumExpression),
+ ])
+ ->from($fromTable)
+ ->leftJoin($productTableJoin, $productTableJoinCondition)
+ ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
+ ->leftJoin('export_import_table ex', $storeJoinCondition)
+ ->where(['<=', key($fromTable) . '.date', $dateFrom])
+ ->andWhere(['>=', key($fromTable) . '.date',(new \DateTime($dateFrom))->modify('-' . self::CATEGORY_LOOKBACK_MONTHS . ' months')->format('Y-m-d')])
+ ->andWhere(['ex.entity_id' => $storeIds])
+ ->andWhere(['<>', 'p1c.category', ''])
+ ->groupBy(['ex.entity_id'])],
+ 'main.ex_entity_id = totals.store_id'
+ );
+
+ // Выполнение запроса и обработка результатов
$rows = $query->all();
$result = [];
foreach ($rows as $row) {
$storeId = $row['store_id'];
- $total = $totals[$storeId] ?? 1;
$result[$storeId][] = [
'category' => $row['category'],
'total_sum' => $row['total_sum'],
- 'share_of_total' => round($row['total_sum'] / $total, 4),
- 'type' => $type,
+ 'percent' => $row['percent'],
+ 'type' => $row['type'],
];
}
+
return $result;
}
- public function getMonthCategoryGoal(array $categoryShare, $datePlan, $filters): array
+ /**
+ * Получение целей по категориям за месяц
+ * @param array $categoryShare Доли категорий
+ * @param string $datePlan Дата плана
+ * @param array $filters Фильтры
+ * @return array Массив с целями по категориям
+ */
+ public function getMonthCategoryGoal(array $categoryShare, string $datePlan, array $filters): array
{
$timestamp = strtotime($datePlan);
$year = date('Y', $timestamp);
$result = [];
foreach ($plans as $storeId => $plan) {
- if (!isset($categoryShare[$storeId])) continue;
+ if (!isset($categoryShare[$storeId])) {
+ continue;
+ }
foreach ($categoryShare[$storeId] as $item) {
$result[] = [
'category' => $item['category'],
'store_id' => $storeId,
- 'store_name' => CityStore::findOne($storeId)->name ?? null,
- 'goal' => round($item['share_of_total'] * ($filters['type'] == 'writeOffs' ? $plan['write_offs_plan'] : $plan['total_sales_plan']), 2),
+ 'goal' => round($item['percent'] * ($filters['type'] === self::TYPE_WRITE_OFFS ? $plan['write_offs_plan'] : $plan['total_sales_plan']), 2),
];
}
}
return $result;
}
- public function getMonthSubcategoryShareOrWriteOff(string $dateFrom, ?array $filters = null, ?array $productFilter = null, string $type = 'sales'): array
+ /**
+ * Получение доли подкатегорий или списаний за месяц
+ * @param string $dateFrom Дата начала периода
+ * @param array|null $filters Дополнительные фильтры
+ * @param string $type Тип операции
+ * @return array Массив с долями подкатегорий
+ * @throws \InvalidArgumentException
+ */
+ public function getMonthSubcategoryShareOrWriteOff(string $dateFrom, ?array $filters = null, string $type = self::TYPE_SALES): array
{
- $stores = $this->getVisibleStores();
- $storeIds = array_map(fn($s) => $s->id, $stores);
+ if (!in_array($type, [self::TYPE_SALES, self::TYPE_WRITE_OFFS], true)) {
+ throw new \InvalidArgumentException("Недопустимый тип операции: $type");
+ }
- if (!empty($filters) && array_key_exists('store_id', $filters) && $filters['store_id'] !== '') {
+ $storeIds = array_map(fn($store) => $store->id, $this->getVisibleStores());
+ if (!empty($filters['store_id'])) {
$storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
}
- $totals = $this->getStoreTotals($storeIds, $dateFrom, $productFilter, $type);
- if (empty($totals)) return [];
+ $sumExpression = $type === self::TYPE_WRITE_OFFS ? 'SUM(wp.summ)' : 'SUM(sp.summ)';
+ $fromTable = $type === self::TYPE_WRITE_OFFS ? ['w' => 'write_offs'] : ['s' => 'sales'];
+ $productJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'wp.product_id' : 'sp.product_id';
+ $storeJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'ex.export_val = w.store_id' : 'ex.export_val = s.store_id_1c';
+ $productTableJoin = $type === self::TYPE_WRITE_OFFS ? ['wp' => 'write_offs_products'] : ['sp' => 'sales_products'];
+ $productTableJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'wp.write_offs_id = w.id' : 'sp.check_id = s.id';
$query = (new Query())
->select([
- 'store_id' => 'ex.entity_id',
- 'subcategory' => 'p1c.subcategory',
- 'category' => 'p1c.category',
- 'total_sum' => new Expression(
- $type === 'writeOffs' ? 'SUM(wp.summ)' : 'SUM(sp.summ)'
- ),
- ]);
-
- if ($type === 'writeOffs') {
- $query->from(['w' => 'write_offs'])
- ->join('LEFT JOIN', 'export_import_table ex', 'ex.export_val = w.store_id')
- ->leftJoin('write_offs_products wp', 'wp.write_offs_id = w.id')
- ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = wp.product_id')
- ->where(['>=', 'w.date', $dateFrom])
- ->andWhere(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.subcategory', ''])
- ->groupBy(['ex.entity_id', 'p1c.subcategory', 'p1c.category']);
-
- if ($productFilter !== null) {
- $query->andWhere(['wp.product_id' => $productFilter]);
- }
- } else {
- $query->from(['s' => 'sales'])
- ->leftJoin('sales_products sp', 'sp.check_id = s.id')
- ->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(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.subcategory', ''])
- ->groupBy(['ex.entity_id', 'p1c.subcategory', 'p1c.category']);
-
- if ($productFilter !== null) {
- $query->andWhere(['sp.product_id' => $productFilter]);
- }
- }
+ 'store_id' => 'main.ex_entity_id',
+ 'category' => 'main.category',
+ 'subcategory' => 'main.subcategory',
+ 'total_sum' => 'main.total_sum',
+ 'percent' => new Expression('ROUND(CAST(main.total_sum AS DECIMAL) / NULLIF(totals.total, 0), 4)'),
+ 'type' => new Expression(':type', ['type' => $type]),
+ ])
+ ->from([
+ 'main' => (new Query())
+ ->select([
+ 'ex_entity_id' => 'ex.entity_id',
+ 'category' => 'p1c.category',
+ 'subcategory' => 'p1c.subcategory',
+ 'total_sum' => new Expression($sumExpression),
+ ])
+ ->from($fromTable)
+ ->leftJoin($productTableJoin, $productTableJoinCondition)
+ ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
+ ->leftJoin('export_import_table ex', $storeJoinCondition)
+ ->where(['>=', 'date', $dateFrom])
+ ->andWhere(['ex.entity_id' => $storeIds])
+ ->andWhere(['<>', 'p1c.subcategory', ''])
+ ->groupBy(['ex.entity_id', 'p1c.category', 'p1c.subcategory']),
+ ])
+ ->innerJoin(
+ ['totals' => (new Query())
+ ->select([
+ 'store_id' => 'ex.entity_id',
+ 'total' => new Expression($sumExpression),
+ ])
+ ->from($fromTable)
+ ->leftJoin($productTableJoin, $productTableJoinCondition)
+ ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
+ ->leftJoin('export_import_table ex', $storeJoinCondition)
+ ->where(['>=', 'date', $dateFrom])
+ ->andWhere(['ex.entity_id' => $storeIds])
+ ->andWhere(['<>', 'p1c.subcategory', ''])
+ ->groupBy(['ex.entity_id'])],
+ 'main.ex_entity_id = totals.store_id'
+ );
$rows = $query->all();
$result = [];
foreach ($rows as $row) {
- $storeId = $row['store_id'];
- $total = $totals[$storeId] ?? 1;
$result[] = [
- 'store_id' => $storeId,
+ 'store_id' => $row['store_id'],
'category' => $row['category'],
'subcategory' => $row['subcategory'],
'total_sum' => $row['total_sum'],
- 'percent_of_month' => round($row['total_sum'] / $total, 4),
+ 'percent' => $row['percent'],
+ 'type' => $row['type'],
];
}
return $result;
}
+ /**
+ * Получение целей по подкатегориям за месяц
+ * @param array $subcategoryShare Доли подкатегорий
+ * @param array $categoryGoals Цели по категориям
+ * @return array Массив с целями по подкатегориям
+ */
public function getMonthSubcategoryGoal(array $subcategoryShare, array $categoryGoals): array
{
$indexedGoals = [];
'category' => $sub['category'],
'subcategory' => $sub['subcategory'],
'store_id' => $sub['store_id'],
- 'goal' => round($sub['percent_of_month'] * $goal, 2),
+ 'goal' => round($sub['percent'] * $goal, 2),
];
}
}
return $result;
}
- public function getMonthSpeciesShareOrWriteOff(string $dateFrom, ?array $filters = null, ?array $productFilter = null, string $type = 'sales'): array
+ /**
+ * Получение доли видов или списаний за месяц
+ * @param string $dateFrom Дата начала периода
+ * @param array|null $filters Дополнительные фильтры
+ * @param string $type Тип операции
+ * @return array Массив с долями видов
+ * @throws \InvalidArgumentException
+ */
+ public function getMonthSpeciesShareOrWriteOff(string $dateFrom, ?array $filters = null, string $type = self::TYPE_SALES): array
{
- $stores = $this->getVisibleStores();
- $storeIds = array_map(fn($s) => $s->id, $stores);
+ if (!in_array($type, [self::TYPE_SALES, self::TYPE_WRITE_OFFS], true)) {
+ throw new \InvalidArgumentException("Недопустимый тип операции: $type");
+ }
- if (!empty($filters) && array_key_exists('store_id', $filters) && $filters['store_id'] !== '') {
+ $storeIds = array_map(fn($store) => $store->id, $this->getVisibleStores());
+ if (!empty($filters['store_id'])) {
$storeIds = array_intersect($storeIds, [(int)$filters['store_id']]);
}
- $totals = $this->getStoreTotals($storeIds, $dateFrom, $productFilter, $type);
- if (empty($totals)) return [];
+ $sumExpression = $type === self::TYPE_WRITE_OFFS ? 'SUM(wp.summ)' : 'SUM(sp.summ)';
+ $fromTable = $type === self::TYPE_WRITE_OFFS ? ['w' => 'write_offs'] : ['s' => 'sales'];
+ $productJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'wp.product_id' : 'sp.product_id';
+ $storeJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'ex.export_val = w.store_id' : 'ex.export_val = s.store_id_1c';
+ $productTableJoin = $type === self::TYPE_WRITE_OFFS ? ['wp' => 'write_offs_products'] : ['sp' => 'sales_products'];
+ $productTableJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'wp.write_offs_id = w.id' : 'sp.check_id = s.id';
$query = (new Query())
->select([
- 'store_id' => 'ex.entity_id',
- 'species' => 'p1c.name',
- 'category' => 'p1c.category',
- 'subcategory' => 'p1c.subcategory',
- 'total_sum' => new Expression(
- $type === 'writeOffs' ? 'SUM(wp.summ)' : 'SUM(sp.summ)'
- ),
- ]);
-
- if ($type === 'writeOffs') {
- $query->from(['w' => 'write_offs'])
- ->join('LEFT JOIN', 'export_import_table ex', 'ex.export_val = w.store_id')
- ->leftJoin('write_offs_products wp', 'wp.write_offs_id = w.id')
- ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = wp.product_id')
- ->where(['>=', 'w.date', $dateFrom])
- ->andWhere(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.name', ''])
- ->groupBy(['ex.entity_id', 'p1c.name', 'p1c.category', 'p1c.subcategory']);
-
- if ($productFilter !== null) {
- $query->andWhere(['item ->> \'product_id\'' => $productFilter]);
- }
- } else {
- $query->from(['s' => 'sales'])
- ->leftJoin('sales_products sp', 'sp.check_id = s.id')
- ->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(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.name', ''])
- ->groupBy(['ex.entity_id', 'p1c.name', 'p1c.category', 'p1c.subcategory']);
-
- if ($productFilter !== null) {
- $query->andWhere(['sp.product_id' => $productFilter]);
- }
- }
+ 'store_id' => 'main.ex_entity_id',
+ 'category' => 'main.category',
+ 'subcategory' => 'main.subcategory',
+ 'species' => 'main.species',
+ 'total_sum' => 'main.total_sum',
+ 'percent' => new Expression('ROUND(CAST(main.total_sum AS DECIMAL) / NULLIF(totals.total, 0), 4)'),
+ 'type' => new Expression(':type', ['type' => $type]),
+ ])
+ ->from([
+ 'main' => (new Query())
+ ->select([
+ 'ex_entity_id' => 'ex.entity_id',
+ 'category' => 'p1c.category',
+ 'subcategory' => 'p1c.subcategory',
+ 'species' => 'p1c.species',
+ 'total_sum' => new Expression($sumExpression),
+ ])
+ ->from($fromTable)
+ ->leftJoin($productTableJoin, $productTableJoinCondition)
+ ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
+ ->leftJoin('export_import_table ex', $storeJoinCondition)
+ ->andWhere(['ex.entity_id' => $storeIds])
+ ->andWhere(['<>', 'p1c.species', ''])
+ ->groupBy(['ex.entity_id', 'p1c.category', 'p1c.subcategory', 'p1c.species']),
+ ])
+ ->innerJoin(
+ ['totals' => (new Query())
+ ->select([
+ 'store_id' => 'ex.entity_id',
+ 'total' => new Expression($sumExpression),
+ ])
+ ->from($fromTable)
+ ->leftJoin($productTableJoin, $productTableJoinCondition)
+ ->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
+ ->leftJoin('export_import_table ex', $storeJoinCondition)
+ ->where(['>=', key($fromTable) . '.date', $dateFrom])
+ ->andWhere(['ex.entity_id' => $storeIds])
+ ->andWhere(['<>', 'p1c.species', ''])
+ ->groupBy(['ex.entity_id'])],
+ 'main.ex_entity_id = totals.store_id'
+ );
$rows = $query->all();
$result = [];
foreach ($rows as $row) {
- $storeId = $row['store_id'];
- $total = $totals[$storeId] ?? 1;
$result[] = [
- 'store_id' => $storeId,
+ 'store_id' => $row['store_id'],
'category' => $row['category'],
'subcategory' => $row['subcategory'],
'species' => $row['species'],
'total_sum' => $row['total_sum'],
- 'percent_of_month' => round($row['total_sum'] / $total, 4),
+ 'percent' => $row['percent'],
+ 'type' => $row['type'],
];
}
return $result;
}
+ /**
+ * Получение целей по видам за месяц
+ * @param array $speciesShare Доли видов
+ * @param array $subcategoryGoals Цели по подкатегориям
+ * @return array Массив с целями по видам
+ */
public function getMonthSpeciesGoalDirty(array $speciesShare, array $subcategoryGoals): array
{
$indexedGoals = [];
'subcategory' => $species['subcategory'],
'species' => $species['species'],
'store_id' => $species['store_id'],
- 'goal' => round($species['percent_of_month'] * $goal, 2),
+ 'goal' => round($species['percent'] * $goal, 2),
];
}
}
return $result;
}
+ /**
+ * Расчет полной цепочки целей
+ * @param array $filters Фильтры
+ * @return array Отфильтрованный массив целей
+ */
public function calculateFullGoalChain(array $filters): array
{
$datePlan = $filters['plan_date'];
- $dateFromForCategory = (new \DateTime($datePlan))->modify('-12 months')->format('Y-m-d');
- $dateFrom = (new \DateTime($datePlan))->modify('-3 months')->format('Y-m-d');
+ $dateFromForCategory = (new \DateTime($datePlan))->modify('-' . (self::CATEGORY_LOOKBACK_MONTHS + self::LOOKBACK_MONTHS) . ' months')->format(self::DATE_FORMAT);
+ $dateToForCategory = (new \DateTime($datePlan))->modify('-' . (self::CATEGORY_LOOKBACK_MONTHS + self::LOOKBACK_MONTHS) . ' months')->format(self::DATE_FORMAT);
+ $dateFrom = (new \DateTime($datePlan))->modify('-' . (self::SUBCATEGORY_LOOKBACK_MONTHS + self::LOOKBACK_MONTHS). ' months')->format(self::DATE_FORMAT);
$monthCategoryShare = $this->getMonthCategoryShareOrWriteOff($dateFromForCategory, $filters);
$monthCategoryGoal = $this->getMonthCategoryGoal($monthCategoryShare, $datePlan, $filters);
return array_values($filtered);
}
-}
+}
\ No newline at end of file