Add production-ready SuperAdmin API features for tenants, SMS, servers, and plugins.
Includes mobile auth, get_user/get_version, SMS wallet parity endpoints, appearance backfill, and related Prisma migrations. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,13 @@
|
||||
import { IsBoolean, IsInt, IsObject, IsOptional, IsString, Min } from 'class-validator';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class UpdateSmsAccountDto {
|
||||
@IsOptional()
|
||||
@@ -61,3 +70,48 @@ export class ChargeSmsWalletDto {
|
||||
@IsString()
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export class UpdateSmsChargeInfoDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
chargeCard?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
chargeIban?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
chargeHolder?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
chargeInstruction?: string | null;
|
||||
}
|
||||
|
||||
export class SmsTableQueryDto {
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
pageSize?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
fromDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
toDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import { ChargeSmsWalletDto, UpdateSmsAccountDto } from './dto/sms-account.dto';
|
||||
import {
|
||||
ChargeSmsWalletDto,
|
||||
SmsTableQueryDto,
|
||||
UpdateSmsAccountDto,
|
||||
UpdateSmsChargeInfoDto,
|
||||
} from './dto/sms-account.dto';
|
||||
import { SmsService } from './sms.service';
|
||||
|
||||
@Controller('sms')
|
||||
@@ -24,6 +39,48 @@ export class SmsController {
|
||||
return this.smsService.getAccount(id);
|
||||
}
|
||||
|
||||
@Get('tenants/:id/charge-info')
|
||||
getChargeInfo(@Param('id') id: string) {
|
||||
return this.smsService.getChargeInfo(id);
|
||||
}
|
||||
|
||||
@Put('tenants/:id/charge-info')
|
||||
updateChargeInfo(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateSmsChargeInfoDto,
|
||||
@CurrentUser() user: { id: string },
|
||||
) {
|
||||
return this.smsService.updateChargeInfo(id, dto, user.id);
|
||||
}
|
||||
|
||||
@Get('tenants/:id/report')
|
||||
report(
|
||||
@Param('id') id: string,
|
||||
@Query('fromDate') fromDate?: string,
|
||||
@Query('toDate') toDate?: string,
|
||||
) {
|
||||
return this.smsService.getPeriodReport(id, fromDate, toDate);
|
||||
}
|
||||
|
||||
@Get('tenants/:id/report/charges')
|
||||
chargeReport(
|
||||
@Param('id') id: string,
|
||||
@Query('fromDate') fromDate?: string,
|
||||
@Query('toDate') toDate?: string,
|
||||
) {
|
||||
return this.smsService.getChargeReport(id, fromDate, toDate);
|
||||
}
|
||||
|
||||
@Post('tenants/:id/logs/table')
|
||||
logsTable(@Param('id') id: string, @Body() dto: SmsTableQueryDto) {
|
||||
return this.smsService.getLogsTable(id, dto);
|
||||
}
|
||||
|
||||
@Post('tenants/:id/ledger/table')
|
||||
ledgerTable(@Param('id') id: string, @Body() dto: SmsTableQueryDto) {
|
||||
return this.smsService.getLedgerTable(id, dto);
|
||||
}
|
||||
|
||||
@Patch('tenants/:id')
|
||||
updateAccount(
|
||||
@Param('id') id: string,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { SmsController } from './sms.controller';
|
||||
import { SmsService } from './sms.service';
|
||||
|
||||
@Module({
|
||||
imports: [NotificationsModule],
|
||||
controllers: [SmsController],
|
||||
providers: [SmsService],
|
||||
exports: [SmsService],
|
||||
|
||||
+343
-34
@@ -3,10 +3,17 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { Prisma, SmsAccount } from '@prisma/client';
|
||||
import { Prisma, SmsAccount, SmsLedgerType } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { ChargeSmsWalletDto, UpdateSmsAccountDto } from './dto/sms-account.dto';
|
||||
import { AdminNotifyService } from '../notifications/admin-notify.service';
|
||||
import { iranYmd } from '../common/iran-time.util';
|
||||
import {
|
||||
ChargeSmsWalletDto,
|
||||
SmsTableQueryDto,
|
||||
UpdateSmsAccountDto,
|
||||
UpdateSmsChargeInfoDto,
|
||||
} from './dto/sms-account.dto';
|
||||
|
||||
type BalanceLevel = 'healthy' | 'warning' | 'critical' | 'unknown';
|
||||
|
||||
@@ -15,6 +22,7 @@ export class SmsService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly audit: AuditService,
|
||||
private readonly notify: AdminNotifyService,
|
||||
) {}
|
||||
|
||||
async listTenants() {
|
||||
@@ -38,7 +46,7 @@ export class SmsService {
|
||||
avatarUrl: tenant.avatarUrl,
|
||||
isActive: tenant.isActive,
|
||||
isBlocked: account.isBlocked,
|
||||
sms: this.toSummary(account),
|
||||
sms: this.toSummaryQuick(account),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -69,35 +77,54 @@ export class SmsService {
|
||||
domain: tenant.domain,
|
||||
avatarUrl: tenant.avatarUrl,
|
||||
},
|
||||
account: {
|
||||
id: account.id,
|
||||
balanceRials: account.balanceRials,
|
||||
pricePerPartRials: account.pricePerPartRials,
|
||||
isBlocked: account.isBlocked,
|
||||
alertCriticalRials: account.alertCriticalRials,
|
||||
alertWarningRials: account.alertWarningRials,
|
||||
gatewayProvider: account.gatewayProvider,
|
||||
melipayamakUser: account.melipayamakUser,
|
||||
melipayamakPassword: account.melipayamakPassword,
|
||||
melipayamakFrom: account.melipayamakFrom,
|
||||
panelUsername: account.panelUsername,
|
||||
panelPassword: account.panelPassword,
|
||||
panelFrom: account.panelFrom,
|
||||
extraSettings: (account.extraSettings as Record<string, string>) || {},
|
||||
todayMessages: account.todayMessages,
|
||||
todayParts: account.todayParts,
|
||||
todayCostRials: account.todayCostRials,
|
||||
monthMessages: account.monthMessages,
|
||||
monthParts: account.monthParts,
|
||||
monthCostRials: account.monthCostRials,
|
||||
},
|
||||
summary: this.toSummary(account),
|
||||
account: this.publicAccount(account),
|
||||
chargeInfo: this.chargeInfoOf(account),
|
||||
summary: await this.toSummaryFromLogs(account),
|
||||
ledgers,
|
||||
safeNote:
|
||||
'این پنل متعلق به سوپرادمین است و فعلاً پنل پیامکی داخل همکار/خانگی را تغییر نمیدهد.',
|
||||
};
|
||||
}
|
||||
|
||||
async getChargeInfo(tenantId: string) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
return this.chargeInfoOf(account);
|
||||
}
|
||||
|
||||
async updateChargeInfo(
|
||||
tenantId: string,
|
||||
dto: UpdateSmsChargeInfoDto,
|
||||
userId?: string,
|
||||
) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const before = this.chargeInfoOf(account);
|
||||
const updated = await this.prisma.smsAccount.update({
|
||||
where: { id: account.id },
|
||||
data: {
|
||||
chargeCard: dto.chargeCard === undefined ? undefined : dto.chargeCard || null,
|
||||
chargeIban: dto.chargeIban === undefined ? undefined : dto.chargeIban || null,
|
||||
chargeHolder:
|
||||
dto.chargeHolder === undefined ? undefined : dto.chargeHolder || null,
|
||||
chargeInstruction:
|
||||
dto.chargeInstruction === undefined
|
||||
? undefined
|
||||
: dto.chargeInstruction || null,
|
||||
},
|
||||
});
|
||||
|
||||
await this.audit.log({
|
||||
action: 'sms.charge_info.update',
|
||||
entityType: 'sms_account',
|
||||
entityId: updated.id,
|
||||
tenantId,
|
||||
userId,
|
||||
before: before as unknown as Prisma.InputJsonValue,
|
||||
after: this.chargeInfoOf(updated) as unknown as Prisma.InputJsonValue,
|
||||
});
|
||||
|
||||
return this.chargeInfoOf(updated);
|
||||
}
|
||||
|
||||
async updateAccount(tenantId: string, dto: UpdateSmsAccountDto, userId?: string) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const before = { ...account };
|
||||
@@ -141,6 +168,15 @@ export class SmsService {
|
||||
after: this.publicAccount(updated) as unknown as Prisma.InputJsonValue,
|
||||
});
|
||||
|
||||
if (before.isBlocked !== updated.isBlocked) {
|
||||
const tenant = await this.requireTenant(tenantId);
|
||||
this.notify.notifyAdmins(
|
||||
updated.isBlocked
|
||||
? `سوپرادمین: ارسال SMS نسخه «${tenant.nameFa}» مسدود شد.`
|
||||
: `سوپرادمین: مسدودسازی SMS نسخه «${tenant.nameFa}» برداشته شد.`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.getAccount(tenantId);
|
||||
}
|
||||
|
||||
@@ -155,7 +191,7 @@ export class SmsService {
|
||||
throw new BadRequestException('موجودی نمیتواند منفی شود');
|
||||
}
|
||||
|
||||
const type = dto.amountRials > 0 ? 'charge' : 'adjust';
|
||||
const type: SmsLedgerType = dto.amountRials > 0 ? 'charge' : 'adjust';
|
||||
|
||||
const [updated] = await this.prisma.$transaction([
|
||||
this.prisma.smsAccount.update({
|
||||
@@ -167,6 +203,7 @@ export class SmsService {
|
||||
smsAccountId: account.id,
|
||||
type,
|
||||
amountRials: dto.amountRials,
|
||||
balanceAfterRials: nextBalance,
|
||||
note: dto.note || (dto.amountRials > 0 ? 'شارژ کیف پول' : 'اصلاح موجودی'),
|
||||
createdById: userId,
|
||||
},
|
||||
@@ -186,6 +223,12 @@ export class SmsService {
|
||||
} as unknown as Prisma.InputJsonValue,
|
||||
});
|
||||
|
||||
const tenant = await this.requireTenant(tenantId);
|
||||
const amountLabel = dto.amountRials.toLocaleString('fa-IR');
|
||||
this.notify.notifyAdmins(
|
||||
`سوپرادمین: کیف پول SMS «${tenant.nameFa}» ${dto.amountRials > 0 ? 'شارژ' : 'اصلاح'} شد (${amountLabel} ریال). موجودی: ${updated.balanceRials.toLocaleString('fa-IR')}`,
|
||||
);
|
||||
|
||||
return this.getAccount(tenantId);
|
||||
}
|
||||
|
||||
@@ -193,6 +236,187 @@ export class SmsService {
|
||||
return this.updateAccount(tenantId, { isBlocked }, userId);
|
||||
}
|
||||
|
||||
async getPeriodReport(tenantId: string, fromDate?: string, toDate?: string) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const range = this.resolveDateRange(fromDate, toDate, true);
|
||||
|
||||
const logs = await this.prisma.smsSendLog.findMany({
|
||||
where: {
|
||||
smsAccountId: account.id,
|
||||
createdAt: { gte: range.from, lte: range.to },
|
||||
},
|
||||
select: {
|
||||
parts: true,
|
||||
totalAmountRials: true,
|
||||
provider: true,
|
||||
},
|
||||
});
|
||||
|
||||
const byProviderMap = new Map<
|
||||
string,
|
||||
{ provider: string; messages: number; parts: number; amount: number }
|
||||
>();
|
||||
let messages = 0;
|
||||
let parts = 0;
|
||||
let amount = 0;
|
||||
|
||||
for (const row of logs) {
|
||||
messages += 1;
|
||||
parts += row.parts;
|
||||
amount += row.totalAmountRials;
|
||||
const key = row.provider || 'نامشخص';
|
||||
const cur = byProviderMap.get(key) || {
|
||||
provider: key,
|
||||
messages: 0,
|
||||
parts: 0,
|
||||
amount: 0,
|
||||
};
|
||||
cur.messages += 1;
|
||||
cur.parts += row.parts;
|
||||
cur.amount += row.totalAmountRials;
|
||||
byProviderMap.set(key, cur);
|
||||
}
|
||||
|
||||
return {
|
||||
fromDate: iranYmd(range.from),
|
||||
toDate: iranYmd(range.to),
|
||||
balance: account.balanceRials,
|
||||
summary: { messages, parts, amount },
|
||||
byProvider: [...byProviderMap.values()].sort((a, b) => b.amount - a.amount),
|
||||
};
|
||||
}
|
||||
|
||||
async getChargeReport(tenantId: string, fromDate?: string, toDate?: string) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const range = this.resolveDateRange(fromDate, toDate, false);
|
||||
|
||||
const where: Prisma.SmsLedgerWhereInput = {
|
||||
smsAccountId: account.id,
|
||||
type: 'charge',
|
||||
};
|
||||
if (range) {
|
||||
where.createdAt = { gte: range.from, lte: range.to };
|
||||
}
|
||||
|
||||
const items = await this.prisma.smsLedger.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 200,
|
||||
});
|
||||
|
||||
return {
|
||||
fromDate: range ? iranYmd(range.from) : null,
|
||||
toDate: range ? iranYmd(range.to) : null,
|
||||
summary: {
|
||||
charges: items.length,
|
||||
amount: items.reduce((sum, row) => sum + row.amountRials, 0),
|
||||
},
|
||||
items: items.map((row) => ({
|
||||
id: row.id,
|
||||
amount: row.amountRials,
|
||||
balanceAfter: row.balanceAfterRials,
|
||||
note: row.note,
|
||||
createdBy: row.createdById,
|
||||
createdAt: row.createdAt,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async getLogsTable(tenantId: string, dto: SmsTableQueryDto) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const page = dto.page || 1;
|
||||
const pageSize = dto.pageSize || 20;
|
||||
const range = this.resolveDateRange(dto.fromDate, dto.toDate, false);
|
||||
|
||||
const where: Prisma.SmsSendLogWhereInput = {
|
||||
smsAccountId: account.id,
|
||||
};
|
||||
if (range) {
|
||||
where.createdAt = { gte: range.from, lte: range.to };
|
||||
}
|
||||
if (dto.search?.trim()) {
|
||||
const q = dto.search.trim();
|
||||
where.OR = [
|
||||
{ receiver: { contains: q } },
|
||||
{ body: { contains: q } },
|
||||
{ provider: { contains: q } },
|
||||
{ source: { contains: q } },
|
||||
];
|
||||
}
|
||||
|
||||
const [total, rows] = await this.prisma.$transaction([
|
||||
this.prisma.smsSendLog.count({ where }),
|
||||
this.prisma.smsSendLog.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
rows: rows.map((row) => ({
|
||||
id: row.id,
|
||||
receiver: row.receiver,
|
||||
body: row.body,
|
||||
parts: row.parts,
|
||||
pricePerPart: row.pricePerPartRials,
|
||||
totalAmount: row.totalAmountRials,
|
||||
provider: row.provider,
|
||||
source: row.source,
|
||||
isAlert: row.isAlert,
|
||||
createdAt: row.createdAt,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async getLedgerTable(tenantId: string, dto: SmsTableQueryDto) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const page = dto.page || 1;
|
||||
const pageSize = dto.pageSize || 20;
|
||||
const range = this.resolveDateRange(dto.fromDate, dto.toDate, false);
|
||||
|
||||
const where: Prisma.SmsLedgerWhereInput = {
|
||||
smsAccountId: account.id,
|
||||
};
|
||||
if (range) {
|
||||
where.createdAt = { gte: range.from, lte: range.to };
|
||||
}
|
||||
if (dto.search?.trim()) {
|
||||
where.note = { contains: dto.search.trim() };
|
||||
}
|
||||
|
||||
const [total, rows] = await this.prisma.$transaction([
|
||||
this.prisma.smsLedger.count({ where }),
|
||||
this.prisma.smsLedger.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
rows: rows.map((row) => ({
|
||||
id: row.id,
|
||||
type: row.type,
|
||||
amount: row.amountRials,
|
||||
balanceAfter: row.balanceAfterRials,
|
||||
pricePerPart: null as number | null,
|
||||
parts: row.parts,
|
||||
note: row.note,
|
||||
createdBy: row.createdById,
|
||||
createdAt: row.createdAt,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private async ensureAccountsForAllTenants() {
|
||||
const tenants = await this.prisma.tenant.findMany({
|
||||
where: { smsAccount: null },
|
||||
@@ -208,7 +432,6 @@ export class SmsService {
|
||||
const existing = await this.prisma.smsAccount.findUnique({ where: { tenantId } });
|
||||
if (existing) return existing;
|
||||
|
||||
// Seed varied demo balances so color cards are visible until real charges exist
|
||||
const demoBalances = [250000, 85000, 7500, 150000, 12000, 3000, 500000, 45000];
|
||||
const hash = [...tenantId].reduce((sum, ch) => sum + ch.charCodeAt(0), 0);
|
||||
const balanceRials = demoBalances[hash % demoBalances.length];
|
||||
@@ -224,7 +447,7 @@ export class SmsService {
|
||||
});
|
||||
}
|
||||
|
||||
private toSummary(account: SmsAccount) {
|
||||
private toSummaryQuick(account: SmsAccount) {
|
||||
const level = this.balanceLevel(
|
||||
account.balanceRials,
|
||||
account.alertCriticalRials,
|
||||
@@ -247,6 +470,47 @@ export class SmsService {
|
||||
};
|
||||
}
|
||||
|
||||
private async toSummaryFromLogs(account: SmsAccount) {
|
||||
const usage = await this.computeUsageStats(account.id);
|
||||
const base = this.toSummaryQuick(account);
|
||||
return {
|
||||
...base,
|
||||
todayMessages: usage.today.messages,
|
||||
todayParts: usage.today.parts,
|
||||
todayCostRials: usage.today.amount,
|
||||
monthMessages: usage.month.messages,
|
||||
monthParts: usage.month.parts,
|
||||
monthCostRials: usage.month.amount,
|
||||
};
|
||||
}
|
||||
|
||||
private async computeUsageStats(smsAccountId: string) {
|
||||
const todayYmd = iranYmd(new Date());
|
||||
const todayStart = new Date(`${todayYmd}T00:00:00+03:30`);
|
||||
const monthStartYmd = `${todayYmd.slice(0, 8)}01`;
|
||||
const monthStart = new Date(`${monthStartYmd}T00:00:00+03:30`);
|
||||
const now = new Date();
|
||||
|
||||
const [todayLogs, monthLogs] = await Promise.all([
|
||||
this.prisma.smsSendLog.findMany({
|
||||
where: { smsAccountId, createdAt: { gte: todayStart, lte: now } },
|
||||
select: { parts: true, totalAmountRials: true },
|
||||
}),
|
||||
this.prisma.smsSendLog.findMany({
|
||||
where: { smsAccountId, createdAt: { gte: monthStart, lte: now } },
|
||||
select: { parts: true, totalAmountRials: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
const fold = (rows: { parts: number; totalAmountRials: number }[]) => ({
|
||||
messages: rows.length,
|
||||
parts: rows.reduce((s, r) => s + r.parts, 0),
|
||||
amount: rows.reduce((s, r) => s + r.totalAmountRials, 0),
|
||||
});
|
||||
|
||||
return { today: fold(todayLogs), month: fold(monthLogs) };
|
||||
}
|
||||
|
||||
private balanceLevel(
|
||||
balanceRials: number,
|
||||
critical = 10000,
|
||||
@@ -261,18 +525,63 @@ export class SmsService {
|
||||
return `${new Intl.NumberFormat('fa-IR').format(value)} ریال`;
|
||||
}
|
||||
|
||||
private chargeInfoOf(account: SmsAccount) {
|
||||
return {
|
||||
card: account.chargeCard || '',
|
||||
iban: account.chargeIban || '',
|
||||
accountHolder: account.chargeHolder || '',
|
||||
instruction: account.chargeInstruction || '',
|
||||
};
|
||||
}
|
||||
|
||||
private publicAccount(account: SmsAccount) {
|
||||
return {
|
||||
id: account.id,
|
||||
balanceRials: account.balanceRials,
|
||||
pricePerPartRials: account.pricePerPartRials,
|
||||
isBlocked: account.isBlocked,
|
||||
alertCriticalRials: account.alertCriticalRials,
|
||||
alertWarningRials: account.alertWarningRials,
|
||||
gatewayProvider: account.gatewayProvider,
|
||||
melipayamakUser: account.melipayamakUser,
|
||||
melipayamakFrom: account.melipayamakFrom,
|
||||
panelUsername: account.panelUsername,
|
||||
panelFrom: account.panelFrom,
|
||||
chargeCard: account.chargeCard,
|
||||
chargeIban: account.chargeIban,
|
||||
chargeHolder: account.chargeHolder,
|
||||
chargeInstruction: account.chargeInstruction,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param defaultMonth when true and no dates: from month start to now (always returns a range)
|
||||
* @returns null when optional and no dates provided
|
||||
*/
|
||||
private resolveDateRange(
|
||||
fromDate: string | undefined,
|
||||
toDate: string | undefined,
|
||||
defaultMonth: true,
|
||||
): { from: Date; to: Date };
|
||||
private resolveDateRange(
|
||||
fromDate?: string,
|
||||
toDate?: string,
|
||||
defaultMonth?: false,
|
||||
): { from: Date; to: Date } | null;
|
||||
private resolveDateRange(
|
||||
fromDate?: string,
|
||||
toDate?: string,
|
||||
defaultMonth = false,
|
||||
): { from: Date; to: Date } | null {
|
||||
const today = iranYmd(new Date());
|
||||
if (!fromDate && !toDate) {
|
||||
if (!defaultMonth) return null;
|
||||
return {
|
||||
from: new Date(`${today.slice(0, 8)}01T00:00:00+03:30`),
|
||||
to: new Date(`${today}T23:59:59.999+03:30`),
|
||||
};
|
||||
}
|
||||
|
||||
const fromYmd = fromDate && /^\d{4}-\d{2}-\d{2}$/.test(fromDate) ? fromDate : today;
|
||||
const toYmd = toDate && /^\d{4}-\d{2}-\d{2}$/.test(toDate) ? toDate : today;
|
||||
return {
|
||||
from: new Date(`${fromYmd}T00:00:00+03:30`),
|
||||
to: new Date(`${toYmd}T23:59:59.999+03:30`),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user