From: Aleksey Filippov Date: Thu, 16 Oct 2025 13:51:18 +0000 (+0300) Subject: ERP-477 Переключить получение себестоимости из другого источника X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=011b4ff36236fc3da64a1fa12caaa070690a3b59;p=erp24_rep%2Fyii-erp24%2F.git ERP-477 Переключить получение себестоимости из другого источника --- diff --git a/erp24/api2/controllers/DataController.php b/erp24/api2/controllers/DataController.php index 742f5dfc..d6228367 100644 --- a/erp24/api2/controllers/DataController.php +++ b/erp24/api2/controllers/DataController.php @@ -66,7 +66,7 @@ use yii_app\services\InfoLogService; use yii_app\services\LogService; use yii_app\services\MarketplaceService; use yii_app\services\TelegramService; -use yii_app\services\SelfCostProductDinamicService; +use yii_app\services\SelfCostProductDynamicService; class DataController extends BaseController { @@ -1153,7 +1153,7 @@ class DataController extends BaseController if (!empty($values)) { $this->setSelfCostUpdate($values); - SelfCostProductDinamicService::UpdateResult($values); + SelfCostProductDynamicService::UpdateResult($values); } } diff --git a/erp24/commands/SelfCostController.php b/erp24/commands/SelfCostController.php index 06a8ca08..34d780a5 100644 --- a/erp24/commands/SelfCostController.php +++ b/erp24/commands/SelfCostController.php @@ -10,7 +10,7 @@ namespace yii_app\commands; use yii\console\Controller; use yii\console\ExitCode; use yii_app\records\SelfCostProduct; -use yii_app\services\SelfCostProductDinamicService; +use yii_app\services\SelfCostProductDynamicService; class SelfCostController extends Controller @@ -55,9 +55,9 @@ class SelfCostController extends Controller ->asArray() ->all(); // var_dump($selfCostProduct); - $resultRow = SelfCostProductDinamicService::PrepareResult($selfCostProduct); + $resultRow = SelfCostProductDynamicService::PrepareResult($selfCostProduct); // print_r($resultRow); - SelfCostProductDinamicService::SaveResult($resultRow); + SelfCostProductDynamicService::SaveResult($resultRow); // echo " Line " . __LINE__ . "\n"; @@ -84,7 +84,7 @@ class SelfCostController extends Controller { echo "Start merging duplicates " . date('Y-m-d H:i:s') . "\n"; - SelfCostProductDinamicService::MergeDuplicates(); + SelfCostProductDynamicService::MergeDuplicates(); echo "Duplicates merged successfully " . date('Y-m-d H:i:s') . "\n"; return ExitCode::OK; diff --git a/erp24/controllers/ShiftTransferController.php b/erp24/controllers/ShiftTransferController.php index d8d0d92e..1a2b7e26 100644 --- a/erp24/controllers/ShiftTransferController.php +++ b/erp24/controllers/ShiftTransferController.php @@ -21,9 +21,7 @@ use yii_app\records\Product1cReplacement; use yii_app\records\Products1c; use yii_app\records\ProductsClass; use yii_app\records\ReplacementInvoice; -use yii_app\records\ReplacementInvoiceProducts; -use yii_app\records\SelfCostProduct; -use yii_app\records\Shift; +use yii_app\records\SelfCostProductDynamic; use yii_app\records\ShiftRemains; use yii_app\records\ShiftTransfer; use yii_app\records\StoreBalance; @@ -31,6 +29,7 @@ use yii_app\records\WaybillIncoming; use yii_app\records\WaybillWriteOffs; use yii_app\records\WriteOffsErp; use yii_app\services\TaskService; +use yii_app\services\SelfCostProductDynamicService; class ShiftTransferController extends Controller { @@ -97,8 +96,15 @@ class ShiftTransferController extends Controller } $price = ArrayHelper::map(Prices::find()->select(['product_id', 'price'])->where(['product_id' => array_keys($products)])->all(), 'product_id', 'price'); $storeEIT = ExportImportTable::find()->select(['entity_id'])->where(['entity' => 'city_store', 'export_val' => $storeGuid, 'export_id' => 1])->one(); - $selfCost = ArrayHelper::map(SelfCostProduct::find()->select(['product_guid', 'price'])->where(['product_guid' => - array_keys($products), 'store_id' => $storeEIT->entity_id /*, 'date' => date('Y-m-d', strtotime($shiftDate))*/])->orderBy(['date' => SORT_ASC])->all(), 'product_guid', 'price'); + $selfCost = ArrayHelper::map( + SelfCostProductDynamic::find() + ->select(['product_guid', 'price']) + ->where(['product_guid' => array_keys($products), 'store_id' => $storeEIT->entity_id]) + ->andWhere(new Expression(":d BETWEEN date_from AND date_to", [':d' => date('Y-m-d', strtotime($shiftDate))])) + ->orderBy(['date_from' => SORT_DESC]) + ->all(), + 'product_guid', 'price' + ); // $a = [['a' => 1, 'b' => 2, 'c' => 3], ['a' => 1, 'b' => 5, 'c' => 4], ['a' => 1, 'b' => 7, 'c' => 5],]; // var_dump(ArrayHelper::map($a, 'a', 'b')); die; // result: array(1) { [1]=> int(7) } @@ -268,7 +274,7 @@ class ShiftTransferController extends Controller $storeEIT = ExportImportTable::find()->select(['entity_id'])->where(['entity' => 'city_store', 'export_val' => $storeGuid, 'export_id' => 1])->one(); - $selfCost = SelfCostProduct::find()->select(['price'])->where(['product_guid' => $productGuid, 'store_id' => $storeEIT->entity_id])->orderBy(['date' => SORT_DESC])->one(); + $selfCost = SelfCostProductDynamicService::getPrice($productGuid, $storeEIT->entity_id, date('Y-m-d')); $balance = Balances::find()->select(['quantity'])->where(['store_id' => $storeGuid, 'product_id' => $productGuid])->one(); @@ -371,7 +377,7 @@ class ShiftTransferController extends Controller $shiftTransfer = ShiftTransfer::findOne($shiftTransferId); $shiftRemains = ShiftRemains::findOne(['shift_transfer_id' => $shiftTransferId, 'product_guid' => $productGuid]); $storeId = array_flip(array_map('strval', CityStore::getAllActiveGuidId()))[$shiftTransfer->store_guid]; - $productSelfCost = SelfCostProduct::findOne(['product_guid' => $productGuid, 'date' => $shiftTransfer->date, 'store_id' => $storeId]); + $productSelfCost = SelfCostProductDynamicService::getPrice($productGuid, $storeId, $shiftTransfer->date); $productReplacement = ArrayHelper::map( Products1c::find() @@ -418,7 +424,7 @@ class ShiftTransferController extends Controller $productPrice = Prices::findOne(['product_id' => $productGuid]); $shiftTransfer = ShiftTransfer::findOne($shiftTransferId); $storeId = array_flip(array_map('strval', CityStore::getAllActiveGuidId()))[$shiftTransfer->store_guid]; - $productSelfCost = SelfCostProduct::findOne(['product_guid' => $productGuid, 'date' => $shiftTransfer->date, 'store_id' => $storeId]); + $productSelfCost = SelfCostProductDynamicService::getPrice($productGuid, $storeId, $shiftTransfer->date); $count = ShiftRemains::findOne(['shift_transfer_id' => $shiftTransferId, 'product_guid' => $productGuid]); if (!$productPrice) { diff --git a/erp24/records/EqualizationRemains.php b/erp24/records/EqualizationRemains.php index 8ecaa3f4..67feb371 100644 --- a/erp24/records/EqualizationRemains.php +++ b/erp24/records/EqualizationRemains.php @@ -273,7 +273,10 @@ class EqualizationRemains extends \yii\db\ActiveRecord */ public function getProductPriceSelfCost() { - return $this->hasOne(SelfCostProduct::class, ['product_guid' => 'product_id', 'date' => $this->shiftRemains->shiftTransfer->date]); + return $this->hasOne(SelfCostProductDynamic::class, ['product_guid' => 'product_id']) + ->andOnCondition(":d BETWEEN date_from AND date_to", [ + ':d' => date('Y-m-d', strtotime($this->shiftRemains->shiftTransfer->date)) + ]); } /** @@ -281,7 +284,10 @@ class EqualizationRemains extends \yii\db\ActiveRecord */ public function getProductReplacementPriceSelfCost() { - return $this->hasOne(SelfCostProduct::class, ['product_guid' => 'product_replacement_id', 'date' => $this->shiftRemains->shiftTransfer->date]); + return $this->hasOne(SelfCostProductDynamic::class, ['product_guid' => 'product_replacement_id']) + ->andOnCondition(":d BETWEEN date_from AND date_to", [ + ':d' => date('Y-m-d', strtotime($this->shiftRemains->shiftTransfer->date)) + ]); } diff --git a/erp24/scripts/tasks/task_33_insert_data_to_monitvation_from_motivation_buh.php b/erp24/scripts/tasks/task_33_insert_data_to_monitvation_from_motivation_buh.php index 32d32555..da167f11 100644 --- a/erp24/scripts/tasks/task_33_insert_data_to_monitvation_from_motivation_buh.php +++ b/erp24/scripts/tasks/task_33_insert_data_to_monitvation_from_motivation_buh.php @@ -14,7 +14,7 @@ use yii_app\records\MotivationBuhValue; use yii_app\records\MotivationCostsItem; use yii_app\records\MotivationValue; use yii_app\records\SchedulerTaskLog; -use yii_app\records\SelfCostProduct; +use yii_app\services\SelfCostProductDynamicService; use yii_app\records\WriteOffs; use yii_app\records\WriteOffsProducts; @@ -224,11 +224,11 @@ try { continue; } - $price = SelfCostProduct::find() - ->andWhere(['product_guid' => $offProduct->product_id]) - ->andWhere(['store_id' => array_search($writeOff->store_id, $stores)]) - ->andWhere(['date' => date('Y-m-d', strtotime($writeOff->date))]) - ->one(); + $price = SelfCostProductDynamicService::getPrice( + $offProduct->product_id, + array_search($writeOff->store_id, $stores), + $writeOff->date + ); if (empty($price)) { $price = $offProduct; diff --git a/erp24/services/SelfCostProductDinamicService.php b/erp24/services/SelfCostProductDinamicService.php deleted file mode 100644 index 9bd5aead..00000000 --- a/erp24/services/SelfCostProductDinamicService.php +++ /dev/null @@ -1,299 +0,0 @@ - $row['product_guid'], - 'store_id' => $row['store_id'], - 'price' => $row['price'], - 'date_from' => $start, - 'date_to' => $prev, - ]; - // новый интервал - $start = $date; - $prev = $date; - } - } - } - - // закрываем последний интервал - if ($start !== null) { - $result[] = [ - 'product_guid' => $row['product_guid'], - 'store_id' => $row['store_id'], - 'price' => $row['price'], - 'date_from' => $start, - 'date_to' => $prev, - ]; - } - } - - // Вывод - return($result); - } - - public static function SaveResult($selfCostProduct) - { - foreach ($selfCostProduct as $row) { - // Ищем ВСЕ существующие записи с теми же параметрами (product_guid, store_id, price) - $existingRecords = SelfCostProductDynamic::find() - ->where([ - 'product_guid' => $row['product_guid'], - 'store_id' => $row['store_id'], - 'price' => $row['price'] - ]) - ->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->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(); - } - } - - $recordUpdated = true; - break; // Нашли подходящий интервал, выходим из цикла - } - } - } - - // Если не удалось обновить ни одну запись - создаем новую - if (!$recordUpdated) { - $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(); - } - } - } - } - - public static function UpdateResult($values) - { - foreach ($values as $row) { - // Ищем ВСЕ существующие записи с теми же параметрами (product_guid, store_id, price) - $existingRecords = SelfCostProductDynamic::find() - ->where([ - 'product_guid' => $row['product_guid'], - 'store_id' => $row['store_id'], - 'price' => $row['price'] - ]) - ->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(); - } - } - - $recordUpdated = true; - break; // Нашли подходящий интервал, выходим из цикла - } - } - } - - // Если не удалось обновить ни одну запись - создаем новую - if (!$recordUpdated) { - $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(); - } - } - } - } - - /** - * Объединяет дублирующиеся записи с одинаковыми параметрами в единые интервалы - * Используется для очистки дублей, созданных старой версией 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] - ]); - } - } - - - -} diff --git a/erp24/services/SelfCostProductDynamicService.php b/erp24/services/SelfCostProductDynamicService.php new file mode 100644 index 00000000..11dee64a --- /dev/null +++ b/erp24/services/SelfCostProductDynamicService.php @@ -0,0 +1,313 @@ + $row['product_guid'], + 'store_id' => $row['store_id'], + 'price' => $row['price'], + 'date_from' => $start, + 'date_to' => $prev, + ]; + // новый интервал + $start = $date; + $prev = $date; + } + } + } + + // закрываем последний интервал + if ($start !== null) { + $result[] = [ + 'product_guid' => $row['product_guid'], + 'store_id' => $row['store_id'], + 'price' => $row['price'], + 'date_from' => $start, + 'date_to' => $prev, + ]; + } + } + + // Вывод + return($result); + } + + public static function SaveResult($selfCostProduct) + { + foreach ($selfCostProduct as $row) { + // Ищем ВСЕ существующие записи с теми же параметрами (product_guid, store_id, price) + $existingRecords = SelfCostProductDynamic::find() + ->where([ + 'product_guid' => $row['product_guid'], + 'store_id' => $row['store_id'], + 'price' => $row['price'] + ]) + ->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->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(); + } + } + + $recordUpdated = true; + break; // Нашли подходящий интервал, выходим из цикла + } + } + } + + // Если не удалось обновить ни одну запись - создаем новую + if (!$recordUpdated) { + $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(); + } + } + } + } + + public static function UpdateResult($values) + { + foreach ($values as $row) { + // Ищем ВСЕ существующие записи с теми же параметрами (product_guid, store_id, price) + $existingRecords = SelfCostProductDynamic::find() + ->where([ + 'product_guid' => $row['product_guid'], + 'store_id' => $row['store_id'], + 'price' => $row['price'] + ]) + ->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(); + } + } + + $recordUpdated = true; + break; // Нашли подходящий интервал, выходим из цикла + } + } + } + + // Если не удалось обновить ни одну запись - создаем новую + if (!$recordUpdated) { + $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(); + } + } + } + } + + /** + * Объединяет дублирующиеся записи с одинаковыми параметрами в единые интервалы + * Используется для очистки дублей, созданных старой версией 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] + ]); + } + } + + + /** + * Возвращает запись себестоимости по продукту/магазину на указанную дату. + */ + public static function getPrice($productGuid, $storeId, $date) + { + $dateStr = is_int($date) ? date('Y-m-d', $date) : date('Y-m-d', strtotime($date)); + + return SelfCostProductDynamic::find() + ->andWhere(['product_guid' => $productGuid]) + ->andWhere(['store_id' => $storeId]) + ->andWhere(new Expression(":d BETWEEN date_from AND date_to", [':d' => $dateStr])) + ->orderBy(['date_from' => SORT_DESC]) + ->one(); + } +}