]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Немаркированные товары пересчет по списаниям
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 26 May 2025 11:48:13 +0000 (14:48 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 26 May 2025 11:48:13 +0000 (14:48 +0300)
erp24/controllers/AutoPlannogrammaController.php
erp24/services/AutoPlannogrammaService.php
erp24/views/auto-plannogramma/1_1.php [new file with mode: 0644]

index 8a98c8dec1268ce30007d50a7768d965306f28a2..57b6fad8cc4f75d4bfef2f0c92903a1e6b793f65 100644 (file)
@@ -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;
index 5bb2ddc15f1c80dcc6c7914fe658be091d43d4af..0b5d8372f95aafb3053e66e0a6dbce165ea4eaeb 100644 (file)
@@ -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 (file)
index 0000000..65a7a89
--- /dev/null
@@ -0,0 +1,121 @@
+<div class="filter-form" style="margin-bottom: 20px;">
+    <?php use kartik\date\DatePicker;
+    use kartik\grid\GridView;
+    use kartik\select2\Select2;
+    use yii\helpers\ArrayHelper;
+    use yii\helpers\Html;
+    use yii\widgets\ActiveForm;
+    use yii_app\records\CityStore;
+    use yii_app\records\Products1cNomenclature;?>
+    <h1 class="ms-3 mb-4"><?= Html::encode("Расчет доли месяца для категорий (month_category_share)") ?></h1>
+    <?php $form = ActiveForm::begin(['method' => 'get']); ?>
+    <div class="row p-3">
+        <div class="col-md">
+            <?= $form->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('Категория') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->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('Магазин') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['month' => $filters['month'] ?? '']), 'month')->dropDownList(\yii_app\helpers\DateHelper::MONTH_NUMBER_NAMES, [
+                'prompt' => 'Месяц',
+                'name' => 'month',
+            ])->label('Плановый месяц') ?>
+        </div>
+
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['year' => $filters['year'] ?? '']), 'year')->dropDownList(['2025' => 2025, '2026' => 2026], [
+                'prompt' => 'Год',
+                'name' => 'year',
+            ])->label('Плановый год') ?>
+        </div>
+        <div class="col-md">
+            <?= $form->field(new \yii\base\DynamicModel(['type' => $filters['type'] ?? '']), 'type')->widget(Select2::class, [
+                'data' => [
+                    'writeOffs' => 'Списания',
+                    'sales' => 'Продажи'
+                ],
+                'options' => ['placeholder' => 'Тип', 'name' => 'type'],
+                'pluginOptions' => ['allowClear' => true],
+            ])->label('По дефолту продажи!') ?>
+        </div>
+        <div class="col-md">
+            <?= Html::submitButton('Фильтровать', ['class' => 'btn btn-primary']) ?>
+        </div>
+        <div class="col-md">
+            <?= Html::a('Сбросить', ['auto-plannogramma/1'], ['class' => 'btn btn-default']) ?>
+        </div>
+    </div>
+
+    <?php ActiveForm::end(); ?>
+</div>
+
+<?= GridView::widget([
+    'dataProvider' => $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],
+        ],
+    ],
+]); ?>
+