]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Правки по интерфейсу
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 5 Jun 2025 14:12:39 +0000 (17:12 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Thu, 5 Jun 2025 14:12:39 +0000 (17:12 +0300)
erp24/controllers/AutoPlannogrammaController.php
erp24/views/auto-plannogramma/test.php [new file with mode: 0644]

index de5b59e3ee81e3af035e2d8030b28105e9e0fdb4..95389b6c941c8c8c81c5ebb0ed219c65c7f04274 100644 (file)
@@ -1617,4 +1617,144 @@ class AutoPlannogrammaController extends BaseController
     }
 
 
+    public function actionTest()
+    {
+        $request = Yii::$app->request;
+
+        $filters = [
+            'category' => 'Срезка',
+            'subcategory' => 'Розы' ?? null,
+            'species' => 'Роза' ?? null,
+            'store_id' => 2 ?? [],
+            'year' => 2025,
+            'month' => 5,
+            'type' => 'sales',
+        ];
+
+        $stores = ArrayHelper::map(
+            CityStore::findAll(['visible' => CityStore::IS_VISIBLE]),
+            'id',
+            'name'
+        );
+      //  var_dump($stores);die();
+        $dataProvider = new ArrayDataProvider([
+            'allModels' => [],
+            'pagination' => ['pageSize' => 100],
+        ]);
+        $flatData =[];
+        $bouquetSpeciesForecast = [];
+        // Обработка даты на год и месяц
+        if (!empty($filters['year']) && !empty($filters['month'])) {
+            $filters['plan_date'] = $filters['year'] . '-' . str_pad($filters['month'], 2, '0', STR_PAD_LEFT) . '-01';
+            // var_dump( $filters['plan_date']); die();
+            $service = new AutoPlannogrammaService();
+//$goals = $service->calculateFullGoalChain($filters);
+            $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);
+            $goals = $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']);
+                $goals = $service->getMonthSpeciesGoalDirty($monthSpeciesWriteOffShare, $monthSubcategoryWriteOffsGoals, $filters['type'], $data);
+            }
+
+
+            $result = StorePlanService::calculateHistoricalShare(
+                $filters['store_id'],
+                $filters['month'],
+                $filters['year'],
+                $filters['category'],
+                $filters['subcategory'],
+                $filters['species']
+            );
+
+            $noHistoryProductData = $service->calculateSpeciesForecastForProductsWithoutHistory($filters['plan_date'], $filters);
+
+            $productSalesShare = StorePlanService::calculateProductSalesShareProductsWithHistory(
+                $filters['store_id'],
+                $filters['month'],
+                $result['with_history']
+            );
+
+            $productSalesForecast = $service->calculateProductForecastInPiecesProductsWithHistory(
+                $filters['store_id'],
+                $filters['month'],
+                $productSalesShare,
+                $goals,
+                $filters['subcategory'],
+                $filters['category'],
+                $filters['species']
+            );
+
+            $matrixForecast = MatrixBouquetForecast::find()
+                ->where(['year' => $filters['year'], 'month' => $filters['month']])
+                ->asArray()
+                ->all();
+            $matrixGroups = array_unique(ArrayHelper::getColumn($matrixForecast, 'group'));
+            $bouquetForecast = StorePlanService::getBouquetSpiecesMonthGoalFromForecast($filters['month'], $filters['year'], $filters['store_id'], $matrixGroups);
+            $speciesData = $bouquetForecast['final'];
+            foreach ($speciesData as $store_id  => $categoryData) {
+                foreach ($categoryData as $category => $subcategoryData) {
+                    foreach ($subcategoryData as $subcategory => $species) {
+                        foreach ($species as $speciesInd => $row) {
+                            $bouquetSpeciesForecast[] = [
+                                'category' => $category,
+                                'subcategory' => $subcategory,
+                                'store_id' => $store_id,
+                                'species' => $speciesInd,
+                                'goal' => $row
+                            ];
+                        }
+                    }
+                }
+
+            }
+            $cleanedSpeciesGoals = $service->subtractSpeciesGoals($goals, $bouquetSpeciesForecast, $noHistoryProductData);
+
+
+            $salesProductForecastShare = $service->calculateProductForecastShare($noHistoryProductData, $productSalesForecast);
+
+            $productForecastSpecies = $service->calculateProductSalesBySpecies($salesProductForecastShare, $cleanedSpeciesGoals);
+
+
+            $weeklySales = $service->getHistoricalSpeciesShareByWeek($filters['plan_date'], $filters);
+
+            $weeklySalesForecast = $service->calculateWeeklyProductForecastPieces($productForecastSpecies, $weeklySales);
+
+
+
+
+            $flatData = array_filter($weeklySalesForecast, 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' => $flatData,
+                'pagination' => ['pageSize' => 100],
+            ]);
+        }
+        return $this->render('test', [
+            'dataProvider' => $dataProvider,
+            'data' => $flatData,
+            'filters' => $filters,
+            'stores' => $stores
+        ]);
+    }
+
+
 }
