From 129c6b052c91c1408de327152aa41ca644a8c2e9 Mon Sep 17 00:00:00 2001 From: Aleksey Filippov Date: Mon, 2 Mar 2026 23:45:48 +0300 Subject: [PATCH] fix(ERP-252): extract fillDeliveryAddressFallback method, eliminate duplication MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit - New method fillDeliveryAddressFallback() handles 3 raw_data formats: 1) string (FlowWow text) → parseAddressFromDeliveryText() 2) array with address (YM API JSON) → extract structured fields 3) null/missing → defaults "Уточняется" - Replaced 3 inline fallback blocks with single method call - Fixed surprise-delivery parsing ("Отправим смс получателю...") - 20 unit tests, 115 assertions — all passing Co-Authored-By: Claude Opus 4.6 --- erp24/services/MarketplaceService.php | 149 +++++++++++------- .../MarketplaceServiceDeliveryParsingTest.php | 135 ++++++++++++++++ 2 files changed, 225 insertions(+), 59 deletions(-) diff --git a/erp24/services/MarketplaceService.php b/erp24/services/MarketplaceService.php index c364217d..00ce157a 100644 --- a/erp24/services/MarketplaceService.php +++ b/erp24/services/MarketplaceService.php @@ -1348,27 +1348,16 @@ class MarketplaceService $deliveryModel->longitude = 0.0; } } - // Fallback: если street всё равно пустой — парсим из raw_data или ставим дефолт + // Fallback: если street пустой — извлекаем из raw_data или ставим дефолт if (empty($deliveryModel->street)) { - $rawData = json_decode($marketplaceOrder->raw_data, true); - $deliveryText = $rawData['delivery'] ?? null; - Yii::warning('[ERP-252-v3] new: street empty для заказа #' . $marketplaceOrder->id . ', address=' . ($address ? 'object' : 'null') . ', deliveryText=' . (is_string($deliveryText) ? $deliveryText : json_encode($deliveryText, JSON_UNESCAPED_UNICODE)), 'marketplace'); - if ($deliveryText && is_string($deliveryText)) { - $parsed = self::parseAddressFromDeliveryText($deliveryText); - $deliveryModel->country = $deliveryModel->country ?: 'Россия'; - $deliveryModel->city = $deliveryModel->city ?: $parsed['city']; - $deliveryModel->street = $parsed['street']; - $deliveryModel->house = $deliveryModel->house ?: $parsed['house']; - $deliveryModel->latitude = $deliveryModel->latitude ?: $parsed['latitude']; - $deliveryModel->longitude = $deliveryModel->longitude ?: $parsed['longitude']; - } else { - $deliveryModel->country = $deliveryModel->country ?: 'Уточняется'; - $deliveryModel->city = $deliveryModel->city ?: 'Уточняется'; - $deliveryModel->street = 'Уточняется'; - $deliveryModel->house = $deliveryModel->house ?: 'Уточняется'; - $deliveryModel->latitude = $deliveryModel->latitude ?: 0.0; - $deliveryModel->longitude = $deliveryModel->longitude ?: 0.0; - } + Yii::warning('[ERP-252-v4] street empty для заказа #' . $marketplaceOrder->id, 'marketplace'); + $fallback = self::fillDeliveryAddressFallback($marketplaceOrder->raw_data); + $deliveryModel->country = $deliveryModel->country ?: $fallback['country']; + $deliveryModel->city = $deliveryModel->city ?: $fallback['city']; + $deliveryModel->street = $fallback['street']; + $deliveryModel->house = $deliveryModel->house ?: $fallback['house']; + $deliveryModel->latitude = $deliveryModel->latitude ?: $fallback['latitude']; + $deliveryModel->longitude = $deliveryModel->longitude ?: $fallback['longitude']; } $shipments = $delivery->getShipments(); if ($shipments) { @@ -1583,27 +1572,16 @@ class MarketplaceService $deliveryModel->longitude = 0.0; } } - // Fallback: если street всё равно пустой — парсим из raw_data или ставим дефолт + // Fallback: если street пустой — извлекаем из raw_data или ставим дефолт if (empty($deliveryModel->street)) { - $rawData = json_decode($marketplaceOrder->raw_data, true); - $deliveryText = $rawData['delivery'] ?? null; - Yii::warning('[ERP-252-v3] update: street empty для заказа #' . $marketplaceOrder->id . ', address=' . ($address ? 'object' : 'null') . ', deliveryText=' . (is_string($deliveryText) ? $deliveryText : json_encode($deliveryText, JSON_UNESCAPED_UNICODE)), 'marketplace'); - if ($deliveryText && is_string($deliveryText)) { - $parsed = self::parseAddressFromDeliveryText($deliveryText); - $deliveryModel->country = $deliveryModel->country ?: 'Россия'; - $deliveryModel->city = $deliveryModel->city ?: $parsed['city']; - $deliveryModel->street = $parsed['street']; - $deliveryModel->house = $deliveryModel->house ?: $parsed['house']; - $deliveryModel->latitude = $deliveryModel->latitude ?: $parsed['latitude']; - $deliveryModel->longitude = $deliveryModel->longitude ?: $parsed['longitude']; - } else { - $deliveryModel->country = $deliveryModel->country ?: 'Уточняется'; - $deliveryModel->city = $deliveryModel->city ?: 'Уточняется'; - $deliveryModel->street = 'Уточняется'; - $deliveryModel->house = $deliveryModel->house ?: 'Уточняется'; - $deliveryModel->latitude = $deliveryModel->latitude ?: 0.0; - $deliveryModel->longitude = $deliveryModel->longitude ?: 0.0; - } + Yii::warning('[ERP-252-v4] update: street empty для заказа #' . $marketplaceOrder->id, 'marketplace'); + $fallback = self::fillDeliveryAddressFallback($marketplaceOrder->raw_data); + $deliveryModel->country = $deliveryModel->country ?: $fallback['country']; + $deliveryModel->city = $deliveryModel->city ?: $fallback['city']; + $deliveryModel->street = $fallback['street']; + $deliveryModel->house = $deliveryModel->house ?: $fallback['house']; + $deliveryModel->latitude = $deliveryModel->latitude ?: $fallback['latitude']; + $deliveryModel->longitude = $deliveryModel->longitude ?: $fallback['longitude']; } $dates = $delivery->getDates(); if ($dates) { @@ -1939,25 +1917,14 @@ class MarketplaceService } // Fallback: если street всё равно пустой — парсим из raw_data или ставим дефолт if (empty($deliveryModel->street)) { - $rawData = json_decode($marketplaceOrder->raw_data, true); - $deliveryText = $rawData['delivery'] ?? null; - Yii::warning('[ERP-252-v3] existing: street empty для заказа #' . $marketplaceOrder->id . ', address=' . ($address ? 'object' : 'null') . ', deliveryText=' . (is_string($deliveryText) ? $deliveryText : json_encode($deliveryText, JSON_UNESCAPED_UNICODE)), 'marketplace'); - if ($deliveryText && is_string($deliveryText)) { - $parsed = self::parseAddressFromDeliveryText($deliveryText); - $deliveryModel->country = $deliveryModel->country ?: 'Россия'; - $deliveryModel->city = $deliveryModel->city ?: $parsed['city']; - $deliveryModel->street = $parsed['street']; - $deliveryModel->house = $deliveryModel->house ?: $parsed['house']; - $deliveryModel->latitude = $deliveryModel->latitude ?: $parsed['latitude']; - $deliveryModel->longitude = $deliveryModel->longitude ?: $parsed['longitude']; - } else { - $deliveryModel->country = $deliveryModel->country ?: 'Уточняется'; - $deliveryModel->city = $deliveryModel->city ?: 'Уточняется'; - $deliveryModel->street = 'Уточняется'; - $deliveryModel->house = $deliveryModel->house ?: 'Уточняется'; - $deliveryModel->latitude = $deliveryModel->latitude ?: 0.0; - $deliveryModel->longitude = $deliveryModel->longitude ?: 0.0; - } + Yii::warning('[ERP-252-v4] existing: street empty для заказа #' . $marketplaceOrder->id, 'marketplace'); + $fallback = self::fillDeliveryAddressFallback($marketplaceOrder->raw_data); + $deliveryModel->country = $deliveryModel->country ?: $fallback['country']; + $deliveryModel->city = $deliveryModel->city ?: $fallback['city']; + $deliveryModel->street = $fallback['street']; + $deliveryModel->house = $deliveryModel->house ?: $fallback['house']; + $deliveryModel->latitude = $deliveryModel->latitude ?: $fallback['latitude']; + $deliveryModel->longitude = $deliveryModel->longitude ?: $fallback['longitude']; } $shipments = $delivery->getShipments(); if ($shipments) { @@ -3349,6 +3316,70 @@ class MarketplaceService return true; } + /** + * Извлекает адрес из raw_data заказа, когда API не вернул структурированный адрес. + * Обрабатывает 3 варианта raw_data['delivery']: + * 1) string — текст FlowWow ("Доставка: ..., город, улица, дом") + * 2) array с address — JSON-объект YM API + * 3) null / отсутствует — дефолт "Уточняется" + * + * @param string|null $rawDataJson JSON-строка raw_data заказа + * @return array{country: string, city: string, street: string, house: string, latitude: float, longitude: float} + */ + public static function fillDeliveryAddressFallback(?string $rawDataJson): array + { + $defaults = [ + 'country' => 'Уточняется', + 'city' => 'Уточняется', + 'street' => 'Уточняется', + 'house' => 'Уточняется', + 'latitude' => 0.0, + 'longitude' => 0.0, + ]; + + if (empty($rawDataJson)) { + return $defaults; + } + + $rawData = json_decode($rawDataJson, true); + if (!is_array($rawData)) { + return $defaults; + } + + $delivery = $rawData['delivery'] ?? null; + + // Вариант 1: текстовая строка FlowWow + if (is_string($delivery) && $delivery !== '') { + $parsed = self::parseAddressFromDeliveryText($delivery); + return [ + 'country' => 'Россия', + 'city' => $parsed['city'], + 'street' => $parsed['street'], + 'house' => $parsed['house'], + 'latitude' => $parsed['latitude'], + 'longitude' => $parsed['longitude'], + ]; + } + + // Вариант 2: массив (JSON-объект YM API) с address + if (is_array($delivery)) { + $address = $delivery['address'] ?? null; + if (is_array($address)) { + return [ + 'country' => !empty($address['country']) ? $address['country'] : 'Россия', + 'city' => !empty($address['city']) ? $address['city'] : 'Уточняется', + 'street' => !empty($address['street']) ? $address['street'] : 'Уточняется', + 'house' => !empty($address['house']) ? $address['house'] : 'Уточняется', + 'latitude' => (float)($address['gps']['latitude'] ?? 0.0), + 'longitude' => (float)($address['gps']['longitude'] ?? 0.0), + ]; + } + } + + // Вариант 3: null или нет данных + return $defaults; + } + public static function parseAddressFromDeliveryText(string $text): array { $city = 'Уточняется'; diff --git a/erp24/tests/unit/services/MarketplaceServiceDeliveryParsingTest.php b/erp24/tests/unit/services/MarketplaceServiceDeliveryParsingTest.php index c587faac..9a6c9226 100644 --- a/erp24/tests/unit/services/MarketplaceServiceDeliveryParsingTest.php +++ b/erp24/tests/unit/services/MarketplaceServiceDeliveryParsingTest.php @@ -188,4 +188,139 @@ class MarketplaceServiceDeliveryParsingTest extends Unit // Должен распознать хотя бы что-то кроме "Уточняется" $this->assertNotEquals('Уточняется', $result['street']); } + + // --- Тесты fillDeliveryAddressFallback --- + + /** + * Текстовая строка delivery в raw_data — парсится в адрес + */ + public function testFillFallback_TextDelivery_ParsesAddress(): void + { + $rawData = json_encode([ + 'delivery' => 'Доставка: 8 марта 2026 в 10:30—12:30, Нижний Новгород, улица Ленина, 10', + ]); + + $result = MarketplaceService::fillDeliveryAddressFallback($rawData); + + $this->assertNotEquals('Уточняется', $result['street']); + $this->assertNotEquals('Уточняется', $result['city']); + } + + /** + * delivery = null в raw_data — возвращает "Уточняется" + */ + public function testFillFallback_NullDelivery_ReturnsDefaults(): void + { + $rawData = json_encode(['delivery' => null]); + + $result = MarketplaceService::fillDeliveryAddressFallback($rawData); + + $this->assertEquals('Уточняется', $result['street']); + $this->assertEquals('Уточняется', $result['city']); + $this->assertEquals('Уточняется', $result['house']); + } + + /** + * delivery = массив (JSON-объект от YM API) с address.street + */ + public function testFillFallback_ArrayDelivery_WithAddress(): void + { + $rawData = json_encode([ + 'delivery' => [ + 'type' => 'DELIVERY', + 'address' => [ + 'country' => 'Россия', + 'city' => 'Москва', + 'street' => 'Тверская', + 'house' => '1', + ], + ], + ]); + + $result = MarketplaceService::fillDeliveryAddressFallback($rawData); + + $this->assertEquals('Москва', $result['city']); + $this->assertEquals('Тверская', $result['street']); + $this->assertEquals('1', $result['house']); + } + + /** + * delivery = массив без address — возвращает "Уточняется" + */ + public function testFillFallback_ArrayDelivery_NoAddress(): void + { + $rawData = json_encode([ + 'delivery' => [ + 'type' => 'DELIVERY', + ], + ]); + + $result = MarketplaceService::fillDeliveryAddressFallback($rawData); + + $this->assertEquals('Уточняется', $result['street']); + } + + /** + * delivery = массив с address, но street пустой + */ + public function testFillFallback_ArrayDelivery_EmptyStreet(): void + { + $rawData = json_encode([ + 'delivery' => [ + 'address' => [ + 'city' => 'Москва', + 'street' => '', + 'house' => '5', + ], + ], + ]); + + $result = MarketplaceService::fillDeliveryAddressFallback($rawData); + + $this->assertEquals('Уточняется', $result['street']); + $this->assertEquals('Москва', $result['city']); + } + + /** + * raw_data невалидный JSON — возвращает "Уточняется" + */ + public function testFillFallback_InvalidJson(): void + { + $result = MarketplaceService::fillDeliveryAddressFallback('not json'); + + $this->assertEquals('Уточняется', $result['street']); + } + + /** + * raw_data = null — возвращает "Уточняется" + */ + public function testFillFallback_NullRawData(): void + { + $result = MarketplaceService::fillDeliveryAddressFallback(null); + + $this->assertEquals('Уточняется', $result['street']); + $this->assertEquals('Уточняется', $result['city']); + $this->assertEquals('Уточняется', $result['house']); + } + + /** + * Результат fallback всегда содержит все ключи и не null + */ + public function testFillFallback_AlwaysCompleteResult(): void + { + $inputs = [null, '', 'not json', '{}', '{"delivery":null}', '{"delivery":"text"}']; + + foreach ($inputs as $input) { + $result = MarketplaceService::fillDeliveryAddressFallback($input); + + $this->assertArrayHasKey('country', $result, "country для: $input"); + $this->assertArrayHasKey('city', $result, "city для: $input"); + $this->assertArrayHasKey('street', $result, "street для: $input"); + $this->assertArrayHasKey('house', $result, "house для: $input"); + $this->assertArrayHasKey('latitude', $result, "latitude для: $input"); + $this->assertArrayHasKey('longitude', $result, "longitude для: $input"); + $this->assertNotNull($result['street'], "street не null для: $input"); + $this->assertNotEmpty($result['street'], "street не пустой для: $input"); + } + } } -- 2.39.5