- 新增文档模板和导航结构 - 实现服务器基础API路由和控制器 - 添加扩展插件配置和前端框架 - 引入多租户和权限管理模块 - 集成日志和数据库配置 - 添加核心业务模型和类型定义
234 lines
7.6 KiB
TypeScript
234 lines
7.6 KiB
TypeScript
import db from '../config/database';
|
|
import { logger } from '../utils/logger';
|
|
import { FXHedgingService } from './FXHedgingService';
|
|
import { TaxService } from './TaxService';
|
|
|
|
export interface OrderRecord {
|
|
id: string;
|
|
tenantId: string;
|
|
shopId?: string;
|
|
taskId?: string;
|
|
traceId: string;
|
|
platform: string;
|
|
productId: string;
|
|
sellingPrice: number;
|
|
purchasePrice: number;
|
|
countryCode: string;
|
|
logisticsCost: number;
|
|
adBudget: number;
|
|
status: string;
|
|
}
|
|
|
|
/**
|
|
* [BIZ_FIN_01] 财务结算与对账服务
|
|
* @description 处理订单回流、利差核算与账单生成
|
|
*/
|
|
export class FinanceService {
|
|
private static readonly TABLE_NAME = 'cf_orders';
|
|
private static readonly TRANSACTION_TABLE = 'cf_transactions';
|
|
|
|
/**
|
|
* 初始化数据库表
|
|
*/
|
|
static async initTable() {
|
|
const hasOrderTable = await db.schema.hasTable(this.TABLE_NAME);
|
|
if (!hasOrderTable) {
|
|
console.log(`📦 Creating ${this.TABLE_NAME} table...`);
|
|
await db.schema.createTable(this.TABLE_NAME, (table) => {
|
|
table.string('id', 64).primary();
|
|
table.string('tenant_id', 64).index();
|
|
table.string('platform', 32);
|
|
table.string('product_id', 64);
|
|
table.decimal('selling_price', 10, 2);
|
|
table.decimal('purchase_price', 10, 2);
|
|
table.string('country_code', 8);
|
|
table.decimal('logistics_cost', 10, 2);
|
|
table.decimal('ad_budget', 10, 2);
|
|
table.string('status', 32);
|
|
table.timestamps(true, true);
|
|
});
|
|
}
|
|
|
|
const hasTransactionTable = await db.schema.hasTable(this.TRANSACTION_TABLE);
|
|
if (!hasTransactionTable) {
|
|
console.log(`📦 Creating ${this.TRANSACTION_TABLE} table...`);
|
|
await db.schema.createTable(this.TRANSACTION_TABLE, (table) => {
|
|
table.increments('id').primary();
|
|
table.string('tenant_id', 64).index();
|
|
table.string('order_id', 64).nullable();
|
|
table.decimal('amount', 10, 2);
|
|
table.string('currency', 8).defaultTo('USD');
|
|
table.string('transaction_type', 32);
|
|
table.string('trace_id', 64);
|
|
table.json('metadata').nullable();
|
|
table.timestamp('created_at').defaultTo(db.fn.now());
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 记录订单
|
|
*/
|
|
static async recordOrder(order: OrderRecord): Promise<void> {
|
|
const { tenantId, shopId, taskId, traceId } = order;
|
|
|
|
// 1. 插入订单主表
|
|
await db(this.TABLE_NAME).insert({
|
|
id: order.id,
|
|
tenant_id: order.tenantId,
|
|
platform: order.platform,
|
|
product_id: order.productId,
|
|
selling_price: order.sellingPrice,
|
|
purchase_price: order.purchasePrice,
|
|
country_code: order.countryCode,
|
|
logistics_cost: order.logisticsCost,
|
|
ad_budget: order.adBudget,
|
|
status: order.status,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
|
|
// 2. 实时税务计提 (BIZ_FIN_02)
|
|
const taxInfo = TaxService.calculateTax({
|
|
countryCode: order.countryCode,
|
|
baseAmount: order.sellingPrice,
|
|
currency: 'USD'
|
|
});
|
|
|
|
await this.accrueTax(order.id, {
|
|
tenantId,
|
|
amount: taxInfo.totalTax,
|
|
currency: 'USD',
|
|
type: taxInfo.isIOSS ? 'IOSS_VAT' : 'STANDARD_VAT',
|
|
traceId
|
|
});
|
|
|
|
// 3. 汇率自动对冲 (BIZ_FIN_03)
|
|
await FXHedgingService.autoHedge({
|
|
tenantId,
|
|
pair: 'USD/CNY',
|
|
amount: order.sellingPrice,
|
|
traceId
|
|
});
|
|
}
|
|
|
|
/**
|
|
* [BIZ_FIN_04] 记录财务交易流水
|
|
*/
|
|
static async recordTransaction(params: {
|
|
tenantId: string;
|
|
amount: number;
|
|
currency?: string;
|
|
type: 'ORDER_REVENUE' | 'LOGISTICS_COST' | 'PLATFORM_FEE' | 'REFUND' | 'COMMISSION' | 'INCOME';
|
|
category?: string;
|
|
orderId?: string;
|
|
traceId?: string;
|
|
metadata?: any;
|
|
}) {
|
|
logger.info(`[Finance] Recording transaction: ${params.type} for Order ${params.orderId || 'N/A'} (${params.amount} ${params.currency || 'USD'})`);
|
|
|
|
// 实际业务中需插入 cf_transactions 表
|
|
await db('cf_transactions').insert({
|
|
tenant_id: params.tenantId,
|
|
order_id: params.orderId || null,
|
|
amount: params.amount,
|
|
currency: params.currency || 'USD',
|
|
transaction_type: params.type,
|
|
trace_id: params.traceId || 'system',
|
|
metadata: params.metadata ? JSON.stringify(params.metadata) : null,
|
|
created_at: new Date()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* [BIZ_FIN_02] 实时税务计提记录
|
|
* @description 将税务计提记录到 cf_tax_accruals 表,用于后期对账与报关
|
|
*/
|
|
private static async accrueTax(orderId: string, params: {
|
|
tenantId: string;
|
|
amount: number;
|
|
currency: string;
|
|
type: string;
|
|
traceId: string;
|
|
}): Promise<void> {
|
|
logger.info(`[Finance] Accruing tax for order ${orderId}: ${params.amount} ${params.currency} (${params.type})`);
|
|
|
|
// 实际业务中需插入 cf_tax_accruals
|
|
await db('cf_tax_accruals').insert({
|
|
order_id: orderId,
|
|
tenant_id: params.tenantId,
|
|
amount: params.amount,
|
|
currency: params.currency,
|
|
tax_type: params.type,
|
|
status: 'ACCRUED',
|
|
trace_id: params.traceId,
|
|
created_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
}
|
|
|
|
/**
|
|
* [BIZ_FIN_06] 全自动化财务利润对账
|
|
* @description 自动勾稽订单、采购成本、物流费与税金,生成最终利润报表
|
|
*/
|
|
static async performAutoProfitRecon(tenantId: string, shopId: string): Promise<any> {
|
|
logger.info(`[Finance] Performing auto profit reconciliation for Tenant: ${tenantId}, Shop: ${shopId}`);
|
|
|
|
// 1. 获取所有已完成且未对账的订单
|
|
const orders = await db('cf_orders')
|
|
.where({ tenant_id: tenantId, shop_id: shopId, status: 'PUBLISHED' }) // 假设 PUBLISHED 状态代表已成单
|
|
.whereNull('reconciled_at');
|
|
|
|
const results = [];
|
|
|
|
for (const order of orders) {
|
|
try {
|
|
// 2. 聚合各项成本
|
|
const taxAccrual = await db('cf_tax_accruals').where({ order_id: order.id }).first();
|
|
const purchaseOrder = await db('cf_purchase_orders').where({ product_id: order.product_id, tenant_id: tenantId }).first();
|
|
const logisticsRoute = await db('cf_logistics_routes').where({ tenant_id: tenantId }).first(); // 简化取第一个
|
|
|
|
const purchaseCost = purchaseOrder ? purchaseOrder.total_amount / purchaseOrder.quantity : 0;
|
|
const taxAmount = taxAccrual ? taxAccrual.amount : 0;
|
|
const logisticsCost = logisticsRoute ? logisticsRoute.cost_per_kg * 0.5 : 5; // 假设 0.5kg
|
|
|
|
const netProfit = order.selling_price - purchaseCost - logisticsCost - taxAmount;
|
|
|
|
// 3. 记录对账结果
|
|
await db('cf_orders').where({ id: order.id }).update({
|
|
net_profit: netProfit,
|
|
reconciled_at: new Date(),
|
|
updated_at: new Date()
|
|
});
|
|
|
|
results.push({ orderId: order.id, netProfit });
|
|
} catch (err: any) {
|
|
logger.error(`[Finance] Recon failed for Order ${order.id}: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
return { totalReconciled: results.length, details: results };
|
|
}
|
|
|
|
/**
|
|
* 获取账单回放
|
|
*/
|
|
static async getBillPlayback(tenantId: string, shopId: string, orderId: string): Promise<any> {
|
|
const order = await db(this.TABLE_NAME)
|
|
.where({ id: orderId, tenant_id: tenantId })
|
|
.first();
|
|
|
|
if (!order) return null;
|
|
|
|
return {
|
|
orderId: order.id,
|
|
timeline: [
|
|
{ event: 'ORDER_CREATED', time: order.created_at, amount: order.selling_price },
|
|
{ event: 'PURCHASE_COMPLETED', time: order.updated_at, amount: -order.purchase_price },
|
|
{ event: 'LOGISTICS_DEDUCTED', time: order.updated_at, amount: -order.logistics_cost }
|
|
],
|
|
finalProfit: order.selling_price - order.purchase_price - order.logistics_cost
|
|
};
|
|
}
|
|
}
|