Files
portal/app/resources/views/emails/csv_drift_alert.blade.php
T
Дмитрий 541f38d452
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
fix(поставщик): отсрочка добора + живой журнал вебхука — конец ложным «вебхук потерял N»
Повод — письмо тревоги 31.07.2026 05:30 МСК: сверка №1417 отрапортовала «потеряно 7».
Разбор на бою показал, что вебхук по тем же семи пришёл через 2,5 минуты (05:32:32 ×2 +
05:33:03 ×5) и получил «уже есть». Потери не было: поставщик кладёт строку в журнал
отданного РАНЬШЕ, чем шлёт вебхук, а наша сверка (каждые 30 мин) попадала в эту щель.
Цена ошибки не только ложная тревога: добранная карточка беднее живой (в журнале нет
tag/time/phones) — у 4 из 7 не определился регион, у сделок пустые регион и город.

1. Отсрочка добора (CsvReconcileJob::GRACE_MINUTES = 15). Недостача, увиденная впервые,
   уходит в карантин (Redis, карта vid => время первого обнаружения) и добирается только
   следующим прогоном, если провисела дольше отсрочки. Реальная потеря доезжает максимум
   через полчаса. Потеря карантина безопасна: в худшем случае добор на прогон позже.
   drift и «потеряно» в письме считаются ТОЛЬКО по просроченному; «в пути» — отдельно
   (новая колонка supplier_csv_reconcile_log.pending_count).

2. Журнал вебхука поставщика (новая таблица supplier_webhook_log). Прежний
   logSupplierWebhook писал в webhook_log, снесённую 24.05 вместе с legacy-каналом, и
   молча выходил по Schema::hasTable — журнала не было ВООБЩЕ. Из-за этого 70 отказов 404
   за 10 дней никто не видел; нашлись случайно в логе nginx. Пишем статус, адрес
   отправителя, запрошенный хост и отпечаток присланного ключа (первые 8 символов md5 —
   отвечает «наш ключ или чужой», секретом не является). Отказы дополнительно уровнем
   warning: на бою LOG_LEVEL=warning, info в журнал не попадает.

3. Текст письма-тревоги переписан: «потеря» = только то, что не дошло даже за отсрочку;
   «в пути» показывается отдельной строкой и потерей не считается.

4. В catch сверки — Log::error ПЕРВЫМ действием, до обращений к БД: при испорченной
   транзакции следующий запрос бросал своё исключение и настоящая причина терялась.

Проверено: тесты поставщика и вебхука 230/230 (в т.ч. 5 новых на карантин, поздний
вебхук, отчёт «в пути» и смоук шаблона письма), Larastan 0, Pint чисто. Прогон всей базы
3566/3575; 5 падений — чужие и до этих правок (замерено откатом файлов): биллинг на стыке
месяцев и маршруты телеграм-ветки.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:52:14 +03:00

23 lines
1.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head><meta charset="UTF-8"><title>Вебхук потерял номера добрали сверкой</title></head>
<body style="font-family: Arial, sans-serif;">
<h3>Вебхук поставщика потерял номера сверка их добрала</h3>
<p>Окно проверки: <strong>{{ $windowStart->format('d.m.Y H:i') }} {{ $windowEnd->format('d.m.Y H:i') }}</strong></p>
<ul>
<li>Поставщик отдал за окно: <strong>{{ $totalCsvRows }}</strong></li>
<li>Не дошло вебхуком и не дошло за отсрочку (считаем потерей): <strong>{{ $missingCount }}</strong></li>
<li>Из них заведено в базу сверкой: <strong>{{ $recoveredCount }}</strong></li>
<li>Ещё в пути ждём вебхук, потерей НЕ считаем: <strong>{{ $pendingCount }}</strong></li>
<li>Доля потерь: <strong>{{ number_format($driftRatio * 100, 2, ',', ' ') }}%</strong> (порог тревоги 5%)</li>
</ul>
<p style="color:#555">
«Потеря» здесь номер, который поставщик отдал, а вебхук не принёс даже спустя отсрочку.
Такие номера портал заводит сам из журнала отданного, но карточка выходит беднее живой:
в журнале нет региона и точного времени. Если потери повторяются смотреть журнал вебхука
(отказы = чужой пароль или старый адрес) и настройки интеграции у поставщика.
</p>
<p>Подробности прогона строка <code>supplier_csv_reconcile_log.id = {{ $reconcileLogId }}</code>.</p>
</body>
</html>