From: fomichev Date: Mon, 26 May 2025 11:48:13 +0000 (+0300) Subject: Немаркированные товары пересчет по списаниям X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=ed3ebdb0adec51ba90bbbc796bf467bc56d7d9cf;p=erp24_rep%2Fyii-erp24%2F.git Немаркированные товары пересчет по списаниям --- diff --git a/erp24/controllers/AutoPlannogrammaController.php b/erp24/controllers/AutoPlannogrammaController.php index 8a98c8de..57b6fad8 100644 --- a/erp24/controllers/AutoPlannogrammaController.php +++ b/erp24/controllers/AutoPlannogrammaController.php @@ -268,6 +268,54 @@ class AutoPlannogrammaController extends BaseController ]); } + public function action1_1() + { + $request = Yii::$app->request; + + $filters = [ + 'category' => $request->get('category'), + 'store_id' => $request->get('store_id'), + 'year' => $request->get('year'), + 'month' => $request->get('month'), + 'type' => $request->get('type'), + ]; + + $dataProvider = new ArrayDataProvider([ + 'allModels' => [], + 'pagination' => ['pageSize' => 100], + ]); + + // Обработка даты на год и месяц + if (!empty($filters['year']) && !empty($filters['month'])) { + $filters['plan_date'] = $filters['year'] . '-' . str_pad($filters['month'], 2, '0', STR_PAD_LEFT) . '-01'; + + $service = new AutoPlannogrammaService(); + $data = $service->getUnmarkedProductsComponents($filters['store_id'], $filters['month'], $filters['year'], $filters['type']); + + $flatData = array_filter($data, function ($row) use ($filters) { + foreach ($filters as $key => $value) { + if (empty($value)) continue; + if (!isset($row[$key])) continue; + + if (stripos((string)$row[$key], (string)$value) === false) { + return false; + } + } + return true; + }); + + $dataProvider = new ArrayDataProvider([ + 'allModels' => array_values($flatData), + 'pagination' => ['pageSize' => 100], + ]); + } + + return $this->render('1_1', [ + 'dataProvider' => $dataProvider, + 'filters' => $filters, + ]); + } + public function action2() { $request = Yii::$app->request; diff --git a/erp24/services/AutoPlannogrammaService.php b/erp24/services/AutoPlannogrammaService.php index 5bb2ddc1..0b5d8372 100644 --- a/erp24/services/AutoPlannogrammaService.php +++ b/erp24/services/AutoPlannogrammaService.php @@ -5,8 +5,18 @@ namespace yii_app\services; use yii\db\Expression; use yii\db\Query; use yii\helpers\ArrayHelper; +use yii_app\records\BouquetComposition; use yii_app\records\CityStore; +use yii_app\records\CityStoreParams; +use yii_app\records\ExportImportTable; +use yii_app\records\PricesDynamic; +use yii_app\records\Products1c; +use yii_app\records\Products1cNomenclature; +use yii_app\records\Sales; +use yii_app\records\SalesProducts; use yii_app\records\SalesWriteOffsPlan; +use yii_app\records\WriteOffs; +use yii_app\records\WriteOffsProducts; class AutoPlannogrammaService { @@ -540,4 +550,239 @@ class AutoPlannogrammaService return array_values($filtered); } + + /** + * @param array|int $storeIds + * @param int $month + * @param int $year + * @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(int $storeId, string $month, string $year, $type = 'sales'): array + { + $region = CityStoreParams::find() + ->where(['store_id' => $storeId]) + ->one()->address_region; + + if (!$region) { + // определяем регион по городу + $cityId = CityStore::find()->select('city_id')->where(['id' => $storeId])->scalar(); + if ($cityId == 1) { + $region = BouquetComposition::REGION_MSK; + } else { + $region = BouquetComposition::REGION_NN; + } + } + + $monthStart = sprintf('%04d-%02d-01 00:00:00', $year, $month); + $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year); + $monthEnd = sprintf('%04d-%02d-%02d 23:59:59', $year, $month, $daysInMonth); + $unmarkedProducts = []; + + if ($type == self::TYPE_SALES) { + $salesProducts = Sales::find() + ->alias('s') + ->select(['s.id', 's.date', 's.operation', 'sp.product_id', 'p1c.type', 'p1c.components' , 'p1c.name']) + ->innerJoin( + ['sp' => SalesProducts::tableName()], + 's.id = sp.check_id' + ) + ->innerJoin( + ['p1c' => Products1c::tableName()], + 'p1c.id = sp.product_id' + ) + ->leftJoin( + ['nom' => Products1cNomenclature::tableName()], + 'nom.id = sp.product_id' + ) + ->andWhere(['s.store_id' => $storeId]) + ->andWhere(['not', ['s.operation' => ['Удален', 'Удаление']]]) + ->andWhere(['between', 's.date', $monthStart, $monthEnd]) + ->andWhere(['not', ['p1c.components' => '']]) + ->andWhere(['nom.category' => null]) + ->asArray() + ->all(); + $unmarkedProducts = $salesProducts; + } else { + $storeJoinCondition = $type === self::TYPE_WRITE_OFFS ? 'ex.export_val = w.store_id' : 'ex.export_val = s.store_id_1c'; + $writeOffsProducts = WriteOffs::find() + ->alias('w') + ->select([ + 'write_off_id' => 'w.id', + 'write_off_date' => 'w.date', + 'product_id' => 'wp.product_id', + 'type' => 'p1c.type', + 'components' => 'p1c.components', + 'product_name' => 'p1c.name', + 'store_id' => 'ex.entity_id', + ]) + ->innerJoin( + ['wp' => WriteOffsProducts::tableName()], + 'wp.write_offs_id = w.id' + ) + ->innerJoin( + ['p1c' => Products1c::tableName()], + 'p1c.id = wp.product_id' + ) + ->leftJoin( + ['nom' => Products1cNomenclature::tableName()], + 'nom.id = wp.product_id' + ) + ->leftJoin( + ['ex' => ExportImportTable::tableName()], + 'ex.export_val = w.store_id' + ) + ->andWhere(['between', 'w.date', $monthStart, $monthEnd]) + ->andWhere(['ex.entity_id' => $storeId]) + ->andWhere(['nom.category' => null]) + ->andWhere(['<>', 'p1c.components', '']) + ->asArray() + ->all(); + //var_dump($writeOffsProducts); die(); + $unmarkedProducts = $writeOffsProducts; + } + + //var_dump( $salesProducts); die(); + + $components = []; + $rows = []; + foreach ($unmarkedProducts as $up) { + + $js = trim($up['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' => $up['id'], + 'sale_date' => $up['date'], + 'product_id' => $up['product_id'], + 'product_name'=> $up['name'], + 'component_guid' => $guid, + 'quantity' => $qty, + 'type' => $type, + 'operation' => $up['operation'] ?? '', + ]; + } + } + + if (empty($rows)) { + return []; + } + $guids = array_keys($components); + + $nomenclatures = Products1cNomenclature::find() + ->andWhere(['id' => $guids]) + ->indexBy('id') + ->all(); + + $priceDynamics = PricesDynamic::find() + ->andWhere(['region_id' => $region]) + ->andWhere(['product_id' => array_values( ArrayHelper::getColumn($nomenclatures, 'id') )]) + ->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[] = [ + 'store_id' => $storeId, + '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, + 'month' => $month, + 'year' => $year, + 'type' => $r['type'], + 'operation' => $r['operation'] ?? '', + ]; + } + + return $result; + } + + + public function sumUnmarkedProductsComponentsByCategory(array $items, string $type): array + { + $aggregated = []; + + foreach ($items as $row) { + $storeId = $row['store_id']; + $category = $row['component_category']; + $month = $row['month']; + $year = $row['year']; + $operation = $row['operation'] ?? null; + + $cost = (float)$row['cost']; + + if ( + $type === AutoPlannogrammaService::TYPE_SALES + || ($row['type'] ?? null) === AutoPlannogrammaService::TYPE_SALES + ) { + if ($operation === 'Возврат') { + $cost = -$cost; + } + + } + + $key = implode('|', [$storeId, $category, $year, $month]); + + if (!isset($aggregated[$key])) { + $aggregated[$key] = [ + 'store_id' => $storeId, + 'category' => $category, + 'sum' => 0.0, + 'month' => $month, + 'year' => $year, + 'type' => $type, + ]; + } + + $aggregated[$key]['sum'] += $cost; + } + + return array_values($aggregated); + } + + } \ No newline at end of file diff --git a/erp24/views/auto-plannogramma/1_1.php b/erp24/views/auto-plannogramma/1_1.php new file mode 100644 index 00000000..65a7a89a --- /dev/null +++ b/erp24/views/auto-plannogramma/1_1.php @@ -0,0 +1,121 @@ +
+ +

+ '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(['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']) ?> +
+
+ + +
+ + $dataProvider, + 'columns' => [ + [ + 'attribute' => 'store_id', + 'label' => 'Магазин', + 'value' => function ($row) { + return CityStore::findOne($row['store_id'])->name ?? null; + }, + ], + [ + 'attribute' => 'sale_id', + 'label' => '№ чека', + ], + [ + 'attribute' => 'sale_date', + 'label' => 'Дата покупки', + 'format' => ['datetime', 'php:Y-m-d H:i'], + ], + [ + 'attribute' => 'product_name', + 'label' => 'Товар', + ], + [ + 'attribute' => 'component_guid', + 'label' => 'GUID компонента', + ], + [ + 'attribute' => 'component_name', + 'label' => 'Название компонента', + ], + [ + 'attribute' => 'component_category', + 'label' => 'Категория компонента', + ], + [ + 'attribute' => 'quantity', + 'label' => 'Количество', + 'format' => 'integer', + ], + [ + 'attribute' => 'price', + 'label' => 'Цена', + 'format' => ['decimal', 2], + ], + [ + 'attribute' => 'cost', + 'label' => 'Стоимость', + 'format' => ['decimal', 2], + ], + ], +]); ?> +