From: fomichev Date: Fri, 23 May 2025 15:44:20 +0000 (+0300) Subject: Рефакторинг X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=c8f12ed8751f6f98168c01b78102d54ae0dfc40f;p=erp24_rep%2Fyii-erp24%2F.git Рефакторинг --- diff --git a/erp24/controllers/AutoPlannogrammaController.php b/erp24/controllers/AutoPlannogrammaController.php index bee0c851..0baeb488 100644 --- a/erp24/controllers/AutoPlannogrammaController.php +++ b/erp24/controllers/AutoPlannogrammaController.php @@ -696,7 +696,12 @@ class AutoPlannogrammaController extends BaseController $filters['species'] ); // var_dump($result); die(); - + $withoutHistoryResults = StorePlanService::calculateMedianSalesForProductsWithoutHistoryExtended( + $filters['store_id'], + $filters['month'], + $filters['year'], + $result['without_history'] + ); $productSalesShare = StorePlanService::calculateProductSalesShare( $filters['store_id'], $filters['month'], @@ -704,7 +709,7 @@ class AutoPlannogrammaController extends BaseController ); - var_dump( $productSalesShare); die(); + var_dump( $withoutHistoryResults); die(); $flatData = array_filter($data, function ($row) use ($filters) { foreach ($filters as $key => $value) { if (empty($value)) continue; @@ -722,7 +727,7 @@ class AutoPlannogrammaController extends BaseController 'pagination' => ['pageSize' => 100], ]); } - return $this->render('8', [ + return $this->render('81', [ 'dataProvider' => $dataProvider, 'filters' => $filters, ]); diff --git a/erp24/controllers/CategoryPlanController.php b/erp24/controllers/CategoryPlanController.php index 9a368a5f..ee292ebb 100644 --- a/erp24/controllers/CategoryPlanController.php +++ b/erp24/controllers/CategoryPlanController.php @@ -348,7 +348,7 @@ class CategoryPlanController extends Controller { $species ); // var_dump($result); die(); - $weightedResults = StorePlanService::calculateWeightedSalesForProductsWithoutHistory( + $weightedResults = StorePlanService::calculateMedianSalesForProductsWithoutHistoryExtended( $storeId, $selectedMonth, $selectedYear, diff --git a/erp24/services/AutoPlannogrammaService.php b/erp24/services/AutoPlannogrammaService.php index 4cdaf1a0..d39bbf85 100644 --- a/erp24/services/AutoPlannogrammaService.php +++ b/erp24/services/AutoPlannogrammaService.php @@ -7,6 +7,9 @@ use yii\db\Expression; use yii\db\Query; use yii\helpers\ArrayHelper; use yii_app\records\CityStore; +use yii_app\records\PricesDynamic; +use yii_app\records\Products1cNomenclature; +use yii_app\records\SalesProducts; use yii_app\records\SalesWriteOffsPlan; class AutoPlannogrammaService @@ -604,20 +607,20 @@ class AutoPlannogrammaService // ——————— WEIGHTED SALES ———————— $t2 = hrtime(true); - $weightedResults = StorePlanService::calculateMedianSalesForProductsWithoutHistory( + $medianResults = StorePlanService::calculateMedianSalesForProductsWithoutHistoryExtended( $storeId, $month, $year, $productsWithoutHistory ); $dur = (hrtime(true) - $t2) / 1e6; Yii::warning("calculateMedianSalesForProductsWithoutHistory for store {$storeId}: {$dur} ms\n"); - if (empty($weightedResults)) { + if (empty($medianResults)) { continue; } // ——————— COST CALCULATION ———————— $t3 = hrtime(true); $costs = StorePlanService::calculateCostForProductsWithoutHistory( - $storeId, $month, $year, $weightedResults + $storeId, $month, $year, $medianResults ); $dur = (hrtime(true) - $t3) / 1e6; Yii::warning( "calculateCostForProductsWithoutHistory for store {$storeId}: {$dur} ms\n"); @@ -674,6 +677,123 @@ class AutoPlannogrammaService return $result; } + /** + * @param array|int $storeIds + * @param string $dateFrom формат 'Y-m-d H:i:s' + * @param string $dateTo формат 'Y-m-d H:i:s' + * @param int $regionId + * @return array список строк с полями: + * sale_id, sale_date, product_id, product_name, + * component_guid, component_name, component_category, + * quantity, price, cost + */ + public function getUnmarkedProductsComponents(array|int $storeIds, string $dateFrom, string $dateTo, int $regionId = 52, $typeFilter = null): array + { + $salesProducts = SalesProducts::find() + ->alias('sp') + ->innerJoinWith([ + 'sale s', + 'product_1c p1c', + 'product.nomenclature nom', + ]) + ->andWhere(['s.store_id' => $storeIds]) + ->andWhere(['between', 's.date', $dateFrom, $dateTo]) + ->andWhere(['nom.category' => null]); + + if ($typeFilter) { + $salesProducts->andWhere(['p1c.type' => $typeFilter]); + } + $salesProducts->all(); + + $components = []; + $rows = []; + foreach ($salesProducts as $sp) { + /** @var SalesProducts $sp */ + $sale = $sp->check_id; + $product = $sp->product_id; + $js = trim($product->components); + if ($js === '' || $js[0] !== '{') { + continue; + } + $data = @json_decode($js, true); + if (!is_array($data)) { + continue; + } + foreach ($data as $guid => $qty) { + $qty = (int)$qty; + if ($qty <= 0) { + continue; + } + + $components[$guid] = true; + + $rows[] = [ + 'sale_id' => $sale->id, + 'sale_date' => $sale->date, + 'product_id' => $product->id, + 'product_name'=> $product->name, + 'component_guid' => $guid, + 'quantity' => $qty, + ]; + } + } + if (empty($rows)) { + return []; + } + $guids = array_keys($components); + + $nomenclatures = Products1cNomenclature::find() + ->andWhere(['guid' => $guids]) + ->indexBy('guid') + ->all(); + + $priceDynamics = PricesDynamic::find() + ->andWhere(['region_id' => $regionId]) + ->andWhere(['product_id' => array_values( ArrayHelper::getColumn($nomenclatures, 'id') )]) + ->andWhere(['<=', 'date_from', $dateTo]) + ->andWhere(['>=', 'date_to', $dateFrom]) + ->orderBy(['date_from' => SORT_DESC]) + ->all(); + + $pricesByProduct = []; + foreach ($priceDynamics as $pd) { + /** @var PricesDynamic $pd */ + $pid = $pd->product_id; + $pricesByProduct[$pid][] = $pd; + } + + $result = []; + foreach ($rows as $r) { + $guid = $r['component_guid']; + $n = $nomenclatures[$guid] ?? null; + $pid = $n?->id; + $price = 0; + if ($pid && isset($pricesByProduct[$pid])) { + foreach ($pricesByProduct[$pid] as $pd) { + if ($pd->date_from <= $r['sale_date'] && $pd->date_to >= $r['sale_date']) { + $price = $pd->price; + break; + } + } + } + $cost = $r['quantity'] * $price; + + $result[] = [ + 'sale_id' => $r['sale_id'], + 'sale_date' => $r['sale_date'], + 'product_id' => $r['product_id'], + 'product_name' => $r['product_name'], + 'component_guid' => $guid, + 'component_name' => $n?->name, + 'component_category' => $n?->category, + 'quantity' => $r['quantity'], + 'price' => $price, + 'cost' => $cost, + ]; + } + + return $result; + } } \ No newline at end of file diff --git a/erp24/services/StorePlanService.php b/erp24/services/StorePlanService.php index d660a6d3..dd26b2cc 100755 --- a/erp24/services/StorePlanService.php +++ b/erp24/services/StorePlanService.php @@ -455,7 +455,7 @@ class StorePlanService /** - * Метод вычисляет взвешенное значение продаж для товаров без истории. + * Метод вычисляет значение продаж для товаров без истории. * * @param int $storeId Идентификатор магазина. * @param string $selectedMonth Выбранный месяц в формате "mm" (целевой месяц). @@ -468,13 +468,13 @@ class StorePlanService * * @return array Возвращает массив, где ключ – GUID товара, а значение – рассчитанное взвешенное значение продаж. */ - public static function calculateWeightedSalesForProductsWithoutHistory($storeId, $selectedMonth, $selectedYear, $productsWithoutHistory) + public static function calculateMedianSalesForProductsWithoutHistoryExtended($storeId, $selectedMonth, $selectedYear, $productsWithoutHistory) { $t0 = hrtime(true); $targetDate = strtotime("{$selectedYear}-{$selectedMonth}-01"); $periods = self::getPeriods($targetDate, 3); - $weightedResults = []; + $medianSalesResults = []; $initTime = (hrtime(true) - $t0) / 1e6; // миллисекунды Yii::warning( "Init (periods): {$initTime} ms\n"); foreach ($productsWithoutHistory as $product) { @@ -482,7 +482,7 @@ class StorePlanService $t1 = hrtime(true); $similarProductIds = self::getSimilarProductIDs($guid); if (empty($similarProductIds)) { - $weightedResults[$guid] = 0; + $medianSalesResults[$guid] = 0; continue; } $dur = (hrtime(true) - $t1) / 1e6; @@ -501,7 +501,7 @@ class StorePlanService $t3 = hrtime(true); $median3 = self::calculateMedianSalesOverPeriods($storeId, $similarProductIds, $periods); - $weightedResults[$guid] = [ + $medianSalesResults[$guid] = [ 'weightedValue' => $median3, 'medianSales' => $medianSales, 'salesValues' => $salesValuesForEachMonth, @@ -513,7 +513,7 @@ class StorePlanService $totalTime = (hrtime(true) - $t0) / 1e6; Yii::warning( "Total calculateWeightedSalesForProductsWithoutHistory: {$totalTime} ms\n"); - return $weightedResults; + return $medianSalesResults; } /** @@ -657,7 +657,7 @@ class StorePlanService * @param int $storeId * @param int $selectedMonth * @param int $selectedYear - * @param array $productsWithoutHistory массив ['guid' => …] + * @param array $medianProductsWithoutHistory массив ['guid' => …] * @return array плоский массив строк: * [ * [ @@ -676,13 +676,13 @@ class StorePlanService int $storeId, int $selectedMonth, int $selectedYear, - array $productsWithoutHistory + array $medianProductsWithoutHistory ): array { $accumulator = []; - - foreach ($productsWithoutHistory as $guid => $info) { + // var_dump($productsWithoutHistory); die(); + foreach ($medianProductsWithoutHistory as $guid => $info) { $quantity = (float)$info; if ($quantity <= 0) { continue; diff --git a/erp24/views/auto-plannogramma/81.php b/erp24/views/auto-plannogramma/81.php new file mode 100644 index 00000000..885a80ed --- /dev/null +++ b/erp24/views/auto-plannogramma/81.php @@ -0,0 +1,114 @@ + +
+ +

