Files
gongxue-base/apps/server/src/imports/imports.policies.ts
wangziqi ab4765cee5 feat: Excel 导入预检与动态问答,AI 聊天/文件解析体验修复
- imports: 新增 preflight_import 预检报告(判定/分阶段统计/阻断归因/问题/下一步/错误示例),导入任务 settings 落库(映射/校区/更新/重复/未匹配策略),预览应用策略,向导提交写操作日志
- ai-chat: 新增 excel_analyze(ExcelJS)工具,移除附件/上下文截断,start_import_wizard 支持确认参数,ui.import_preflight SSE,预览确认写操作日志
- admin: ImportPreflightCard 渲染与持久化,聊天抽屉布局/侧边栏修复,考勤页 CSS 引入,费用/学生页接口 schema 校验修复
2026-08-05 21:12:50 +08:00

73 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type {
ImportRowAction,
ImportRowStatus,
ImportRunSettings,
} from './imports.types';
import type { ValidatedRow } from './imports.rows';
export interface PolicyPreviewResult {
errors: string[];
action: ImportRowAction | null;
status: ImportRowStatus;
}
function isDuplicateError(error: string): boolean {
return (
error.includes('请勿重复导入') ||
error.includes('请勿重复换宿') ||
error.includes('本次文件中已有')
);
}
function isReferenceError(error: string): boolean {
return (
error.includes('未找到匹配学生') ||
error.includes('缺少学生标识') ||
error.includes('未找到宿舍') ||
error.includes('未找到原宿舍') ||
error.includes('未找到新宿舍') ||
error.includes('未找到该学生在原宿舍的在住记录')
);
}
/**
* 把 AI 预检确认的策略应用到预览行:
* - updateExisting=false已匹配行改为跳过
* - duplicatePolicy=skip文件内重复行跳过并保留提示
* - skipUnmatched=true关系表找不到学生/宿舍的行跳过并保留提示。
*/
export function applyPreviewPolicies(
result: ValidatedRow,
settings: ImportRunSettings | null,
): PolicyPreviewResult {
const errors = [...result.errors];
let action = result.action;
let status: ImportRowStatus = errors.length > 0 ? 'error' : 'valid';
const duplicatePolicy = settings?.duplicatePolicy ?? 'error';
const updateExisting = settings?.updateExisting ?? true;
const skipUnmatched = settings?.skipUnmatched ?? false;
if (duplicatePolicy === 'skip' && errors.some(isDuplicateError)) {
action = 'skip';
status = 'valid';
const keptErrors = errors.filter((error) => !isDuplicateError(error));
keptErrors.push('文件内重复行,已按策略跳过');
errors.splice(0, errors.length, ...keptErrors);
}
if (!updateExisting && errors.length === 0 && action === 'update') {
action = 'skip';
status = 'valid';
errors.push('已匹配现有记录,按策略跳过更新');
}
if (skipUnmatched && status === 'error' && errors.every(isReferenceError)) {
action = 'skip';
status = 'valid';
errors.push('未匹配学生/宿舍,按策略跳过');
}
return { errors, action, status };
}