feat(round-9): 修复3项关键交互缺陷并新增NFC国际物流发货追踪 (77套件946测试全绿)
这个提交包含在:
@@ -1112,3 +1112,84 @@ export interface TeamRankBroadcastPayload {
|
|||||||
meta?: TeamRankBroadcastMeta;
|
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()
|
@Inject()
|
||||||
nfcBatchExportService: NfcBatchExportService;
|
nfcBatchExportService: NfcBatchExportService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
nfcDeviceService: NfcDeviceService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 展会批量制卡二维码与离线数据包 Zip 导出
|
* 展会批量制卡二维码与离线数据包 Zip 导出
|
||||||
*/
|
*/
|
||||||
@@ -57,5 +60,20 @@ export class AdminNfcDeviceController extends BaseController {
|
|||||||
this.ctx.set('X-Total-Count', String(totalCount));
|
this.ctx.set('X-Total-Count', String(totalCount));
|
||||||
this.ctx.body = zipBuffer;
|
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,
|
nullable: true,
|
||||||
})
|
})
|
||||||
lastScannedAt?: string | Date | null;
|
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 },
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户