Redesign of the SMS automation page
This commit is contained in:
@@ -46,9 +46,16 @@ function testCancelMap() {
|
||||
|
||||
function testTemplate() {
|
||||
assert(
|
||||
applyMessageTemplate('سلام {title}', 'فاکتور ۱۲') === 'سلام فاکتور ۱۲',
|
||||
applyMessageTemplate('سلام {title}', { title: 'فاکتور ۱۲' }) === 'سلام فاکتور ۱۲',
|
||||
'title var',
|
||||
);
|
||||
assert(
|
||||
applyMessageTemplate('سلام {نام} {شماره}', {
|
||||
name: 'علی',
|
||||
phone: '0912',
|
||||
}) === 'سلام علی 0912',
|
||||
'name phone vars',
|
||||
);
|
||||
assert(clipSms('الف'.repeat(200)).length === 160, 'sms clip');
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,11 @@ export class CreateMessageAutomationRuleDto {
|
||||
@IsIn(CHANNELS)
|
||||
channel?: MessageAutomationChannel;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
description?: string | null;
|
||||
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@MaxLength(120)
|
||||
@@ -135,6 +140,11 @@ export class UpdateMessageAutomationRuleDto {
|
||||
@IsIn(CHANNELS)
|
||||
channel?: MessageAutomationChannel;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
description?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
|
||||
@@ -63,8 +63,32 @@ export function buildSendAts(
|
||||
return times;
|
||||
}
|
||||
|
||||
export function applyMessageTemplate(text: string, title: string): string {
|
||||
return text.replace(/\{title\}/g, title).replace(/\{عنوان\}/g, title);
|
||||
export function applyMessageTemplate(
|
||||
text: string,
|
||||
vars: {
|
||||
title?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
date?: string;
|
||||
company?: string;
|
||||
},
|
||||
): string {
|
||||
const map: Record<string, string> = {
|
||||
title: vars.title ?? '',
|
||||
عنوان: vars.title ?? '',
|
||||
نام: vars.name ?? '',
|
||||
name: vars.name ?? '',
|
||||
شماره: vars.phone ?? '',
|
||||
phone: vars.phone ?? '',
|
||||
تاریخ: vars.date ?? '',
|
||||
date: vars.date ?? '',
|
||||
شرکت: vars.company ?? '',
|
||||
company: vars.company ?? '',
|
||||
};
|
||||
return text.replace(/\{([^}]+)\}/g, (full, key: string) => {
|
||||
const next = map[key.trim()];
|
||||
return next === undefined ? full : next;
|
||||
});
|
||||
}
|
||||
|
||||
export function clipSms(text: string): string {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { SmsService } from '../common/sms.service';
|
||||
import { dateToJalali, jalaliMonthName } from '../common/jalali';
|
||||
import {
|
||||
applyMessageTemplate,
|
||||
buildSendAts,
|
||||
@@ -97,6 +98,9 @@ export class MessageAutomationsService {
|
||||
: {}),
|
||||
...(dto.channel !== undefined ? { channel: dto.channel } : {}),
|
||||
...(dto.title !== undefined ? { title: dto.title.trim() } : {}),
|
||||
...(dto.description !== undefined
|
||||
? { description: dto.description?.trim() || null }
|
||||
: {}),
|
||||
...(dto.body !== undefined ? { body: dto.body.trim() } : {}),
|
||||
},
|
||||
});
|
||||
@@ -328,11 +332,33 @@ export class MessageAutomationsService {
|
||||
notifyTarget: string;
|
||||
};
|
||||
}) {
|
||||
const title = applyMessageTemplate(job.rule.title, job.entityTitle).slice(
|
||||
0,
|
||||
200,
|
||||
);
|
||||
const body = applyMessageTemplate(job.rule.body, job.entityTitle);
|
||||
const person = job.personId
|
||||
? await this.prisma.person.findFirst({
|
||||
where: {
|
||||
id: job.personId,
|
||||
organizationId: job.organizationId,
|
||||
},
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
mobile: true,
|
||||
phone: true,
|
||||
company: { select: { name: true } },
|
||||
},
|
||||
})
|
||||
: null;
|
||||
const personName = person
|
||||
? [person.firstName, person.lastName].filter(Boolean).join(' ').trim()
|
||||
: '';
|
||||
const vars = {
|
||||
title: job.entityTitle,
|
||||
name: personName || job.entityTitle,
|
||||
phone: person?.mobile || person?.phone || '',
|
||||
date: formatJalaliDayFa(new Date()),
|
||||
company: person?.company?.name ?? '',
|
||||
};
|
||||
const title = applyMessageTemplate(job.rule.title, vars).slice(0, 200);
|
||||
const body = applyMessageTemplate(job.rule.body, vars);
|
||||
const smsText = clipSms(body || title);
|
||||
const wantApp = job.rule.channel === 'app' || job.rule.channel === 'both';
|
||||
const wantSms = job.rule.channel === 'sms' || job.rule.channel === 'both';
|
||||
@@ -366,15 +392,6 @@ export class MessageAutomationsService {
|
||||
}
|
||||
|
||||
if (targetPerson && wantSms) {
|
||||
const person = job.personId
|
||||
? await this.prisma.person.findFirst({
|
||||
where: {
|
||||
id: job.personId,
|
||||
organizationId: job.organizationId,
|
||||
},
|
||||
select: { mobile: true, phone: true },
|
||||
})
|
||||
: null;
|
||||
await this.sendSms(person?.mobile || person?.phone, smsText);
|
||||
}
|
||||
}
|
||||
@@ -413,6 +430,7 @@ export class MessageAutomationsService {
|
||||
notifyTarget: dto.notifyTarget ?? 'person',
|
||||
channel: dto.channel ?? 'sms',
|
||||
title: dto.title.trim(),
|
||||
description: dto.description?.trim() || null,
|
||||
body: dto.body.trim(),
|
||||
};
|
||||
}
|
||||
@@ -430,6 +448,7 @@ export class MessageAutomationsService {
|
||||
notifyTarget: string;
|
||||
channel: string;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
body: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -447,6 +466,7 @@ export class MessageAutomationsService {
|
||||
notifyTarget: row.notifyTarget as MessageAutomationRuleDto['notifyTarget'],
|
||||
channel: row.channel as MessageAutomationRuleDto['channel'],
|
||||
title: row.title,
|
||||
description: row.description ?? null,
|
||||
body: row.body,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
@@ -454,6 +474,15 @@ export class MessageAutomationsService {
|
||||
}
|
||||
}
|
||||
|
||||
function toFaDigits(value: number | string) {
|
||||
return String(value).replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[Number(d)]!);
|
||||
}
|
||||
|
||||
function formatJalaliDayFa(d: Date) {
|
||||
const { jy, jm, jd } = dateToJalali(d);
|
||||
return `${toFaDigits(jd)} ${jalaliMonthName(jm)} ${toFaDigits(jy)}`;
|
||||
}
|
||||
|
||||
function entityLink(entityType: string, entityId: string) {
|
||||
if (entityType === 'deal') return `/deals/${entityId}`;
|
||||
if (entityType === 'ticket') return `/tickets/${entityId}`;
|
||||
|
||||
Reference in New Issue
Block a user