修复变更、增加筛选条件
This commit is contained in:
1 parent
712ec51d68
commit
e06afc8c33
137 files changed
+35872
-275
No files matched your search
@@ -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 现有结果';
|
||||
|
||||
@@ -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。');
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user