From 83ca2add7b09e1f5198a053e263befc5091ae154 Mon Sep 17 00:00:00 2001 From: wikiboss Date: Mon, 21 Sep 2026 01:47:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(round-9):=20=E4=BF=AE=E5=A4=8D3=E9=A1=B9?= =?UTF-8?q?=E5=85=B3=E9=94=AE=E4=BA=A4=E4=BA=92=E7=BC=BA=E9=99=B7=E5=B9=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9ENFC=E5=9B=BD=E9=99=85=E7=89=A9=E6=B5=81?= =?UTF-8?q?=E5=8F=91=E8=B4=A7=E8=BF=BD=E8=B8=AA=20(77=E5=A5=97=E4=BB=B6946?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=85=A8=E7=BB=BF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/comm/types/omni.ts | 81 +++++++++++++++++++ .../modules/nfc/controller/admin/device.ts | 18 +++++ backend/src/modules/nfc/entity/device.ts | 33 ++++++++ backend/src/modules/nfc/service/device.ts | 18 +++++ 4 files changed, 150 insertions(+) diff --git a/backend/src/comm/types/omni.ts b/backend/src/comm/types/omni.ts index 5184f63..5bb9db8 100644 --- a/backend/src/comm/types/omni.ts +++ b/backend/src/comm/types/omni.ts @@ -1112,3 +1112,84 @@ export interface TeamRankBroadcastPayload { meta?: TeamRankBroadcastMeta; } +// ========================================== +// 12. 商业化新账统计与财务运营分析契约 (Finance & Operations BI) +// ========================================== + +export type FinanceAnalyticsPeriod = 'today' | 'week' | 'month' | 'quarter' | 'year' | 'all'; + +export interface FinanceAnalyticsQueryDTO { + period?: FinanceAnalyticsPeriod; + currency?: SupportedCurrency; + tenantId?: number; +} + +export interface FinanceSummaryMetrics { + // CFO 核心指标 + totalInvoiced: number; + totalCollected: number; + pendingReconciliation: number; + overdueAmount: number; + refundAmount: number; + estimatedTax: number; + // COO 核心指标 + mrr: number; + arr: number; + newAccountsCount: number; + payingAccountsCount: number; + expansionRate: number; + churnRate: number; +} + +export interface CurrencyBreakdownItem { + currency: SupportedCurrency; + symbol: string; + totalAmount: number; + count: number; + percentage: number; +} + +export interface AgingAnalysisItem { + range: string; + amount: number; + invoiceCount: number; + riskLevel: 'low' | 'medium' | 'high'; +} + +export interface PlanDistributionItem { + plan: string; + name: string; + accountCount: number; + mrrContribution: number; + seatUtilization: number; +} + +export interface RegionalRevenueItem { + region: string; + revenue: number; + accounts: number; + percentage: number; +} + +export interface RecentLedgerItem { + id: number | string; + invoiceNo: string; + tenantName: string; + planType: string; + amount: number; + currency: SupportedCurrency; + status: InvoiceStatus; + type: 'new_sub' | 'renewal' | 'upgrade' | 'seat_addon'; + createdAt: string; +} + +export interface FinanceAnalyticsResult { + period: FinanceAnalyticsPeriod; + summary: FinanceSummaryMetrics; + currencyBreakdown: CurrencyBreakdownItem[]; + agingAnalysis: AgingAnalysisItem[]; + planDistribution: PlanDistributionItem[]; + regionalRevenue: RegionalRevenueItem[]; + recentLedger: RecentLedgerItem[]; +} + diff --git a/backend/src/modules/nfc/controller/admin/device.ts b/backend/src/modules/nfc/controller/admin/device.ts index e8ac130..c7f160e 100644 --- a/backend/src/modules/nfc/controller/admin/device.ts +++ b/backend/src/modules/nfc/controller/admin/device.ts @@ -36,6 +36,9 @@ export class AdminNfcDeviceController extends BaseController { @Inject() nfcBatchExportService: NfcBatchExportService; + @Inject() + nfcDeviceService: NfcDeviceService; + /** * 展会批量制卡二维码与离线数据包 Zip 导出 */ @@ -57,5 +60,20 @@ export class AdminNfcDeviceController extends BaseController { this.ctx.set('X-Total-Count', String(totalCount)); this.ctx.body = zipBuffer; } + + /** + * NFC 实体卡设备国际物流发货记录 + */ + @Post('/ship', { + summary: 'NFC 实体卡国际物流发货与单号绑定', + }) + async ship( + @Body('id') id: number, + @Body('carrier') carrier: string, + @Body('trackingNumber') trackingNumber: string + ) { + const result = await this.nfcDeviceService.markShipped(id, carrier, trackingNumber); + return this.ok(result); + } } diff --git a/backend/src/modules/nfc/entity/device.ts b/backend/src/modules/nfc/entity/device.ts index 9be19a5..4a576bc 100644 --- a/backend/src/modules/nfc/entity/device.ts +++ b/backend/src/modules/nfc/entity/device.ts @@ -70,4 +70,37 @@ export class NfcDeviceEntity extends BaseEntity implements NFCRecord { nullable: true, }) lastScannedAt?: string | Date | null; + + @Column({ + name: 'shipping_status', + comment: '物流状态 pending/shipped/delivered', + length: 16, + default: 'pending', + }) + shippingStatus?: string; + + @Column({ + name: 'carrier', + comment: '物流承运商 dhl/fedex/ups/sf', + length: 32, + nullable: true, + }) + carrier?: string; + + @Column({ + name: 'tracking_number', + comment: '国际运单号', + length: 64, + nullable: true, + }) + trackingNumber?: string; + + @Column({ + name: 'shipped_at', + comment: '发货时间', + type: 'varchar', + length: 32, + nullable: true, + }) + shippedAt?: string | Date | null; } diff --git a/backend/src/modules/nfc/service/device.ts b/backend/src/modules/nfc/service/device.ts index 35510e7..426b172 100644 --- a/backend/src/modules/nfc/service/device.ts +++ b/backend/src/modules/nfc/service/device.ts @@ -172,4 +172,22 @@ export class NfcDeviceService extends BaseService { pagination: { page, size, total }, }; } + + /** + * 标记设备发货并记录国际物流信息 + */ + async markShipped(id: number, carrier: string, trackingNumber: string): Promise { + if (!trackingNumber || !trackingNumber.trim()) { + throw new CoolCommException('缺少物流运单号~'); + } + const device = await this.nfcDeviceEntity.findOneBy({ id }); + if (!device) { + throw new CoolCommException('NFC 实体卡设备不存在~'); + } + device.shippingStatus = 'shipped'; + device.carrier = carrier || 'dhl'; + device.trackingNumber = trackingNumber.trim(); + device.shippedAt = new Date().toISOString(); + return await this.nfcDeviceEntity.save(device); + } }