Files
LWLT-AIBOT/tools/split-plan-reconciliation.test.mjs
2026-08-25 10:33:29 +08:00

256 lines
9.6 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import reconciliation from '../chrome-extension/ltjt-order-assistant/split-plan-reconciliation.js';
const expected = {
date: '2026-9-7',
product_name: '广东直飞行程--衡阳(晚航班)',
product_label: '广东直飞行程--衡阳(晚航班) 6D5N',
customer_name: 'LW辽宁康辉',
planned_capacity: 40,
passenger_counts: { adult: 8, child_bed: 1, leader: 1 }
};
test('matches a persisted split parent row after an ERP error response', () => {
const row = {
group_no: 'LW-260907A-B',
text: '收客中 LW-260907A-B 测试ai员工账号 常规团. 广东直飞行程--衡阳(晚航班) 2026-9-7 6D5N 计划40 2026-9-12 成人8+小(占)1+领队1=合计10'
};
const result = reconciliation.chooseUniqueRows([row], expected);
assert.equal(result.selected.group_no, 'LW-260907A-B');
assert.equal(result.selected.match.customer_match, false);
assert.equal(result.ambiguous, false);
});
test('does not match a row from a missing requested date', () => {
const result = reconciliation.matchRowText(
'LW-260907A-B 广东直飞行程--衡阳(晚航班) 2026-9-7 计划40 成人8+小(占)1+领队1=合计10',
{ ...expected, date: '2026-9-15' }
);
assert.equal(result.matched, false);
assert.ok(result.missing.includes('date'));
});
test('matches a mother-plan row without child customer or passenger criteria', () => {
const result = reconciliation.matchRowText(
'LW-260907A-B 广东直飞行程--衡阳(晚航班) 2026-9-7 计划40',
{
date: '2026-9-7',
product_name: '广东直飞行程--衡阳(晚航班)',
planned_capacity: 40
}
);
assert.equal(result.matched, true);
assert.equal(result.passenger_criteria_present, false);
assert.deepEqual(result.missing, []);
});
test('prefers a unique customer match when generic row criteria are ambiguous', () => {
const rows = [
{
group_no: 'LW-260907A-A',
text: 'LW-260907A-A 广东直飞行程--衡阳(晚航班) 2026-9-7 计划40 成人8+小(占)1+领队1=合计10'
},
{
group_no: 'LW-260907A-B',
text: 'LW-260907A-B LW辽宁康辉 广东直飞行程--衡阳(晚航班) 2026-9-7 计划40 成人8+小(占)1+领队1=合计10'
}
];
const result = reconciliation.chooseUniqueRows(rows, expected);
assert.equal(result.selected.group_no, 'LW-260907A-B');
assert.equal(result.ambiguous, false);
});
test('extracts the concrete child ddid instead of the parent tid', () => {
assert.equal(reconciliation.extractDdidFromAction('OPEN_update(14389,14457)'), '14457');
assert.equal(reconciliation.extractDdidFromAction("OPEN_update('14453','0','LW-260912A-A')"), '14453');
assert.equal(reconciliation.extractDdidFromAction("OPEN_update('14389',0)"), '');
assert.equal(reconciliation.extractDdidFromAction('/system/Business/plan_order.asp?tdid=14389&ddid=14457'), '14457');
});
test('extracts only concrete shared children for the exact parent tid', () => {
const refs = reconciliation.extractSharedChildRefs([
{ onclick: 'OPEN_update(14389,0)', text: '新增子单' },
{ onclick: 'OPEN_update(14389,14457)', text: 'D14457-LW辽宁康辉' },
{ href: '/System/Business/plan_order.asp?tdid=14389&ddid=14458', text: 'D14458-LW辽宁康辉' },
{ onclick: 'OPEN_update(14390,14459)', text: '其他母团' }
], '14389');
assert.deepEqual(refs.map((ref) => ref.child_order_no), ['D14457', 'D14458']);
assert.ok(refs.every((ref) => ref.tid === '14389' && ref.ddid !== '0'));
});
test('classifies a per-date split-order fact with a concrete child', () => {
const row = {
group_no: 'LW-260907TEST-202609-E2E3-A',
tid: '14389',
text: 'LW-260907TEST-202609-E2E3-A 测试ai员工账号 LW辽宁康辉 广东直飞行程--衡阳(晚航班) 2026-9-7 计划40 成人8+小(占)1+领队1=合计10',
child_reference_scan_complete: true,
child_refs: [{ tid: '14389', ddid: '14457', child_order_no: 'D14457', value_redacted: true }]
};
const fact = reconciliation.splitOrderFact(row, {
...expected,
group_suffix: 'TEST-202609-E2E3-A',
marker: 'TEST-202609',
owner_account: '测试ai员工账号'
});
assert.equal(fact.facts_determined, true);
assert.equal(fact.outcome, 'concrete_shared_children_with_requested_facts');
assert.equal(fact.child_count, 1);
assert.equal(fact.customer_persisted_on_native_list, true);
assert.equal(fact.passenger_counts_persisted_on_native_list, true);
});
test('keeps a parent-only split-order result explicit instead of inventing a child', () => {
const fact = reconciliation.splitOrderFact({
group_no: 'LW-260907TEST-202609-E2E3-A',
tid: '14389',
text: 'LW-260907TEST-202609-E2E3-A 测试ai员工账号 LW辽宁康辉 广东直飞行程--衡阳(晚航班) 2026-9-7 计划40 成人8+小(占)1+领队1=合计10',
child_reference_scan_complete: true,
child_refs: []
}, {
...expected,
group_suffix: 'TEST-202609-E2E3-A',
marker: 'TEST-202609',
owner_account: '测试ai员工账号'
});
assert.equal(fact.facts_determined, true);
assert.equal(fact.outcome, 'parent_row_facts_without_concrete_child');
assert.equal(fact.child_count, 0);
});
test('does not call a split-order fact determined without a parent tid or complete child scan', () => {
const fact = reconciliation.splitOrderFact({
group_no: 'LW-260907TEST-202609-E2E3-A',
tid: '',
text: 'LW-260907TEST-202609-E2E3-A 测试ai员工账号 LW辽宁康辉 广东直飞行程--衡阳(晚航班) 2026-9-7 计划40 成人8+小(占)1+领队1=合计10',
child_reference_scan_complete: false,
child_refs: []
}, {
...expected,
group_suffix: 'TEST-202609-E2E3-A',
marker: 'TEST-202609',
owner_account: '测试ai员工账号'
});
assert.equal(fact.facts_determined, false);
assert.equal(fact.outcome, 'split_order_fact_indeterminate');
});
test('builds split-parent requery filters without retaining unrelated list state', () => {
assert.deepEqual(
reconciliation.splitParentExactSearchValues('2027-1-1', '2027-1-30', 'LW-270107A-B'),
{
S_chufariqi: '2027-1-1',
S_chufarizhi: '2027-1-30',
S_tuanxuhao: 'LW-270107A-B',
S_kehuming: '',
S_chanpinming: '',
S_youkexinxi: '',
S_lingdui: '',
S_zhuangtai: ''
}
);
assert.deepEqual(
reconciliation.splitParentDateSearchValues('2027-1-15'),
{
S_chufariqi: '2027-1-15',
S_chufarizhi: '2027-1-15',
S_tuanxuhao: '',
S_kehuming: '',
S_chanpinming: '',
S_youkexinxi: '',
S_lingdui: '',
S_zhuangtai: ''
}
);
});
test('retries returned group-number queries without resubmitting and converges on visible rows', async () => {
const calls = [];
const waits = [];
const result = await reconciliation.verifyReturnedGroupNumbers({
group_numbers: ['LW-270107A-B', 'LW-270115A-A', 'LW-270126A-A'],
retry_delays_ms: [0, 750, 2000],
wait: async (delay) => waits.push(delay),
search_group: async (groupNumber, context) => {
calls.push({ groupNumber, ...context });
return {
found: context.attempt === 2,
row_text_length: context.attempt === 2 ? 120 : 0
};
}
});
assert.equal(result.complete, true);
assert.equal(result.attempt_count, 2);
assert.equal(result.retry_count, 1);
assert.deepEqual(waits, [750]);
assert.equal(calls.length, 6);
assert.ok(result.rows.every((row) => row.status === 'parent_group_found' && row.attempt === 2));
assert.deepEqual(result.attempts.map((attempt) => attempt.missing_count), [3, 0]);
});
test('keeps a persistently missing returned group incomplete after the bounded retry budget', async () => {
const waits = [];
const result = await reconciliation.verifyReturnedGroupNumbers({
group_numbers: ['LW-270107A-B'],
retry_delays_ms: [0, 750, 2000],
wait: async (delay) => waits.push(delay),
search_group: async () => ({ found: false, row_text_length: 0 })
});
assert.equal(result.complete, false);
assert.equal(result.attempt_count, 3);
assert.equal(result.retry_count, 2);
assert.deepEqual(waits, [750, 2000]);
assert.equal(result.rows[0].status, 'parent_group_not_found');
assert.deepEqual(result.attempts.map((attempt) => attempt.missing_count), [1, 1, 1]);
});
test('requires date reconciliation when any complete ERP response group remains unverified', () => {
const common = {
write_attempted: true,
capture_success: true,
group_numbers: ['LW-270107A-B', 'LW-270115A-A', 'LW-270126A-A'],
requested_date_count: 3,
fact_probe_enabled: false
};
assert.equal(reconciliation.shouldReconcileSplitParent({
...common,
verification_complete: false
}), true);
assert.equal(reconciliation.shouldReconcileSplitParent({
...common,
verification_complete: true
}), false);
assert.equal(reconciliation.shouldReconcileSplitParent({
...common,
capture_success: false,
verification_complete: true
}), true);
assert.equal(reconciliation.shouldReconcileSplitParent({
...common,
write_attempted: false,
verification_complete: false
}), false);
});
test('prefers a successful date fallback row over an earlier exact-query miss', () => {
assert.deepEqual(reconciliation.mergeVerificationRows([
{
group_number: 'LW-270107A-B',
source: 'erp_response_retry',
status: 'parent_group_not_found',
row_text_length: 0
},
{
group_number: 'LW-270107A-B',
source: 'post_write_list_fallback',
status: 'parent_group_found',
row_text_length: 120
}
]), [{
group_number: 'LW-270107A-B',
source: 'post_write_list_fallback',
status: 'parent_group_found',
row_text_length: 120
}]);
});