Add optional SMS auto-block threshold below balance.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
IsString,
|
||||
Max,
|
||||
Min,
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
import { Type, Transform } from 'class-transformer';
|
||||
|
||||
@@ -19,6 +20,18 @@ export class UpdateSmsAccountDto {
|
||||
@IsBoolean()
|
||||
isBlocked?: boolean;
|
||||
|
||||
/** Null clears auto-block; omit leaves unchanged. UI sends tomans×10 as rials. */
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (value === null || value === '' || value === undefined) return value === undefined ? undefined : null;
|
||||
const n = Number(value);
|
||||
return Number.isFinite(n) ? Math.trunc(n) : value;
|
||||
})
|
||||
@ValidateIf((_, v) => v !== null && v !== undefined)
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
autoBlockBelowRials?: number | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
|
||||
+30
-4
@@ -146,6 +146,10 @@ export class SmsService {
|
||||
data: {
|
||||
pricePerPartRials: dto.pricePerPartRials ?? undefined,
|
||||
isBlocked: dto.isBlocked ?? undefined,
|
||||
autoBlockBelowRials:
|
||||
dto.autoBlockBelowRials === undefined
|
||||
? undefined
|
||||
: dto.autoBlockBelowRials,
|
||||
alertCriticalRials: dto.alertCriticalRials ?? undefined,
|
||||
alertWarningRials: dto.alertWarningRials ?? undefined,
|
||||
gatewayProvider: dto.gatewayProvider ?? undefined,
|
||||
@@ -254,21 +258,25 @@ export class SmsService {
|
||||
let reason: string | null = null;
|
||||
let allowed = true;
|
||||
|
||||
// Only the explicit block switch stops sending (parity with Hamkar/Khanegi panels).
|
||||
// Negative / low balance is allowed; UI can still show balanceLevel as warning.
|
||||
// Manual block switch, then optional auto-block threshold (empty = no auto block).
|
||||
// Alert SMS (isAlert) always bypasses both. Negative balance alone does not block.
|
||||
if (account.isBlocked && !isAlert) {
|
||||
allowed = false;
|
||||
reason = 'blocked';
|
||||
} else if (this.isAutoBlocked(account) && !isAlert) {
|
||||
allowed = false;
|
||||
reason = 'auto_blocked';
|
||||
} else if (!isAlert && account.balanceRials < estimatedCostRials) {
|
||||
reason = 'insufficient_balance';
|
||||
}
|
||||
|
||||
return {
|
||||
allowed,
|
||||
blocked: account.isBlocked,
|
||||
blocked: account.isBlocked || this.isAutoBlocked(account),
|
||||
reason,
|
||||
balanceRials: account.balanceRials,
|
||||
pricePerPartRials: account.pricePerPartRials,
|
||||
autoBlockBelowRials: account.autoBlockBelowRials,
|
||||
parts: safeParts,
|
||||
estimatedCostRials,
|
||||
balanceLevel,
|
||||
@@ -303,9 +311,16 @@ export class SmsService {
|
||||
});
|
||||
}
|
||||
|
||||
if (this.isAutoBlocked(account) && !isAlert) {
|
||||
throw new ForbiddenException({
|
||||
message: 'ارسال پیامک بهخاطر کمتر بودن موجودی از آستانه خودکار مسدود است',
|
||||
reason: 'auto_blocked',
|
||||
});
|
||||
}
|
||||
|
||||
const costRials = parts * account.pricePerPartRials;
|
||||
|
||||
// Allow negative wallet (same as product panels): only isBlocked stops send/consume.
|
||||
// Negative wallet still allowed unless manual/auto block applies.
|
||||
const nextBalance = account.balanceRials - costRials;
|
||||
|
||||
const updated = await tx.smsAccount.update({
|
||||
@@ -869,6 +884,16 @@ export class SmsService {
|
||||
return `${new Intl.NumberFormat('fa-IR').format(toman)} تومان`;
|
||||
}
|
||||
|
||||
private isAutoBlocked(account: {
|
||||
balanceRials: number;
|
||||
autoBlockBelowRials: number | null;
|
||||
}) {
|
||||
return (
|
||||
account.autoBlockBelowRials != null &&
|
||||
account.balanceRials < account.autoBlockBelowRials
|
||||
);
|
||||
}
|
||||
|
||||
private chargeInfoOf(account: SmsAccount) {
|
||||
return {
|
||||
card: account.chargeCard || '',
|
||||
@@ -884,6 +909,7 @@ export class SmsService {
|
||||
balanceRials: account.balanceRials,
|
||||
pricePerPartRials: account.pricePerPartRials,
|
||||
isBlocked: account.isBlocked,
|
||||
autoBlockBelowRials: account.autoBlockBelowRials,
|
||||
alertCriticalRials: account.alertCriticalRials,
|
||||
alertWarningRials: account.alertWarningRials,
|
||||
chargeCard: account.chargeCard,
|
||||
|
||||
Reference in New Issue
Block a user