]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
корректировка логики
authorVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 19 Jan 2026 13:37:11 +0000 (16:37 +0300)
committerVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 19 Jan 2026 13:37:11 +0000 (16:37 +0300)
erp24/records/MarketplaceOrder1cStatuses.php
erp24/services/OrderControlReportService.php
erp24/services/dto/OrderIssue.php

index b013fe5d77beb73f4bd9dd3cce4f837428f6ba63..71482ec7f04e73d060f46ede73b17a9741d20f3e 100644 (file)
@@ -145,4 +145,59 @@ class MarketplaceOrder1cStatuses extends \yii\db\ActiveRecord
     {
         return $this->hasOne(MarketplaceOrderStatusTypes::class, ['id' => 'order_substatus_id']);
     }
+
+    /**
+     * Получает ID статусов "Успех" (successful_order = 1)
+     *
+     * @return int[]
+     */
+    public static function getSuccessfulOrderIds(): array
+    {
+        return self::find()
+            ->select('id')
+            ->where(['successful_order' => 1])
+            ->column();
+    }
+
+    /**
+     * Получает ID статусов "Отказ" (cancelled_order = 1)
+     *
+     * @return int[]
+     */
+    public static function getCancelledOrderIds(): array
+    {
+        return self::find()
+            ->select('id')
+            ->where(['cancelled_order' => 1])
+            ->column();
+    }
+
+    /**
+     * Получает ID статусов "Передан курьеру" по связи с МП-статусами DELIVERY/COURIER_RECEIVED
+     *
+     * @return int[]
+     */
+    public static function getCourierOrderIds(): array
+    {
+        // Получаем ID МП-статусов доставки
+        $deliveryStatusIds = MarketplaceOrderStatusTypes::find()
+            ->select('id')
+            ->where(['code' => [
+                MarketplaceOrderStatusTypes::DELIVERY_CODE,
+                MarketplaceOrderStatusTypes::COURIER_RECEIVED_CODE,
+            ]])
+            ->column();
+
+        if (empty($deliveryStatusIds)) {
+            return [];
+        }
+
+        return self::find()
+            ->select('id')
+            ->where(['or',
+                ['order_status_id' => $deliveryStatusIds],
+                ['order_substatus_id' => $deliveryStatusIds],
+            ])
+            ->column();
+    }
 }
index 7e702fed34fbca06ef943b27e36f42299259061f..7ef95e33b8b85e5bf212279fe99f4ca3bed3f6b4 100644 (file)
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace yii_app\services;
 
 use Yii;
+use yii_app\records\MarketplaceOrder1cStatuses;
 use yii_app\records\MarketplaceOrderStatusTypes;
 use yii_app\records\MarketplaceOrderDailyIssues;
 use yii_app\services\dto\OrderIssue;
@@ -59,22 +60,61 @@ class OrderControlReportService
     private const TELEGRAM_BOT_PROD = '5456741805:AAG7xOSiYDwUdV5NMb2v9vh8CWzEczDP4yU';
 
     /**
-     * Ð¡Ñ\82аÑ\82Ñ\83Ñ\81Ñ\8b 1С Ð´Ð»Ñ\8f Ð¿Ñ\80овеÑ\80ок
+     * Ð\9aонÑ\84игÑ\83Ñ\80аÑ\86иÑ\8f Ð¾Ñ\82Ñ\87Ñ\91Ñ\82а
      */
-    private const RMK_STATUS_COURIER = ['1004', '1011']; // "Передан курьеру"
-    private const RMK_STATUS_SUCCESS = ['1005', '1012']; // "Успех"
-    private const RMK_STATUS_CANCEL = ['1006', '1013'];  // "Отказ"
+    private array $config;
 
     /**
-     * Ð\9aонÑ\84игÑ\83Ñ\80аÑ\86иÑ\8f Ð¾Ñ\82Ñ\87Ñ\91Ñ\82а
+     * Ð\9aеÑ\88 ID Ñ\81Ñ\82аÑ\82Ñ\83Ñ\81ов 1С (лениваÑ\8f Ð·Ð°Ð³Ñ\80Ñ\83зка)
      */
