]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
[ERP-468] Свертка данных по себестоимости origin/feature_filippov_erp-468_self_cost_data_compress
authorAleksey Filippov <Aleksey.Filippov@erp-flowers.ru>
Fri, 3 Oct 2025 06:47:04 +0000 (09:47 +0300)
committerAleksey Filippov <Aleksey.Filippov@erp-flowers.ru>
Fri, 3 Oct 2025 06:47:04 +0000 (09:47 +0300)
erp24/commands/SelfCostController.php
erp24/media/.gitignore
erp24/services/SelfCostProductDinamicService.php

index f57e53dc5e27e755af5d08e3f0b8e94beefde088..06a8ca087eeca6a945a1f565627b9985ddc68cd8 100644 (file)
@@ -73,4 +73,20 @@ class SelfCostController extends Controller
         echo "\nStop " . time() . "\n";
         return ExitCode::OK;
     }
+
+    /**
+     * Объединяет дублирующиеся записи в таблице self_cost_product_dynamic
+     * Команда для очистки дублей, созданных старой версией UpdateResult
+     * 
+     * Использование: php yii self-cost/merge-duplicates
+     */
+    public function actionMergeDuplicates()
+    {
+        echo "Start merging duplicates " . date('Y-m-d H:i:s') . "\n";
+        
+        SelfCostProductDinamicService::MergeDuplicates();
+        
+        echo "Duplicates merged successfully " . date('Y-m-d H:i:s') . "\n";
+        return ExitCode::OK;
+    }
 }
