From 58744d0f9153fa4ccb03169cc127e1fa357d3b21 Mon Sep 17 00:00:00 2001 From: adonis Date: Tue, 8 Sep 2026 18:00:56 +0330 Subject: [PATCH] added some feature for plugin --- src/get-user/get-user.module.ts | 2 + src/get-user/get-user.service.ts | 9 ++- .../dto/update-khanegi-deposit.dto.ts | 28 ++++++++ .../runtime-settings.controller.ts | 13 ++++ src/notifications/runtime-settings.service.ts | 69 ++++++++++++++++++- src/plugins/plugin-store.service.ts | 9 ++- src/sms/sms.service.ts | 24 ++++++- 7 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 src/notifications/dto/update-khanegi-deposit.dto.ts diff --git a/src/get-user/get-user.module.ts b/src/get-user/get-user.module.ts index 2aa722b..0dd01ea 100644 --- a/src/get-user/get-user.module.ts +++ b/src/get-user/get-user.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; +import { NotificationsModule } from '../notifications/notifications.module'; import { GetUserController } from './get-user.controller'; import { GetUserService } from './get-user.service'; @Module({ + imports: [NotificationsModule], controllers: [GetUserController], providers: [GetUserService], exports: [GetUserService], diff --git a/src/get-user/get-user.service.ts b/src/get-user/get-user.service.ts index 7ab890b..ab07a2c 100644 --- a/src/get-user/get-user.service.ts +++ b/src/get-user/get-user.service.ts @@ -4,6 +4,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { applyKhanegiNavSample } from './khanegi-nav-sample'; import { rewriteLegacyUploadUrl } from '../uploads/uploads.util'; import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys'; +import { RuntimeSettingsService } from '../notifications/runtime-settings.service'; import { findTenantForHost as matchTenantForHost, normalizeTenantHost, @@ -29,7 +30,10 @@ type SubscriptionAlerts = { @Injectable() export class GetUserService { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly runtimeSettings: RuntimeSettingsService, + ) {} async resolveByHost(hostHeader?: string, domainQuery?: string) { const host = normalizeTenantHost(domainQuery || hostHeader || ''); @@ -89,6 +93,8 @@ export class GetUserService { tenant.backgroundColor || null + const khanegiDeposit = isKhanegi ? this.runtimeSettings.getKhanegiDeposit() : null; + const branding = { id: tenant.id, name: tenant.nameEn || tenant.slug, @@ -120,6 +126,7 @@ export class GetUserService { plugin_store_enabled: isKhanegi ? await this.isPluginStoreEnabled(tenant.id) : false, + ...(khanegiDeposit || {}), }; return { diff --git a/src/notifications/dto/update-khanegi-deposit.dto.ts b/src/notifications/dto/update-khanegi-deposit.dto.ts new file mode 100644 index 0000000..2f8a41a --- /dev/null +++ b/src/notifications/dto/update-khanegi-deposit.dto.ts @@ -0,0 +1,28 @@ +import { IsOptional, IsString, MaxLength } from 'class-validator'; + +export class UpdateKhanegiDepositDto { + @IsOptional() + @IsString() + @MaxLength(64) + sms_charge_card_number?: string; + + @IsOptional() + @IsString() + @MaxLength(64) + sms_charge_sheba?: string; + + @IsOptional() + @IsString() + @MaxLength(120) + sms_charge_account_holder?: string; + + @IsOptional() + @IsString() + @MaxLength(4000) + sms_charge_note?: string; + + @IsOptional() + @IsString() + @MaxLength(4000) + plugin_charge_note?: string; +} diff --git a/src/notifications/runtime-settings.controller.ts b/src/notifications/runtime-settings.controller.ts index 3dfe885..9334c01 100644 --- a/src/notifications/runtime-settings.controller.ts +++ b/src/notifications/runtime-settings.controller.ts @@ -3,6 +3,7 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard'; import { PermissionsGuard } from '../auth/permissions.guard'; import { RequirePermissions } from '../auth/permissions.decorator'; import { UpdateAdminSmsDto } from './dto/update-admin-sms.dto'; +import { UpdateKhanegiDepositDto } from './dto/update-khanegi-deposit.dto'; import { RuntimeSettingsService } from './runtime-settings.service'; @Controller('settings') @@ -22,4 +23,16 @@ export class RuntimeSettingsController { this.settings.setAdminSmsEnabled(dto.enabled); return this.settings.getPublic(); } + + @Get('khanegi-deposit') + @RequirePermissions('khanegi:read') + getKhanegiDeposit() { + return this.settings.getKhanegiDeposit(); + } + + @Patch('khanegi-deposit') + @RequirePermissions('khanegi:write') + setKhanegiDeposit(@Body() dto: UpdateKhanegiDepositDto) { + return this.settings.setKhanegiDeposit(dto); + } } diff --git a/src/notifications/runtime-settings.service.ts b/src/notifications/runtime-settings.service.ts index 232db67..18c6a3f 100644 --- a/src/notifications/runtime-settings.service.ts +++ b/src/notifications/runtime-settings.service.ts @@ -2,13 +2,33 @@ import { Injectable, Logger } from '@nestjs/common'; import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; +export type KhanegiDeposit = { + sms_charge_card_number: string; + sms_charge_sheba: string; + sms_charge_account_holder: string; + sms_charge_note: string; + plugin_charge_note: string; +}; + type RuntimeSettings = { /** When false, SuperAdmin stops SMS alerts to managers */ adminSmsEnabled: boolean; + khanegiDeposit: KhanegiDeposit; +}; + +const DEPOSIT_DEFAULTS: KhanegiDeposit = { + sms_charge_card_number: '6219861961033817', + sms_charge_sheba: 'IR080560611828005603604201', + sms_charge_account_holder: 'حسین منتظر', + sms_charge_note: + 'لطفا مبلغ مورد نظر خود جهت شارژ سامانه پیامکی را به شماره کارت یا حساب زیر واریز نمایید و تصویر فیش پرداختی را برای پشتیبانی بفرستید.', + plugin_charge_note: + 'لطفا مبلغ افزونه را به شماره کارت یا حساب زیر واریز نمایید و تصویر فیش را از تب درخواست‌ها ارسال کنید.', }; const DEFAULTS: RuntimeSettings = { adminSmsEnabled: true, + khanegiDeposit: { ...DEPOSIT_DEFAULTS }, }; @Injectable() @@ -28,6 +48,30 @@ export class RuntimeSettingsService { return next; } + getKhanegiDeposit(): KhanegiDeposit { + return { ...this.read().khanegiDeposit }; + } + + setKhanegiDeposit(patch: Partial) { + const current = this.read(); + const next: RuntimeSettings = { + ...current, + khanegiDeposit: this.normalizeDeposit({ ...current.khanegiDeposit, ...patch }), + }; + this.write(next); + return next.khanegiDeposit; + } + + pluginStoreDeposit() { + const d = this.getKhanegiDeposit(); + return { + card_number: d.sms_charge_card_number, + sheba: d.sms_charge_sheba, + account_holder: d.sms_charge_account_holder, + note: d.plugin_charge_note || d.sms_charge_note, + }; + } + getPublic() { try { return { adminSmsEnabled: this.getAdminSmsEnabled() }; @@ -37,21 +81,42 @@ export class RuntimeSettingsService { } } + private normalizeDeposit(raw?: Partial | null): KhanegiDeposit { + const pick = (key: keyof KhanegiDeposit) => { + const value = String(raw?.[key] ?? '').trim(); + return value || DEPOSIT_DEFAULTS[key]; + }; + return { + sms_charge_card_number: pick('sms_charge_card_number'), + sms_charge_sheba: pick('sms_charge_sheba'), + sms_charge_account_holder: pick('sms_charge_account_holder'), + sms_charge_note: String(raw?.sms_charge_note ?? DEPOSIT_DEFAULTS.sms_charge_note), + plugin_charge_note: String(raw?.plugin_charge_note ?? DEPOSIT_DEFAULTS.plugin_charge_note), + }; + } + private read(): RuntimeSettings { if (this.cache) return this.cache; try { if (!existsSync(this.filePath)) { - this.cache = { ...DEFAULTS }; + this.cache = { + adminSmsEnabled: DEFAULTS.adminSmsEnabled, + khanegiDeposit: { ...DEPOSIT_DEFAULTS }, + }; return this.cache; } const raw = JSON.parse(readFileSync(this.filePath, 'utf8')) as Partial; this.cache = { adminSmsEnabled: raw.adminSmsEnabled !== false, + khanegiDeposit: this.normalizeDeposit(raw.khanegiDeposit), }; return this.cache; } catch (error: any) { this.logger.warn(`Failed to read runtime settings: ${error?.message || error}`); - this.cache = { ...DEFAULTS }; + this.cache = { + adminSmsEnabled: DEFAULTS.adminSmsEnabled, + khanegiDeposit: { ...DEPOSIT_DEFAULTS }, + }; return this.cache; } } diff --git a/src/plugins/plugin-store.service.ts b/src/plugins/plugin-store.service.ts index 4b93385..c7a2343 100644 --- a/src/plugins/plugin-store.service.ts +++ b/src/plugins/plugin-store.service.ts @@ -21,6 +21,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { AuditService } from '../audit/audit.service'; import { AdminNotifyService } from '../notifications/admin-notify.service'; import { CentralSmsService } from '../notifications/central-sms.service'; +import { RuntimeSettingsService } from '../notifications/runtime-settings.service'; import { ProgramSettingsService } from '../program-settings/program-settings.service'; import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys'; import { DEPLOY_SETTING_CATALOG_KEYS } from '../program-settings/deploy-setting-catalog-keys'; @@ -59,6 +60,7 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy { private readonly sms: CentralSmsService, private readonly programSettings: ProgramSettingsService, private readonly config: ConfigService, + private readonly runtime: RuntimeSettingsService, ) {} onModuleInit() { @@ -558,11 +560,14 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy { async remoteListRequests(tenantId: string, product: string) { await this.assertRemoteKhanegi(tenantId, product); try { - return await this.buildRemoteRequestRows(tenantId); + return { + requests: await this.buildRemoteRequestRows(tenantId), + deposit: this.runtime.pluginStoreDeposit(), + }; } catch (error: any) { if (error instanceof ForbiddenException) throw error; this.logger.warn(`remoteListRequests skipped: ${error?.message || error}`); - return []; + return { requests: [], deposit: this.runtime.pluginStoreDeposit() }; } } diff --git a/src/sms/sms.service.ts b/src/sms/sms.service.ts index da8250a..643465b 100644 --- a/src/sms/sms.service.ts +++ b/src/sms/sms.service.ts @@ -8,6 +8,7 @@ import { Prisma, SmsAccount, SmsLedgerType } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { AuditService } from '../audit/audit.service'; import { AdminNotifyService } from '../notifications/admin-notify.service'; +import { RuntimeSettingsService } from '../notifications/runtime-settings.service'; import { iranYmd } from '../common/iran-time.util'; import { ChargeSmsWalletDto, @@ -27,6 +28,7 @@ export class SmsService { private readonly prisma: PrismaService, private readonly audit: AuditService, private readonly notify: AdminNotifyService, + private readonly runtime: RuntimeSettingsService, ) {} async listTenants() { @@ -374,7 +376,7 @@ export class SmsService { async remoteOverview(tenantId: string) { const account = await this.ensureAccount(tenantId); const usage = await this.computeUsageStats(account.id); - const charge = this.chargeInfoOf(account); + const charge = await this.chargeInfoForRemote(tenantId, account); return { wallet: { @@ -417,7 +419,8 @@ export class SmsService { } async remoteChargeInfo(tenantId: string) { - const charge = await this.getChargeInfo(tenantId); + const account = await this.ensureAccount(tenantId); + const charge = await this.chargeInfoForRemote(tenantId, account); return { card: charge.card, iban: charge.iban, @@ -894,6 +897,23 @@ export class SmsService { ); } + private async chargeInfoForRemote(tenantId: string, account: SmsAccount) { + const tenant = await this.prisma.tenant.findUnique({ + where: { id: tenantId }, + select: { product: true }, + }); + if (tenant?.product === 'khanegi') { + const deposit = this.runtime.getKhanegiDeposit(); + return { + card: deposit.sms_charge_card_number, + iban: deposit.sms_charge_sheba, + accountHolder: deposit.sms_charge_account_holder, + instruction: deposit.sms_charge_note, + }; + } + return this.chargeInfoOf(account); + } + private chargeInfoOf(account: SmsAccount) { return { card: account.chargeCard || '',