count character
This commit is contained in:
@@ -9,6 +9,7 @@ import { CampaignTargetingService } from './campaign-targeting.service';
|
|||||||
import { CampaignsService } from './campaigns.service';
|
import { CampaignsService } from './campaigns.service';
|
||||||
import { CampaignTrigger } from './enums/campaign-trigger.enum';
|
import { CampaignTrigger } from './enums/campaign-trigger.enum';
|
||||||
import { CampaignType } from './enums/campaign-type.enum';
|
import { CampaignType } from './enums/campaign-type.enum';
|
||||||
|
import { getCampaignTypeDefinition } from './constants/campaign-types.constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CampaignAutomationService {
|
export class CampaignAutomationService {
|
||||||
@@ -114,7 +115,13 @@ export class CampaignAutomationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Skipping ${type} campaign for salon ${salonId}: ${(error as Error).message}`,
|
`Skipping ${type} campaign for salon ${salonId}: ${(error as Error).message}`,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { Salon } from '../salons/entities/salon.entity';
|
|||||||
import { SentMessage } from '../sent-messages/entities/sent-message.entity';
|
import { SentMessage } from '../sent-messages/entities/sent-message.entity';
|
||||||
import { SentMessageStatus } from '../sent-messages/enums/sent-message-status.enum';
|
import { SentMessageStatus } from '../sent-messages/enums/sent-message-status.enum';
|
||||||
import { SmsDispatchService } from '../sms-settings/sms-dispatch.service';
|
import { SmsDispatchService } from '../sms-settings/sms-dispatch.service';
|
||||||
|
import { calculateSmsParts } from '../sms/helpers/sms-segment.helper';
|
||||||
import { SmsService } from '../sms/sms.service';
|
import { SmsService } from '../sms/sms.service';
|
||||||
import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions.service';
|
import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions.service';
|
||||||
import { UserRole } from '../users/enums/user-role.enum';
|
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({
|
const run = await this.runCampaignForSalon({
|
||||||
salon,
|
salon,
|
||||||
@@ -211,6 +217,34 @@ export class CampaignsService {
|
|||||||
return targets.reduce((sum, target) => sum + target.estimatedRevenue, 0);
|
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(
|
async findRuns(
|
||||||
user: AuthUser,
|
user: AuthUser,
|
||||||
query: FindCampaignRunsQueryDto,
|
query: FindCampaignRunsQueryDto,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Salon } from '../salons/entities/salon.entity';
|
|||||||
import { SentMessage } from '../sent-messages/entities/sent-message.entity';
|
import { SentMessage } from '../sent-messages/entities/sent-message.entity';
|
||||||
import { SentMessageStatus } from '../sent-messages/enums/sent-message-status.enum';
|
import { SentMessageStatus } from '../sent-messages/enums/sent-message-status.enum';
|
||||||
import { SmsService } from '../sms/sms.service';
|
import { SmsService } from '../sms/sms.service';
|
||||||
|
import { calculateSmsParts } from '../sms/helpers/sms-segment.helper';
|
||||||
import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions.service';
|
import { SalonSubscriptionsService } from '../subscriptions/salon-subscriptions.service';
|
||||||
import { UserRole } from '../users/enums/user-role.enum';
|
import { UserRole } from '../users/enums/user-role.enum';
|
||||||
import { UsersService } from '../users/users.service';
|
import { UsersService } from '../users/users.service';
|
||||||
@@ -123,8 +124,9 @@ export class CustomersService {
|
|||||||
): Promise<SentMessage> {
|
): Promise<SentMessage> {
|
||||||
const salon = await this.resolveSalonForUser(user);
|
const salon = await this.resolveSalonForUser(user);
|
||||||
const detail = await this.findOneForSalon(id, 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 sent = await this.smsService.sendSms(detail.mobile, dto.body);
|
||||||
const status = sent ? SentMessageStatus.SENT : SentMessageStatus.FAILED;
|
const status = sent ? SentMessageStatus.SENT : SentMessageStatus.FAILED;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user