refactor(ProductService): 修复createProduct方法和其他方法错误 fix(InventoryAgingService): 修复AGING_THRESHOLD_DAYS引用问题 fix(InventoryService): 修复predictSKUDemand方法 refactor(ChatBotController): 从tsoa风格改为Express风格 fix(CommandCenterController): 修复类型问题 fix(AdAutoService): 修复stock可能为undefined的问题 docs: 更新SERVICE_MAP、DOMAIN_MODEL等架构文档 chore: 启动前端服务(运行在http://localhost:8000)
208 lines
6.0 KiB
TypeScript
208 lines
6.0 KiB
TypeScript
import db from '../config/database';
|
|
import { logger } from '../utils/logger';
|
|
|
|
/**
|
|
* [BE-I002] 库存同步服务
|
|
* @description 实现多商户库存的实时同步和管理
|
|
* @taskId BE-I002
|
|
* @version 1.0
|
|
*/
|
|
export class InventorySyncService {
|
|
private static generateId(prefix: string): string {
|
|
return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
}
|
|
|
|
private static generateTraceId(): string {
|
|
return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 12)}`;
|
|
}
|
|
|
|
/**
|
|
* 同步商户库存
|
|
* @param merchantId 商户ID
|
|
* @param inventoryData 库存数据
|
|
*/
|
|
static async syncMerchantInventory(
|
|
merchantId: string,
|
|
inventoryData: Array<{
|
|
skuId: string;
|
|
quantity: number;
|
|
warehouseId?: string;
|
|
platform?: string;
|
|
}>
|
|
): Promise<{ success: boolean; syncedCount: number; failedCount: number }> {
|
|
const traceId = this.generateTraceId();
|
|
|
|
logger.info('[InventorySyncService] Syncing merchant inventory', {
|
|
merchantId,
|
|
itemCount: inventoryData.length,
|
|
traceId,
|
|
});
|
|
|
|
try {
|
|
const merchant = await db('cf_merchant').where({ id: merchantId }).first();
|
|
if (!merchant || merchant.status !== 'ACTIVE') {
|
|
throw new Error('Merchant not found or not active');
|
|
}
|
|
|
|
let syncedCount = 0;
|
|
let failedCount = 0;
|
|
|
|
for (const item of inventoryData) {
|
|
try {
|
|
const existingInventory = await db('cf_inventory')
|
|
.where({
|
|
tenant_id: merchant.tenant_id,
|
|
sku_id: item.skuId,
|
|
warehouse_id: item.warehouseId || 'default',
|
|
})
|
|
.first();
|
|
|
|
if (existingInventory) {
|
|
await db('cf_inventory')
|
|
.where({ id: existingInventory.id })
|
|
.update({
|
|
total_qty: item.quantity,
|
|
available_qty: item.quantity - (existingInventory.reserved_qty || 0),
|
|
updated_at: new Date(),
|
|
});
|
|
} else {
|
|
await db('cf_inventory').insert({
|
|
id: this.generateId('inv'),
|
|
tenant_id: merchant.tenant_id,
|
|
sku_id: item.skuId,
|
|
warehouse_id: item.warehouseId || 'default',
|
|
total_qty: item.quantity,
|
|
available_qty: item.quantity,
|
|
reserved_qty: 0,
|
|
trace_id: traceId,
|
|
business_type: 'TOC',
|
|
created_at: new Date(),
|
|
updated_at: new Date(),
|
|
});
|
|
}
|
|
|
|
// 记录同步日志
|
|
await db('cf_inventory_sync_log').insert({
|
|
id: this.generateId('sync_log'),
|
|
merchant_id: merchantId,
|
|
sku_id: item.skuId,
|
|
quantity: item.quantity,
|
|
platform: item.platform || 'unknown',
|
|
status: 'SUCCESS',
|
|
trace_id: traceId,
|
|
created_at: new Date(),
|
|
});
|
|
|
|
syncedCount++;
|
|
} catch (error: any) {
|
|
logger.error('[InventorySyncService] Failed to sync inventory item', {
|
|
merchantId,
|
|
skuId: item.skuId,
|
|
error: error.message,
|
|
traceId,
|
|
});
|
|
|
|
// 记录失败日志
|
|
await db('cf_inventory_sync_log').insert({
|
|
id: this.generateId('sync_log'),
|
|
merchant_id: merchantId,
|
|
sku_id: item.skuId,
|
|
quantity: item.quantity,
|
|
platform: item.platform || 'unknown',
|
|
status: 'FAILED',
|
|
error_message: error.message,
|
|
trace_id: traceId,
|
|
created_at: new Date(),
|
|
});
|
|
|
|
failedCount++;
|
|
}
|
|
}
|
|
|
|
logger.info('[InventorySyncService] Inventory sync completed', {
|
|
merchantId,
|
|
syncedCount,
|
|
failedCount,
|
|
traceId,
|
|
});
|
|
|
|
return { success: failedCount === 0, syncedCount, failedCount };
|
|
} catch (error: any) {
|
|
logger.error('[InventorySyncService] Inventory sync failed', {
|
|
merchantId,
|
|
traceId,
|
|
error: error.message,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取库存同步历史
|
|
* @param merchantId 商户ID
|
|
* @param limit 限制数量
|
|
*/
|
|
static async getSyncHistory(
|
|
merchantId: string,
|
|
limit: number = 50
|
|
): Promise<any[]> {
|
|
const traceId = this.generateTraceId();
|
|
|
|
logger.info('[InventorySyncService] Getting sync history', {
|
|
merchantId,
|
|
limit,
|
|
traceId,
|
|
});
|
|
|
|
try {
|
|
const history = await db('cf_inventory_sync_log')
|
|
.where({ merchant_id: merchantId })
|
|
.orderBy('created_at', 'desc')
|
|
.limit(limit)
|
|
.select('*');
|
|
|
|
logger.info('[InventorySyncService] Sync history retrieved', {
|
|
merchantId,
|
|
recordCount: history.length,
|
|
traceId,
|
|
});
|
|
|
|
return history;
|
|
} catch (error: any) {
|
|
logger.error('[InventorySyncService] Failed to get sync history', {
|
|
merchantId,
|
|
traceId,
|
|
error: error.message,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 初始化库存同步相关数据库表
|
|
*/
|
|
static async initTables(): Promise<void> {
|
|
const syncLogTableExists = await db.schema.hasTable('cf_inventory_sync_log');
|
|
if (!syncLogTableExists) {
|
|
logger.info('📦 Creating cf_inventory_sync_log table...');
|
|
await db.schema.createTable('cf_inventory_sync_log', (table) => {
|
|
table.string('id', 64).primary();
|
|
table.string('merchant_id', 64).notNullable();
|
|
table.string('sku_id', 64).notNullable();
|
|
table.integer('quantity').notNullable();
|
|
table.string('platform', 50);
|
|
table.enum('status', ['SUCCESS', 'FAILED']).notNullable();
|
|
table.text('error_message');
|
|
table.string('trace_id', 64);
|
|
table.timestamp('created_at').defaultTo(db.fn.now());
|
|
|
|
table.index(['merchant_id']);
|
|
table.index(['sku_id']);
|
|
table.index(['status']);
|
|
table.index(['created_at']);
|
|
});
|
|
logger.info('✅ Table cf_inventory_sync_log created');
|
|
}
|
|
}
|
|
}
|