修复变更、增加筛选条件
This commit is contained in:
1 parent
712ec51d68
commit
e06afc8c33
137 files changed
+35872
-275
No files matched your search
@@ -56,14 +56,14 @@
|
||||
label: '团队安排-导游',
|
||||
route: 'arrangement_guide',
|
||||
execution: 'browser_lifecycle',
|
||||
capabilityState: 'validated_unconfirmed_create_clear_only',
|
||||
capabilityState: 'native_create_clear_program_update_pending_live_acceptance',
|
||||
source: 'native-live-validated-2026-09'
|
||||
},
|
||||
arrangement_vehicle: {
|
||||
label: '团队安排-车辆',
|
||||
route: 'arrangement_vehicle',
|
||||
execution: 'browser_lifecycle',
|
||||
capabilityState: 'validated_unconfirmed_create_clear_only',
|
||||
capabilityState: 'native_create_clear_program_update_pending_live_acceptance',
|
||||
source: 'native-live-validated-2026-09'
|
||||
},
|
||||
arrangement_hotel: {
|
||||
@@ -77,14 +77,14 @@
|
||||
label: '团队安排-大交通',
|
||||
route: 'arrangement_transport',
|
||||
execution: 'browser_lifecycle',
|
||||
capabilityState: 'validated_unconfirmed_create_clear_only',
|
||||
capabilityState: 'native_create_clear_program_update_pending_live_acceptance',
|
||||
source: 'native-live-validated-2026-09'
|
||||
},
|
||||
arrangement_other: {
|
||||
label: '团队安排-其他/备案',
|
||||
route: 'arrangement_other',
|
||||
execution: 'browser_lifecycle',
|
||||
capabilityState: 'validated_unconfirmed_create_clear_preserve_filing_only',
|
||||
capabilityState: 'native_create_clear_program_update_preserve_filing_date_pending_live_acceptance',
|
||||
source: 'native-live-validated-2026-09'
|
||||
},
|
||||
order_update_independent: {
|
||||
@@ -234,11 +234,11 @@
|
||||
...['adult', 'child_bed', 'child_no_bed', 'infant', 'leader'].map((key) => `prices.${key}`)
|
||||
]));
|
||||
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 HOTEL_ARRANGEMENT_STATUSES = new Set(['未安排', '未确认']);
|
||||
const PASSENGER_HEADERS = Object.freeze([
|
||||
@@ -856,7 +856,11 @@
|
||||
blockers.push('安排 action 必须包含 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())) blockers.push('用车类型只允许用于安排用车新增/变更,且必须是非空文本。');
|
||||
if (value.ticket_description !== undefined && (action !== 'arrangement_transport' || !['create', 'update'].includes(value.mode)
|
||||
|| typeof value.ticket_description !== 'string' || !value.ticket_description.trim())) blockers.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']);
|
||||
if (Object.keys(value).some((key) => !allowed.has(key))) blockers.push('data.arrangement 包含未声明字段。');
|
||||
if (!['create', 'update', 'clear'].includes(value.mode)) blockers.push('data.arrangement.mode 必须是 create、update 或 clear。');
|
||||
if (value.mode === 'create' && value.target !== undefined) blockers.push('create 模式必须由 ERP 页面证明空槽位,不能预填 target。');
|
||||
@@ -864,16 +868,26 @@
|
||||
if (!isPlainObject(value.target)) blockers.push(`${value.mode} 模式必须提供 data.arrangement.target。`);
|
||||
else {
|
||||
if (Object.keys(value.target).some((key) => !new Set(['slot_index', 'row_id']).has(key))) blockers.push('data.arrangement.target 只允许 slot_index 和 row_id。');
|
||||
if (value.target.slot_index !== 0) blockers.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) blockers.push(`当前安排 ${value.mode} 的目标槽位无效。`);
|
||||
if (action !== 'arrangement_guide' && (typeof value.target.row_id !== 'string' || !value.target.row_id.trim())) blockers.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') blockers.push(`${action} 尚未实测开放 update;当前只开放未确认酒店安排的离店日期和房间数。`);
|
||||
if (!isPlainObject(changes) || Object.keys(changes).length === 0) blockers.push('update 模式必须提供非空 data.arrangement.changes。');
|
||||
for (const key of Object.keys(changes || {})) if (!updateKeys.has(key)) blockers.push(`${action} 尚未实测开放安排变更字段:${key}。`);
|
||||
if (changes?.resource !== undefined) {
|
||||
validateResolvedNamedRef(changes.resource, 'data.arrangement.changes.resource', blockers, { required: true });
|
||||
if (action === 'arrangement_hotel' && (typeof changes.room_type !== 'string' || !changes.room_type.trim())) blockers.push('更换酒店必须由 ERP 联动解析新房型。');
|
||||
} else if (changes?.room_type !== undefined) blockers.push(`${action} 尚未实测开放安排变更字段:room_type。`);
|
||||
if (changes?.supplier !== undefined) {
|
||||
validateResolvedNamedRef(changes.supplier, 'arrangement.changes.supplier', blockers, { required: true });
|
||||
if (typeof changes.item !== 'string' || !changes.item.trim()) blockers.push('变更项目必须由 ERP 供应商候选联动带入。');
|
||||
} else if (changes?.item !== undefined) blockers.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()))) blockers.push('备案变更字段无效。');
|
||||
if (!arrangementStatusAllowed(action, value.mode, value.status, { testMode })) blockers.push(action === 'arrangement_hotel'
|
||||
? 'update 只能作用于 ERP 当前“未安排”或“未确认”酒店安排。'
|
||||
: 'update 只能作用于 ERP 当前“未确认”安排。');
|
||||
@@ -884,7 +898,7 @@
|
||||
if (action === 'arrangement_other' && !isPlainObject(value.filing)) blockers.push('arrangement_other clear 必须携带待保留备案快照。');
|
||||
}
|
||||
if (value.mode !== 'clear' && value.preserve_filing !== undefined) blockers.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, 'data.arrangement.resource', blockers, { required: true });
|
||||
} else if (value.resource !== undefined) {
|
||||
@@ -927,7 +941,7 @@
|
||||
if (finalValue[key] !== undefined && (!isValidDate(finalValue[key]) || (testMode && !finalValue[key].startsWith('2026-09-')))) blockers.push(testMode ? `data.arrangement.${key} 必须是 2026 年 9 月有效日期。` : `data.arrangement.${key} 必须是有效的 YYYY-MM-DD 日期。`);
|
||||
}
|
||||
for (const key of ['quantity', 'room_count']) {
|
||||
if (finalValue[key] !== undefined && (!Number.isInteger(finalValue[key]) || finalValue[key] <= 0)) blockers.push(`data.arrangement.${key} 必须是正整数。`);
|
||||
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)))) blockers.push(`data.arrangement.${key} 必须是正整数。`);
|
||||
}
|
||||
if (action === 'arrangement_hotel' && isValidDate(finalValue.start_date) && isValidDate(finalValue.end_date)
|
||||
&& Date.parse(`${finalValue.end_date}T00:00:00Z`) <= Date.parse(`${finalValue.start_date}T00:00:00Z`)) blockers.push('酒店安排 end_date 必须晚于 start_date。');
|
||||
@@ -1158,26 +1172,75 @@
|
||||
if (manualReviewTargets.length) warnings.push(`已解析但尚未纳入当前自动写入白名单的字段将转人工复核:${[...new Set(manualReviewTargets)].join('、')}。`);
|
||||
}
|
||||
|
||||
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('安排变更日期仅用于定位,不能修改日期或直接指定联动项目。');
|
||||
validateDispatchNamedRef(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 (!isValidDate(selection?.start_date) || !isValidDate(selection?.end_date) || selection.end_date < selection.start_date) errors.push('原用车日期范围无效。');
|
||||
} else if (!isValidDate(selection?.date)) errors.push('原业务日期无效。');
|
||||
if (selection?.filing_date !== undefined && !isValidDate(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 validateDispatchArrangement(value, action, blockers) {
|
||||
if (!isPlainObject(value)) {
|
||||
blockers.push('data.arrangement 必须是对象。');
|
||||
return;
|
||||
}
|
||||
if (value.vehicle_type !== undefined && (action !== 'arrangement_vehicle' || !['create', 'update'].includes(value.mode)
|
||||
|| typeof value.vehicle_type !== 'string' || !value.vehicle_type.trim())) blockers.push('用车类型只允许用于安排用车新增/变更,且必须是非空文本。');
|
||||
if (value.ticket_description !== undefined && (action !== 'arrangement_transport' || !['create', 'update'].includes(value.mode)
|
||||
|| typeof value.ticket_description !== 'string' || !value.ticket_description.trim())) blockers.push('车票说明只允许用于安排大交通新增/变更,且必须是非空文本。');
|
||||
if (value.mode === 'update' && action !== 'arrangement_hotel') {
|
||||
validateOriginalArrangementUpdate(value, action, blockers);
|
||||
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 allowed = action === 'arrangement_hotel'
|
||||
? value.mode === 'update' ? new Set(['mode', 'changes']) : new Set(['mode', 'resource', 'room_type', 'start_date', 'end_date', 'room_count', 'remark'])
|
||||
? value.mode === 'update' ? new Set(['mode', 'changes', 'selection']) : new Set(['mode', 'resource', 'room_type', 'start_date', 'end_date', 'room_count', 'remark'])
|
||||
: common[action] || new Set();
|
||||
if (Object.keys(value).some((key) => !allowed.has(key))) blockers.push('data.arrangement 包含当前业务未开放或执行态字段。');
|
||||
if (action === 'arrangement_hotel' && value.mode === 'update') {
|
||||
const changes = value.changes;
|
||||
if (!isPlainObject(changes) || !Object.keys(changes).length || Object.keys(changes).some((key) => !new Set(['end_date', 'room_count']).has(key))) blockers.push('酒店安排 update 只允许非空 changes.end_date/room_count。');
|
||||
if (!isPlainObject(changes) || !Object.keys(changes).length || Object.keys(changes).some((key) => !new Set(['end_date', 'room_count', 'resource', 'remark']).has(key))) blockers.push('酒店安排 update 只允许非空 changes.end_date/room_count/resource。');
|
||||
if (value.selection !== undefined) {
|
||||
const selection = value.selection;
|
||||
if (!isPlainObject(selection) || Object.keys(selection).some((key) => !['start_date', 'end_date'].includes(key))
|
||||
|| !isValidDate(selection.start_date) || !isValidDate(selection.end_date) || selection.end_date <= selection.start_date) blockers.push('酒店变更必须提供有效的原安排日期。');
|
||||
}
|
||||
if (changes?.resource !== undefined) {
|
||||
validateDispatchNamedRef(changes.resource, 'data.arrangement.changes.resource', blockers, { required: true });
|
||||
if (!isPlainObject(value.selection)) blockers.push('更换酒店必须提供原安排的入住和离店日期。');
|
||||
}
|
||||
if (changes?.end_date !== undefined && !isValidDate(changes.end_date)) blockers.push('changes.end_date 必须是有效的 YYYY-MM-DD 日期。');
|
||||
if (changes?.room_count !== undefined && (!Number.isInteger(changes.room_count) || changes.room_count < 1)) blockers.push('changes.room_count 必须是正整数。');
|
||||
if (value.changes?.remark !== undefined && (typeof value.changes.remark !== 'string' || !value.changes.remark.trim())) blockers.push('变更备注必须为非空文本。');
|
||||
return;
|
||||
}
|
||||
if (value.mode !== 'create') blockers.push(`${action} 当前只允许 create${action === 'arrangement_hotel' ? ' 或已开放的 update' : ''}。`);
|
||||
|
||||
Reference in new issue
Block a user