From: Vladimir Fomichev Date: Wed, 18 Jun 2025 11:54:59 +0000 (+0300) Subject: Вычисление долей списания товара в виде X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=419bf33f0f62ac30a187a1e40c08e06fc6ea9582;p=erp24_rep%2Fyii-erp24%2F.git Вычисление долей списания товара в виде --- diff --git a/erp24/controllers/AutoPlannogrammaController.php b/erp24/controllers/AutoPlannogrammaController.php index 5a39e992..138aaa82 100644 --- a/erp24/controllers/AutoPlannogrammaController.php +++ b/erp24/controllers/AutoPlannogrammaController.php @@ -1761,9 +1761,8 @@ class AutoPlannogrammaController extends BaseController } $service = new AutoPlannogrammaService(); - //$result = $service->getWeeklyBouquetProductsForecast($monthRequest, $yearRequest, $storeIdRequest); - $writeOffs = $service->getWeeklyProductsWriteoffsForecast($monthRequest, $yearRequest, $storeIdRequest); - var_dump($writeOffs); die(); + $result = $service->getWeeklyBouquetProductsForecast($monthRequest, $yearRequest, $storeIdRequest); + if (!is_array($result)) { return ['success' => false, 'message' => 'Ошибка структуры данных']; } diff --git a/erp24/services/AutoPlannogrammaService.php b/erp24/services/AutoPlannogrammaService.php index 4587c0b1..6f03a63f 100644 --- a/erp24/services/AutoPlannogrammaService.php +++ b/erp24/services/AutoPlannogrammaService.php @@ -2938,7 +2938,7 @@ class AutoPlannogrammaService return $grouped; } - public function getWeeklyProductsWriteoffsForecast($month, $year, $storeId = null, $weekNumber = null) + public function getWeeklyProductsWriteoffsForecast($month, $year, $weeklySalesForecast, $storeId = null, $weekNumber = null) { $weeksProductForecast = []; $filters = []; @@ -2957,6 +2957,11 @@ class AutoPlannogrammaService [$monthSpeciesGoal['subcategory']] [$monthSpeciesGoal['species']] = $monthSpeciesGoal['goal']; } + $productsWriteoffsShare = $this->calculateWriteOffShareByMonth($weeklySalesForecast, $month, $year); + $productsWriteoffsShareMap = []; + foreach ($productsWriteoffsShare as $productWriteoffs) { + $productsWriteoffsShareMap[$productWriteoffs['store_id']][$productWriteoffs['product_id']] = $productWriteoffs['share']; + } $weeksShareResult = $this->getHistoricalWeeklySpeciesShare($monthYear, $filters, null, 'writeOffs'); $weeksData = $this->calculateWeeklySpeciesGoals($weeksShareResult, $monthSpeciesGoals); @@ -2965,6 +2970,8 @@ class AutoPlannogrammaService $forecasts = $this->calculateWeekForecastSpeciesProducts($r['category'], $r['subcategory'], $r['species'], $r['store_id'], $r['weekly_goal']); foreach ($forecasts as $forecast) { + $productWriteoffsForecastSpeciesShare = $productsWriteoffsShareMap[$r['store_id']][$forecast['product_id']] ?? 0; + $productWriteoffsForecast = $productWriteoffsForecastSpeciesShare * $forecast['forecast']; $weeksProductForecast[] = [ 'category' => $forecast['category'] ?? '', 'subcategory' => $forecast['subcategory'] ?? '', @@ -2973,7 +2980,7 @@ class AutoPlannogrammaService 'name' => $forecast['name'] ?? '', 'price' => $forecast['price'] ?? '', 'goal' => $forecast['goal'] ?? 0, - 'forecast' => $forecast['forecast'] ?? 0, + 'forecast' => $productWriteoffsForecast ?? 0, 'week' => $r['week'], ]; } @@ -2982,4 +2989,137 @@ class AutoPlannogrammaService return $weeksProductForecast; } + /** + * Рассчитывает долю списания (write-offs) каждого товара за заданный месяц + * на основании исторических данных за два предыдущих года. + * + * @param array $productList Список товаров: + * [ + * [ + * 'week' => int, + * 'store_id' => int, + * 'category' => string, + * 'subcategory' => string, + * 'species' => string, + * 'product_id' => string, + * 'forecast_month_pieces' => float, + * 'forecast_week_pieces' => float, + * ], … + * ] + * @param string $monthYear Формат 'YYYY-MM' + * @return array [ + * [ + * 'store_id' => int, + * 'month' => int, + * 'year' => int, + * 'category' => string, + * 'subcategory'=> string, + * 'species' => string, + * 'product_id' => string, + * 'writeoff_qty' => float, + * 'species_qty' => float, + * 'share' => float, // доля + * ], … + * ] + */ + public function calculateWriteOffShareByMonth(array $productList, int $month, int $year ): array + { + $year = $year ?? (int)date("Y"); + $month = $month ?? (int)date("m", strtotime( "+ 2 month" . date('Y-m-01'))); + + $storeIds = array_unique(array_column($productList, 'store_id')); + if (empty($storeIds)) { + return []; + } + + $productQtyMap = []; // [store][cat][sub][spec][pid] => qty + $speciesQtyMap = []; // [store][cat][sub][spec] => total qty + + $storeJoinCondition = 'ex.export_val = w.store_id'; + + for ($offset = 2; $offset >= 1; $offset--) { + $histYear = $year - $offset; + $start = sprintf('%04d-%02d-01 00:00:00', $histYear, $month); + $end = date('Y-m-d 23:59:59', strtotime("$start +1 month -1 second")); + + $productsWriteOffs = (new Query()) + ->select([ + 'store_id' => 'ex.entity_id', + 'category' => 'p1c.category', + 'subcategory' => 'p1c.subcategory', + 'species' => 'p1c.species', + 'product_id' => 'wp.product_id', + 'qty' => new \yii\db\Expression('SUM(wp.quantity)'), + ]) + ->from(['w' => 'write_offs']) + ->innerJoin(['wp' => 'write_offs_products'], 'wp.write_offs_id = w.id') + ->leftJoin('export_import_table ex', $storeJoinCondition) + ->leftJoin('products_1c_nomenclature p1c', 'p1c.id = wp.product_id') + ->leftJoin('products_1c p1', 'p1.id = wp.product_id') + + ->andWhere(['ex.entity_id' => $storeIds]) + ->andWhere(['>=', 'w.date', $start]) + ->andWhere(['<=', 'w.date', $end]) + + ->andWhere(['p1.components' => '']) + ->andWhere(['not in', 'p1c.category', ['', 'букет', 'сборка', 'сервис']]) + ->groupBy(['ex.entity_id','p1c.category','p1c.subcategory','p1c.species','wp.product_id']) + ->all(); + + foreach ($productsWriteOffs as $productsWriteOffsItem) { + $sid = (int)$productsWriteOffsItem['store_id']; + $cat = $productsWriteOffsItem['category']; + $sub = $productsWriteOffsItem['subcategory']; + $spec = $productsWriteOffsItem['species']; + $pid = $productsWriteOffsItem['product_id']; + $qty = (float)$productsWriteOffsItem['qty']; + + + $productQtyMap[$sid][$cat][$sub][$spec][$pid] = + ($productQtyMap[$sid][$cat][$sub][$spec][$pid] ?? 0.0) + $qty; + + $speciesQtyMap[$sid][$cat][$sub][$spec] = + ($speciesQtyMap[$sid][$cat][$sub][$spec] ?? 0.0) + $qty; + } + } + + $seen = []; + $result = []; + foreach ($productList as $item) { + $pid = $item['product_id']; + $sid = $item['store_id']; + $cat = $item['category']; + $sub = $item['subcategory']; + $spec = $item['species']; + + $key = "{$sid}|{$cat}|{$sub}|{$spec}|{$pid}"; + if (isset($seen[$key])) { + continue; + } + $seen[$key] = true; + + $productsQty = $productQtyMap[$sid][$cat][$sub][$spec][$pid] ?? 0.0; + $speciesQty = $speciesQtyMap[$sid][$cat][$sub][$spec] ?? 0.0; + $share = $speciesQty > 0 + ? round($productsQty / $speciesQty, 4) + : 0.0; + + $result[] = [ + 'store_id' => $sid, + 'month' => $month, + 'year' => $year, + 'category' => $cat, + 'subcategory' => $sub, + 'species' => $spec, + 'product_id' => $pid, + 'writeoff_qty' => $productsQty, + 'species_qty' => $speciesQty, + 'share' => $share, + ]; + } + + return $result; + } + + } \ No newline at end of file