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:
2026-10-19 15:55:48 +03:30
parent f15ebb72ea
commit f5d68d83fc
42 changed files with 2073 additions and 121 deletions
+103
View File
@@ -3866,8 +3866,23 @@ export const MESSAGE_AUTOMATION_FIELDS = [
{ token: "{شرکت}", label: "نام شرکت" },
] as const;
/** First-part Persian SMS limit (legacy UI estimate). Prefer countSmsParts(). */
export const MESSAGE_AUTOMATION_SMS_SEGMENT = 70;
/** Hamkar-compatible Persian multipart limits: 70 / 64 / 67 */
export function countSmsParts(text: string): number {
const length = [...String(text ?? "").trim()].length;
if (length <= 0) return 0;
const first = 70;
const second = 64;
const rest = 67;
if (length <= first) return 1;
if (length <= first + second) return 2;
const thirdThreshold = first + second + rest;
if (length <= thirdThreshold) return 3;
return 3 + Math.ceil((length - thirdThreshold) / rest);
}
export const MESSAGE_AUTOMATION_CHANNEL_LABELS: Record<
MessageAutomationChannel,
string
@@ -5734,6 +5749,94 @@ export interface TreasuryDashboardDto {
upcomingPayables: FinancialDocumentDto[];
}
/* ───────── کیف‌پول پیامک (صورتحساب محیط) ───────── */
export type SmsBalanceLevel = "healthy" | "warning" | "critical" | "unknown";
export type SmsPanelMode = "platform" | "own";
export interface SmsWalletSummaryDto {
billingEnabled: boolean;
balanceRials: number;
pricePerPartRials: number;
isBlocked: boolean;
autoBlocked: boolean;
balanceLevel: SmsBalanceLevel;
panelMode: SmsPanelMode | string;
chargeInfo: SmsChargeInfoDto | null;
}
export interface SmsChargeInfoDto {
card: string;
iban: string;
accountHolder: string;
instruction: string;
}
export interface PlatformSmsTenantRowDto {
id: string;
name: string;
slug: string;
isActive: boolean;
isBlocked: boolean;
panelMode: string;
sms: {
balanceRials: number;
pricePerPartRials: number;
autoBlockBelowRials: number | null;
balanceLevel: SmsBalanceLevel;
billingEnabled: boolean;
};
}
export interface PlatformSmsOverviewDto {
organization: {
id: string;
name: string;
slug: string;
isActive: boolean;
};
account: {
id: string;
organizationId: string;
balanceRials: number;
pricePerPartRials: number;
isBlocked: boolean;
autoBlockBelowRials: number | null;
alertCriticalRials: number;
alertWarningRials: number;
panelMode: SmsPanelMode;
ownPanelType: string;
ownPanelUsername: string | null;
ownPanelFrom: string | null;
hasOwnPanelPassword: boolean;
balanceLevel: SmsBalanceLevel;
billingEnabled: boolean;
};
chargeInfo: SmsChargeInfoDto;
today: { messages: number; parts: number; amountRials: number };
month: { messages: number; parts: number; amountRials: number };
ledgers: Array<{
id: string;
type: string;
amountRials: number;
balanceAfterRials: number;
parts: number | null;
note: string | null;
createdAt: string;
}>;
sendLogs: Array<{
id: string;
receiver: string;
body: string;
parts: number;
totalAmountRials: number;
provider: string | null;
source: string | null;
isAlert: boolean;
createdAt: string;
}>;
}
/* ───────── باشگاه امتیازات (گیمیفیکیشن کارکنان) ───────── */
export * from "./gamification";