修复变更、增加筛选条件

This commit is contained in:
andy committed 2026-09-11 21:22:14 +08:00
1 parent 712ec51d68
commit e06afc8c33
137 files changed
+35872 -275

No files matched your search

+1 -1
View File
@@ -100,7 +100,7 @@ const persistedExtensionResultVersions = new Map();
let taskCreateInProgress = false;
const AUTO_HANDOFF_RETRY_MS = 30_000;
const REQUIRED_EXTENSION_VERSION = '0.5.173';
const REQUIRED_EXTENSION_VERSION = '0.5.177';
const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件';
const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件';
const RECONCILE_LABEL = '只读回查 ERP 现有结果';
+80 -14
View File
@@ -1493,11 +1493,11 @@ const VALIDATED_LIFECYCLE_UPDATE_TARGETS = Object.freeze({
order_update_shared_child: new Set(['lodging_note'])
});
const ARRANGEMENT_UPDATE_KEYS = Object.freeze({
arrangement_guide: new Set(),
arrangement_vehicle: new Set(),
arrangement_hotel: new Set(['end_date', 'room_count']),
arrangement_transport: new Set(),
arrangement_other: new Set()
arrangement_guide: new Set(["resource", "remark"]),
arrangement_vehicle: new Set(["supplier", "item", "quantity", "remark"]),
arrangement_hotel: new Set(['end_date', 'room_count', 'resource', 'room_type', 'remark']),
arrangement_transport: new Set(["supplier", "item", "quantity", "remark"]),
arrangement_other: new Set(["supplier", "item", "quantity", "remark", "filing"])
});
const PASSENGER_HEADERS = Object.freeze([
'序号', '姓名', 'NAME', '性别', '出生日期', '出生地', '证件类型', '证件号码',
@@ -2116,20 +2116,59 @@ function validateAgentFiling(value, errors) {
if (value.date !== undefined && !isValidDateString(value.date)) errors.push('operation.data.arrangement.filing.date 必须是有效的 YYYY-MM-DD 日期。');
}
function validateOriginalArrangementUpdate(value, action, errors) {
const guide = action === 'arrangement_guide';
const vehicle = action === 'arrangement_vehicle';
const other = action === 'arrangement_other';
const keys = new Set(guide ? ['resource', 'remark'] : ['supplier', 'quantity', 'remark', ...(other ? ['filing'] : [])]);
if (Object.keys(value).some((key) => !['mode', 'selection', 'changes', ...(vehicle ? ['vehicle_type'] : []), ...(action === 'arrangement_transport' ? ['ticket_description'] : [])].includes(key))) errors.push('安排变更包含未声明字段。');
const changes = value.changes;
if (!isPlainObject(changes)) { errors.push('安排变更缺少新的目标值。'); return; }
if (Object.keys(changes).some((key) => !keys.has(key))) errors.push('安排变更日期仅用于定位,不能修改日期或直接指定联动项目。');
validateAgentNamedRef(changes[guide ? 'resource' : 'supplier'], 'arrangement.changes.resource_or_supplier', errors, { required: true });
if (changes.quantity !== undefined && (!Number.isInteger(changes.quantity) || changes.quantity < 1)) errors.push('安排变更数量必须为正整数。');
if ((vehicle || action === 'arrangement_transport') && changes.quantity === undefined) errors.push('安排变更缺少数量。');
if (changes.remark !== undefined && (typeof changes.remark !== 'string' || !changes.remark.trim())) errors.push('安排变更备注必须为非空文本。');
if (guide) {
if (value.selection !== undefined) errors.push('导游变更按团号定位唯一已有导游,不接受日期。');
} else {
const selection = value.selection;
const dateKeys = vehicle ? ['start_date', 'end_date'] : ['date', ...(other ? ['filing_date'] : [])];
if (!isPlainObject(selection) || Object.keys(selection).some((key) => !dateKeys.includes(key))) errors.push('安排变更需要原安排日期。');
if (vehicle) {
if (!isValidDateString(selection?.start_date) || !isValidDateString(selection?.end_date) || selection.end_date < selection.start_date) errors.push('原用车日期范围无效。');
} else if (!isValidDateString(selection?.date)) errors.push('原业务日期无效。');
if (selection?.filing_date !== undefined && !isValidDateString(selection.filing_date)) errors.push('原备案日期无效。');
}
if (changes.filing !== undefined) {
if (!other || !isPlainObject(changes.filing) || !Object.keys(changes.filing).length
|| Object.keys(changes.filing).some((key) => !['number', 'entry_port', 'exit_port'].includes(key))
|| Object.values(changes.filing).some((v) => typeof v !== 'string' || !v.trim())) errors.push('备案变更只允许明确填写的备案号和入出境口岸;日期保持原值。');
}
}
function validateAgentArrangement(value, action, errors) {
const path = 'operation.data.arrangement';
if (!isPlainObject(value)) {
errors.push(`${path} 必须是对象。`);
return;
}
if (value.vehicle_type !== undefined && (action !== 'arrangement_vehicle' || !['create', 'update'].includes(value.mode)
|| typeof value.vehicle_type !== 'string' || !value.vehicle_type.trim())) errors.push('用车类型只允许用于安排用车新增/变更,且必须是非空文本。');
if (value.ticket_description !== undefined && (action !== 'arrangement_transport' || !['create', 'update'].includes(value.mode)
|| typeof value.ticket_description !== 'string' || !value.ticket_description.trim())) errors.push('车票说明只允许用于安排大交通新增/变更,且必须是非空文本。');
if (value.mode === 'update' && action !== 'arrangement_hotel') {
validateOriginalArrangementUpdate(value, action, errors);
return;
}
const common = {
arrangement_guide: new Set(['mode', 'resource', 'remark']),
arrangement_vehicle: new Set(['mode', 'supplier', 'start_date', 'end_date', 'quantity', 'remark']),
arrangement_transport: new Set(['mode', 'supplier', 'item', 'quantity', 'date', 'remark']),
arrangement_vehicle: new Set(['mode', 'supplier', 'start_date', 'end_date', 'quantity', 'remark', 'vehicle_type']),
arrangement_transport: new Set(['mode', 'supplier', 'item', 'quantity', 'date', 'remark', 'ticket_description']),
arrangement_other: new Set(['mode', 'supplier', 'item', 'quantity', 'date', 'remark', 'filing'])
};
const hotelCreate = new Set(['mode', 'resource', 'room_type', 'start_date', 'end_date', 'room_count', 'remark']);
const hotelUpdate = new Set(['mode', 'changes']);
const hotelUpdate = new Set(['mode', 'changes', 'selection']);
const allowed = action === 'arrangement_hotel'
? value.mode === 'update' ? hotelUpdate : hotelCreate
: common[action] || new Set();
@@ -2141,10 +2180,23 @@ function validateAgentArrangement(value, action, errors) {
return;
}
const keys = Object.keys(value.changes);
pushUnknownKeyError(`${path}.changes`, value.changes, new Set(['end_date', 'room_count']), errors);
pushUnknownKeyError(`${path}.changes`, value.changes, new Set(['end_date', 'room_count', 'resource', 'remark']), errors);
if (value.selection !== undefined) {
if (!isPlainObject(value.selection)) errors.push(`${path}.selection 必须是原酒店安排的日期对象。`);
else {
pushUnknownKeyError(`${path}.selection`, value.selection, new Set(['start_date', 'end_date']), errors);
if (!isValidDateString(value.selection.start_date) || !isValidDateString(value.selection.end_date)
|| value.selection.end_date <= value.selection.start_date) errors.push(`${path}.selection 必须包含有效且离店晚于入住的原安排日期。`);
}
}
if (value.changes.resource !== undefined) {
validateAgentNamedRef(value.changes.resource, `${path}.changes.resource`, errors, { required: true });
if (!isPlainObject(value.selection)) errors.push('更换酒店必须提供原安排的入住和离店日期。');
}
if (!keys.length) errors.push(`${path}.changes 至少需要一个目标字段。`);
if (value.changes.end_date !== undefined && !isValidDateString(value.changes.end_date)) errors.push(`${path}.changes.end_date 必须是有效的 YYYY-MM-DD 日期。`);
if (value.changes.room_count !== undefined && (!Number.isInteger(value.changes.room_count) || value.changes.room_count < 1)) errors.push(`${path}.changes.room_count 必须是正整数。`);
if (value.changes?.remark !== undefined && (typeof value.changes.remark !== 'string' || !value.changes.remark.trim())) errors.push('变更备注必须为非空文本。');
return;
}
@@ -2498,7 +2550,11 @@ function validateArrangementShape(value, action, errors, { testMode = false } =
errors.push('operation.data.arrangement 必须是对象。');
return;
}
const allowed = new Set(['mode', 'target', 'changes', 'resource', 'supplier', 'item', 'room_type', 'date', 'start_date', 'end_date', 'quantity', 'room_count', 'status', 'remark', 'side_effect_policy', 'marker', 'filing', 'preserve_filing']);
if (value.vehicle_type !== undefined && (action !== 'arrangement_vehicle' || !['create', 'update'].includes(value.mode)
|| typeof value.vehicle_type !== 'string' || !value.vehicle_type.trim())) errors.push('用车类型只允许用于安排用车新增/变更,且必须是非空文本。');
if (value.ticket_description !== undefined && (action !== 'arrangement_transport' || !['create', 'update'].includes(value.mode)
|| typeof value.ticket_description !== 'string' || !value.ticket_description.trim())) errors.push('车票说明只允许用于安排大交通新增/变更,且必须是非空文本。');
const allowed = new Set(['mode', 'target', 'changes', 'resource', 'supplier', 'item', 'room_type', 'date', 'start_date', 'end_date', 'quantity', 'room_count', 'status', 'remark', 'side_effect_policy', 'marker', 'filing', 'preserve_filing', 'vehicle_type', 'ticket_description']);
pushUnknownKeyError('operation.data.arrangement', value, allowed, errors);
if (!['create', 'update', 'clear'].includes(value.mode)) errors.push('operation.data.arrangement.mode 必须是 create、update 或 clear。');
if (value.mode === 'create' && value.target !== undefined) errors.push('create 模式必须由 ERP 页面证明空槽位,不能预填 target。');
@@ -2506,16 +2562,26 @@ function validateArrangementShape(value, action, errors, { testMode = false } =
if (!isPlainObject(value.target)) errors.push(`${value.mode} 模式必须提供 operation.data.arrangement.target。`);
else {
if (Object.keys(value.target).some((key) => !new Set(['slot_index', 'row_id']).has(key))) errors.push('operation.data.arrangement.target 只允许 slot_index 和 row_id。');
if (value.target.slot_index !== 0) errors.push(`当前安排 ${value.mode} 只允许精确槽位 0。`);
if (action !== 'arrangement_guide' && value.mode === 'update'
? !Number.isInteger(value.target.slot_index) || value.target.slot_index < 0
: value.target.slot_index !== 0) errors.push(`当前安排 ${value.mode} 的目标槽位无效。`);
if (action !== 'arrangement_guide' && (typeof value.target.row_id !== 'string' || !value.target.row_id.trim())) errors.push(`${action} ${value.mode} 必须提供原生 row_id。`);
}
}
if (value.mode === 'update') {
const changes = value.changes;
const updateKeys = ARRANGEMENT_UPDATE_KEYS[action] || new Set();
if (action !== 'arrangement_hotel') errors.push(`${action} 尚未实测开放 update;当前只开放未确认酒店安排的离店日期和房间数。`);
if (!isPlainObject(changes) || Object.keys(changes).length === 0) errors.push('update 模式必须提供非空 operation.data.arrangement.changes。');
for (const key of Object.keys(changes || {})) if (!updateKeys.has(key)) errors.push(`${action} 尚未实测开放安排变更字段:${key}。`);
if (changes?.resource !== undefined) {
validateResolvedNamedRef(changes.resource, 'operation.data.arrangement.changes.resource', errors, { required: true });
if (action === 'arrangement_hotel' && (typeof changes.room_type !== 'string' || !changes.room_type.trim())) errors.push('更换酒店必须由 ERP 联动解析新房型。');
} else if (changes?.room_type !== undefined) errors.push(`${action} 尚未实测开放安排变更字段:room_type。`);
if (changes?.supplier !== undefined) {
validateResolvedNamedRef(changes.supplier, 'arrangement.changes.supplier', errors, { required: true });
if (typeof changes.item !== 'string' || !changes.item.trim()) errors.push('变更项目必须由 ERP 供应商候选联动带入。');
} else if (changes?.item !== undefined) errors.push('禁止单独修改联动项目。');
if (changes?.filing !== undefined && (!isPlainObject(changes.filing) || !Object.keys(changes.filing).length || Object.keys(changes.filing).some((key) => !['number', 'entry_port', 'exit_port'].includes(key)) || Object.values(changes.filing).some((v) => typeof v !== 'string' || !v.trim()))) errors.push('备案变更字段无效。');
if (action === 'arrangement_hotel'
? !['未安排', '未确认'].includes(value.status)
: value.status !== '未确认') errors.push(action === 'arrangement_hotel'
@@ -2528,7 +2594,7 @@ function validateArrangementShape(value, action, errors, { testMode = false } =
if (action === 'arrangement_other' && !isPlainObject(value.filing)) errors.push('arrangement_other clear 必须携带待保留备案快照。');
}
if (value.mode !== 'clear' && value.preserve_filing !== undefined) errors.push('preserve_filing 只允许用于 arrangement_other clear。');
const finalValue = value.mode === 'update' && isPlainObject(value.changes) ? { ...value, ...value.changes } : value;
const finalValue = value.mode === 'update' && isPlainObject(value.changes) ? { ...value, ...value.changes, ...(value.changes.filing ? { filing: { ...value.filing, ...value.changes.filing } } : {}) } : value;
if (action === 'arrangement_guide' || action === 'arrangement_hotel') validateResolvedNamedRef(value.resource, 'operation.data.arrangement.resource', errors, { required: true });
else if (value.resource !== undefined) errors.push(`${action} 使用 supplier + item,不接受模糊 resource。`);
if (action === 'arrangement_vehicle') {
@@ -2573,7 +2639,7 @@ function validateArrangementShape(value, action, errors, { testMode = false } =
: `operation.data.arrangement.${key} 必须是有效的 YYYY-MM-DD 日期。`);
}
}
for (const key of ['quantity', 'room_count']) if (finalValue[key] !== undefined && (!Number.isInteger(finalValue[key]) || finalValue[key] <= 0)) errors.push(`operation.data.arrangement.${key} 必须是正整数。`);
for (const key of ['quantity', 'room_count']) if (finalValue[key] !== undefined && (!Number.isInteger(finalValue[key]) || (finalValue[key] <= 0 && !(key === 'quantity' && action === 'arrangement_other' && value.mode === 'update' && value.changes?.quantity === undefined && finalValue[key] === 0)))) errors.push(`operation.data.arrangement.${key} 必须是正整数。`);
if (action === 'arrangement_hotel' && isValidDateString(finalValue.start_date) && isValidDateString(finalValue.end_date)
&& Date.parse(`${finalValue.end_date}T00:00:00Z`) <= Date.parse(`${finalValue.start_date}T00:00:00Z`)) errors.push('酒店安排 end_date 必须晚于 start_date。');
}