$service = new AutoPlannogrammaService();
- $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
- $data = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters);
+ $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $data = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters['type']);
$flatData = array_filter($data, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
$service = new AutoPlannogrammaService();
- $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
- $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters);
- $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
+ $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date']);
+ $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters);
$data = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
+ if ($filters['type'] == AutoPlannogrammaService::TYPE_WRITE_OFFS) {
+ $monthCategoryWriteOffsShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $monthCategoryWriteOffsGoal = $service->getMonthCategoryGoal($monthCategoryWriteOffsShare, $filters['plan_date'], $filters['type']);
+ $monthSubcategoryWriteOffsShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $data = $service->getMonthSubcategoryGoal($monthSubcategoryWriteOffsShare, $monthCategoryWriteOffsGoal, $filters['type'], $data);
+ }
+
$flatData = array_filter($data, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
if (empty($value)) continue;
$filters['plan_date'] = $filters['year'] . '-' . str_pad($filters['month'], 2, '0', STR_PAD_LEFT) . '-01';
$service = new AutoPlannogrammaService();
- $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
- $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date'], $filters);
- $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $monthCategoryShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters);
+ $monthCategoryGoal = $service->getMonthCategoryGoal($monthCategoryShare, $filters['plan_date']);
+ $monthSubcategoryShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters);
$monthSubcategoryGoal = $service->getMonthSubcategoryGoal($monthSubcategoryShare, $monthCategoryGoal);
- $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $monthSpeciesShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters);
$data = $service->getMonthSpeciesGoalDirty($monthSpeciesShare, $monthSubcategoryGoal);
+ if ($filters['type'] == AutoPlannogrammaService::TYPE_WRITE_OFFS) {
+ $monthCategoryWriteOffsShare = $service->getMonthCategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $monthCategoryWriteOffsGoal = $service->getMonthCategoryGoal($monthCategoryWriteOffsShare, $filters['plan_date'], $filters['type']);
+ $monthSubcategoryWriteOffsShare = $service->getMonthSubcategoryShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $monthSubcategoryWriteOffsGoals = $service->getMonthSubcategoryGoal($monthSubcategoryWriteOffsShare, $monthCategoryWriteOffsGoal, $filters['type']);
+ $monthSpeciesWriteOffShare = $service->getMonthSpeciesShareOrWriteOff($filters['plan_date'], $filters, $filters['type']);
+ $data = $service->getMonthSpeciesGoalDirty($monthSpeciesWriteOffShare, $monthSubcategoryWriteOffsGoals, $filters['type'], $data);
+ }
+
$flatData = array_filter($data, function ($row) use ($filters) {
foreach ($filters as $key => $value) {
if (empty($value)) continue;
return true;
});
-
-
$dataProvider = new ArrayDataProvider([
'allModels' => $flatData,
'pagination' => ['pageSize' => 100],
class AutoPlannogrammaService
{
- private const TYPE_SALES = 'sales'; // Тип операции: продажи
- private const TYPE_WRITE_OFFS = 'writeOffs'; // Тип операции: списания
+ public const TYPE_SALES = 'sales'; // Тип операции: продажи
+ public const TYPE_WRITE_OFFS = 'writeOffs'; // Тип операции: списания
private const CATEGORY_LOOKBACK_MONTHS = 3; // Период для анализа категорий (месяцы)
private const LOOKBACK_MONTHS = 2; // Отступаемый шаг от плановой даты перед расчетами
return CityStore::findAll(['visible' => CityStore::IS_VISIBLE]);
}
- /** Корректировка процента списаний, если он превышает 10% от процента продаж.
- * @param float $currentPercent Текущий процент списаний.
- * @param float $salesPercent Процент продаж для того же магазина, категории и подкатегории.
- * @return float Скорректированный процент списаний.
+ /**
+ * Корректировка цели списаний, если она превышает 10% от цели продаж.
+ * @param float|null $writeOffGoal Текущая цель списаний
+ * @param float|null $salesGoal Цель продаж для того же магазина, категории и подкатегории
+ * @return float Скорректированная цель списаний
*/
- private function adjustWriteOffPercent(
- float $currentPercent,
- float $salesPercent
- ): float {
- if ($currentPercent > ($salesPercent * 0.1)) {
- return $currentPercent * 0.1;
+ private function adjustWriteOffPercent(?float $writeOffGoal, ?float $salesGoal): float
+ {
+ if ($writeOffGoal === null || $salesGoal === null || $salesGoal <= 0) {
+ return $writeOffGoal ?? 0.0;
}
- return $currentPercent;
- }
+ return ($writeOffGoal / $salesGoal >= 0.1) ? $salesGoal * 0.1 : $writeOffGoal;
+ }
/**
* Получение доли категорий или списаний за месяц
->andWhere(['>=', "$alias.date", (new \DateTime($month1 . '-01'))->format('Y-m-d')])
->andWhere(['<=', "$alias.date", (new \DateTime($month3 . '-01'))->modify('last day of this month')->format('Y-m-d')])
->groupBy(['ex.entity_id']),
- ], 'main.ex_entity_id = totals.store_id');
+ ], 'main.ex_entity_id = totals.store_id')
+ ->orderBy('category');
// Выполнение запроса и форматирование
$rows = $query->all();
* @param array $filters Фильтры
* @return array Массив с целями по категориям
*/
- public function getMonthCategoryGoal(array $categoryShare, string $datePlan, array $filters): array
+ public function getMonthCategoryGoal(array $categoryShare, string $datePlan, string $type = self::TYPE_SALES): array
{
$timestamp = strtotime($datePlan);
$year = date('Y', $timestamp);
$month = date('m', $timestamp);
+
$plans = SalesWriteOffsPlan::find()
->where(['year' => $year, 'month' => $month])
->asArray()
$result[] = [
'category' => $item['category'],
'store_id' => $storeId,
- 'goal' => round($item['percent'] * ($filters['type'] === self::TYPE_WRITE_OFFS ? $plan['write_offs_plan'] : $plan['total_sales_plan']), 2),
+ 'goal' => round($item['percent'] * ($type === self::TYPE_WRITE_OFFS ? $plan['write_offs_plan'] : $plan['total_sales_plan']), 2),
];
}
}
->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
->leftJoin('export_import_table ex', $storeJoinCondition)
->andWhere(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.subcategory', ''])
+ ->andWhere(['<>', 'p1c.category', ''])
->andWhere(['or', ...$months])
->groupBy(['ex.entity_id', 'p1c.category', 'p1c.subcategory']),
])
['totals' => (new Query())
->select([
'store_id' => 'ex.entity_id',
+ 'category' => 'p1c.category',
'total' => new Expression($sumExpression),
])
->from($fromTable)
->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
->leftJoin('export_import_table ex', $storeJoinCondition)
->andWhere(['ex.entity_id' => $storeIds])
- ->andWhere(['<>', 'p1c.subcategory', ''])
+ ->andWhere(['<>', 'p1c.category', ''])
->andWhere(['or', ...$months])
- ->groupBy(['ex.entity_id'])],
- 'main.ex_entity_id = totals.store_id'
- );
-
- $rows = $query->all();
- $result = [];
- $salesPercents = [];
-
- // Сначала вычисляем проценты для продаж (они понадобятся для сравнения)
- foreach ($rows as $row) {
- $key = "{$row['store_id']}_{$row['category']}_{$row['subcategory']}";
- if ($row['type'] === self::TYPE_SALES) {
- $salesPercents[$key] = $row['percent'];
- }
- }
+ ->groupBy(['ex.entity_id', 'p1c.category'])],
+ 'main.ex_entity_id = totals.store_id AND main.category = totals.category'
+ )
+ ->orderBy('category, subcategory');
- foreach ($rows as $row) {
- $key = "{$row['store_id']}_{$row['category']}_{$row['subcategory']}";
- $percent = $row['percent'];
-
- if ($row['type'] === self::TYPE_WRITE_OFFS) {
- $percent = $this->adjustWriteOffPercent($percent, $salesPercents[$key] ?? 0);
- }
-
- $result[] = [
- 'store_id' => $row['store_id'],
- 'category' => $row['category'],
- 'subcategory' => $row['subcategory'],
- 'total_sum' => $row['total_sum'],
- 'percent' => $percent,
- 'type' => $row['type'],
- ];
- }
+ $result = $query->all();
return $result;
}
* Получение целей по подкатегориям за месяц
* @param array $subcategoryShare Доли подкатегорий
* @param array $categoryGoals Цели по категориям
+ * @param string $type Тип операции
* @return array Массив с целями по подкатегориям
*/
- public function getMonthSubcategoryGoal(array $subcategoryShare, array $categoryGoals): array
+ public function getMonthSubcategoryGoal(array $subcategoryShare, array $categoryGoals, string $type = self::TYPE_SALES, array $salesGoals = []): array
{
$indexedGoals = [];
foreach ($categoryGoals as $goal) {
];
}
}
+
+ if ($type == self::TYPE_WRITE_OFFS) {
+ foreach ($result as &$row) {
+ foreach ($salesGoals as $salesGoal) {
+ if ($row['category'] === $salesGoal['category']
+ && $row['subcategory'] === $salesGoal['subcategory']
+ && $row['store_id'] === $salesGoal['store_id']) {
+ $row['old_value'] = $row['goal'];
+ $row['sales_goal'] = $salesGoal['goal'];
+ $row['goal'] = $this->adjustWriteOffPercent($row['goal'], $salesGoal['goal']);
+ }
+ }
+ }
+ unset($row);
+ }
+
return $result;
}
['totals' => (new Query())
->select([
'store_id' => 'ex.entity_id',
+ 'category' => 'p1c.category',
+ 'subcategory' => 'p1c.subcategory',
'total' => new Expression($sumExpression),
])
->from($fromTable)
->leftJoin($productTableJoin, $productTableJoinCondition)
->leftJoin('products_1c_nomenclature p1c', "p1c.id = $productJoinCondition")
->leftJoin('export_import_table ex', $storeJoinCondition)
- ->andWhere(['or', ...$months])
->andWhere(['ex.entity_id' => $storeIds])
->andWhere(['<>', 'p1c.species', ''])
- ->groupBy(['ex.entity_id'])],
- 'main.ex_entity_id = totals.store_id'
- );
+ ->andWhere(['or', ...$months])
+ ->groupBy(['ex.entity_id', 'p1c.category', 'p1c.subcategory'])],
+ 'main.ex_entity_id = totals.store_id
+ AND main.category = totals.category
+ AND main.subcategory = totals.subcategory'
+ )
+ ->orderBy('category, subcategory, species');
$rows = $query->all();
$result = [];
* Получение целей по видам за месяц
* @param array $speciesShare Доли видов
* @param array $subcategoryGoals Цели по подкатегориям
+ * @param string $type Тип операции
* @return array Массив с целями по видам
*/
- public function getMonthSpeciesGoalDirty(array $speciesShare, array $subcategoryGoals): array
+ public function getMonthSpeciesGoalDirty(array $speciesShare, array $subcategoryGoals, $type = self::TYPE_SALES, $salesGoals = []): array
{
$indexedGoals = [];
foreach ($subcategoryGoals as $goal) {
}
}
+ if ($type == self::TYPE_WRITE_OFFS) {
+ foreach ($result as &$row) {
+ foreach ($salesGoals as $salesGoal) {
+ if ($row['category'] === $salesGoal['category']
+ && $row['subcategory'] === $salesGoal['subcategory']
+ && $row['species'] === $salesGoal['species']
+ && $row['store_id'] === $salesGoal['store_id']) {
+ $row['old_value'] = $row['goal'];
+ $row['sales_goal'] = $salesGoal['goal'];
+ $row['goal'] = $this->adjustWriteOffPercent($row['goal'], $salesGoal['goal']);
+ }
+ }
+ }
+ unset($row);
+ }
+
return $result;
}