+ 'get']); ?> +
+
+ field(new \yii\base\DynamicModel(['category' => $filters['category'] ?? '']), 'category')->widget(Select2::class, [ + 'data' => ArrayHelper::map( + Products1cNomenclature::find()->select('category')->distinct()->asArray()->all(), + 'category', + 'category' + ), + 'options' => ['placeholder' => 'Категория', 'name' => 'category'], + 'pluginOptions' => ['allowClear' => true], + ])->label('Категория') ?> +
+
+ field(new \yii\base\DynamicModel(['subcategory' => $filters['subcategory'] ?? '']), 'subcategory')->widget(Select2::class, [ + 'data' => ArrayHelper::map( + Products1cNomenclature::find()->select('subcategory')->distinct()->asArray()->all(), + 'subcategory', + 'subcategory' + ), + 'options' => ['placeholder' => 'Подкатегория', 'name' => 'subcategory'], + 'pluginOptions' => ['allowClear' => true], + ])->label('Подкатегория') ?> +
+
+ field(new \yii\base\DynamicModel(['species' => $filters['species'] ?? '']), 'species')->widget(Select2::class, [ + 'data' => ArrayHelper::map( + Products1cNomenclature::find()->select('species')->distinct()->asArray()->all(), + 'species', + 'species' + ), + 'options' => ['placeholder' => 'Тип товара', 'name' => 'species'], + 'pluginOptions' => ['allowClear' => true], + ])->label('Товар') ?> +
+
+ field(new \yii\base\DynamicModel(['store_id' => $filters['store_id'] ?? '']), 'store_id')->widget(Select2::class, [ + 'data' => ArrayHelper::map( + CityStore::findAll(['visible' => CityStore::IS_VISIBLE]), + 'id', + 'name' + ), + 'options' => ['placeholder' => 'Магазин', 'name' => 'store_id'], + 'pluginOptions' => ['allowClear' => true], + ])->label('Магазин') ?> +
+
+ field(new \yii\base\DynamicModel(['month' => $filters['month'] ?? '']), 'month')->dropDownList(\yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, [ + 'prompt' => 'Месяц', + 'name' => 'month', + ])->label('Плановый месяц') ?> +
+ +
+ field(new \yii\base\DynamicModel(['year' => $filters['year'] ?? '']), 'year')->dropDownList(['2025' => 2025, '2026' => 2026], [ + 'prompt' => 'Год', + 'name' => 'year', + ])->label('Плановый год') ?> +
+
+ field(new \yii\base\DynamicModel(['type' => $filters['type'] ?? '']), 'type')->widget(Select2::class, [ + 'data' => [ + 'writeOffs' => 'Списания', + 'sales' => 'Продажи' + ], + 'options' => ['placeholder' => 'Тип', 'name' => 'type'], + 'pluginOptions' => ['allowClear' => true], + ])->label('По дефолту продажи!') ?> +
+
+ 'btn btn-primary']) ?> +
+
+ 'btn btn-default']) ?> +
+
+ + +
+ + + + 'store_id', 'label' => 'Магазин', 'value' => function ($data) { + return CityStore::findOne($data['store_id'])->name ?? null; + }], + ['attribute' => 'category', 'label' => 'Категория'], + ['attribute' => 'subcategory', 'label' => 'Подкатегория'], + ['attribute' => 'species', 'label' => 'Тип'], + ['attribute' => 'goal', 'label' => 'Сумма', 'format' => ['decimal', 2]], +]; + + +?> + $dataProvider, + 'columns' => $columns, +]); ?> +