diff --git a/docs/superpowers/specs/2026-10-17-outbound-sms-log-design.md b/docs/superpowers/specs/2026-10-17-outbound-sms-log-design.md new file mode 100644 index 0000000..55e90d7 --- /dev/null +++ b/docs/superpowers/specs/2026-10-17-outbound-sms-log-design.md @@ -0,0 +1,87 @@ +# لاگ یکپارچهٔ پیامک خروجی در دیالوگ ارسال + +تاریخ: ۲۰۲۶-۱۰-۱۷ +وضعیت: تأیید شده؛ آمادهٔ پلن پیاده‌سازی. + +## هدف + +در دیالوگ ارسال پیامک (لید و شخص)، لیست پیامک‌هایی که از این به بعد برای همان مخاطب رفته نمایش داده شود: دستی، کمپین گروهی، اتوماسیون. + +## تصمیم‌ها + +| موضوع | تصمیم | +| --- | --- | +| منبع حقیقت | جدول جدید `OutboundSmsLog` | +| تاریخچهٔ قبل از استقرار | مهم نیست؛ فقط از این به بعد | +| پوشش | manual + campaign + automation | +| UI | همان `SmsComposeModal` برای لید و شخص | + +## مدل داده + +`OutboundSmsLog`: + +- `id`, `organizationId` +- `personId` اختیاری، `leadId` اختیاری (حداقل یکی در عمل پر می‌شود) +- `mobile`, `body` (متن نهایی پس از توکن/لینک) +- `source`: `manual` | `campaign` | `automation` +- `sourceId` اختیاری (شناسهٔ کمپین / قانون اتوماسیون) +- `status`: `sent` | `failed` +- `error` اختیاری +- `actorUserId` اختیاری (برای دستی) +- `createdAt` +- ایندکس: `(organizationId, personId, createdAt)`, `(organizationId, leadId, createdAt)` + +## نقاط ثبت + +1. `PeopleService.sendSms` — source=`manual` +2. `LeadsService.sendSms` — source=`manual` (LeadEvent فعلی می‌تواند بماند) +3. `SmsCampaignsService.processCampaign` پس از هر تلاش ارسال — source=`campaign`, `sourceId`=campaignId +4. ارسال SMS در message-automations — source=`automation`, `sourceId`=ruleId، با `personId` وقتی موجود است + +ثبت نباید ارسال را بشکند: اگر لاگ fail شد، فقط log خطا؛ نتیجهٔ SMS حفظ شود. + +## API + +- `GET /people/:id/sms-history?limit=50` +- `GET /leads/:id/sms-history?limit=50` + +`OutboundSmsHistoryResponse`: + +```ts +{ + items: Array<{ + id: string; + body: string; + mobile: string; + source: 'manual' | 'campaign' | 'automation'; + sourceLabel: string; // دستی | کمپین | اتوماسیون + status: 'sent' | 'failed'; + createdAt: string; + }>; +} +``` + +مرتب‌سازی: جدیدترین اول. Scope دسترسی همان دسترسی به شخص/لید. + +## UI + +در `SmsComposeModal`: + +- prop اختیاری `historyEndpoint` +- با `open` لود تاریخچه +- بلوک «پیامک‌های قبلی» زیر پیش‌نمایش؛ اسکرول؛ خالی = «هنوز پیامکی ثبت نشده» +- پس از ارسال موفق، رفرش لیست (یا prepend محلی) + +`LeadDetailView` و `PersonProfileView` endpoint تاریخچه را پاس می‌دهند. + +## خارج از محدوده + +- بک‌فیل تاریخچهٔ قدیمی +- نمایش در صفحهٔ جدا / گزارش سازمانی کامل +- ویرایش یا حذف لاگ از UI + +## معیار پذیرش + +1. بعد از ارسال دستی به لید/شخص، همان متن در دیالوگ دیده شود. +2. پیامک کمپین و اتوماسیون (برای شخص) در تاریخچه ظاهر شود. +3. لید و شخص هر دو از همان مودال تاریخچه بگیرند.