feat(round-9): 修复3项关键交互缺陷并新增NFC国际物流发货追踪 (77套件946测试全绿)
这个提交包含在:
@@ -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[];
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -172,4 +172,22 @@ export class NfcDeviceService extends BaseService {
|
||||
pagination: { page, size, total },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记设备发货并记录国际物流信息
|
||||
*/
|
||||
async markShipped(id: number, carrier: string, trackingNumber: string): Promise<NfcDeviceEntity> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户