count character

This commit is contained in:
2026-07-09 20:17:58 +03:30
parent aa2bbe3caf
commit ad9bca8e6c
4 changed files with 65 additions and 3 deletions
+8 -1
View File
@@ -9,6 +9,7 @@ import { CampaignTargetingService } from './campaign-targeting.service';
import { CampaignsService } from './campaigns.service';
import { CampaignTrigger } from './enums/campaign-trigger.enum';
import { CampaignType } from './enums/campaign-type.enum';
import { getCampaignTypeDefinition } from './constants/campaign-types.constants';
@Injectable()
export class CampaignAutomationService {
@@ -114,7 +115,13 @@ export class CampaignAutomationService {
}
try {
await this.salonSubscriptionsService.consumeSms(salonId, targets.length);
const definition = getCampaignTypeDefinition(type);
const smsParts = await this.campaignsService.calculateCampaignSmsParts(
definition.scenarioKey,
salon,
targets,
);
await this.salonSubscriptionsService.consumeSms(salonId, smsParts);
} catch (error) {
this.logger.warn(
`Skipping ${type} campaign for salon ${salonId}: ${(error as Error).message}`,
+35 -1
View File
@@ -12,6 +12,7 @@ import { Salon } from '../salons/entities/salon.entity';
import { SentMessage } from '../sent-messages/entities/sent-message.entity';
import { SentMessageStatus } from '../sent-messages/enums/sent-message-status.enum';
import { SmsDispatchService } from '../sms-settings/sms-dispatch.service';
import { calculateSmsParts } from '../sms/helpers/sms-segment.helper';
import { SmsService } from '../sms/sms.service';
import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions.service';
import { UserRole } from '../users/enums/user-role.enum';
@@ -133,7 +134,12 @@ export class CampaignsService {
);
}
await this.salonSubscriptionsService.consumeSms(salon.id, targets.length);
const smsParts = await this.calculateCampaignSmsParts(
definition.scenarioKey,
salon,
targets,
);
await this.salonSubscriptionsService.consumeSms(salon.id, smsParts);
const run = await this.runCampaignForSalon({
salon,
@@ -211,6 +217,34 @@ export class CampaignsService {
return targets.reduce((sum, target) => sum + target.estimatedRevenue, 0);
}
async calculateCampaignSmsParts(
scenarioKey: (typeof CAMPAIGN_TYPES)[number]['scenarioKey'],
salon: Salon,
targets: CampaignTargetCustomer[],
): Promise<number> {
let totalParts = 0;
for (const target of targets) {
const variables: Record<string, string> = {
customerName:
`${target.firstName} ${target.lastName}`.trim() || 'مشتری',
salonName: salon.name,
...target.meta,
};
const message = await this.smsDispatchService.resolveMessage(
scenarioKey,
variables,
);
if (message) {
totalParts += calculateSmsParts(message);
}
}
return totalParts;
}
async findRuns(
user: AuthUser,
query: FindCampaignRunsQueryDto,
+3 -1
View File
@@ -13,6 +13,7 @@ import { Salon } from '../salons/entities/salon.entity';
import { SentMessage } from '../sent-messages/entities/sent-message.entity';
import { SentMessageStatus } from '../sent-messages/enums/sent-message-status.enum';
import { SmsService } from '../sms/sms.service';
import { calculateSmsParts } from '../sms/helpers/sms-segment.helper';
import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions.service';
import { UserRole } from '../users/enums/user-role.enum';
import { UsersService } from '../users/users.service';
@@ -123,8 +124,9 @@ export class CustomersService {
): Promise<SentMessage> {
const salon = await this.resolveSalonForUser(user);
const detail = await this.findOneForSalon(id, user);
const smsParts = calculateSmsParts(dto.body);
await this.salonSubscriptionsService.consumeSms(salon.id, 1);
await this.salonSubscriptionsService.consumeSms(salon.id, smsParts);
const sent = await this.smsService.sendSms(detail.mobile, dto.body);
const status = sent ? SentMessageStatus.SENT : SentMessageStatus.FAILED;
+19
View File
@@ -0,0 +1,19 @@
const GSM_7_REGEX =
/^[\n\r !"#$%&'()*+,\-./0-9:;<=>?@A-Z\[\\\]^_`a-z{|}~€£¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ]*$/;
const ENGLISH_CHARS_PER_PART = 154;
const PERSIAN_CHARS_PER_PART = 64;
/** Returns how many SMS units a message consumes (GSM-7 or Unicode/Persian rules). */
export function calculateSmsParts(text: string): number {
const length = [...text].length;
if (length === 0) {
return 0;
}
if (GSM_7_REGEX.test(text)) {
return Math.ceil(length / ENGLISH_CHARS_PER_PART);
}
return Math.ceil(length / PERSIAN_CHARS_PER_PART);
}