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>