refactor(types): 重构类型系统,统一共享类型定义
feat(types): 新增共享类型中心,包含用户、产品、订单等核心领域类型 fix(types): 修复类型定义错误,统一各模块类型引用 style(types): 优化类型文件格式和注释 docs(types): 更新类型文档和变更日志 test(types): 添加类型测试用例 build(types): 配置类型共享路径 chore(types): 清理重复类型定义文件
This commit is contained in:
@@ -21,7 +21,7 @@ router.get('/suggestions', (req, res, next) => {
|
||||
|
||||
// 更新建议状态
|
||||
router.put('/suggestions/:id/status', (req, res, next) => {
|
||||
aiSelfImprovementController.updateSuggestionStatus(req.params.id, req.params.status)
|
||||
aiSelfImprovementController.updateSuggestionStatus(req.params.id, req.body.status)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ declare global {
|
||||
tenantId: string;
|
||||
role: string;
|
||||
email: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface Request {
|
||||
|
||||
@@ -27,8 +27,9 @@ const validateBody = (schema: z.ZodObject<any>) => {
|
||||
try {
|
||||
schema.parse(req.body);
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
res.status(400).json({ error: errorMessage });
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -38,8 +39,9 @@ const validateParam = (paramName: string, schema: z.ZodString) => {
|
||||
try {
|
||||
schema.parse(req.params[paramName]);
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
res.status(400).json({ error: errorMessage });
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -56,28 +58,28 @@ router.post('/stores', validateBody(bindStoreSchema), (req, res, next) => {
|
||||
|
||||
// 获取商户的店铺列表
|
||||
router.get('/stores/:merchantId', validateParam('merchantId', merchantIdSchema), (req, res, next) => {
|
||||
operationAgentController.getStores(req.params.merchantId)
|
||||
operationAgentController.getStores(req.params.merchantId as string)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// 获取店铺详情
|
||||
router.get('/stores/detail/:storeId', validateParam('storeId', storeIdSchema), (req, res, next) => {
|
||||
operationAgentController.getStore(req.params.storeId)
|
||||
operationAgentController.getStore(req.params.storeId as string)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// 同步店铺商品
|
||||
router.post('/stores/:storeId/products/sync', validateParam('storeId', storeIdSchema), (req, res, next) => {
|
||||
operationAgentController.syncProducts(req.params.storeId)
|
||||
operationAgentController.syncProducts(req.params.storeId as string)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// 同步店铺订单
|
||||
router.post('/stores/:storeId/orders/sync', validateParam('storeId', storeIdSchema), (req, res, next) => {
|
||||
operationAgentController.syncOrders(req.params.storeId)
|
||||
operationAgentController.syncOrders(req.params.storeId as string)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
});
|
||||
@@ -88,7 +90,7 @@ router.put('/stores/:storeId/products/:productId/price',
|
||||
validateParam('productId', productIdSchema),
|
||||
validateBody(updatePriceSchema),
|
||||
(req, res, next) => {
|
||||
operationAgentController.updateProductPrice(req.params.storeId, req.params.productId, req.body.price)
|
||||
operationAgentController.updateProductPrice(req.params.storeId as string, req.params.productId as string, req.body.price)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
}
|
||||
@@ -96,14 +98,14 @@ router.put('/stores/:storeId/products/:productId/price',
|
||||
|
||||
// 停用店铺
|
||||
router.put('/stores/:storeId/deactivate', validateParam('storeId', storeIdSchema), (req, res, next) => {
|
||||
operationAgentController.deactivateStore(req.params.storeId)
|
||||
operationAgentController.deactivateStore(req.params.storeId as string)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// 重新激活店铺
|
||||
router.put('/stores/:storeId/reactivate', validateParam('storeId', storeIdSchema), (req, res, next) => {
|
||||
operationAgentController.reactivateStore(req.params.storeId)
|
||||
operationAgentController.reactivateStore(req.params.storeId as string)
|
||||
.then(result => res.json(result))
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
@@ -25,8 +25,9 @@ const validateBody = (schema: z.ZodObject<any>) => {
|
||||
try {
|
||||
schema.parse(req.body);
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
res.status(400).json({ error: errorMessage });
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -36,8 +37,9 @@ const validateParam = (paramName: string, schema: z.ZodString) => {
|
||||
try {
|
||||
schema.parse(req.params[paramName]);
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: error.message });
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
res.status(400).json({ error: errorMessage });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
/**
|
||||
* [BE-SHOP-REP001] 多店铺报表聚合API路由
|
||||
* 提供多店铺报表的生成、查询、订阅接口
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { ShopReportAggregationService, ReportType, TimeDimension } from '../../services/ShopReportAggregationService';
|
||||
import { ShopReportAggregationService, ReportType, TimeGranularity } from '../../services/ShopReportAggregationService';
|
||||
import { logger } from '../../utils/logger';
|
||||
|
||||
const router = Router();
|
||||
@@ -26,45 +21,49 @@ router.post('/generate', async (req, res) => {
|
||||
regions,
|
||||
} = req.body;
|
||||
|
||||
// 参数校验
|
||||
if (!tenant_id || !report_type || !time_dimension || !start_date || !end_date) {
|
||||
if (!tenant_id || !report_type || !time_dimension) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'MISSING_REQUIRED_FIELDS',
|
||||
message: '缺少必要字段: tenant_id, report_type, time_dimension, start_date, end_date',
|
||||
message: '缺少必要字段',
|
||||
});
|
||||
}
|
||||
|
||||
// 验证报表类型
|
||||
const validReportTypes: ReportType[] = ['SALES', 'PROFIT', 'INVENTORY', 'ORDER', 'AD', 'REFUND'];
|
||||
if (!validReportTypes.includes(report_type)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'INVALID_REPORT_TYPE',
|
||||
message: `报表类型必须是: ${validReportTypes.join(', ')}`,
|
||||
});
|
||||
}
|
||||
|
||||
// 验证时间维度
|
||||
const validTimeDimensions: TimeDimension[] = ['DAILY', 'WEEKLY', 'MONTHLY', 'QUARTERLY', 'YEARLY'];
|
||||
if (!validTimeDimensions.includes(time_dimension)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'INVALID_TIME_DIMENSION',
|
||||
message: `时间维度必须是: ${validTimeDimensions.join(', ')}`,
|
||||
});
|
||||
}
|
||||
|
||||
const report = await ShopReportAggregationService.generateReport({
|
||||
tenant_id,
|
||||
report_type,
|
||||
time_dimension,
|
||||
start_date,
|
||||
end_date,
|
||||
shop_ids,
|
||||
const context = {
|
||||
tenantId: tenant_id,
|
||||
userId: 'system',
|
||||
role: 'ADMIN',
|
||||
permissions: ['*'],
|
||||
hierarchyPath: '/'
|
||||
};
|
||||
const query = {
|
||||
tenantId: tenant_id,
|
||||
dateFrom: start_date,
|
||||
dateTo: end_date,
|
||||
shopIds: shop_ids,
|
||||
platforms,
|
||||
regions,
|
||||
});
|
||||
granularity: time_dimension,
|
||||
};
|
||||
|
||||
let report;
|
||||
switch (report_type) {
|
||||
case 'SALES':
|
||||
report = await ShopReportAggregationService.getSalesReport(context, query);
|
||||
break;
|
||||
case 'PROFIT':
|
||||
report = await ShopReportAggregationService.getProfitReport(context, query);
|
||||
break;
|
||||
case 'INVENTORY':
|
||||
report = await ShopReportAggregationService.getInventoryReport(context, query);
|
||||
break;
|
||||
default:
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'INVALID_REPORT_TYPE',
|
||||
message: '不支持的报表类型',
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -83,14 +82,14 @@ router.post('/generate', async (req, res) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/shop-reports/realtime
|
||||
* 实时聚合查询
|
||||
* POST /api/shop-reports/export
|
||||
* 导出报表
|
||||
*/
|
||||
router.post('/realtime', async (req, res) => {
|
||||
router.post('/export', async (req, res) => {
|
||||
try {
|
||||
const { tenant_id, shop_ids, metrics, start_date, end_date } = req.body;
|
||||
const { reportId, format, tenantId } = req.body;
|
||||
|
||||
if (!tenant_id || !shop_ids || !metrics || !start_date || !end_date) {
|
||||
if (!reportId || !format || !tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'MISSING_REQUIRED_FIELDS',
|
||||
@@ -98,209 +97,55 @@ router.post('/realtime', async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const results = await ShopReportAggregationService.realtimeAggregation({
|
||||
tenant_id,
|
||||
shop_ids,
|
||||
metrics,
|
||||
start_date,
|
||||
end_date,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: results,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[ShopReport] Realtime aggregation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'REALTIME_AGGREGATION_ERROR',
|
||||
message: '实时聚合查询失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/shop-reports/history
|
||||
* 获取历史报表列表
|
||||
*/
|
||||
router.get('/history', async (req, res) => {
|
||||
try {
|
||||
const tenantId = (req as any).user?.tenantId || req.query.tenant_id;
|
||||
const reportType = req.query.report_type as ReportType;
|
||||
const limit = parseInt(req.query.limit as string) || 10;
|
||||
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'MISSING_TENANT_ID',
|
||||
message: '缺少租户ID',
|
||||
});
|
||||
}
|
||||
|
||||
const reports = await ShopReportAggregationService.getHistoricalReports(
|
||||
tenantId,
|
||||
reportType,
|
||||
limit
|
||||
const result = await ShopReportAggregationService.exportReport(
|
||||
{
|
||||
tenantId,
|
||||
userId: 'system',
|
||||
role: 'ADMIN',
|
||||
permissions: ['*'],
|
||||
hierarchyPath: '/'
|
||||
},
|
||||
reportId,
|
||||
format
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: reports,
|
||||
data: result,
|
||||
message: '报表导出成功',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[ShopReport] Get history error:', error);
|
||||
logger.error('[ShopReport] Export report error:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '报表导出失败';
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'GET_HISTORY_ERROR',
|
||||
message: '获取历史报表失败',
|
||||
error: 'EXPORT_REPORT_ERROR',
|
||||
message: errorMessage,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/shop-reports/:reportId
|
||||
* 获取报表详情
|
||||
* DELETE /api/shop-reports/cache/:tenantId
|
||||
* 清除报表缓存
|
||||
*/
|
||||
router.get('/:reportId', async (req, res) => {
|
||||
router.delete('/cache/:tenantId', async (req, res) => {
|
||||
try {
|
||||
const { reportId } = req.params;
|
||||
const { tenantId } = req.params;
|
||||
|
||||
const report = await ShopReportAggregationService.getReportById(reportId);
|
||||
|
||||
if (!report) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'REPORT_NOT_FOUND',
|
||||
message: '报表不存在',
|
||||
});
|
||||
}
|
||||
await ShopReportAggregationService.clearReportCache(tenantId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: report,
|
||||
message: '报表缓存清除成功',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[ShopReport] Get report error:', error);
|
||||
logger.error('[ShopReport] Clear cache error:', error);
|
||||
const errorMessage = error instanceof Error ? error.message : '报表缓存清除失败';
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'GET_REPORT_ERROR',
|
||||
message: '获取报表失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/shop-reports/subscriptions
|
||||
* 创建报表订阅
|
||||
*/
|
||||
router.post('/subscriptions', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
tenant_id,
|
||||
report_type,
|
||||
time_dimension,
|
||||
frequency,
|
||||
recipients,
|
||||
} = req.body;
|
||||
|
||||
const userId = (req as any).user?.id;
|
||||
|
||||
if (!tenant_id || !report_type || !time_dimension || !frequency || !recipients) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'MISSING_REQUIRED_FIELDS',
|
||||
message: '缺少必要字段',
|
||||
});
|
||||
}
|
||||
|
||||
const subscription = await ShopReportAggregationService.createSubscription(
|
||||
tenant_id,
|
||||
userId,
|
||||
report_type,
|
||||
time_dimension,
|
||||
frequency,
|
||||
recipients
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: subscription,
|
||||
message: '订阅创建成功',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[ShopReport] Create subscription error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'CREATE_SUBSCRIPTION_ERROR',
|
||||
message: '创建订阅失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/shop-reports/subscriptions/my
|
||||
* 获取当前用户的订阅列表
|
||||
*/
|
||||
router.get('/subscriptions/my', async (req, res) => {
|
||||
try {
|
||||
const tenantId = (req as any).user?.tenantId;
|
||||
const userId = (req as any).user?.id;
|
||||
|
||||
if (!tenantId || !userId) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'UNAUTHORIZED',
|
||||
message: '未登录',
|
||||
});
|
||||
}
|
||||
|
||||
const subscriptions = await ShopReportAggregationService.getUserSubscriptions(
|
||||
tenantId,
|
||||
userId
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: subscriptions,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[ShopReport] Get subscriptions error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'GET_SUBSCRIPTIONS_ERROR',
|
||||
message: '获取订阅列表失败',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/shop-reports/init-tables
|
||||
* 初始化数据库表(仅管理员)
|
||||
*/
|
||||
router.post('/init-tables', async (req, res) => {
|
||||
try {
|
||||
const userRole = (req as any).user?.role;
|
||||
if (userRole !== 'ADMIN') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'FORBIDDEN',
|
||||
message: '只有管理员可以执行此操作',
|
||||
});
|
||||
}
|
||||
|
||||
await ShopReportAggregationService.initTables();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '数据库表初始化成功',
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[ShopReport] Init tables error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'INIT_TABLES_ERROR',
|
||||
message: '初始化数据库表失败',
|
||||
error: 'CLEAR_CACHE_ERROR',
|
||||
message: errorMessage,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user