feat: add per-org SMS wallet with adm panel and header credit
Bill all platform SMS (including OTP), allow negative balance with manual/auto cutoffs, and support own-panel bypass without wallet debit.
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { NotificationsController } from './notifications.controller';
|
||||
import { NotificationsService } from './notifications.service';
|
||||
import { SmsService } from '../common/sms.service';
|
||||
|
||||
@Module({
|
||||
controllers: [NotificationsController],
|
||||
providers: [NotificationsService, SmsService],
|
||||
providers: [NotificationsService],
|
||||
exports: [NotificationsService],
|
||||
})
|
||||
export class NotificationsModule {}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Cron } from '@nestjs/schedule';
|
||||
import { brand } from '@mento/shared';
|
||||
import type { NotificationDto, NotificationsListDto } from '@mento/shared';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { SmsService } from '../common/sms.service';
|
||||
import { SmsGatewayService } from '../sms-billing/sms-gateway.service';
|
||||
|
||||
/** یادآورهایی که بیش از این دیرتر پردازش شوند (مثلاً قطعی سرور) فقط اعلان میگیرند، نه پیامک. */
|
||||
const REMINDER_SMS_MAX_DELAY_MS = 6 * 60 * 60 * 1000;
|
||||
@@ -41,7 +41,7 @@ export class NotificationsService {
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly sms: SmsService,
|
||||
private readonly sms: SmsGatewayService,
|
||||
) {}
|
||||
|
||||
async list(
|
||||
@@ -249,7 +249,7 @@ export class NotificationsService {
|
||||
});
|
||||
}
|
||||
if (activity.reminderNotifySms && !stale) {
|
||||
await this.sendActivityReminderSms(mobile, {
|
||||
await this.sendActivityReminderSms(activity.organizationId, mobile, {
|
||||
title: activity.title,
|
||||
related,
|
||||
relatedMobile: activity.person?.mobile ?? null,
|
||||
@@ -295,6 +295,7 @@ export class NotificationsService {
|
||||
}
|
||||
|
||||
private async sendActivityReminderSms(
|
||||
organizationId: string,
|
||||
mobile: string | null | undefined,
|
||||
info: { title: string; related: string; relatedMobile: string | null; dueAt: Date | null },
|
||||
) {
|
||||
@@ -302,10 +303,6 @@ export class NotificationsService {
|
||||
this.logger.warn('Activity reminder SMS skipped: assignee has no mobile');
|
||||
return;
|
||||
}
|
||||
if (!this.sms.isConfigured()) {
|
||||
this.logger.warn('Activity reminder SMS skipped: SMS not configured');
|
||||
return;
|
||||
}
|
||||
const lines = [`${brand.reminderPrefixFa} ${info.title}`];
|
||||
if (info.related) {
|
||||
lines.push(
|
||||
@@ -314,7 +311,12 @@ export class NotificationsService {
|
||||
}
|
||||
if (info.dueAt) lines.push(`زمان: ${formatTehranDateTime(info.dueAt)}`);
|
||||
const text = lines.join('\n').slice(0, 300);
|
||||
const result = await this.sms.sendSmsDetailed(mobile, text);
|
||||
const result = await this.sms.send({
|
||||
organizationId,
|
||||
phone: mobile,
|
||||
text,
|
||||
source: 'activity_reminder',
|
||||
});
|
||||
if (!result.ok) {
|
||||
this.logger.warn(`Activity reminder SMS failed: ${result.reason ?? 'unknown'}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user