-    private array $config;
+    private ?array $rmkStatusCourier = null;
+    private ?array $rmkStatusSuccess = null;
+    private ?array $rmkStatusCancel = null;
 
     public function __construct()
     {
         $this->config = Yii::$app->params['MARKETPLACE_ORDER_CONTROL_REPORT'] ?? [];
     }
 
+    /**
+     * Получает ID статусов 1С "Передан курьеру" (с кешированием)
+     *
+     * @return int[]
+     */
+    private function getRmkStatusCourier(): array
+    {
+        if ($this->rmkStatusCourier === null) {
+            $this->rmkStatusCourier = MarketplaceOrder1cStatuses::getCourierOrderIds();
+        }
+        return $this->rmkStatusCourier;
+    }
+
+    /**
+     * Получает ID статусов 1С "Успех" (successful_order = 1) с кешированием
+     *
+     * @return int[]
+     */
+    private function getRmkStatusSuccess(): array
+    {
+        if ($this->rmkStatusSuccess === null) {
+            $this->rmkStatusSuccess = MarketplaceOrder1cStatuses::getSuccessfulOrderIds();
+        }
+        return $this->rmkStatusSuccess;
+    }
+
+    /**
+     * Получает ID статусов 1С "Отказ" (cancelled_order = 1) с кешированием
+     *
+     * @return int[]
+     */
+    private function getRmkStatusCancel(): array
+    {
+        if ($this->rmkStatusCancel === null) {
+            $this->rmkStatusCancel = MarketplaceOrder1cStatuses::getCancelledOrderIds();
+        }
+        return $this->rmkStatusCancel;
+    }
+
     /**
      * Генерирует отчёт контроля статусов заказов МП
      *
@@ -82,7 +122,7 @@ class OrderControlReportService
      * @param bool $onlyNew Отправлять только новые проблемы
      * @return ControlReportResult
      */
-    public function generateControlReport(int $hoursAgo = 24, bool $onlyNew = true): ControlReportResult
+    public function generateControlReport(int $hoursAgo = 12, bool $onlyNew = true): ControlReportResult
     {
         $result = new ControlReportResult();
 
@@ -175,9 +215,10 @@ class OrderControlReportService
     {
         $this->logInfo('Выборка кандидатов "Завис в доставке"', ['hours_ago' => $hoursAgo]);
 
-        $startDate = new \DateTime('now', new \DateTimeZone(self::TIMEZONE));
-        $startDate->modify("-{$hoursAgo} hours");
-        $startDateStr = $startDate->format('Y-m-d H:i:s');
+        // Получаем диапазон дат на основе конца смены
+        $dateRange = $this->getShiftBasedDateRange($hoursAgo);
+        $startDateStr = $dateRange['startDate'];
+        $endDateStr = $dateRange['endDate'];
 
         // Выбираем заказы с РМК-статусом "Передан курьеру", где МП-статус НЕ "Выполнен"
         $sql = "
@@ -194,7 +235,7 @@ class OrderControlReportService
                 mocs.status as rmk_status,
                 most.code as mp_status_code,
                 mosub.code as mp_substatus_code,
-                most.name as mp_status_name
+                COALESCE(most.name, mosub.name) as mp_status_name
             FROM marketplace_orders mo
             LEFT JOIN city_store cs ON cs.id = mo.store_id
             LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id = mo.status_processing_1c::integer
@@ -203,6 +244,7 @@ class OrderControlReportService
             WHERE mo.fake = 0
               AND mo.status_processing_1c::integer IN (:rmk_1004, :rmk_1011)
               AND mo.updated_at >= :start_date
+              AND mo.updated_at <= :end_date
               AND (
                   most.code IS NULL
                   OR (most.code != :delivered AND mosub.code IS DISTINCT FROM :delivery_service_delivered)
@@ -214,6 +256,7 @@ class OrderControlReportService
             ':rmk_1004' => 1004,
             ':rmk_1011' => 1011,
             ':start_date' => $startDateStr,
+            ':end_date' => $endDateStr,
             ':delivered' => MarketplaceOrderStatusTypes::DELIVERED_CODE,
             ':delivery_service_delivered' => MarketplaceOrderStatusTypes::DELIVERY_SERVICE_DELIVERED_CODE,
         ])->queryAll();
@@ -302,7 +345,7 @@ class OrderControlReportService
                 'report_date' => $prevDate,
                 'interval' => $prevInterval,
             ])
