feat: 初始化项目结构并添加核心功能模块
- 新增文档模板和导航结构 - 实现服务器基础API路由和控制器 - 添加扩展插件配置和前端框架 - 引入多租户和权限管理模块 - 集成日志和数据库配置 - 添加核心业务模型和类型定义
This commit is contained in:
233
server/src/services/FinanceService.ts
Normal file
233
server/src/services/FinanceService.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user