index 69f5430420cea75743623746858f089c81e66534..c2950577aec5e1f1eed7c340e9af01f831bbe426 100644 (file)
@@ -1,3 +1,4 @@
 runtime
 web/assets/*
-json
\ No newline at end of file
+json
+/feeds/
index 5d4f2212dcb2717e9be4b478866f0b37d7b2a616..9bd5aead769c90841b764be83830c994df5daef5 100644 (file)
@@ -64,62 +64,59 @@ class SelfCostProductDinamicService
     public static function SaveResult($selfCostProduct)
     {
         foreach ($selfCostProduct as $row) {
-            // Ищем существующие записи с теми же параметрами (product_guid, store_id, price)
-            $existingRecord = SelfCostProductDynamic::find()
+            // Ищем ВСЕ существующие записи с теми же параметрами (product_guid, store_id, price)
+            $existingRecords = SelfCostProductDynamic::find()
                 ->where([
                     'product_guid' => $row['product_guid'],
                     'store_id' => $row['store_id'],
                     'price' => $row['price']
                 ])
-                ->one();
-
-            if (!empty($existingRecord)) {
-                // Запись найдена - проверяем пересечение или примыкание интервалов
-                $existingDateFrom = strtotime($existingRecord->date_from);
-                $existingDateTo = strtotime($existingRecord->date_to);
-                $newDateFrom = strtotime($row['date_from']);
-                $newDateTo = strtotime($row['date_to']);
-
-                // Проверяем, примыкают ли интервалы (разница <= 1 день) или пересекаются
-                $daysBetween = min(
-                    abs($newDateFrom - $existingDateTo) / 86400,
-                    abs($existingDateFrom - $newDateTo) / 86400
-                );
-
-                if ($daysBetween <= 1 || 
-                    ($newDateFrom <= $existingDateTo && $newDateTo >= $existingDateFrom)) {
-                    // Вычисляем новые границы интервала
-                    $newCalculatedDateFrom = date('Y-m-d', min($existingDateFrom, $newDateFrom));
-                    $newCalculatedDateTo = date('Y-m-d', max($existingDateTo, $newDateTo));
-                    
-                    // Проверяем, изменились ли даты
-                    if ($existingRecord->date_from !== $newCalculatedDateFrom || 
-                        $existingRecord->date_to !== $newCalculatedDateTo) {
-                        // Расширяем интервал только если данные изменились
-                        $existingRecord->date_from = $newCalculatedDateFrom;
-                        $existingRecord->date_to = $newCalculatedDateTo;
-                        $existingRecord->updated_at = date('Y-m-d H:i:s');
+                ->orderBy(['date_from' => SORT_ASC])
+                ->all();
+
+            $newDateFrom = strtotime($row['date_from']);
+            $newDateTo = strtotime($row['date_to']);
+            $recordUpdated = false;
+
+            if (!empty($existingRecords)) {
+                // Проверяем каждый существующий интервал
+                foreach ($existingRecords as $existingRecord) {
+                    $existingDateFrom = strtotime($existingRecord->date_from);
+                    $existingDateTo = strtotime($existingRecord->date_to);
+
+                    // Проверяем, примыкают ли интервалы (разница <= 1 день) или пересекаются
+                    $daysBetween = min(
+                        abs($newDateFrom - $existingDateTo) / 86400,
+                        abs($existingDateFrom - $newDateTo) / 86400
+                    );
+
+                    if ($daysBetween <= 1 || 
+                        ($newDateFrom <= $existingDateTo && $newDateTo >= $existingDateFrom)) {
+                        // Вычисляем новые границы интервала
+                        $newCalculatedDateFrom = date('Y-m-d', min($existingDateFrom, $newDateFrom));
+                        $newCalculatedDateTo = date('Y-m-d', max($existingDateTo, $newDateTo));
                         
-                        if ($existingRecord->validate()) {
-                            $existingRecord->save();
+                        // Проверяем, изменились ли даты
+                        if ($existingRecord->date_from !== $newCalculatedDateFrom || 
+                            $existingRecord->date_to !== $newCalculatedDateTo) {
+                            // Расширяем интервал только если данные изменились
+                            $existingRecord->date_from = $newCalculatedDateFrom;
+                            $existingRecord->date_to = $newCalculatedDateTo;
+                            $existingRecord->updated_at = date('Y-m-d H:i:s');
+                            
+                            if ($existingRecord->validate()) {
+                                $existingRecord->save();
+                            }
                         }
-                    }
-                } else {
-                    // Интервалы не примыкают - создаем новую запись
-                    $rowSelfCost = new SelfCostProductDynamic();
-                    $rowSelfCost->product_guid = $row['product_guid'];
-                    $rowSelfCost->store_id = $row['store_id'];
-                    $rowSelfCost->price = $row['price'];
-                    $rowSelfCost->date_from = $row['date_from'];
-                    $rowSelfCost->date_to = $row['date_to'];
-                    $rowSelfCost->updated_at = date('Y-m-d H:i:s');
-
-                    if ($rowSelfCost->validate()) {
-                        $rowSelfCost->save();
+                        
+                        $recordUpdated = true;
+                        break; // Нашли подходящий интервал, выходим из цикла
                     }
                 }
-            } else {
-                // Записи нет - создаем новую
+            }
+
+            // Если не удалось обновить ни одну запись - создаем новую
+            if (!$recordUpdated) {
                 $rowSelfCost = new SelfCostProductDynamic();
                 $rowSelfCost->product_guid = $row['product_guid'];
                 $rowSelfCost->store_id = $row['store_id'];
@@ -138,58 +135,55 @@ class SelfCostProductDinamicService
     public static function UpdateResult($values)
     {
         foreach ($values as $row) {
-            // Ищем существующие записи с теми же параметрами (product_guid, store_id, price)
-            $existingRecord = SelfCostProductDynamic::find()
+            // Ищем ВСЕ существующие записи с теми же параметрами (product_guid, store_id, price)
+            $existingRecords = SelfCostProductDynamic::find()
                 ->where([
                     'product_guid' => $row['product_guid'],
                     'store_id' => $row['store_id'],
                     'price' => $row['price']
                 ])
-                ->one();
-
-            if (!empty($existingRecord)) {
-                // Запись найдена - проверяем, примыкает ли новая дата к интервалу
-                $existingDateFrom = strtotime($existingRecord->date_from);
-                $existingDateTo = strtotime($existingRecord->date_to);
-                $newDate = strtotime($row['date']);
-
-                // Проверяем, примыкает ли новая дата (±1 день от границ интервала)
-                $dayBeforeDateFrom = strtotime('-1 day', $existingDateFrom);
-                $dayAfterDateTo = strtotime('+1 day', $existingDateTo);
-
-                if ($newDate >= $dayBeforeDateFrom && $newDate <= $dayAfterDateTo) {
-                    // Новая дата примыкает к интервалу или находится внутри него
-                    $newCalculatedDateFrom = date('Y-m-d', min($existingDateFrom, $newDate));
-                    $newCalculatedDateTo = date('Y-m-d', max($existingDateTo, $newDate));
-
-                    // Проверяем, изменились ли даты
-                    if ($existingRecord->date_from !== $newCalculatedDateFrom || 
-                        $existingRecord->date_to !== $newCalculatedDateTo) {
-                        // Расширяем интервал только если данные изменились
-                        $existingRecord->date_from = $newCalculatedDateFrom;
-                        $existingRecord->date_to = $newCalculatedDateTo;
-                        $existingRecord->updated_at = $row['updated_at'];
-
-                        if ($existingRecord->validate()) {
-                            $existingRecord->save();
+                ->orderBy(['date_from' => SORT_ASC])
+                ->all();
+
+            $newDate = strtotime($row['date']);
+            $recordUpdated = false;
+
+            if (!empty($existingRecords)) {
+                // Проверяем каждый существующий интервал
+                foreach ($existingRecords as $existingRecord) {
+                    $existingDateFrom = strtotime($existingRecord->date_from);
+                    $existingDateTo = strtotime($existingRecord->date_to);
+
+                    // Проверяем, примыкает ли новая дата (±1 день от границ интервала) или находится внутри
+                    $dayBeforeDateFrom = strtotime('-1 day', $existingDateFrom);
+                    $dayAfterDateTo = strtotime('+1 day', $existingDateTo);
+
+                    if ($newDate >= $dayBeforeDateFrom && $newDate <= $dayAfterDateTo) {
+                        // Новая дата примыкает к интервалу или находится внутри него
+                        $newCalculatedDateFrom = date('Y-m-d', min($existingDateFrom, $newDate));
+                        $newCalculatedDateTo = date('Y-m-d', max($existingDateTo, $newDate));
+
+                        // Проверяем, изменились ли даты
+                        if ($existingRecord->date_from !== $newCalculatedDateFrom || 
+                            $existingRecord->date_to !== $newCalculatedDateTo) {
+                            // Расширяем интервал только если данные изменились
+                            $existingRecord->date_from = $newCalculatedDateFrom;
+                            $existingRecord->date_to = $newCalculatedDateTo;
+                            $existingRecord->updated_at = $row['updated_at'];
+
+                            if ($existingRecord->validate()) {
+                                $existingRecord->save();
+                            }
                         }
-                    }
-                } else {
-                    // Дата не примыкает к интервалу - создаем новую запись
-                    $newRecord = new SelfCostProductDynamic();
-                    $newRecord->product_guid = $row['product_guid'];
-                    $newRecord->store_id = $row['store_id'];
-                    $newRecord->price = $row['price'];
-                    $newRecord->date_from = $row['date'];
-                    $newRecord->date_to = $row['date'];
-                    $newRecord->updated_at = $row['updated_at'];
-
-                    if ($newRecord->validate()) {
-                        $newRecord->save();
+                        
+                        $recordUpdated = true;
+                        break; // Нашли подходящий интервал, выходим из цикла
                     }
                 }
-            } else {
-                // Записи нет - создаем новую
+            }
+
+            // Если не удалось обновить ни одну запись - создаем новую
+            if (!$recordUpdated) {
                 $newRecord = new SelfCostProductDynamic();
                 $newRecord->product_guid = $row['product_guid'];
                 $newRecord->store_id = $row['store_id'];
@@ -204,6 +198,101 @@ class SelfCostProductDinamicService
             }
         }
     }
+
+    /**
+     * Объединяет дублирующиеся записи с одинаковыми параметрами в единые интервалы
+     * Используется для очистки дублей, созданных старой версией UpdateResult
+     */
+    public static function MergeDuplicates()
+    {
+        // Находим все уникальные комбинации product_guid, store_id, price
+        $uniqueCombinations = SelfCostProductDynamic::find()
+            ->select(['product_guid', 'store_id', 'price'])
+            ->distinct()
+            ->asArray()
+            ->all();
+
+        foreach ($uniqueCombinations as $combination) {
+            // Получаем все записи для этой комбинации, отсортированные по дате
+            $records = SelfCostProductDynamic::find()
+                ->where([
+                    'product_guid' => $combination['product_guid'],
+                    'store_id' => $combination['store_id'],
+                    'price' => $combination['price']
+                ])
+                ->orderBy(['date_from' => SORT_ASC])
+                ->all();
+
+            if (count($records) <= 1) {
+                continue; // Нет дублей, переходим к следующей комбинации
+            }
+
+            // Объединяем интервалы
+            $mergedIntervals = [];
+            $currentInterval = null;
+
+            foreach ($records as $record) {
+                $dateFrom = strtotime($record->date_from);
+                $dateTo = strtotime($record->date_to);
+
+                if ($currentInterval === null) {
+                    // Первый интервал
+                    $currentInterval = [
+                        'date_from' => $dateFrom,
+                        'date_to' => $dateTo,
+                        'record' => $record
+                    ];
+                } else {
+                    // Проверяем, примыкает ли или пересекается ли с текущим интервалом
+                    $dayAfterCurrentEnd = strtotime('+1 day', $currentInterval['date_to']);
+                    
+                    if ($dateFrom <= $dayAfterCurrentEnd) {
+                        // Интервалы примыкают или пересекаются - объединяем
+                        $currentInterval['date_to'] = max($currentInterval['date_to'], $dateTo);
+                    } else {
+                        // Интервалы не примыкают - сохраняем текущий и начинаем новый
+                        $mergedIntervals[] = $currentInterval;
+                        $currentInterval = [
+                            'date_from' => $dateFrom,
+                            'date_to' => $dateTo,
+                            'record' => $record
+                        ];
+                    }
+                }
+            }
+
+            // Добавляем последний интервал
+            if ($currentInterval !== null) {
+                $mergedIntervals[] = $currentInterval;
+            }
+
+            // Обновляем базу данных: оставляем только объединенные интервалы
+            // Первый интервал обновляем, остальные записи удаляем
+            $keepIds = [];
+            foreach ($mergedIntervals as $interval) {
+                $record = $interval['record'];
+                $record->date_from = date('Y-m-d', $interval['date_from']);
+                $record->date_to = date('Y-m-d', $interval['date_to']);
+                $record->updated_at = date('Y-m-d H:i:s');
+                
+                if ($record->validate()) {
+                    $record->save();
+                }
+                $keepIds[] = $record->id;
+            }
+
+            // Удаляем все остальные записи для этой комбинации
+            SelfCostProductDynamic::deleteAll([
+                'and',
+                [
+                    'product_guid' => $combination['product_guid'],
+                    'store_id' => $combination['store_id'],
+                    'price' => $combination['price']
+                ],
+                ['not in', 'id', $keepIds]
+            ]);
+        }
+    }