-            ->andWhere(['rmk_status_id' => self::RMK_STATUS_COURIER])
+            ->andWhere(['rmk_status_id' => $this->getRmkStatusCourier()])
             ->indexBy('order_id')
             ->asArray()
             ->all();
@@ -334,20 +377,47 @@ class OrderControlReportService
      * Получает заказы типа "Успех без чека"
      *
      * Критерий: МП статус = "Выполнен" (DELIVERED или DELIVERY_SERVICE_DELIVERED)
-     *           + РМК статус НЕ "Успех" (НЕ 1005/1012)
+     *           + (seller_id пустой/нулевой ИЛИ чек не создан в create_checks)
      *
-     * @param int $hoursAgo Период выборки в часах (по умолчанию 24)
+     * Причина проблемы:
+     * - Если seller_id пустой или '00000000-0000-0000-0000-000000000000' — чек не создаётся
+     * - Если чек не создан — 1С не получает сигнала о доставке заказа
+     *
+     * @see MarketplaceService::createCheckForMarketplaceOrder() — логика создания чека
+     *
+     * @param int $hoursAgo Период выборки в часах (по умолчанию 12)
      * @return OrderIssue[]
      */
-    public function getSuccessNoCheckOrders(int $hoursAgo = 24): array
+    public function getSuccessNoCheckOrders(int $hoursAgo = 12): array
     {
         $this->logInfo('Выборка заказов "Успех без чека"', ['hours_ago' => $hoursAgo]);
 
-        $startDate = new \DateTime('now', new \DateTimeZone(self::TIMEZONE));
-        $startDate->modify("-{$hoursAgo} hours");
-        $startDateStr = $startDate->format('Y-m-d H:i:s');
+        // Получаем диапазон дат на основе конца смены
+        $dateRange = $this->getShiftBasedDateRange($hoursAgo);
+        $startDateStr = $dateRange['startDate'];
+        $endDateStr = $dateRange['endDate'];
+
+        // Нулевой GUID — признак отсутствия продавца
+        $emptySellerGuid = '00000000-0000-0000-0000-000000000000';
 
-        // Выбираем заказы с МП-статусом "Выполнен", где РМК-статус НЕ "Успех"
+        // Получаем ID статусов "Успех" из БД
+        $rmkSuccessIds = $this->getRmkStatusSuccess();
+
+        // Формируем плейсхолдеры для IN-условия
+        $rmkSuccessPlaceholders = [];
+        $rmkSuccessParams = [];
+        foreach ($rmkSuccessIds as $index => $id) {
+            $placeholder = ':rmk_success_' . $index;
+            $rmkSuccessPlaceholders[] = $placeholder;
+            $rmkSuccessParams[$placeholder] = $id;
+        }
+        $rmkSuccessInClause = !empty($rmkSuccessPlaceholders)
+            ? implode(', ', $rmkSuccessPlaceholders)
+            : '0'; // fallback если статусов нет
+
+        // Выбираем заказы с МП-статусом "Выполнен", где:
+        // 1. РМК-статус НЕ "Успех" (1С не знает о доставке)
+        // 2. Причина: seller_id пустой или нулевой GUID, ИЛИ чек не создан в create_checks
         $sql = "
             SELECT
                 mo.id,
@@ -358,43 +428,73 @@ class OrderControlReportService
                 mo.marketplace_id,
                 mo.total,
                 mo.creation_date,
+                mo.seller_id,
                 mo.status_processing_1c as rmk_status_id,
                 mocs.status as rmk_status,
                 most.code as mp_status_code,
                 mosub.code as mp_substatus_code,
-                most.name as mp_status_name
+                COALESCE(most.name, mosub.name) as mp_status_name,
+                cc.id as check_id,
+                CASE
+                    WHEN cc.id IS NOT NULL THEN true
+                    ELSE false
+                END as check_exists,
+                CASE
+                    WHEN mo.seller_id IS NULL OR mo.seller_id = '' OR mo.seller_id = :empty_seller_guid
+                    THEN 'no_seller_id'
+                    WHEN cc.id IS NULL
+                    THEN 'no_check'
+                    ELSE 'unknown'
+                END as issue_reason
             FROM marketplace_orders mo
             LEFT JOIN city_store cs ON cs.id = mo.store_id
             LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id = mo.status_processing_1c::integer
             LEFT JOIN marketplace_order_status_types most ON most.id = mo.status_id
             LEFT JOIN marketplace_order_status_types mosub ON mosub.id = mo.substatus_id
+            LEFT JOIN create_checks cc ON cc.marketplace_order_id = mo.marketplace_order_id
             WHERE mo.fake = 0
               AND mo.updated_at >= :start_date
+              AND mo.updated_at <= :end_date
+              -- МП-статус = Выполнен (DELIVERED)
               AND (
                   most.code = :delivered
                   OR mosub.code = :delivery_service_delivered
               )
+              -- РМК-статус НЕ Успех (1С не знает о доставке)
               AND (
                   mo.status_processing_1c IS NULL
-                  OR mo.status_processing_1c::integer NOT IN (:rmk_1005, :rmk_1012)
+                  OR mo.status_processing_1c::integer NOT IN ({$rmkSuccessInClause})
+              )
+              -- Причина: seller_id пустой ИЛИ чек не создан
+              AND (
+                  mo.seller_id IS NULL
+                  OR mo.seller_id = ''
+                  OR mo.seller_id = :empty_seller_guid
+                  OR cc.id IS NULL
               )
             ORDER BY cs.name ASC, mo.creation_date DESC
         ";
 
-        $orders = Yii::$app->db->createCommand($sql, [
+        $params = array_merge([
             ':start_date' => $startDateStr,
+            ':end_date' => $endDateStr,
             ':delivered' => MarketplaceOrderStatusTypes::DELIVERED_CODE,
             ':delivery_service_delivered' => MarketplaceOrderStatusTypes::DELIVERY_SERVICE_DELIVERED_CODE,
-            ':rmk_1005' => 1005,
-            ':rmk_1012' => 1012,
-        ])->queryAll();
+            ':empty_seller_guid' => $emptySellerGuid,
+        ], $rmkSuccessParams);
+
+        $orders = Yii::$app->db->createCommand($sql, $params)->queryAll();
 
         $issues = [];
         foreach ($orders as $orderData) {
             $issues[] = OrderIssue::fromOrderData(OrderIssue::TYPE_SUCCESS_NO_CHECK, $orderData);
         }
 
-        $this->logInfo('Найдено "Успех без чека"', ['count' => count($issues)]);
+        $this->logInfo('Найдено "Успех без чека"', [
+            'count' => count($issues),
+            'no_seller_id' => count(array_filter($orders, fn($o) => $o['issue_reason'] === 'no_seller_id')),
+            'no_check' => count(array_filter($orders, fn($o) => $o['issue_reason'] === 'no_check')),
+        ]);
 
         return $issues;
     }
