feat: Excel 导入预检与动态问答,AI 聊天/文件解析体验修复

- imports: 新增 preflight_import 预检报告(判定/分阶段统计/阻断归因/问题/下一步/错误示例),导入任务 settings 落库(映射/校区/更新/重复/未匹配策略),预览应用策略,向导提交写操作日志
- ai-chat: 新增 excel_analyze(ExcelJS)工具,移除附件/上下文截断,start_import_wizard 支持确认参数,ui.import_preflight SSE,预览确认写操作日志
- admin: ImportPreflightCard 渲染与持久化,聊天抽屉布局/侧边栏修复,考勤页 CSS 引入,费用/学生页接口 schema 校验修复
This commit is contained in:
2026-08-05 21:12:50 +08:00
parent b70f45fb04
commit ab4765cee5
44 changed files with 2449 additions and 166 deletions

View File

@@ -0,0 +1,74 @@
import { ImportsController } from './imports.controller';
describe('ImportsController', () => {
const principalRequest = {
user: { id: 7, username: 'admin', permissions: [], isSuperAdmin: true },
headers: { 'user-agent': 'jest-agent' },
connection: { remoteAddress: '127.0.0.1' },
};
it('提交导入阶段成功后写入操作日志', async () => {
const commitStep = jest.fn().mockResolvedValue({
runId: 'run-1',
stepKey: 'students',
status: 'committed',
created: 2,
updated: 0,
skipped: 0,
failed: 0,
total: 2,
nextStepKey: null,
runStatus: 'committed',
message: '阶段「学生档案」提交完成:新建 2、更新 0、跳过 0、失败 0全部阶段已完成',
});
const opLog = { log: jest.fn().mockResolvedValue(undefined) };
const controller = new ImportsController({ commitStep } as never, opLog as never);
await controller.commit(
principalRequest as never,
'run-1',
'students',
{ decisions: [] },
);
expect(opLog.log).toHaveBeenCalledWith(
expect.objectContaining({
userId: 7,
username: 'admin',
module: '批量导入',
action: '提交导入阶段',
detail: expect.stringContaining('提交完成'),
targetType: 'import_run',
ipAddress: '127.0.0.1',
userAgent: 'jest-agent',
}),
);
});
it('非 committed 回执conflict/already_committed不重复写日志', async () => {
const commitStep = jest.fn().mockResolvedValue({
runId: 'run-1',
stepKey: 'students',
status: 'conflict',
created: 0,
updated: 0,
skipped: 0,
failed: 0,
total: 0,
nextStepKey: null,
runStatus: 'ready',
message: '请先完成前置阶段',
});
const opLog = { log: jest.fn().mockResolvedValue(undefined) };
const controller = new ImportsController({ commitStep } as never, opLog as never);
await controller.commit(
principalRequest as never,
'run-1',
'transfers',
{ decisions: [] },
);
expect(opLog.log).not.toHaveBeenCalled();
});
});