feat: log outbound SMS and show history in compose modal

Persist manual, campaign, and automation SMS; expose history APIs for people and leads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-17 21:23:07 +03:30
co-authored by Cursor
parent 9325d879a5
commit 23f8da2c65
16 changed files with 457 additions and 3 deletions
@@ -132,6 +132,19 @@ export class PeopleController {
return this.peopleService.birthdaysThisMonth(user);
}
@Get(':id/sms-history')
smsHistory(
@CurrentUser() user: Actor,
@Param('id') id: string,
@Query('limit') limit?: string,
) {
return this.peopleService.listSmsHistory(
user,
id,
limit ? Number(limit) : 50,
);
}
@Post(':id/sms')
sms(
@CurrentUser() user: Actor,
+28
View File
@@ -20,6 +20,7 @@ import type {
import { FILTERED_IDS_MAX } from '@mento/shared';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { OutboundSmsLogService } from '../outbound-sms/outbound-sms-log.service';
import { CompaniesService } from './companies.service';
import { emptyToNull, normalizeEmail, normalizePhone } from './normalize';
import {
@@ -111,6 +112,7 @@ export class PeopleService implements OnModuleInit {
private readonly webhooks: WebhooksService,
private readonly game: GamificationService,
private readonly sms: SmsService,
private readonly outboundSms: OutboundSmsLogService,
) {}
private personListWhere(
@@ -633,6 +635,16 @@ export class PeopleService implements OnModuleInit {
});
if (!message) throw new BadRequestException('متن پیامک خالی است.');
const result = await this.sms.sendSmsDetailed(mobile, message);
await this.outboundSms.record({
organizationId: actor.organizationId,
personId: person.id,
mobile,
body: message,
source: 'manual',
status: result.ok ? 'sent' : 'failed',
error: result.ok ? null : result.reason,
actorUserId: actor.userId,
});
if (!result.ok) {
throw new ServiceUnavailableException(
result.reason === 'SMS not configured'
@@ -659,6 +671,16 @@ export class PeopleService implements OnModuleInit {
});
if (!message) throw new BadRequestException('متن پیامک خالی است.');
const result = await this.sms.sendSmsDetailed(mobile, message);
await this.outboundSms.record({
organizationId: actor.organizationId,
personId: person.id,
mobile,
body: message,
source: 'manual',
status: result.ok ? 'sent' : 'failed',
error: result.ok ? null : result.reason,
actorUserId: actor.userId,
});
if (!result.ok) {
throw new ServiceUnavailableException(
result.reason === 'SMS not configured'
@@ -669,6 +691,12 @@ export class PeopleService implements OnModuleInit {
return { ok: true };
}
listSmsHistory(actor: Actor, personId: string, limit?: number) {
return this.get(actor, personId).then(() =>
this.outboundSms.historyForPerson(actor.organizationId, personId, limit),
);
}
private toDto(row: PersonRow): PersonDto {
const previousFullName = `${row.previousFirstName ?? ''} ${row.previousLastName ?? ''}`.trim();
const importSource = row.importSource || 'manual';