fix(imports): 修复 AI 导入向导多工作表与表头误判
- AI resolve 生成向导时按阶段携带全部 sheetNames,不再只取第一张表 - ImportStageRequest 支持 sheets 数组并兼容旧 sheet;手动重传同步修复 - headerMatches 收窄为单向包含,避免宿舍号被原/新宿舍号反向匹配 - suggestStep 增加入住/换宿显式表头信号,修复入住表误判为换宿 - 预检与预览按工作表逐表解析列映射,兼容异构表头 - 修复预检卡生成向导成功后按钮未复位 loading 的问题 - 补充 mapping/预检/run/ai-chat 多工作表测试
This commit is contained in:
@@ -1884,7 +1884,7 @@ describe('AiChatService', () => {
|
||||
'ai',
|
||||
expect.objectContaining({ originalName: 'students.xlsx' }),
|
||||
3,
|
||||
[{ stepKey: 'students', sheet: '学生', headerRow: 1 }],
|
||||
[{ stepKey: 'students', sheets: ['学生'], headerRow: 1 }],
|
||||
{ students: { name: '姓名', studentNo: '学号' } },
|
||||
{ updateExisting: false },
|
||||
);
|
||||
@@ -1906,6 +1906,101 @@ describe('AiChatService', () => {
|
||||
).toMatchObject({ resolved: true, runId: 'run-9' });
|
||||
});
|
||||
|
||||
it('resolveImportPreflight 多工作表阶段携带全部 sheetNames 生成导入任务', async () => {
|
||||
const { service } = createService();
|
||||
const conversations = {
|
||||
findOne: jest.fn().mockResolvedValue({ id: 3, userId: 7 }),
|
||||
};
|
||||
const message = {
|
||||
id: 42,
|
||||
conversationId: 3,
|
||||
role: 'assistant',
|
||||
metadata: {
|
||||
a2uiImportPreflight: {
|
||||
verdict: 'needs_input',
|
||||
stages: [
|
||||
{
|
||||
stepKey: 'checkins',
|
||||
label: '入住管理',
|
||||
sheetNames: ['四人间女', '四人间男'],
|
||||
headers: ['姓名', '学号', '手机号', '宿舍号', '入住日期'],
|
||||
mapping: { name: '姓名', roomNumber: '宿舍号' },
|
||||
missingRequired: [],
|
||||
total: 2,
|
||||
create: 2,
|
||||
update: 0,
|
||||
error: 0,
|
||||
skip: 0,
|
||||
},
|
||||
],
|
||||
blocks: [],
|
||||
questions: [],
|
||||
nextSteps: [],
|
||||
errorSamples: [],
|
||||
attachmentId: 9,
|
||||
headerRow: 1,
|
||||
permittedSteps: ['checkins'],
|
||||
resolved: false,
|
||||
runId: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
const messages = {
|
||||
findOne: jest.fn().mockResolvedValue(message),
|
||||
save: jest.fn(async (value) => value),
|
||||
exists: jest.fn().mockResolvedValue(false),
|
||||
};
|
||||
const attachmentService = {
|
||||
requireReadyOwned: jest.fn().mockResolvedValue([
|
||||
{
|
||||
id: 9,
|
||||
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
originalName: 'dorm.xlsx',
|
||||
size: 10,
|
||||
},
|
||||
]),
|
||||
readStoredBuffer: jest.fn().mockResolvedValue(Buffer.from('x')),
|
||||
};
|
||||
const importsService = {
|
||||
createRun: jest.fn().mockResolvedValue({
|
||||
id: 'run-9',
|
||||
fileName: 'dorm.xlsx',
|
||||
sheets: [],
|
||||
steps: [
|
||||
{
|
||||
stepKey: 'checkins',
|
||||
label: '入住管理',
|
||||
sheets: ['四人间女', '四人间男'],
|
||||
status: 'pending',
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
(service as unknown as { conversations: unknown }).conversations = conversations;
|
||||
(service as unknown as { messages: unknown }).messages = messages;
|
||||
(service as unknown as { attachmentService: unknown }).attachmentService = attachmentService;
|
||||
(service as unknown as { importsService: unknown }).importsService = importsService;
|
||||
|
||||
await service.resolveImportPreflight(
|
||||
authenticatedUser,
|
||||
42,
|
||||
{ clientRequestId: 'multi-sheet', mapping: {}, settings: {} },
|
||||
new AbortController().signal,
|
||||
jest.fn(),
|
||||
jest.fn(),
|
||||
);
|
||||
|
||||
expect(importsService.createRun).toHaveBeenCalledWith(
|
||||
{ id: 7, permissions: ['ai:chat:use'], isSuperAdmin: false },
|
||||
'ai',
|
||||
expect.objectContaining({ originalName: 'dorm.xlsx' }),
|
||||
3,
|
||||
[{ stepKey: 'checkins', sheets: ['四人间女', '四人间男'], headerRow: 1 }],
|
||||
{},
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it('resolveImportPreflight 已生成向导时幂等重放,不重复建任务', async () => {
|
||||
const { service } = createService();
|
||||
const conversations = {
|
||||
|
||||
Reference in New Issue
Block a user