refactor(services): 重构服务模块结构,按功能分类移动文件

将服务文件按功能分类移动到对应子目录,包括财务、营销、订单等模块
更新相关路由和导入路径,修复文件引用错误
归档旧版任务文档,更新README和任务统计信息
This commit is contained in:
2026-03-23 15:41:50 +08:00
parent 2b86715c09
commit e59d7c6620
156 changed files with 14658 additions and 7774 deletions

View File

@@ -0,0 +1,182 @@
import { Request, Response } from 'express';
import { AfterSalesService, AfterSalesContext } from '../../services/AfterSalesService';
export class AfterSalesController {
static async initTables(req: Request, res: Response): Promise<void> {
try {
await AfterSalesService.initTables();
res.json({ success: true, message: 'After-sales tables initialized' });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async createRequest(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { orderId, type, reason, description, items, customerId, customerName, customerEmail } = req.body;
const result = await AfterSalesService.createRequest(context, {
orderId,
type,
reason,
description,
items,
customerId,
customerName,
customerEmail
});
res.status(201).json(result);
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async getRequests(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { status, type, orderId, page, pageSize } = req.query;
const result = await AfterSalesService.getRequests(context.tenantId, {
status: status as any,
type: type as any,
orderId: orderId as string,
page: page ? parseInt(page as string) : 1,
pageSize: pageSize ? parseInt(pageSize as string) : 20
});
res.json(result);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getRequestById(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { id } = req.params;
const request = await AfterSalesService.getRequestById(context.tenantId, id);
if (!request) {
res.status(404).json({ success: false, error: 'After-sales request not found' });
return;
}
res.json(request);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async approveRequest(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { id } = req.params;
const { approved, approvedBy, rejectedReason, refundAmount } = req.body;
const result = await AfterSalesService.approveRequest(context, id, {
approved,
approvedBy,
rejectedReason,
refundAmount
});
res.json(result);
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async processRefund(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { refundId } = req.params;
const { transactionId, refundMethod } = req.body;
const result = await AfterSalesService.processRefund(context, refundId, {
transactionId,
refundMethod
});
res.json(result);
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async getRefundById(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { refundId } = req.params;
const refund = await AfterSalesService.getRefundById(context.tenantId, refundId);
if (!refund) {
res.status(404).json({ success: false, error: 'Refund not found' });
return;
}
res.json(refund);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async processReturn(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { returnId } = req.params;
const { action, ...actionData } = req.body;
const result = await AfterSalesService.processReturn(context, returnId, action, actionData);
res.json(result);
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async getReturnById(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { returnId } = req.params;
const returnRecord = await AfterSalesService.getReturnById(context.tenantId, returnId);
if (!returnRecord) {
res.status(404).json({ success: false, error: 'Return not found' });
return;
}
res.json(returnRecord);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async closeRequest(req: Request, res: Response): Promise<void> {
try {
const context = AfterSalesController.getContext(req);
const { id } = req.params;
const { reason } = req.body;
const result = await AfterSalesService.closeRequest(context, id, reason);
res.json(result);
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
private static getContext(req: Request): AfterSalesContext {
return {
tenantId: req.headers['x-tenant-id'] as string || 'default',
shopId: req.headers['x-shop-id'] as string || 'default',
taskId: req.headers['x-task-id'] as string || `task-${Date.now()}`,
traceId: req.headers['x-trace-id'] as string || `trace-${Date.now()}`,
businessType: (req.headers['x-business-type'] as 'TOC' | 'TOB') || 'TOC'
};
}
}

View File

@@ -0,0 +1,136 @@
import { Request, Response } from 'express';
import { OrderCentralService, OrderCentralContext, Platform } from '../../services/OrderCentralService';
export class OrderCentralController {
static async initTables(req: Request, res: Response): Promise<void> {
try {
await OrderCentralService.initTables();
res.json({ success: true, message: 'Order central tables initialized' });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async registerPlatform(req: Request, res: Response): Promise<void> {
try {
const context = OrderCentralController.getContext(req);
const { platform, shopId, shopName, credentials, syncEnabled, syncInterval } = req.body;
const result = await OrderCentralService.registerPlatform(context, {
platform,
shopId,
shopName,
credentials,
syncEnabled: syncEnabled ?? true,
syncInterval: syncInterval ?? 30
});
res.status(201).json(result);
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async collectOrders(req: Request, res: Response): Promise<void> {
try {
const context = OrderCentralController.getContext(req);
const { platform, shopId, startDate, endDate } = req.body;
const result = await OrderCentralService.collectOrders(context, {
platform,
shopId,
startDate: startDate ? new Date(startDate) : undefined,
endDate: endDate ? new Date(endDate) : undefined
});
res.status(201).json(result);
} catch (error: any) {
res.status(400).json({ success: false, error: error.message });
}
}
static async getOrders(req: Request, res: Response): Promise<void> {
try {
const context = OrderCentralController.getContext(req);
const { platforms, shopIds, funnelStage, status, startDate, endDate, search, page, pageSize } = req.query;
const result = await OrderCentralService.getCentralizedOrders(context.tenantId, {
platforms: platforms ? (platforms as string).split(',') as Platform[] : undefined,
shopIds: shopIds ? (shopIds as string).split(',') : undefined,
funnelStage: funnelStage as any,
status: status as string,
startDate: startDate ? new Date(startDate as string) : undefined,
endDate: endDate ? new Date(endDate as string) : undefined,
search: search as string,
page: page ? parseInt(page as string) : 1,
pageSize: pageSize ? parseInt(pageSize as string) : 20
});
res.json(result);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getFunnelAnalysis(req: Request, res: Response): Promise<void> {
try {
const context = OrderCentralController.getContext(req);
const { platforms, startDate, endDate } = req.query;
const result = await OrderCentralService.getFunnelAnalysis(context.tenantId, {
platforms: platforms ? (platforms as string).split(',') as Platform[] : undefined,
startDate: startDate ? new Date(startDate as string) : undefined,
endDate: endDate ? new Date(endDate as string) : undefined
});
res.json(result);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getPlatformStats(req: Request, res: Response): Promise<void> {
try {
const context = OrderCentralController.getContext(req);
const { platforms, startDate, endDate } = req.query;
const result = await OrderCentralService.getPlatformStats(context.tenantId, {
platforms: platforms ? (platforms as string).split(',') as Platform[] : undefined,
startDate: startDate ? new Date(startDate as string) : undefined,
endDate: endDate ? new Date(endDate as string) : undefined
});
res.json(result);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
static async getCollectionHistory(req: Request, res: Response): Promise<void> {
try {
const context = OrderCentralController.getContext(req);
const { platform, status, page, pageSize } = req.query;
const result = await OrderCentralService.getCollectionHistory(context.tenantId, {
platform: platform as Platform,
status: status as any,
page: page ? parseInt(page as string) : 1,
pageSize: pageSize ? parseInt(pageSize as string) : 20
});
res.json(result);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
private static getContext(req: Request): OrderCentralContext {
return {
tenantId: req.headers['x-tenant-id'] as string || 'default',
shopId: req.headers['x-shop-id'] as string || 'default',
taskId: req.headers['x-task-id'] as string || `task-${Date.now()}`,
traceId: req.headers['x-trace-id'] as string || `trace-${Date.now()}`,
businessType: (req.headers['x-business-type'] as 'TOC' | 'TOB') || 'TOC'
};
}
}

View File

@@ -0,0 +1,300 @@
import { Request, Response } from 'express';
import { OrderFulfillmentService, OrderFulfillmentContext, OrderFulfillmentStatus } from '../services/OrderFulfillmentService';
import { logger } from '../utils/logger';
export class OrderFulfillmentController {
static async initTables(req: Request, res: Response): Promise<void> {
try {
await OrderFulfillmentService.initTables();
res.json({ success: true, message: 'Order fulfillment tables initialized' });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Init tables failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async pullOrder(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { businessType, ...orderData } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: businessType || 'TOC'
};
const result = await OrderFulfillmentService.pullOrder(context, orderData);
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Pull order failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async reviewOrder(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { id } = req.params;
const { approved, reason, note } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.reviewOrder(context, id, {
approved,
reason,
note
});
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Review order failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async allocateWarehouse(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { id } = req.params;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.allocateWarehouse(context, id);
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Allocate warehouse failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async prepareShipment(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { id } = req.params;
const { logisticsOption } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.prepareShipment(context, id, logisticsOption);
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Prepare shipment failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async shipOrder(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { id } = req.params;
const { trackingNumber, carrier, estimatedDelivery } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.shipOrder(context, id, {
trackingNumber,
carrier,
estimatedDelivery: estimatedDelivery ? new Date(estimatedDelivery) : undefined
});
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Ship order failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async confirmDelivery(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { id } = req.params;
const { deliveredAt, signature } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.confirmDelivery(context, id, {
deliveredAt: deliveredAt ? new Date(deliveredAt) : undefined,
signature
});
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Confirm delivery failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async markAsException(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { id } = req.params;
const { reason } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.markAsException(context, id, reason);
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Mark as exception failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async cancelOrder(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { id } = req.params;
const { reason } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.cancelOrder(context, id, reason);
res.json({ success: result.success, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Cancel order failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async getOrder(req: Request, res: Response): Promise<void> {
try {
const { tenantId } = (req as any).traceContext;
const { id } = req.params;
const order = await OrderFulfillmentService.getOrderById(tenantId, id);
if (!order) {
res.status(404).json({ success: false, error: 'Order not found' });
return;
}
res.json({ success: true, data: order });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Get order failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async getOrders(req: Request, res: Response): Promise<void> {
try {
const { tenantId } = (req as any).traceContext;
const { status, shopId, startDate, endDate, page, pageSize } = req.query;
const result = await OrderFulfillmentService.getOrders(tenantId, {
status: status as OrderFulfillmentStatus,
shopId: shopId as string,
startDate: startDate ? new Date(startDate as string) : undefined,
endDate: endDate ? new Date(endDate as string) : undefined,
page: page ? parseInt(page as string) : 1,
pageSize: pageSize ? parseInt(pageSize as string) : 20
});
res.json({ success: true, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Get orders failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async batchReview(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { orderIds } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.batchReview(context, orderIds);
res.json({ success: true, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Batch review failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async batchAllocate(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { orderIds } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.batchAllocate(context, orderIds);
res.json({ success: true, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Batch allocate failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
static async batchShip(req: Request, res: Response): Promise<void> {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { shipments } = req.body;
const context: OrderFulfillmentContext = {
tenantId,
shopId,
taskId: `TASK-${Date.now()}`,
traceId,
businessType: 'TOC'
};
const result = await OrderFulfillmentService.batchShip(context, shipments);
res.json({ success: true, data: result });
} catch (error: any) {
logger.error(`[OrderFulfillmentController] Batch ship failed: ${error.message}`);
res.status(500).json({ success: false, error: error.message });
}
}
}

View File

@@ -10,10 +10,13 @@ import { ConfigService } from '../../services/ConfigService';
import { CrawlerService } from '../../services/CrawlerService';
import { DynamicPricingService } from '../../services/DynamicPricingService';
import { MultiPlatformProductService } from '../../services/MultiPlatformProductService';
import { ProductCollectionService } from '../../services/ProductCollectionService';
import { ProductListingService } from '../../services/ProductListingService';
import { ProductService } from '../../services/ProductService';
import { SupplierInquiryService } from '../../services/SupplierInquiryService';
import { SupplyChainService } from '../../services/SupplyChainService';
import { CrawlerWorker } from '../../workers/CrawlerWorker';
import { logger } from '../../utils/logger';
export class ProductController {
@@ -833,4 +836,205 @@ export class ProductController {
res.status(500).json({ success: false, error: errorMessage });
}
}
/**
* [BE-P101] 创建数据采集任务
*/
static async createCollectionTask(req: Request, res: Response) {
const { platform, sourceUrl, businessType } = req.body;
const traceContext = req.traceContext;
if (!traceContext) {
res.status(400).json({ success: false, error: 'Missing trace context' });
return;
}
const { tenantId, shopId, traceId, userId } = traceContext;
try {
const collectionService = new ProductCollectionService();
const taskId = await collectionService.createTask(
tenantId,
shopId || '',
businessType as 'TOC' | 'TOB',
platform,
sourceUrl,
traceId
);
await AuditService.log({
tenantId,
shopId: shopId || '',
traceId,
userId,
module: 'PRODUCT',
action: 'CREATE_COLLECTION_TASK',
resourceType: 'collection_task',
resourceId: taskId,
afterSnapshot: { platform, sourceUrl, businessType },
result: 'success',
source: 'console'
});
res.status(201).json({ success: true, data: { taskId } });
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
res.status(500).json({ success: false, error: errorMessage });
}
}
/**
* [BE-P101] 执行数据采集任务
*/
static async executeCollectionTask(req: Request, res: Response) {
const { taskId } = req.params;
const traceContext = req.traceContext;
if (!traceContext) {
res.status(400).json({ success: false, error: 'Missing trace context' });
return;
}
const { tenantId, userId } = traceContext;
try {
const collectionService = new ProductCollectionService();
// 异步执行采集任务
collectionService.executeCollection(taskId).catch((error) => {
logger.error(`[ProductController] Collection task failed: ${taskId}`, error);
});
await AuditService.log({
tenantId,
shopId: '',
traceId: traceContext.traceId,
userId,
module: 'PRODUCT',
action: 'EXECUTE_COLLECTION_TASK',
resourceType: 'collection_task',
resourceId: taskId,
result: 'success',
source: 'console'
});
res.json({ success: true, data: { taskId, status: 'started' } });
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
res.status(500).json({ success: false, error: errorMessage });
}
}
/**
* [BE-P101] 获取采集任务列表
*/
static async getCollectionTasks(req: Request, res: Response) {
const traceContext = req.traceContext;
if (!traceContext) {
res.status(400).json({ success: false, error: 'Missing trace context' });
return;
}
const { tenantId } = traceContext;
const { status, platform, limit = '20', offset = '0' } = req.query;
try {
const collectionService = new ProductCollectionService();
const tasks = await collectionService.getTasks(tenantId, {
status: status as any,
platform: platform as string,
limit: parseInt(limit as string),
offset: parseInt(offset as string)
});
res.json({ success: true, data: tasks });
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
res.status(500).json({ success: false, error: errorMessage });
}
}
/**
* [BE-P101] 重试失败的采集任务
*/
static async retryCollectionTask(req: Request, res: Response) {
const { taskId } = req.params;
const traceContext = req.traceContext;
if (!traceContext) {
res.status(400).json({ success: false, error: 'Missing trace context' });
return;
}
const { tenantId, userId } = traceContext;
try {
const collectionService = new ProductCollectionService();
await collectionService.retryTask(taskId);
await AuditService.log({
tenantId,
shopId: '',
traceId: traceContext.traceId,
userId,
module: 'PRODUCT',
action: 'RETRY_COLLECTION_TASK',
resourceType: 'collection_task',
resourceId: taskId,
result: 'success',
source: 'console'
});
res.json({ success: true, data: { taskId, status: 'retrying' } });
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
res.status(500).json({ success: false, error: errorMessage });
}
}
/**
* [BE-P101] 批量创建采集任务
*/
static async batchCreateCollectionTasks(req: Request, res: Response) {
const { tasks, businessType } = req.body;
const traceContext = req.traceContext;
if (!traceContext) {
res.status(400).json({ success: false, error: 'Missing trace context' });
return;
}
const { tenantId, shopId, traceId, userId } = traceContext;
try {
const collectionService = new ProductCollectionService();
const taskIds = await collectionService.batchCreateTasks(
tenantId,
shopId || '',
businessType as 'TOC' | 'TOB',
tasks as Array<{ platform: string; sourceUrl: string }>,
traceId
);
await AuditService.log({
tenantId,
shopId: shopId || '',
traceId,
userId,
module: 'PRODUCT',
action: 'BATCH_CREATE_COLLECTION_TASKS',
resourceType: 'collection_task',
resourceId: taskIds.join(','),
afterSnapshot: { count: taskIds.length },
result: 'success',
source: 'console'
});
res.status(201).json({ success: true, data: { taskIds, count: taskIds.length } });
} catch (err: unknown) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
res.status(500).json({ success: false, error: errorMessage });
}
}
}

View File

@@ -0,0 +1,216 @@
import { Request, Response } from 'express';
import { ReconciliationService } from '../../services/reconciliationService';
import { CreateReconciliationParams } from '../../services/FinanceReconciliationService';
export class ReconciliationController {
/**
* 创建对账记录
*/
static async createReconciliation(req: Request, res: Response) {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { platform, periodStart, periodEnd, expectedAmount, actualAmount, businessType } = req.body;
if (!platform || !periodStart || !periodEnd || expectedAmount === undefined || actualAmount === undefined || !businessType) {
return res.status(400).json({ success: false, error: 'Missing required fields' });
}
const params: CreateReconciliationParams = {
tenantId,
shopId,
platform,
periodStart: new Date(periodStart),
periodEnd: new Date(periodEnd),
expectedAmount,
actualAmount,
traceId,
taskId: `reconciliation_${Date.now()}`,
businessType: businessType as 'TOC' | 'TOB'
};
const result = await ReconciliationService.createReconciliation(params);
res.json({ success: true, data: result });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 获取对账记录列表
*/
static async getReconciliations(req: Request, res: Response) {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { status, platform, startDate, endDate } = req.query;
const reconciliations = await ReconciliationService.getReconciliations(
tenantId,
shopId,
status as any,
platform as string,
startDate ? new Date(startDate as string) : undefined,
endDate ? new Date(endDate as string) : undefined,
traceId
);
res.json({ success: true, data: reconciliations });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 获取对账记录详情
*/
static async getReconciliationById(req: Request, res: Response) {
try {
const { traceId } = (req as any).traceContext;
const { id } = req.params;
if (!id) {
return res.status(400).json({ success: false, error: 'Missing reconciliation ID' });
}
const reconciliation = await ReconciliationService.getReconciliationById(id, traceId);
if (!reconciliation) {
return res.status(404).json({ success: false, error: 'Reconciliation not found' });
}
res.json({ success: true, data: reconciliation });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 处理异常对账
*/
static async handleException(req: Request, res: Response) {
try {
const { traceId } = (req as any).traceContext;
const { id } = req.params;
const { action } = req.body;
if (!id || !action) {
return res.status(400).json({ success: false, error: 'Missing reconciliation ID or action' });
}
const result = await ReconciliationService.handleException(id, action, traceId);
res.json({ success: true, data: result });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 批量对账
*/
static async batchReconciliation(req: Request, res: Response) {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { platforms, periodStart, periodEnd } = req.body;
if (!platforms || !Array.isArray(platforms) || !periodStart || !periodEnd) {
return res.status(400).json({ success: false, error: 'Missing required fields' });
}
const results = await ReconciliationService.batchReconciliation(
tenantId,
shopId,
platforms,
new Date(periodStart),
new Date(periodEnd),
traceId
);
res.json({ success: true, data: results });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 获取对账汇总
*/
static async getReconciliationSummary(req: Request, res: Response) {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { startDate, endDate } = req.query;
if (!startDate || !endDate) {
return res.status(400).json({ success: false, error: 'Missing startDate or endDate' });
}
const summary = await ReconciliationService.getReconciliationSummary(
tenantId,
shopId,
new Date(startDate as string),
new Date(endDate as string),
traceId
);
res.json({ success: true, data: summary });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 自动对账任务
*/
static async autoReconciliation(req: Request, res: Response) {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { platforms } = req.body;
if (!platforms || !Array.isArray(platforms)) {
return res.status(400).json({ success: false, error: 'Missing platforms' });
}
await ReconciliationService.autoReconciliationTask(tenantId, shopId, platforms, traceId);
res.json({ success: true, message: 'Auto reconciliation task started' });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 导出对账报告
*/
static async exportReconciliationReport(req: Request, res: Response) {
try {
const { tenantId, shopId, traceId } = (req as any).traceContext;
const { startDate, endDate } = req.query;
if (!startDate || !endDate) {
return res.status(400).json({ success: false, error: 'Missing startDate or endDate' });
}
const report = await ReconciliationService.exportReconciliationReport(
tenantId,
shopId,
new Date(startDate as string),
new Date(endDate as string),
traceId
);
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename=reconciliation_report_${new Date().toISOString().split('T')[0]}.csv`);
res.send(report);
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
/**
* 初始化对账表
*/
static async initReconciliationTable(req: Request, res: Response) {
try {
await ReconciliationService.initTable();
res.json({ success: true, message: 'Reconciliation table initialized' });
} catch (error: any) {
res.status(500).json({ success: false, error: error.message });
}
}
}

View File

@@ -0,0 +1,28 @@
import { Router } from 'express';
import { AfterSalesController } from '../controllers/AfterSalesController';
import { requireTraceContext } from '../../core/guards/trace-context.guard';
import { requirePermission } from '../../core/guards/rbac.guard';
const router = Router();
router.post('/init-tables', requireTraceContext, requirePermission('admin'), AfterSalesController.initTables);
router.post('/', requireTraceContext, requirePermission('after_sales:write'), AfterSalesController.createRequest);
router.get('/', requireTraceContext, requirePermission('after_sales:read'), AfterSalesController.getRequests);
router.get('/:id', requireTraceContext, requirePermission('after_sales:read'), AfterSalesController.getRequestById);
router.post('/:id/approve', requireTraceContext, requirePermission('after_sales:write'), AfterSalesController.approveRequest);
router.post('/:id/close', requireTraceContext, requirePermission('after_sales:write'), AfterSalesController.closeRequest);
router.post('/refunds/:refundId/process', requireTraceContext, requirePermission('after_sales:write'), AfterSalesController.processRefund);
router.get('/refunds/:refundId', requireTraceContext, requirePermission('after_sales:read'), AfterSalesController.getRefundById);
router.post('/returns/:returnId/process', requireTraceContext, requirePermission('after_sales:write'), AfterSalesController.processReturn);
router.get('/returns/:returnId', requireTraceContext, requirePermission('after_sales:read'), AfterSalesController.getReturnById);
export default router;

View File

@@ -7,6 +7,7 @@ import { SovereignCreditPoolService } from '../../services/SovereignCreditPoolSe
import { SovereigntySettlementService } from '../../services/SovereigntySettlementService';
import { PricingController } from '../controllers/PricingController';
import { SettlementController } from '../controllers/SettlementController';
import { ReconciliationController } from '../controllers/ReconciliationController';
const router = Router();
@@ -131,4 +132,17 @@ router.get('/pool/active', requireTraceContext, requirePermission('finance:read'
}
});
/**
* [BE-F101] 资金对账闭环
*/
router.post('/reconciliation', requireTraceContext, requirePermission('finance:write'), ReconciliationController.createReconciliation);
router.get('/reconciliation', requireTraceContext, requirePermission('finance:read'), ReconciliationController.getReconciliations);
router.get('/reconciliation/:id', requireTraceContext, requirePermission('finance:read'), ReconciliationController.getReconciliationById);
router.post('/reconciliation/:id/exception', requireTraceContext, requirePermission('finance:write'), ReconciliationController.handleException);
router.post('/reconciliation/batch', requireTraceContext, requirePermission('finance:write'), ReconciliationController.batchReconciliation);
router.get('/reconciliation/summary', requireTraceContext, requirePermission('finance:read'), ReconciliationController.getReconciliationSummary);
router.post('/reconciliation/auto', requireTraceContext, requirePermission('finance:write'), ReconciliationController.autoReconciliation);
router.get('/reconciliation/export', requireTraceContext, requirePermission('finance:read'), ReconciliationController.exportReconciliationReport);
router.post('/reconciliation/init', requireTraceContext, requirePermission('finance:admin'), ReconciliationController.initReconciliationTable);
export default router;

View File

@@ -0,0 +1,22 @@
import { Router } from 'express';
import { OrderCentralController } from '../controllers/OrderCentralController';
import { requireTraceContext } from '../../core/guards/trace-context.guard';
import { requirePermission } from '../../core/guards/rbac.guard';
const router = Router();
router.post('/init-tables', requireTraceContext, requirePermission('admin'), OrderCentralController.initTables);
router.post('/platforms', requireTraceContext, requirePermission('order:config'), OrderCentralController.registerPlatform);
router.post('/collect', requireTraceContext, requirePermission('order:write'), OrderCentralController.collectOrders);
router.get('/orders', requireTraceContext, requirePermission('order:read'), OrderCentralController.getOrders);
router.get('/funnel-analysis', requireTraceContext, requirePermission('order:read'), OrderCentralController.getFunnelAnalysis);
router.get('/platform-stats', requireTraceContext, requirePermission('order:read'), OrderCentralController.getPlatformStats);
router.get('/collections', requireTraceContext, requirePermission('order:read'), OrderCentralController.getCollectionHistory);
export default router;

View File

@@ -0,0 +1,36 @@
import { Router } from 'express';
import { OrderFulfillmentController } from '../controllers/OrderFulfillmentController';
import { requireTraceContext } from '../../core/guards/trace-context.guard';
import { requirePermission } from '../../core/guards/rbac.guard';
const router = Router();
router.post('/init-tables', requireTraceContext, requirePermission('admin'), OrderFulfillmentController.initTables);
router.post('/pull', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.pullOrder);
router.get('/', requireTraceContext, requirePermission('order:read'), OrderFulfillmentController.getOrders);
router.get('/:id', requireTraceContext, requirePermission('order:read'), OrderFulfillmentController.getOrder);
router.post('/:id/review', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.reviewOrder);
router.post('/:id/allocate', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.allocateWarehouse);
router.post('/:id/prepare-shipment', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.prepareShipment);
router.post('/:id/ship', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.shipOrder);
router.post('/:id/confirm-delivery', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.confirmDelivery);
router.post('/:id/exception', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.markAsException);
router.post('/:id/cancel', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.cancelOrder);
router.post('/batch/review', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.batchReview);
router.post('/batch/allocate', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.batchAllocate);
router.post('/batch/ship', requireTraceContext, requirePermission('order:write'), OrderFulfillmentController.batchShip);
export default router;

View File

@@ -76,4 +76,11 @@ router.post('/batch', requireTraceContext, requirePermission('product:write'), P
router.post('/:productId/sync-inventory', requireTraceContext, requirePermission('product:write'), ProductController.syncInventory);
router.get('/:productId/sync-status', requireTraceContext, ProductController.getSyncStatus);
// [BE-P101] Product Collection APIs
router.post('/collection-tasks', requireTraceContext, requirePermission('product:write'), ProductController.createCollectionTask);
router.get('/collection-tasks', requireTraceContext, ProductController.getCollectionTasks);
router.post('/collection-tasks/batch', requireTraceContext, requirePermission('product:write'), ProductController.batchCreateCollectionTasks);
router.post('/collection-tasks/:taskId/execute', requireTraceContext, requirePermission('product:write'), ProductController.executeCollectionTask);
router.post('/collection-tasks/:taskId/retry', requireTraceContext, requirePermission('product:write'), ProductController.retryCollectionTask);
export default router;