From 2e717aaaad2962b1774846cb21ef0b54450cac80 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Fri, 3 Oct 2025 09:47:04 +0300 Subject: [PATCH] =?utf8?q?[ERP-468]=20=D0=A1=D0=B2=D0=B5=D1=80=D1=82=D0=BA?= =?utf8?q?=D0=B0=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=BE=20?= =?utf8?q?=D1=81=D0=B5=D0=B1=D0=B5=D1=81=D1=82=D0=BE=D0=B8=D0=BC=D0=BE?= =?utf8?q?=D1=81=D1=82=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/commands/SelfCostController.php | 16 ++ erp24/media/.gitignore | 3 +- .../SelfCostProductDinamicService.php | 271 ++++++++++++------ 3 files changed, 198 insertions(+), 92 deletions(-) diff --git a/erp24/commands/SelfCostController.php b/erp24/commands/SelfCostController.php index f57e53dc..06a8ca08 100644 --- a/erp24/commands/SelfCostController.php +++ b/erp24/commands/SelfCostController.php @@ -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; + } } diff --git a/erp24/media/.gitignore b/erp24/media/.gitignore index 69f54304..c2950577 100644 --- a/erp24/media/.gitignore +++ b/erp24/media/.gitignore @@ -1,3 +1,4 @@ runtime web/assets/* -json \ No newline at end of file +json +/feeds/ diff --git a/erp24/services/SelfCostProductDinamicService.php b/erp24/services/SelfCostProductDinamicService.php index 5d4f2212..9bd5aead 100644 --- a/erp24/services/SelfCostProductDinamicService.php +++ b/erp24/services/SelfCostProductDinamicService.php @@ -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] + ]); + } + } -- 2.39.5