Commit Graph

2 Commits

Author SHA1 Message Date
Дмитрий 4c33323f0e phase2(notifications-stage6): low_balance + zero_balance + topup + invoice email/inapp
P0 этап 6 — 4 оставшихся email-события. Авто-план P0 (6 этапов) закрыт
полностью: все 8 schema-default событий имеют рабочую интеграцию
(new_lead/reminder/low_balance/zero_balance/topup_success/invoice_paid +
заглушки для new_device_login/marketing).

Backend:
- 4 новых Mailable: LowBalanceNotification (threshold), ZeroBalanceNotification,
  TopupSuccessNotification (amountRub, amountLeads?), InvoicePaidNotification
  (amountRub, invoiceNumber?, tariffName?).
- 4 blade-шаблона в emails/ (Forest-палитра, таблицы balance/amount/invoice).
- NotificationService +4 методов: notifyLowBalance / notifyZeroBalance /
  notifyTopupSuccess / notifyInvoicePaid. Все шлют email + inapp по prefs.

Интеграция в ProcessWebhookJob:
- chargeNewLead после lead_charge: notifyLowBalance при пересечении порога
  сверху-вниз (balance_after <= threshold AND (balance_after+1) > threshold).
  Иначе спам при каждом lead_charge при balance < threshold.
- logRejection(zero_balance): notifyZeroBalance ТОЛЬКО если в последний час
  не было другого RejectedDealsLog с тем же reason (anti-spam 1 email/час).
  Защита от self-just-inserted через id!= (timestamp-сравнение ненадёжно
  из-за PG microsecond precision).
- topup_success / invoice_paid — service-методы готовы, integration после
  появления endpoints для пополнения (ЮKassa-webhook) и оплаты тарифа.
- lowBalanceThreshold() читает system_settings.low_balance_threshold_leads
  (default 10, schema seed).

Pest +12 в BalanceNotificationsTest (359/359 за 41.37 сек, 1233 assertions):
- low_balance: пересечение порога / уже < threshold / > threshold /
  prefs.email=false (только inapp).
- zero_balance: первое отклонение / 2-е в час не дублирует / >1ч снова шлёт.
- topup_success / invoice_paid: notify создаёт email+inapp / prefs=email:false.
- balance events изолированы между tenants.

NewLeadNotificationTest: «balance=0 не шлёт» обновлён —
Mail::assertNotSent(NewLeadNotification) вместо Mail::assertNothingSent
(ZeroBalanceNotification теперь шлётся при balance=0 — новое поведение).

PHPStan baseline регенерирован.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:51:32 +03:00
Дмитрий a4601fe84b phase2(notifications-stage1): NotificationService + new_lead email (P0 этап 1)
Старт closing «Notification delivery» из карты P0. Этап 1/6 плана:
NotificationService + Mailable + интеграция в ProcessWebhookJob::chargeNewLead.

- App\Services\NotificationService — диспетчер 8 событий × 3 каналов
  (inapp/push/email) согласно schema.sql:699 users.notification_preferences.
  Этап 1 реализует только email-канал для new_lead.
- App\Mail\NewLeadNotification + emails/new_lead.blade.php — HTML-письмо
  в Forest-палитре с таблицей phone/contact_name/received_at/deal_id.
- ProcessWebhookJob::chargeNewLead — после ActivityLog вызывает
  notifyNewLead. Throwable от Mail::send проглатывается + Log::warning
  (отказ канала не должен валить транзакцию).
- Pest 11/11 в tests/Feature/Notifications/NewLeadNotificationTest.php:
  email=true получает / email=false не получает / schema-default не шлёт /
  inactive не получает / soft-deleted не получает / другой тенант не
  получает / Биз-19 дубль не дублирует / повторный vid не дублирует /
  balance=0 не шлёт / subject содержит project_name.
- IDE-helper регенерирован (4 модели получили @mixin docblocks).
- PHPStan baseline регенерирован (138 ignore.unmatched схлопнулись).

Pest 280/280 за 31.27 сек (+11 от 269, 1029 assertions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:03:43 +03:00