feat: send one SMS from a person or lead and keep the dashboard inside the phone frame

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-13 21:05:17 +03:30
co-authored by Cursor
parent 13ee70764b
commit 42f0573a5f
13 changed files with 733 additions and 113 deletions
@@ -110,6 +110,15 @@ export class PeopleController {
return this.peopleService.birthdaysThisMonth(user);
}
@Post(':id/sms')
sms(
@CurrentUser() user: Actor,
@Param('id') id: string,
@Body() dto: BirthdaySmsDto,
) {
return this.peopleService.sendSms(user, id, dto.text);
}
@Post(':id/birthday-sms')
birthdaySms(
@CurrentUser() user: Actor,
+40 -2
View File
@@ -39,7 +39,8 @@ import { WebhooksService } from '../public-api/webhooks.service';
import { GamificationService } from '../gamification/gamification.service';
import { parseBirthDate } from '../common/excel';
import { SmsService } from '../common/sms.service';
import { fillBirthdaySmsText, selectMonthBirthdays } from './birthday-month';
import { selectMonthBirthdays } from './birthday-month';
import { renderSmsText } from '../common/sms-text';
import { normalizeTags } from '../common/tags';
import { buildSearchWhere, ci } from '../common/search';
import {
@@ -467,6 +468,7 @@ export class PeopleService implements OnModuleInit {
mobile: true,
phone: true,
birthDate: true,
company: { select: { name: true } },
},
});
const selected = selectMonthBirthdays(
@@ -484,12 +486,14 @@ export class PeopleService implements OnModuleInit {
: [],
),
);
const companyById = new Map(rows.map((row) => [row.id, row.company?.name ?? '']));
const toItem = (row: (typeof selected.items)[number]) => ({
id: row.id,
firstName: row.firstName,
lastName: row.lastName,
fullName: `${row.firstName} ${row.lastName}`.trim(),
mobile: row.mobile,
company: companyById.get(row.id) ?? '',
day: row.day,
});
return {
@@ -517,6 +521,7 @@ export class PeopleService implements OnModuleInit {
phone: true,
ownerId: true,
birthDate: true,
company: { select: { name: true } },
},
});
if (!person) throw new NotFoundException('مشتری پیدا نشد');
@@ -542,7 +547,40 @@ export class PeopleService implements OnModuleInit {
}
const mobile = person.mobile || person.phone;
if (!mobile) throw new BadRequestException('برای این مشتری شماره موبایل ثبت نشده است.');
const message = fillBirthdaySmsText(body, person.firstName, person.lastName);
const fullName = `${person.firstName} ${person.lastName}`.trim();
const message = renderSmsText(body, {
name: fullName,
phone: mobile,
title: fullName,
company: person.company?.name ?? '',
});
if (!message) throw new BadRequestException('متن پیامک خالی است.');
const result = await this.sms.sendSmsDetailed(mobile, message);
if (!result.ok) {
throw new ServiceUnavailableException(
result.reason === 'SMS not configured'
? 'ارسال پیامک روی سرور فعال نیست.'
: 'پیامک ارسال نشد. کمی بعد دوباره تلاش کنید.',
);
}
return { ok: true };
}
async sendSms(actor: Actor, personId: string, text: string) {
const body = text.trim();
if (!body || body.length > 500) {
throw new BadRequestException('متن پیامک را تا ۵۰۰ نویسه بنویسید.');
}
const person = await this.get(actor, personId);
const mobile = person.mobile || person.phone;
if (!mobile) throw new BadRequestException('برای این مشتری شماره موبایل ثبت نشده است.');
const message = renderSmsText(body, {
name: person.fullName,
phone: mobile,
title: person.fullName,
company: person.company?.name ?? '',
});
if (!message) throw new BadRequestException('متن پیامک خالی است.');
const result = await this.sms.sendSmsDetailed(mobile, message);
if (!result.ok) {
throw new ServiceUnavailableException(