diff --git a/erp24/views/auto-plannogramma/test.php b/erp24/views/auto-plannogramma/test.php
new file mode 100644 (file)
index 0000000..e37d771
--- /dev/null
@@ -0,0 +1,153 @@
+<?php
+use yii\helpers\Html;
+use kartik\select2\Select2;
+use yii\helpers\ArrayHelper;
+
+/** @var $data array - массив данных из контроллера */
+/** @var $stores array - массив магазинов id => name */
+
+// Группируем данные: category > subcategory > species > product_id
+$grouped = [];
+foreach ($data as $row) {
+    $cat = $row['category'];
+    $sub = $row['subcategory'];
+    $sp  = $row['species'];
+    $pid = $row['product_id'];
+    $sid = $row['store_id'];
+
+    $grouped[$cat][$sub][$sp][$pid]['values'][$sid] = $row['forecast_week_pieces'];
+    $grouped[$cat][$sub][$sp][$pid]['product_id'] = $pid;
+}
+
+$storeHasData = [];
+foreach ($grouped as $cat => $subcats) {
+    foreach ($subcats as $sub => $species) {
+        foreach ($species as $sp => $products) {
+            foreach ($products as $p) {
+                foreach ($p['values'] as $storeId => $v) {
+                    $storeHasData[$storeId] = true;
+                }
+            }
+        }
+    }
+}
+?>
+
+<div class="container py-3">
+  <h2>Автопланограмма</h2>
+  <button class="btn btn-primary mb-2" onclick="toggleFilters()">Показать / Скрыть фильтры</button>
+
+  <div id="filters" style="display: none;">
+    <div class="row g-2">
+      <div class="col-md">
+        <?= Select2::widget([
+          'name' => 'year-filter',
+          'data' => array_combine(range(date('Y') - 5, date('Y') + 5), range(date('Y') - 5, date('Y') + 5)),
+          'options' => ['placeholder' => 'Год', 'id' => 'year'],
+          'pluginOptions' => ['allowClear' => true],
+        ]) ?>
+      </div>
+    </div>
+  </div>
+
+  <div style="overflow-x: auto;">
+    <div class="pseudo-table" style="display: grid; grid-template-columns: 300px repeat(<?= count($storeHasData) ?>, 80px);">
+      <div class="header-cell"></div>
+      <?php foreach ($stores as $storeId => $storeName): ?>
+        <?php if (!isset($storeHasData[$storeId])) continue; ?>
+        <div class="header-cell vertical"> <?= Html::encode($storeName) ?> </div>
+      <?php endforeach; ?>
+
+      <?php foreach ($grouped as $category => $subcategories): ?>
+        <div class="row-label category" data-category="<?= $category ?>" onclick="toggleByAttr('category', '<?= $category ?>')">▶ <?= Html::encode($category) ?></div>
+        <?php foreach ($storeHasData as $_ => $__) echo '<div></div>'; ?>
+
+        <?php foreach ($subcategories as $subcategory => $speciesList): ?>
+          <div class="row-label subcategory" data-category="<?= $category ?>" data-subcategory="<?= $subcategory ?>" style="padding-left: 1rem; display: none;" onclick="toggleByAttr('subcategory', '<?= $subcategory ?>', 'category', '<?= $category ?>')">▶ <?= Html::encode($subcategory) ?></div>
+          <?php foreach ($storeHasData as $_ => $__) echo '<div style="display: none;" data-category="' . $category . '" data-subcategory="' . $subcategory . '"></div>'; ?>
+
+          <?php foreach ($speciesList as $species => $products): ?>
+            <div class="row-label species" data-category="<?= $category ?>" data-subcategory="<?= $subcategory ?>" data-species="<?= $species ?>" style="padding-left: 2rem; display: none;" onclick="toggleByAttr('species', '<?= $species ?>', 'subcategory', '<?= $subcategory ?>')">▶ <?= Html::encode($species) ?></div>
+            <?php foreach ($storeHasData as $_ => $__) echo '<div style="display: none;" data-category="' . $category . '" data-subcategory="' . $subcategory . '" data-species="' . $species . '"></div>'; ?>
+
+            <?php foreach ($products as $productId => $info): ?>
+              <div class="row-label product" data-category="<?= $category ?>" data-subcategory="<?= $subcategory ?>" data-species="<?= $species ?>" style="padding-left: 3rem; display: none;">Товар <?= Html::encode($productId) ?></div>
+              <?php foreach ($stores as $storeId => $storeName): ?>
+                <?php if (!isset($storeHasData[$storeId])) continue; ?>
+                <?php $val = $info['values'][$storeId] ?? ''; ?>
+                <div class="cell input-cell" data-category="<?= $category ?>" data-subcategory="<?= $subcategory ?>" data-species="<?= $species ?>" style="display: none;">
+                  <input type="text" value="<?= $val ?>" onchange="markChanged(this)">
+                  <button onclick="resetValue(this, '<?= $val ?>')">↩</button>
+                </div>
+              <?php endforeach; ?>
+            <?php endforeach; ?>
+          <?php endforeach; ?>
+        <?php endforeach; ?>
+      <?php endforeach; ?>
+    </div>
+  </div>
+</div>
+
+<script>
+function toggleFilters() {
+  const filters = document.getElementById('filters');
+  filters.style.display = filters.style.display === 'none' ? 'block' : 'none';
+}
+
+function toggleByAttr(level, value, parentLevel = null, parentValue = null) {
+  const trigger = event.currentTarget;
+  const open = trigger.textContent.includes('▶');
+  trigger.textContent = trigger.textContent.replace(open ? '▶' : '▼', open ? '▼' : '▶');
+
+  const selector = `[data-${level}="${value}"]`;
+  document.querySelectorAll(selector).forEach(el => {
+    el.style.display = open ? 'grid' : 'none';
+  });
+
+  // закрыть дочерние, если свернули родителя
+  if (!open && level === 'category') {
+    document.querySelectorAll(`[data-category="${value}"]`).forEach(el => {
+      if (el.dataset.subcategory || el.dataset.species) {
+        el.style.display = 'none';
+        if (el.textContent.includes('▼')) el.textContent = el.textContent.replace('▼', '▶');
+      }
+    });
+  }
+  if (!open && level === 'subcategory') {
+    document.querySelectorAll(`[data-subcategory="${value}"]`).forEach(el => {
+      if (el.dataset.species) {
+        el.style.display = 'none';
+        if (el.textContent.includes('▼')) el.textContent = el.textContent.replace('▼', '▶');
+      }
+    });
+  }
+}
+
+function markChanged(input) {
+  input.style.backgroundColor = '#ffffcc';
+}
+
+function resetValue(button, val) {
+  const input = button.previousElementSibling;
+  input.value = val;
+  input.style.backgroundColor = '';
+}
+</script>
+
+<style>
+.header-cell, .row-label, .cell {
+  border: 1px solid #ccc;
+  padding: 4px;
+  background: #fafafa;
+  text-align: center;
+}
+.row-label { font-weight: bold; cursor: pointer; text-align: left; }
+.input-cell { display: flex; gap: 4px; align-items: center; justify-content: center; }
+.input-cell input { width: 45px; }
+.header-cell.vertical {
+  writing-mode: vertical-rl;
+  transform: rotate(180deg);
+  white-space: nowrap;
+  font-size: 12px;
+}
+</style>