From 54c991ed3a57cc8ac82a601efb3f98a3aea39130 Mon Sep 17 00:00:00 2001 From: Vladimir Fomichev Date: Fri, 6 Mar 2026 15:57:46 +0300 Subject: [PATCH] =?utf8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- plan_email.md | 293 -------------------------------------------------- 1 file changed, 293 deletions(-) delete mode 100644 plan_email.md diff --git a/plan_email.md b/plan_email.md deleted file mode 100644 index fc1ac924..00000000 --- a/plan_email.md +++ /dev/null @@ -1,293 +0,0 @@ -План: Улучшение системы регистрации и обработки писем Flowwow - -Контекст - -Крон-задача php yii marketplace/get-flowwow-orders читает письма от Flowwow через IMAP и создаёт/обновляет заказы в системе. Текущие проблемы: - -1. Нет разделения "регистрация" / "обработка" — email_status=1 ставится ДО processMessage() (строка 2203-2205), поэтому при сбое обработки письмо уже помечено как - обработанное -2. Повторная обработка невозможна — если saveEmailIfNotExists вернул null (письмо есть), processMessage() всё равно вызывается, но нет контроля завершённости обработки -3. SEEN ставится только для новых заказов — processFlowwowOrders возвращает счётчик только для NEW (строка 2721), а SEEN зависит от $output > 0 (строка 2218). Для - APPROVED/CHANGED/CANCELLED/DELIVERED SEEN не ставится -4. Нет индексов на таблице marketplace_flowwow_emails для проверки дубликатов -5. Нет отслеживания ошибок — если обработка упала, нет записи почему и сколько раз пытались -6. Нет связки письмо↔заказ — невозможно из интерфейса писем найти, какие письма относятся к конкретному заказу - -Файлы для изменения -┌──────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────┐ -│ Файл │ Изменение │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/migrations/m260218_*_improve_flowwow_emails.php │ Новый — миграция: колонки + индексы │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/records/MarketplaceFlowwowEmails.php │ Константы статусов, новые поля, relation, helper-методы │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/records/MarketplaceFlowwowEmailsSearch.php │ Фильтрация по новым полям + поиск по заказу │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/services/MarketplaceService.php │ Рефакторинг getFlowwowOrdersFromMail, saveEmailIfNotExists, fix processFlowwowOrders │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/commands/MarketplaceController.php │ Новый action actionRetryFlowwowEmails │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/media/controllers/FlowwowController.php │ Fix actionCheckMail (несовместимый вызов) │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/views/marketplace-flowwow-emails/index.php │ Обновление GridView: статусы, связка с заказом, поиск │ -├──────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────┤ -│ erp24/controllers/MarketplaceFlowwowEmailsController.php │ Мелкие правки (если нужны для view) │ -└──────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────┘ - --- -Шаг 1. Миграция БД - -Файл: erp24/migrations/m260218_000001_improve_marketplace_flowwow_emails.php - -Добавить в таблицу marketplace_flowwow_emails: -┌──────────────────────┬────────────────────┬─────────────────────────────────────────────────────────────────────┐ -│ Колонка │ Тип │ Описание │ -├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤ -│ subject_type │ smallint, NULL │ Тип письма (1=NEW, 2=APPROVED, 3=CHANGED, 4=CANCELLED, 5=DELIVERED) │ -├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤ -│ processing_attempts │ integer, default 0 │ Счётчик попыток обработки │ -├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤ -│ processed_at │ timestamp, NULL │ Время успешной обработки │ -├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤ -│ error_message │ text, NULL │ Текст последней ошибки │ -├──────────────────────┼────────────────────┼─────────────────────────────────────────────────────────────────────┤ -│ marketplace_order_id │ varchar(50), NULL │ ID заказа (связка с marketplace_orders) │ -└──────────────────────┴────────────────────┴─────────────────────────────────────────────────────────────────────┘ -Индексы: -- idx_flowwow_emails_dedup на (subject, "from", date) — ускорение проверки дубликатов -- idx_flowwow_emails_status на (email_status) — выборка необработанных -- idx_flowwow_emails_order на (marketplace_order_id) — связка с заказами - -Важно: from — зарезервированное слово PostgreSQL, использовать "from" в кавычках через raw SQL. - - --- -Шаг 2. Модель MarketplaceFlowwowEmails - -Файл: erp24/records/MarketplaceFlowwowEmails.php - -2.1 Константы статусов -public const STATUS_NEW = 0; // Зарегистрировано, ожидает обработки -public const STATUS_PROCESSED = 1; // Успешно обработано -public const STATUS_ERROR = 2; // Ошибка (исчерпаны попытки) -public const STATUS_RETRY = 3; // Ожидает повторной обработки - -public const MAX_PROCESSING_ATTEMPTS = 5; - -2.2 Relation с MarketplaceOrders (связка письмо↔заказ) -/** -* Связь с заказом маркетплейса по marketplace_order_id. -* Поле marketplace_order_id в emails хранит ID заказа из маркетплейса (например "123456"), -* которому соответствует marketplace_orders.marketplace_order_id. - */ - public function getOrder(): \yii\db\ActiveQuery - { - return $this->hasOne(MarketplaceOrders::class, ['marketplace_order_id' => 'marketplace_order_id']) - ->andWhere(['marketplace_id' => \yii_app\records\MarketplaceStore::FLOWWOW_WAREHOUSE_ID]); - } - -Это позволит: -- Из письма перейти к заказу: $email->order -- Из GridView показать ссылку на заказ - -2.3 Helper-методы - -- markAsProcessed(): bool — ставит STATUS_PROCESSED + processed_at -- markAsError(string $errorMessage): bool — ставит STATUS_ERROR + error_message + инкремент attempts -- markForRetry(string $reason): bool — ставит STATUS_RETRY + инкремент attempts -- isRetryAllowed(): bool — processing_attempts < MAX_PROCESSING_ATTEMPTS -- static findUnprocessed(): ActiveQuery — WHERE email_status IN (0, 3) AND processing_attempts < MAX -- static statusLabels(): array — массив текстовых меток статусов -- static subjectTypeLabels(): array — [1 => 'Новый заказ', 2 => 'Принят', 3 => 'Изменён', 4 => 'Отменён', 5 => 'Доставлен'] - -2.4 Обновить rules() и attributeLabels() - -Добавить правила валидации и метки для новых полей. - - --- -Шаг 3. Рефакторинг MarketplaceService - -3.1 Новый метод detectSubjectType -private static function detectSubjectType(string $subject): ?int - -Определяет тип письма по теме через SUBJECT_INDEX regex patterns. - -3.2 Изменение saveEmailIfNotExists (строка 2260) - -При создании нового письма дополнительно заполнять subject_type через detectSubjectType(). Остальная логика без изменений — метод по-прежнему возвращает объект если -создано, null если уже существует. - -3.3 Рефакторинг getFlowwowOrdersFromMail (строки 2198-2242) - -Ключевое изменение — логика цикла обработки каждого письма: -ДЛЯ КАЖДОГО ПИСЬМА ИЗ IMAP: -├── saveEmailIfNotExists() → регистрация -├── Если письмо новое (savedEmail != null): -│ └── emailRecord = savedEmail -├── Иначе: загружаем из БД: -│ └── emailRecord = MarketplaceFlowwowEmails::find()->where(...) -│ -├── Если emailRecord.email_status == STATUS_PROCESSED: -│ ├── Только ставим SEEN на IMAP (чтобы не читать повторно) -│ └── continue (пропускаем обработку) -│ -├── Определяем паттерн темы → subject_index -├── try: -│ ├── processMessage($message) → обработка заказа -│ ├── emailRecord->markAsProcessed() → статус ПОСЛЕ успешной обработки -│ ├── emailRecord->marketplace_order_id = key($order) -│ ├── imap_setflag_full(SEEN) → для ВСЕХ успешных, не только NEW -│ └── countProcessedMessages++ -├── catch (Throwable): -│ ├── Логирование ошибки -│ ├── Если isRetryAllowed() → markForRetry() -│ └── Иначе → markAsError() - -Что убираем: -- Строки 2203-2205: преждевременная установка email_status = 1 ДО обработки -- Строки 2218: условие if ($output > 0) для SEEN — теперь ставится безусловно - -3.4 Fix processFlowwowOrders (строка 2677) - -Добавить переменную $processingSuccess = false. Устанавливать в true при: -- Создании нового заказа (строка 2719, $marketplaceOrder->save()) -- Успешном обновлении статуса (строки 2749, 2783, 2798) - -Изменить return (строка 2808): -return $processingSuccess ? max($newOrdersCount, 1) : 0; - -Это обеспечит что $output > 0 для всех типов писем, а не только NEW. - -3.5 Новый метод processUnprocessedEmails -public static function processUnprocessedEmails(?callable $progressCallback = null): array - -Выбирает из БД все письма со статусом STATUS_NEW или STATUS_RETRY (через findUnprocessed()), у которых заполнен subject_type, и обрабатывает каждое через -processMessage(). - -Возвращает: ['processed' => int, 'failed' => int, 'total' => int] - -Фильтр andWhere(['not', ['subject_type' => null]]) гарантирует, что старые записи (до миграции) не будут затронуты. - - --- -Шаг 4. Консольная команда для retry - -Файл: erp24/commands/MarketplaceController.php - -Новый action: -php yii marketplace/retry-flowwow-emails - -Вызывает MarketplaceService::processUnprocessedEmails() с progress callback в консоль. Выводит итоговую статистику. - - --- -Шаг 5. Fix FlowwowController::actionCheckMail - -Файл: erp24/media/controllers/FlowwowController.php (строки 64-82) - -Текущий код несовместим: getFlowwowOrdersFromMail возвращает ['processed' => N, 'all' => M], а actionCheckMail вызывает count($messages) (вернёт 2) и -processMessages($messages) (передаст массив с ключами 'processed'/'all' вместо писем). - -Исправить: убрать вызов processMessages, использовать возвращённый массив напрямую. - - --- -Шаг 6. Обновить Search-модель (поиск писем по заказу) - -Файл: erp24/records/MarketplaceFlowwowEmailsSearch.php - -6.1 Новые правила фильтрации - -- В rules(): добавить subject_type, processing_attempts как integer; processed_at, error_message, marketplace_order_id как safe - -6.2 Поиск в search() -// Фильтрация по новым полям -$query->andFilterWhere([ -'subject_type' => $this->subject_type, -'processing_attempts' => $this->processing_attempts, -]); -$query->andFilterWhere(['ilike', 'error_message', $this->error_message]); - -// Поиск писем по ID заказа маркетплейса -$query->andFilterWhere(['ilike', 'marketplace_order_id', $this->marketplace_order_id]); - -Это позволит: -- Ввести номер заказа Flowwow в фильтр → увидеть все письма, связанные с этим заказом -- Фильтровать по типу письма (новый, принят, отменён и т.д.) -- Фильтровать по статусу обработки - - --- -Шаг 7. Обновить View (связка с заказами и поиск) - -Файл: erp24/views/marketplace-flowwow-emails/index.php - -7.1 Обновить статусы - -- Обновить отображение email_status — добавить статусы "Ошибка" (красный) и "Повтор" (синий) -- Заменить текстовый фильтр email_status на dropdown: - 'filter' => MarketplaceFlowwowEmails::statusLabels(), - -7.2 Добавить колонку "Тип письма" -[ -'attribute' => 'subject_type', -'value' => fn($model) => MarketplaceFlowwowEmails::subjectTypeLabels()[$model->subject_type] ?? '—', -'filter' => MarketplaceFlowwowEmails::subjectTypeLabels(), -], - -7.3 Добавить колонку "Заказ" со ссылкой -[ -'attribute' => 'marketplace_order_id', -'format' => 'raw', -'value' => function ($model) { -if (!$model->marketplace_order_id) { -return '—'; -} -$order = $model->order; -if ($order) { -return Html::a( -'№' . $model->marketplace_order_id, -['/marketplace-orders/view', 'id' => $order->id], -['class' => 'btn btn-xs btn-outline-primary', 'target' => '_blank'] -); -} -return $model->marketplace_order_id . ' (не найден)'; -}, -'filter' => Html::input('text', 'MarketplaceFlowwowEmailsSearch[marketplace_order_id]', -$searchModel->marketplace_order_id, ['class' => 'form-control', 'placeholder' => '№ заказа']), -], - -Это даёт: -- Поиск писем по заказу: ввести номер заказа в фильтр → увидеть ВСЕ связанные письма (создание, принятие, изменения, отмена, доставка) -- Клик по номеру заказа → переход на страницу заказа в ERP -- Фильтр по типу письма → например, увидеть все "отменённые" письма - -7.4 Добавить колонку "Попытки" -'processing_attempts', - -7.5 Eager loading для оптимизации - -В контроллере MarketplaceFlowwowEmailsController::actionIndex добавить: -$dataProvider->query->with(['order']); - - --- -Порядок реализации -1. Миграция БД (независимый) -2. Модель MarketplaceFlowwowEmails (зависит от 1) -3. MarketplaceFlowwowEmailsSearch (зависит от 2) -4. MarketplaceService::detectSubjectType (независимый) -5. MarketplaceService::saveEmailIfNotExists (зависит от 2, 4) -6. MarketplaceService::processFlowwowOrders (независимый fix) -7. MarketplaceService::getFlowwowOrdersFromMail (зависит от 5, 6) -8. MarketplaceService::processUnprocessedEmails (зависит от 2) -9. MarketplaceController::actionRetryFlowwowEmails (зависит от 8) -10. FlowwowController::actionCheckMail fix (зависит от 7) -11. MarketplaceFlowwowEmailsController (eager loading) -12. View index.php (зависит от 2, 3, 11) - - --- -Верификация - -1. Миграция: php yii migrate — проверить создание колонок и индексов -2. Регистрация: запустить php yii marketplace/get-flowwow-orders, проверить что новые письма получили email_status=0, subject_type заполнен -3. Обработка: проверить что после processMessage — email_status=1, processed_at заполнен, marketplace_order_id заполнен -4. Повторный запуск: запустить команду снова — уже обработанные письма получают SEEN и пропускаются -5. Retry: вручную UPDATE marketplace_flowwow_emails SET email_status=3 WHERE id=..., запустить php yii marketplace/retry-flowwow-emails — повторная обработка -6. SEEN для всех типов: проверить что SEEN ставится для APPROVED, CANCELLED, DELIVERED (ранее не ставился) -7. Поиск по заказу: в UI /marketplace-flowwow-emails/index ввести номер заказа Flowwow в фильтр → отображаются все связанные письма -8. Ссылка на заказ: кликнуть по номеру заказа в колонке → переход на /marketplace-orders/view?id=... -9. Фильтр по типу: выбрать "Отменён" в dropdown типа письма → только отменённые письма -- 2.39.5