修复变更、增加筛选条件

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

View File

@@ -10,10 +10,14 @@ export type BusinessRouteId =
| 'passenger_list_import_independent'
| 'passenger_list_import_shared_child'
| 'arrangement_guide_create'
| 'arrangement_guide_update'
| 'arrangement_vehicle_create'
| 'arrangement_vehicle_update'
| 'arrangement_hotel_create'
| 'arrangement_transport_create'
| 'arrangement_transport_update'
| 'arrangement_other_create'
| 'arrangement_other_update'
| 'order_update_shared_plan'
| 'order_update_shared_child'
| 'order_update_independent'
@@ -75,8 +79,8 @@ export function effectiveParserModeForRoute(route: BusinessRouteDefinition, conf
return route.parserPolicy === 'program_only' ? 'program' : 'ai';
}
const PROGRAM_VERSION = 'ltjt-program-parser-v1.0.7';
export const BUSINESS_INPUT_CONTRACT_VERSION = 'business-input-templates-0.5.127';
const PROGRAM_VERSION = 'ltjt-program-parser-v1.0.11';
export const BUSINESS_INPUT_CONTRACT_VERSION = 'business-input-templates-0.5.131';
const lookupFields = ({ customerRequired = true } = {}): ProgramFieldDefinition[] => [
{ key: 'order_no', label: '单号', aliases: ['团号', '订单号'], kind: 'text' },
@@ -236,6 +240,7 @@ export const BUSINESS_ROUTES: readonly BusinessRouteDefinition[] = Object.freeze
{ key: 'group_no', label: '团号', aliases: ['单号'], required: true, kind: 'text' },
{ key: 'supplier', label: '结算单位搜索', aliases: ['供应商搜索', '结算单位'], required: true, kind: 'named_ref' },
{ key: 'vehicle_range', label: '用车日期', aliases: ['日期'], required: true, kind: 'date_range' },
{ key: 'vehicle_type', label: '用车类型', aliases: ['车型'], kind: 'text' },
{ key: 'quantity', label: '数量', required: true, kind: 'positive_integer' },
{ key: 'remark', label: '备注', kind: 'text' }
]
@@ -272,6 +277,7 @@ export const BUSINESS_ROUTES: readonly BusinessRouteDefinition[] = Object.freeze
{ key: 'supplier', label: '结算单位搜索', aliases: ['供应商搜索', '结算单位'], required: true, kind: 'named_ref' },
{ key: 'quantity', label: '数量', required: true, kind: 'positive_integer' },
{ key: 'date', label: '交通日期', aliases: ['业务日期'], required: true, kind: 'date' },
{ key: 'ticket_description', label: '车票说明', aliases: ['车票说明搜索'], kind: 'text' },
{ key: 'item', label: '车票/控位说明', aliases: ['项目', '说明'], kind: 'text' },
{ key: 'remark', label: '备注', kind: 'text' }
]
@@ -343,14 +349,19 @@ export const BUSINESS_ROUTES: readonly BusinessRouteDefinition[] = Object.freeze
routeId: 'arrangement_hotel_update',
releaseOrder: 9,
directive: '安排变更',
aliases: [],
aliases: ['安排酒店变更'],
action: 'arrangement_hotel',
discriminator: 'update',
programVersion: PROGRAM_VERSION,
inputContractVersion: BUSINESS_INPUT_CONTRACT_VERSION,
fields: [
{ key: 'group_no', label: '团号', aliases: ['单号'], required: true, kind: 'text' },
{ key: 'hotel_changes', label: '目标变更', aliases: ['变更目标'], required: true, kind: 'hotel_changes' },
{ key: 'hotel_changes', label: '目标变更', aliases: ['变更目标'], kind: 'hotel_changes' },
{ key: 'start_date', label: '入住日期', kind: 'date' },
{ key: 'end_date', label: '离店日期', aliases: ['离房日期', '退房日期'], kind: 'date' },
{ key: 'resource', label: '酒店搜索', aliases: ['酒店'], kind: 'named_ref' },
{ key: 'room_count', label: '间数', aliases: ['房间数'], kind: 'positive_integer' },
{ key: 'remark', label: '备注', kind: 'text' },
{ key: 'arrangement_type', label: '安排类型', kind: 'text' },
{ key: 'business_reason', label: '业务原因', kind: 'text' }
]
@@ -391,6 +402,84 @@ export const BUSINESS_ROUTES: readonly BusinessRouteDefinition[] = Object.freeze
{ key: 'export_type', label: '文件类型', aliases: ['导出类型'], required: true, kind: 'export_type' },
{ key: 'visitor_name', label: '游客姓名', aliases: ['接机牌姓名'], kind: 'text' }
]
},
{
routeId: 'arrangement_guide_update',
releaseOrder: 20,
directive: '安排导游变更',
aliases: [],
action: 'arrangement_guide',
discriminator: 'update',
parserPolicy: 'program_only',
programVersion: PROGRAM_VERSION,
inputContractVersion: BUSINESS_INPUT_CONTRACT_VERSION,
fields: [
{ key: 'group_no', label: '团号', aliases: ['单号'], required: true, kind: 'text' },
{ key: 'resource', label: '导游搜索', aliases: ['导游'], required: true, kind: 'named_ref' },
{ key: 'remark', label: '备注', kind: 'text' }
]
},
{
routeId: 'arrangement_vehicle_update',
releaseOrder: 21,
directive: '安排用车变更',
aliases: [],
action: 'arrangement_vehicle',
discriminator: 'update',
parserPolicy: 'program_only',
programVersion: PROGRAM_VERSION,
inputContractVersion: BUSINESS_INPUT_CONTRACT_VERSION,
fields: [
{ key: 'group_no', label: '团号', aliases: ['单号'], required: true, kind: 'text' },
{ key: 'supplier', label: '结算单位搜索', aliases: ['供应商搜索', '结算单位'], required: true, kind: 'named_ref' },
{ key: 'vehicle_range', label: '用车日期', aliases: ['日期'], required: true, kind: 'date_range' },
{ key: 'vehicle_type', label: '用车类型', aliases: ['车型'], kind: 'text' },
{ key: 'quantity', label: '数量', required: true, kind: 'positive_integer' },
{ key: 'remark', label: '备注', kind: 'text' }
]
},
{
routeId: 'arrangement_transport_update',
releaseOrder: 22,
directive: '安排大交通变更',
aliases: [],
action: 'arrangement_transport',
discriminator: 'update',
parserPolicy: 'program_only',
programVersion: PROGRAM_VERSION,
inputContractVersion: BUSINESS_INPUT_CONTRACT_VERSION,
fields: [
{ key: 'group_no', label: '团号', aliases: ['单号'], required: true, kind: 'text' },
{ key: 'supplier', label: '结算单位搜索', aliases: ['供应商搜索', '结算单位'], required: true, kind: 'named_ref' },
{ key: 'quantity', label: '数量', required: true, kind: 'positive_integer' },
{ key: 'date', label: '交通日期', aliases: ['业务日期'], required: true, kind: 'date' },
{ key: 'ticket_description', label: '车票说明', aliases: ['车票说明搜索'], kind: 'text' },
{ key: 'item', label: '车票/控位说明', aliases: ['项目', '说明'], kind: 'text' },
{ key: 'remark', label: '备注', kind: 'text' }
]
},
{
routeId: 'arrangement_other_update',
releaseOrder: 23,
directive: '安排其他/备案变更',
aliases: ['安排其他变更', '安排备案变更'],
action: 'arrangement_other',
discriminator: 'update',
parserPolicy: 'program_only',
programVersion: PROGRAM_VERSION,
inputContractVersion: BUSINESS_INPUT_CONTRACT_VERSION,
fields: [
{ key: 'group_no', label: '团号', aliases: ['单号'], required: true, kind: 'text' },
{ key: 'supplier', label: '结算单位搜索', aliases: ['供应商搜索', '结算单位'], required: true, kind: 'named_ref' },
{ key: 'date', label: '业务日期', aliases: ['交通日期'], required: true, kind: 'date' },
{ key: 'item', label: '项目', aliases: ['备案说明'], kind: 'text' },
{ key: 'quantity', label: '数量', kind: 'positive_integer' },
{ key: 'remark', label: '备注', kind: 'text' },
{ key: 'filing_number', label: '备案号', kind: 'text' },
{ key: 'filing_date', label: '备案日期', kind: 'date' },
{ key: 'entry_port', label: '入境口岸', kind: 'text' },
{ key: 'exit_port', label: '出境口岸', kind: 'text' }
]
}
]);