@@ -412,9 +512,10 @@ class OrderControlReportService
     {
         $this->logInfo('Выборка заказов "Отмена без обработки"', ['hours_ago' => $hoursAgo]);
 
-        $startDate = new \DateTime('now', new \DateTimeZone(self::TIMEZONE));
-        $startDate->modify("-{$hoursAgo} hours");
-        $startDateStr = $startDate->format('Y-m-d H:i:s');
+        // Получаем диапазон дат на основе конца смены
+        $dateRange = $this->getShiftBasedDateRange($hoursAgo);
+        $startDateStr = $dateRange['startDate'];
+        $endDateStr = $dateRange['endDate'];
 
         // Выбираем заказы с МП-статусом "Отменён", где РМК-статус НЕ "Отказ"
         $sql = "
@@ -431,7 +532,7 @@ class OrderControlReportService
                 mocs.status as rmk_status,
                 most.code as mp_status_code,
                 mosub.code as mp_substatus_code,
-                most.name as mp_status_name
+                COALESCE(most.name, mosub.name) as mp_status_name
             FROM marketplace_orders mo
             LEFT JOIN city_store cs ON cs.id = mo.store_id
             LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id = mo.status_processing_1c::integer
@@ -439,6 +540,7 @@ class OrderControlReportService
             LEFT JOIN marketplace_order_status_types mosub ON mosub.id = mo.substatus_id
             WHERE mo.fake = 0
               AND mo.updated_at >= :start_date
+              AND mo.updated_at <= :end_date
               AND most.code = :cancelled
               AND (
                   mo.status_processing_1c IS NULL
@@ -449,6 +551,7 @@ class OrderControlReportService
 
         $orders = Yii::$app->db->createCommand($sql, [
             ':start_date' => $startDateStr,
+            ':end_date' => $endDateStr,
             ':cancelled' => MarketplaceOrderStatusTypes::CANSELLED_CODE,
             ':rmk_1006' => 1006,
             ':rmk_1013' => 1013,
@@ -573,71 +676,78 @@ class OrderControlReportService
     /**
      * Формирует текстовый отчёт контроля статусов для Telegram (MarkdownV2)
      *
+     * Использует моноширинный блок для корректного отображения таблицы.
+     *
      * @param ControlReportResult $result Результат отчёта
      * @return string Текст сообщения
      */
     public function formatTelegramControlReport(ControlReportResult $result): string
     {
         $lines = [];
-        $lines[] = $this->escapeMarkdownV2('[Контроль MP]') . ' *Отчёт за ' . $this->escapeMarkdownV2($result->reportDate) . ' ' . $this->escapeMarkdownV2($result->interval) . '*';
+        $lines[] = '*\[Контроль MP\]* Отчёт за ' . $this->escapeMarkdownV2($result->reportDate) . ' ' . $this->escapeMarkdownV2($result->interval);
         $lines[] = '';
 
         // Секция "Завис в доставке"
         if (!empty($result->hungInDelivery)) {
-            $lines[] = '*' . $this->escapeMarkdownV2('Завис в доставке') . '*';
-            $lines[] = $this->escapeMarkdownV2('| Дата | Интервал | Заказ | РМК | МП');
-
-            foreach ($result->hungInDelivery as $issue) {
-                $line = $this->formatIssueLineForTelegram($issue);
-                $lines[] = $this->escapeMarkdownV2($line);
-            }
+            $lines[] = '*Завис в доставке* \\(' . count($result->hungInDelivery) . '\\)';
+            $lines[] = $this->formatIssuesTable($result->hungInDelivery);
             $lines[] = '';
         }
 
         // Секция "Успех без чека"
         if (!empty($result->successNoCheck)) {
-            $lines[] = '*' . $this->escapeMarkdownV2('Успех без чека') . '*';
-            $lines[] = $this->escapeMarkdownV2('| Дата | Интервал | Заказ | РМК | МП');
-
-            foreach ($result->successNoCheck as $issue) {
-                $line = $this->formatIssueLineForTelegram($issue);
-                $lines[] = $this->escapeMarkdownV2($line);
-            }
+            $lines[] = '*Успех без чека* \\(' . count($result->successNoCheck) . '\\)';
+            $lines[] = $this->formatIssuesTable($result->successNoCheck);
             $lines[] = '';
         }
 
         // Секция "Отмена без обработки"
         if (!empty($result->cancelNoProcess)) {
-            $lines[] = '*' . $this->escapeMarkdownV2('Отмена без обработки') . '*';
-            $lines[] = $this->escapeMarkdownV2('| Дата | Интервал | Заказ | РМК | МП');
-
-            foreach ($result->cancelNoProcess as $issue) {
-                $line = $this->formatIssueLineForTelegram($issue);
-                $lines[] = $this->escapeMarkdownV2($line);
-            }
+            $lines[] = '*Отмена без обработки* \\(' . count($result->cancelNoProcess) . '\\)';
+            $lines[] = $this->formatIssuesTable($result->cancelNoProcess);
             $lines[] = '';
         }
 
+        $lines[] = '*Всего:* ' . $this->escapeMarkdownV2((string)$result->totalIssues);
+
         return implode("\n", $lines);
     }
 
     /**
-     * Форматирует строку проблемы для Telegram
+     * Форматирует таблицу проблем для Telegram (моноширинный блок)
+     *
+     * @param OrderIssue[] $issues
+     * @return string
+     */
+    private function formatIssuesTable(array $issues): string
+    {
+        $rows = [];
+        $rows[] = '```';
+
+        foreach ($issues as $issue) {
+            $rows[] = $this->formatIssueRow($issue);
+        }
+
+        $rows[] = '```';
+
+        return implode("\n", $rows);
+    }
+
+    /**
+     * Форматирует строку таблицы для проблемы
+     *
+     * Формат: Заказ | РМК | МП
      *
      * @param OrderIssue $issue
      * @return string
      */
-    private function formatIssueLineForTelegram(OrderIssue $issue): string
+    private function formatIssueRow(OrderIssue $issue): string
     {
-        $date = $issue->reportDate ?: date('d.m.Y');
-        $interval = $issue->interval ?: ((int)date('H') < 12 ? '08:00' : '20:00');
         $rmk = $issue->rmkStatus ?? '-';
         $mp = $issue->mpStatus ?? '-';
 
         return sprintf(
-            '| %s | %s | %s | %s | %s',
-            $date,
-            $interval,
+            '%s | %s | %s',
             $issue->orderNumber,
             $rmk,
             $mp
@@ -1067,6 +1177,54 @@ class OrderControlReportService
         return $recipients;
     }
 
+    /**
+     * Вычисляет диапазон дат на основе конца смены
+     *
+     * Логика:
+     * - Если текущий час < 20, база = 08:00 сегодня (конец утренней смены)
+     * - Если текущий час >= 20, база = 20:00 сегодня (конец вечерней смены)
+     * - startDate = база - $hoursAgo часов
+     * - endDate = база
+     *
+     * Пример:
+     * - 15:00, hoursAgo=12 → база 08:00 → диапазон: вчера 20:00 - сегодня 08:00
+     * - 22:00, hoursAgo=12 → база 20:00 → диапазон: сегодня 08:00 - сегодня 20:00
+     *
+     * @param int $hoursAgo Количество часов назад от конца смены
+     * @return array{startDate: string, endDate: string} Массив с датами в формате 'Y-m-d H:i:s'
+     */
+    private function getShiftBasedDateRange(int $hoursAgo): array
+    {
+        $now = new \DateTime('now', new \DateTimeZone(self::TIMEZONE));
+        $currentHour = (int)$now->format('H');
+
+        // Определяем конец текущей смены (база для расчёта)
+        $shiftEnd = clone $now;
+        if ($currentHour < 20) {
+            // До 20:00 — база 08:00 сегодня
+            $shiftEnd->setTime(8, 0, 0);
+        } else {
+            // После 20:00 — база 20:00 сегодня
+            $shiftEnd->setTime(20, 0, 0);
+        }
+
+        // Вычисляем начало периода
+        $shiftStart = clone $shiftEnd;
+        $shiftStart->modify("-{$hoursAgo} hours");
+
+        $this->logInfo('Вычислен диапазон дат на основе смены', [
+            'current_time' => $now->format('Y-m-d H:i:s'),
+            'shift_end' => $shiftEnd->format('Y-m-d H:i:s'),
+            'shift_start' => $shiftStart->format('Y-m-d H:i:s'),
+            'hours_ago' => $hoursAgo,
+        ]);
+
+        return [
+            'startDate' => $shiftStart->format('Y-m-d H:i:s'),
+            'endDate' => $shiftEnd->format('Y-m-d H:i:s'),
+        ];
+    }
+
     /**
      * Логирование в структурированном JSON-формате
      *
index c938157f0b120f8302e02efb6673864bec282376..511662774ecec6a0f90dbbc567f69884bab24969 100644 (file)
@@ -121,6 +121,30 @@ class OrderIssue
      */
     public ?string $creationDate;
 
+    /**
+     * ID продавца (seller_id)
+     */
+    public ?string $sellerId;
+
+    /**
+     * Существует ли чек в create_checks
+     */
+    public bool $checkExists = false;
+
+    /**
+     * Причина проблемы (no_seller_id, no_check)
+     */
+    public ?string $issueReason;
+
+    /**
+     * Человекочитаемые метки причин
+     */
+    public const ISSUE_REASON_LABELS = [
+        'no_seller_id' => 'Нет seller_id',
+        'no_check' => 'Чек не создан',
+        'unknown' => 'Неизвестно',
+    ];
+
     /**
      * Конструктор с параметрами
      *
@@ -172,9 +196,20 @@ class OrderIssue
 
         $issue->rmkStatus = $orderData['rmk_status'] ?? null;
         $issue->rmkStatusId = isset($orderData['rmk_status_id']) ? (string)$orderData['rmk_status_id'] : null;
-        $issue->mpStatus = $orderData['mp_status_name'] ?? null;
         $issue->mpStatusCode = $orderData['mp_status_code'] ?? null;
         $issue->mpSubstatusCode = $orderData['mp_substatus_code'] ?? null;
+
+        // МП статус: приоритет — mp_status_name, затем формируем из кодов
+        $mpStatusName = $orderData['mp_status_name'] ?? null;
+        if ($mpStatusName) {
+            $issue->mpStatus = $mpStatusName;
+        } elseif ($issue->mpStatusCode || $issue->mpSubstatusCode) {
+            // Если название пустое, но есть коды — показываем коды
+            $codes = array_filter([$issue->mpStatusCode, $issue->mpSubstatusCode]);
+            $issue->mpStatus = implode('/', $codes);
+        } else {
+            $issue->mpStatus = null;
+        }
         $issue->storeId = isset($orderData['store_id']) ? (int)$orderData['store_id'] : null;
         $issue->storeName = $orderData['store_name'] ?? null;
         $issue->marketplaceName = $orderData['marketplace_name'] ?? null;
@@ -182,6 +217,11 @@ class OrderIssue
         $issue->total = (float)($orderData['total'] ?? 0);
         $issue->creationDate = $orderData['creation_date'] ?? null;
 
+        // Поля для диагностики "Успех без чека"
+        $issue->sellerId = $orderData['seller_id'] ?? null;
+        $issue->checkExists = (bool)($orderData['check_exists'] ?? false);
+        $issue->issueReason = $orderData['issue_reason'] ?? null;
+
         return $issue;
     }
 
@@ -210,6 +250,10 @@ class OrderIssue
             'marketplace_id' => $this->marketplaceId,
             'total' => $this->total,
             'creation_date' => $this->creationDate,
+            'seller_id' => $this->sellerId,
+            'check_exists' => $this->checkExists,
+            'issue_reason' => $this->issueReason,
+            'issue_reason_label' => $this->getIssueReasonLabel(),
         ];
     }
 
@@ -246,4 +290,18 @@ class OrderIssue
     {
         return number_format($this->total, 0, '.', ' ') . ' ₽';
     }
+
+    /**
+     * Получает человекочитаемую метку причины проблемы
+     *
+     * @return string|null
+     */
+    public function getIssueReasonLabel(): ?string
+    {
+        if ($this->issueReason === null) {
+            return null;
+        }
+
+        return self::ISSUE_REASON_LABELS[$this->issueReason] ?? $this->issueReason;
+    }
 }