]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
ERP-477 Переключить получение себестоимости из другого источника origin/feature_filippov_erp-477_change_data_src_self_cost
authorAleksey Filippov <Aleksey.Filippov@erp-flowers.ru>
Thu, 16 Oct 2025 13:51:18 +0000 (16:51 +0300)
committerAleksey Filippov <Aleksey.Filippov@erp-flowers.ru>
Thu, 16 Oct 2025 13:51:18 +0000 (16:51 +0300)
erp24/api2/controllers/DataController.php
erp24/commands/SelfCostController.php
erp24/controllers/ShiftTransferController.php
erp24/records/EqualizationRemains.php
erp24/scripts/tasks/task_33_insert_data_to_monitvation_from_motivation_buh.php
erp24/services/SelfCostProductDinamicService.php [deleted file]
erp24/services/SelfCostProductDynamicService.php [new file with mode: 0644]

index 742f5dfca3279393b88b127b024abc5c1132f1f6..d62283677795d573830a8189f6f2acf067e6e450 100644 (file)
@@ -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);
                 }
             }
 
index 06a8ca087eeca6a945a1f565627b9985ddc68cd8..34d780a582cab5ff72b45cabd2b4a54387f11203 100644 (file)
@@ -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;
index d8d0d92edfc609b4212d78cbd7155677b4722339..1a2b7e26b4485d294edd4973b9427a7860d1af1f 100644 (file)
@@ -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) {
index 8ecaa3f47899503cab5555ff72cce35f5f1c223d..67feb37190a60941019c75d0723b268e99a7fd9e 100644 (file)
@@ -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))
+            ]);
     }
 
 
index 32d32555096cb4c805fae0bbb6e24dd313903cca..da167f1129e0d2448ccec567be31fa9aacc7870f 100644 (file)
@@ -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 (file)
index 9bd5aea..0000000
+++ /dev/null
@@ -1,299 +0,0 @@
-<?php
-
-namespace yii_app\services;
-
-use yii_app\records\SelfCostProductDynamic;
-
-class SelfCostProductDinamicService
-{
-
-    public static function PrepareResult($selfCostProduct):array
-    {
-        $result = [];
-
-        foreach ($selfCostProduct as $row) {
-            $dates = array_map('trim', explode(',', $row['dates']));
-            sort($dates); // сортировка дат на всякий случай
-
-            $start = null;
-            $prev = null;
-
-            foreach ($dates as $date) {
-                if ($start === null) {
-                    // начало интервала
-                    $start = $date;
-                    $prev  = $date;
-                } else {
-                    // проверим, что дата идёт на следующий день за предыдущей
-                    $expectedNext = date('Y-m-d', strtotime($prev . ' +1 day'));
-                    if ($date === $expectedNext) {
-                        // продолжаем интервал
-                        $prev = $date;
-                    } else {
-                        // закрываем интервал
-                        $result[] = [
-                            'product_guid' => $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 (file)
index 0000000..11dee64
--- /dev/null
@@ -0,0 +1,313 @@
+<?php
+
+namespace yii_app\services;
+
+use yii\db\Expression;
+use yii_app\records\SelfCostProductDynamic;
+
+class SelfCostProductDynamicService
+{
+
+    public static function PrepareResult($selfCostProduct):array
+    {
+        $result = [];
+
+        foreach ($selfCostProduct as $row) {
+            $dates = array_map('trim', explode(',', $row['dates']));
+            sort($dates); // сортировка дат на всякий случай
+
+            $start = null;
+            $prev = null;
+
+            foreach ($dates as $date) {
+                if ($start === null) {
+                    // начало интервала
+                    $start = $date;
+                    $prev  = $date;
+                } else {
+                    // проверим, что дата идёт на следующий день за предыдущей
+                    $expectedNext = date('Y-m-d', strtotime($prev . ' +1 day'));
+                    if ($date === $expectedNext) {
+                        // продолжаем интервал
+                        $prev = $date;
+                    } else {
+                        // закрываем интервал
+                        $result[] = [
+                            'product_guid' => $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();
+    }
+}