View File

@@ -5,7 +5,7 @@ import { writeEmergencyDiagnostic } from './diagnostics.js';
const { Pool } = pg;
let pool: pg.Pool | null = null;
export const REQUIRED_SCHEMA_VERSION = '023_leader_summary_webhook_delivery';
export const REQUIRED_SCHEMA_VERSION = '024_arrangement_update_routes';
export interface DatabaseReadiness {
ready: boolean;

View File

@@ -584,7 +584,7 @@ function parseHotelChanges(rawValue: string, context: DateContext, nextOccurrenc
if (!parsed.ok) return { ok: false, code: 'program_invalid_value', message: parsed.message, field: 'hotel_changes' };
changes.room_count = parsed.value;
} else {
return { ok: false, code: 'program_business_rule_blocked', message: '安排变更当前只支持酒店离店日期和房间数。', field: 'hotel_changes' };
return { ok: false, code: 'program_business_rule_blocked', message: '目标变更只支持酒店离店日期和房间数;更换酒店请使用安排酒店变更,并填写原入住日期、原离店日期、酒店搜索和间数。', field: 'hotel_changes' };
}
}
return Object.keys(changes).length
@@ -807,6 +807,7 @@ function buildOperation(route: BusinessRouteDefinition, values: Map<string, unkn
existing_refs: { identifier: values.get('group_no') },
arrangement: {
mode: 'create', supplier: values.get('supplier'), start_date: range.start_date, end_date: range.end_date,
...(values.has('vehicle_type') ? { vehicle_type: values.get('vehicle_type') } : {}),
quantity: values.get('quantity'), ...(values.get('remark') ? { remark: values.get('remark') } : {})
}
}) };
@@ -826,6 +827,7 @@ function buildOperation(route: BusinessRouteDefinition, values: Map<string, unkn
return { ok: true, operation: operation(route.action, {
existing_refs: { identifier: values.get('group_no') }, arrangement: {
mode: 'create', supplier: values.get('supplier'), quantity: values.get('quantity'), date: values.get('date'),
...(values.has('ticket_description') ? { ticket_description: values.get('ticket_description') } : {}),
...(values.get('item') ? { item: values.get('item') } : {}), ...(values.get('remark') ? { remark: values.get('remark') } : {})
}
}) };
@@ -867,11 +869,59 @@ function buildOperation(route: BusinessRouteDefinition, values: Map<string, unkn
...(special ? { special_requests: special } : {})
}) };
}
case 'arrangement_guide_update':
case 'arrangement_vehicle_update':
case 'arrangement_transport_update':
case 'arrangement_other_update': {
const guide = route.action === 'arrangement_guide';
const vehicle = route.action === 'arrangement_vehicle';
const other = route.action === 'arrangement_other';
const filing = {
...(values.get('filing_number') ? { number: values.get('filing_number') } : {}),
...(values.get('entry_port') ? { entry_port: values.get('entry_port') } : {}),
...(values.get('exit_port') ? { exit_port: values.get('exit_port') } : {})
};
return { ok: true, operation: operation(route.action, {
existing_refs: { identifier: values.get('group_no') },
arrangement: {
mode: 'update',
...(!guide ? { selection: {
...(vehicle ? values.get('vehicle_range') as JsonRecord : { date: values.get('date') }),
...(other && values.get('filing_date') ? { filing_date: values.get('filing_date') } : {})
} } : {}),
...(vehicle && values.has('vehicle_type') ? { vehicle_type: values.get('vehicle_type') } : {}),
...(route.action === 'arrangement_transport' && values.has('ticket_description') ? { ticket_description: values.get('ticket_description') } : {}),
changes: {
...(guide ? { resource: values.get('resource') } : { supplier: values.get('supplier') }),
...(values.has('quantity') ? { quantity: values.get('quantity') } : {}),
...(values.has('remark') ? { remark: values.get('remark') } : {}),
...(other && Object.keys(filing).length ? { filing } : {})
}
}
}) };
}
case 'arrangement_hotel_update': {
const type = compactText(values.get('arrangement_type'));
if (type && !['酒店', '安排酒店'].includes(type)) {
return { ok: false, code: 'program_business_rule_blocked', message: '安排变更程序模式当前只支持酒店。', field: 'arrangement_type' };
}
const datedFields = ['start_date', 'end_date', 'resource', 'room_count'];
if (datedFields.some((key) => values.has(key))) {
if (values.get('hotel_changes')) return { ok: false, code: 'program_field_conflict', message: '按日期变更酒店时,请使用入住日期、离店日期、酒店搜索和间数,不要混填目标变更。', field: 'hotel_changes' };
const missing = datedFields.find((key) => !values.has(key));
if (missing) return { ok: false, code: 'program_missing_required', message: '请补齐原安排的入住日期、离店日期,以及新酒店搜索和间数。', field: missing };
if (String(values.get('end_date')) <= String(values.get('start_date'))) return { ok: false, code: 'program_invalid_value', message: '原安排的离店日期必须晚于入住日期。', field: 'end_date' };
return { ok: true, operation: operation(route.action, {
existing_refs: { identifier: values.get('group_no') },
arrangement: {
mode: 'update',
selection: { start_date: values.get('start_date'), end_date: values.get('end_date') },
changes: { resource: values.get('resource'), room_count: values.get('room_count'), ...(values.has('remark') ? { remark: values.get('remark') } : {}) }
},
...(special ? { special_requests: special } : {})
}) };
}
if (!values.get('hotel_changes')) return { ok: false, code: 'program_missing_required', message: '请填写目标变更,或提供原安排的入住日期、离店日期、新酒店搜索和间数。', field: 'hotel_changes' };
const changes = parseHotelChanges(String(values.get('hotel_changes') || ''), context, true);
if (!changes.ok) return changes;
return { ok: true, operation: operation(route.action, {

View File

@@ -405,6 +405,9 @@ function arrangementReply(
const contextArrangement = object(context.arrangement);
const target = object(object(object(result.report).requery).subpage).arrangement_target;
const mode = firstText(contextArrangement.mode, context.mode, arrangement.mode);
const updates = mode === 'update'
? { ...object(arrangement.changes), ...object(contextArrangement.changes) }
: {};
const id = identifier ? `团号:${identifier}` : '';
if (mode === 'clear') {
return {
@@ -413,16 +416,19 @@ function arrangementReply(
fields: { identifier, mode: 'clear' }
};
}
const ref = object(contextArrangement.resource || arrangement.resource || contextArrangement.supplier || arrangement.supplier);
const ref = object(updates.resource || updates.supplier || contextArrangement.resource || arrangement.resource || contextArrangement.supplier || arrangement.supplier);
const item = firstText(
updates.item,
contextArrangement.item,
arrangement.item,
object(target).item_value
);
const start = firstText(contextArrangement.start_date, arrangement.start_date);
const end = firstText(contextArrangement.end_date, arrangement.end_date);
const end = firstText(updates.end_date, contextArrangement.end_date, arrangement.end_date);
const date = firstText(contextArrangement.date, arrangement.date);
const quantity = firstNumber(
updates.room_count,
updates.quantity,
contextArrangement.quantity,
arrangement.quantity,
contextArrangement.room_count,

View File

@@ -1893,10 +1893,9 @@ function operationsDashboardRouteFromOperation(value: unknown, fallbackText: unk
if (referenceKind === 'independent_order') return businessRouteById('passenger_list_import_independent');
if (referenceKind === 'shared_child_order') return businessRouteById('passenger_list_import_shared_child');
}
if (action === 'arrangement_hotel') {
if (action.startsWith('arrangement_')) {
const mode = text(jsonObject(data.arrangement).mode || data.mode).trim();
if (mode === 'create') return businessRouteById('arrangement_hotel_create');
if (mode === 'update') return businessRouteById('arrangement_hotel_update');
return candidates.find((route) => route.discriminator === mode) || null;
}
return null;
}