import test from 'node:test'; import assert from 'node:assert/strict'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const plans = require('../chrome-extension/ltjt-order-assistant/operation-plans.js'); const baseCreate = { action: 'team_order_create', order_nature: 'test', submit_mode: 'dry_run', data: { test_marker: 'PLAN-TEST', product: { name: '遇见老挝' }, departure_dates: ['2026-08-11'], passenger_counts: { adult: 2, expected_total: 2 }, op_user: { name: '测试OP' }, sales_user: { name: '测试销售' }, }, }; test('team-single remains the only live browser execution path', () => { const result = plans.validateOperation(baseCreate); assert.equal(result.ok, true); assert.equal(result.action, 'team_order_create'); assert.equal(result.execution, 'browser_team_single'); assert.equal(result.noErpWrite, false); }); test('team-batch absorbs the handoff fallback rule without enabling native batch submit', () => { const operation = { ...baseCreate, action: 'team_order_batch_create', data: { ...baseCreate.data, departure_dates: ['2026-08-11', '2026-08-18'], }, }; const result = plans.validateOperation(operation); assert.equal(result.ok, true); assert.equal(result.execution, 'browser_team_single_fallback'); assert.equal(result.noErpWrite, false); assert.match(result.warnings.join('\n'), /DoInfoJHs/); const expanded = plans.expandTeamBatch(operation); assert.equal(expanded.length, 2); assert.deepEqual(expanded.map((item) => item.data.departure_dates[0]), ['2026-8-11', '2026-8-18']); assert.deepEqual(expanded.map((item) => item.data.test_marker), [ 'PLAN-TEST-D20260811', 'PLAN-TEST-D20260818', ]); }); test('order updates require an identifier and only run a no-write browser preview', () => { const result = plans.validateOperation({ action: 'order_update', identifier: 'LW-270520A-A', data: { updates: { actions: [{ target: 'rooms.TWN', operation: 'set', value: 10 }], }, }, }); assert.equal(result.ok, true); assert.equal(result.execution, 'browser_dry_run'); assert.equal(result.noErpWrite, true); assert.equal(result.identifier, 'LW-270520A-A'); }); test('parent confirmation export is blocked before any browser operation', () => { const result = plans.validateOperation({ action: 'confirmation_export', route: 'split_parent', identifier: 'PARENT-EXAMPLE', data: { existing_refs: { parent_group_no: 'PARENT-EXAMPLE' } }, }); assert.equal(result.ok, false); assert.match(result.blockers.join('\n'), /母团/); assert.equal(result.execution, 'blocked'); }); test('confirmation export normalizes supported aliases and blocks unknown artifacts', () => { const valid = plans.validateOperation({ action: 'confirmation_export', identifier: 'LW-270520A-A', exportTypes: ['confirmation', 'job'], data: { existing_refs: { identifier: 'LW-270520A-A' } }, }); assert.equal(valid.ok, true); assert.deepEqual(valid.exportTypes, ['xingyou-confirm', 'job-order']); const invalid = plans.validateOperation({ action: 'confirmation_export', identifier: 'LW-270520A-A', exportTypes: ['visitor-detail'], data: { existing_refs: { identifier: 'LW-270520A-A' } }, }); assert.equal(invalid.ok, false); assert.match(invalid.blockers.join('\n'), /不支持/); }); test('unknown operations fail closed', () => { const result = plans.validateOperation({ action: 'delete_order', data: {} }); assert.equal(result.ok, false); assert.equal(result.execution, 'blocked'); assert.match(result.blockers.join('\n'), /不支持/); }); test('normalizes handoff create tasks without inventing missing ERP values', () => { const operation = plans.normalizeOperation({ operation: 'create_order', route: 'team_single', fields: { productName: '遇见老挝', departureDate: '2026-08-11', pax: 4, op: '测试OP', salesperson: '测试销售', }, originalText: '交接包格式任务', }); assert.equal(operation.action, 'team_order_create'); assert.equal(operation.data.product.name, '遇见老挝'); assert.equal(operation.data.passenger_counts.adult, 4); assert.equal(operation.data.customer.name, ''); assert.equal(operation.data.test_marker, ''); }); test('routes a test split-parent task into the guarded live browser adapter', () => { const operation = plans.normalizeOperation({ operation: 'create_order', route: 'split_parent', fields: { productName: '示例线路', departureDates: ['2026-08-15'], cycle: 'weekly', plannedPax: 20, groupsPerDate: 1, bookingCustomer: '测试渠道', followOp: '测试OP', salesperson: '测试销售', orderNature: 'test', testMarker: 'SPLIT-PARENT-TEST', }, }); const result = plans.validateOperation(operation); assert.equal(operation.action, 'shared_plan_create'); assert.equal(result.ok, true); assert.equal(result.execution, 'browser_live_split_parent'); assert.equal(result.definition.liveSubmit, 'test-only'); assert.equal(operation.data.planned_capacity, 20); assert.equal(operation.data.op_user.name, '测试OP'); }); test('absorbs split-child parent reference without fabricating a product or parent id', () => { const operation = plans.normalizeOperation({ operation: 'create_order', route: 'split_child', fields: { parentGroupNo: 'PARENT-EXAMPLE', departureDate: '2026-08-15', bookingCustomer: '测试渠道', pax: 8, prices: { adult: 100 }, op: '测试OP', salesperson: '测试销售', orderNature: 'test', testMarker: 'SPLIT-CHILD-TEST', }, }); const result = plans.validateOperation(operation); assert.equal(operation.action, 'shared_child_order_create'); assert.equal(result.ok, true); assert.equal(operation.data.parent_group_no, 'PARENT-EXAMPLE'); assert.equal(operation.data.product.name, ''); assert.equal(result.execution, 'browser_live_split_child'); }); test('routes a traveler-only handoff update into the traveler import business action', () => { const operation = plans.normalizeOperation({ operation: 'update_order', identifier: 'LW-270520A-A', updatePlan: { actions: [{ target: 'travelerList', operation: 'traveler_upsert', value: { mode: 'append_and_verify' } }], }, attachments: ['inputs/traveler-list-example.xlsx'], }); const result = plans.validateOperation(operation); assert.equal(operation.action, 'passenger_list_import'); assert.equal(result.ok, true); assert.equal(result.execution, 'browser_dry_run'); assert.equal(result.definition.dryRunAdapter, 'inspect_existing_order_traveler'); }); test('applies room and remark updates to a browser-read snapshot without writing ERP', () => { const preview = plans.buildUpdatePreview({ action: 'order_update', identifier: 'LW-270520A-A', data: { updates: { actions: [ { target: 'rooms.TWN', operation: 'delta', value: 2 }, { target: 'remark', operation: 'append', value: '客户补充备注' }, ], }, }, }, { rooms: { TWN: 3 }, pax: { adult: 4 }, prices: { adult: 100 }, remark: '原备注', }); assert.equal(preview.status, 'ready'); assert.equal(preview.snapshot.rooms.TWN, 5); assert.match(preview.snapshot.remark, /原备注/); assert.match(preview.snapshot.remark, /客户补充备注/); assert.equal(preview.no_erp_write, true); }); test('builds export-only source paths and never emits a save action', () => { const plan = plans.buildConfirmationExportPlan({ action: 'confirmation_export', identifier: 'LW-270520A-A', exportTypes: ['confirmation', 'liantai', 'job'], data: { existing_refs: { identifier: 'LW-270520A-A' } }, }, { ddid: '123', tid: '456' }); assert.equal(plan.export_only, true); assert.equal(plan.never_resave, true); assert.deepEqual(plan.types, ['xingyou-confirm', 'liantai-confirm', 'job-order']); assert.deepEqual(plan.artifacts.map((item) => item.path), [ '/System/Business/orders_confirm_news.asp', '/System/Business/orders_confirm_new.asp', '/System/Business/teams_beian1.asp', ]); assert.equal(plan.no_erp_write, true); });