merge: integrate shared child batch creation
This commit is contained in:
commit
0d20544d52
32 files changed
+958
-90
No files matched your search
@@ -405,6 +405,62 @@ test('identifier-only native list searches receive a calendar-safe plus/minus on
|
||||
assert.match(adapter, /listExpectedPresent && present && !ddidMatched/);
|
||||
});
|
||||
|
||||
test('shared-child batch resolver expands the inclusive range and hard-matches concrete parent entry rows by product', async () => {
|
||||
const inpage = await readFile(new URL('../chrome-extension/ltjt-order-assistant/inpage.js', import.meta.url), 'utf8');
|
||||
const sandbox = { window: {} };
|
||||
vm.runInNewContext(inpage, sandbox);
|
||||
const assistant = sandbox.window.LTJTOrderAssistant;
|
||||
const dates = Array.from(assistant.inspectInclusiveDateRange('2026-10-06', '2026-11-03'));
|
||||
assert.equal(dates.length, 29);
|
||||
assert.equal(dates[0], '2026-10-06');
|
||||
assert.equal(dates.at(-1), '2026-11-03');
|
||||
|
||||
const parentLink = {
|
||||
tagName: 'A',
|
||||
textContent: '新增子单',
|
||||
closest() { return parentRow; },
|
||||
getAttribute(name) {
|
||||
return name === 'onclick' ? "OPEN_update('61006','0')" : '';
|
||||
}
|
||||
};
|
||||
const parentRow = {
|
||||
id: 'tr_61006',
|
||||
tagName: 'TR',
|
||||
textContent: 'LW-261006-A 老挝行程--广东 2026-10-6',
|
||||
getAttribute() { return ''; },
|
||||
querySelectorAll(selector) { return selector === 'a' ? [parentLink] : []; }
|
||||
};
|
||||
const scopeDocument = { querySelectorAll: () => [parentRow] };
|
||||
const matched = assistant.inspectSharedPlanBatchCandidates(scopeDocument, '2026-10-06', '老挝行程--广东');
|
||||
assert.deepEqual(JSON.parse(JSON.stringify(matched.candidates)), [{
|
||||
kind: 'shared_plan',
|
||||
identifier: 'LW-261006-A',
|
||||
parent_group_no: 'LW-261006-A',
|
||||
plan_no: 'LW-261006-A',
|
||||
tid: '61006',
|
||||
departure_date: '2026-10-06',
|
||||
row_id: 'tr_61006',
|
||||
row_text: 'LW-261006-A 老挝行程--广东 2026-10-6',
|
||||
internal_ref_complete: true
|
||||
}]);
|
||||
assert.equal(assistant.inspectSharedPlanBatchCandidates(scopeDocument, '2026-10-06', '其他产品').candidates.length, 0);
|
||||
|
||||
const resolutionBody = inpage.slice(
|
||||
inpage.indexOf('async function resolveSharedChildBatchOperation'),
|
||||
inpage.indexOf('async function resolveLifecycleOperation')
|
||||
);
|
||||
const executionBody = inpage.slice(
|
||||
inpage.indexOf('async function createSplitChildBatchLive'),
|
||||
inpage.indexOf('function normalizeExportType')
|
||||
);
|
||||
assert.match(resolutionBody, /for \(const departureDate of dates\)/);
|
||||
assert.match(resolutionBody, /shared_child_batch_no_matching_parent/);
|
||||
assert.match(resolutionBody, /batch_targets: batchTargets/);
|
||||
assert.match(executionBody, /await createSplitChildLive\(childOperation\)/);
|
||||
assert.match(executionBody, /status: 'not_started'/);
|
||||
assert.doesNotMatch(executionBody, /retry/i);
|
||||
});
|
||||
|
||||
test('business lookup uses customer/date as the native primary lookup and keeps optional facts supplemental', async () => {
|
||||
const inpage = await readFile(new URL('../chrome-extension/ltjt-order-assistant/inpage.js', import.meta.url), 'utf8');
|
||||
const sandbox = { window: {} };
|
||||
@@ -1318,7 +1374,7 @@ test('passenger explicit server success is terminal without a post-save row requ
|
||||
assert.doesNotMatch(passengerBranch, /passengerRequery|verifyLifecycleOperation/);
|
||||
assert.doesNotMatch(background, /attemptAutomaticPassengerReconciliation/);
|
||||
assert.match(background, /名单已取得 ERP 明确成功响应,按业务规则确认录入成功/);
|
||||
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.169'/);
|
||||
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.170'/);
|
||||
});
|
||||
|
||||
test('uncertain lifecycle writes can only converge through a read-only plugin requery', async () => {
|
||||
@@ -1528,7 +1584,7 @@ test('lifecycle execution keeps the MV3 worker alive until the durable task sett
|
||||
assert.match(background, /arrangement_resource_candidate_data_missing/);
|
||||
});
|
||||
|
||||
test('extension 0.5.169 keeps reload dormant unless its durable execution state is fully idle', async () => {
|
||||
test('extension 0.5.170 keeps reload dormant unless its durable execution state is fully idle', async () => {
|
||||
const [background, bridge] = await Promise.all([
|
||||
readFile(new URL('../chrome-extension/ltjt-order-assistant/background.js', import.meta.url), 'utf8'),
|
||||
readFile(new URL('../chrome-extension/ltjt-order-assistant/business-bridge.js', import.meta.url), 'utf8')
|
||||
@@ -1967,7 +2023,7 @@ test('delete guard distinguishes independent, shared child, and shared parent ro
|
||||
for (const operation of operations) assert.equal(plans.validateOperation(operation).ok, true, plans.validateOperation(operation).blockers.join('; '));
|
||||
|
||||
const mapping = JSON.parse(await readFile(new URL('../mappings/lifecycle.mapping.json', import.meta.url), 'utf8'));
|
||||
assert.equal(mapping.current_extension_version, '0.5.169');
|
||||
assert.equal(mapping.current_extension_version, '0.5.170');
|
||||
assert.equal(
|
||||
mapping.updates.order_update_independent.field_mapped_pending_live_validation['pax.child_no_bed'],
|
||||
'ertrenshu'
|
||||
@@ -2351,10 +2407,10 @@ test('schema and browser adapters contain the v2 safety fields and no confirm ov
|
||||
assert.match(inpage, /`ys_danweiid\$\{index\}`, resolvedCustomerId/);
|
||||
assert.doesNotMatch(inpage, /product_customer_source_region|sourceRegionCheck|source_reference/);
|
||||
assert.doesNotMatch(teamBatchInpage, /product_customer_source_region|sourceRegionCheck/);
|
||||
assert.equal(extensionManifest.version, '0.5.169');
|
||||
assert.match(inpage, /version: '0\.5\.169'/);
|
||||
assert.match(teamBatchInpage, /version: '0\.5\.169'/);
|
||||
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.169'/);
|
||||
assert.equal(extensionManifest.version, '0.5.170');
|
||||
assert.match(inpage, /version: '0\.5\.170'/);
|
||||
assert.match(teamBatchInpage, /version: '0\.5\.170'/);
|
||||
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.170'/);
|
||||
assert.match(inpage, /function strictIsoDate\(value\)/);
|
||||
assert.match(inpage, /const startDate = strictIsoDate\(controlCanonicalValue\(form, 'riqi0'\)\)/);
|
||||
assert.match(inpage, /const endDate = strictIsoDate\(controlCanonicalValue\(form, 'riqis0'\)\)/);
|
||||
|
||||
@@ -214,6 +214,71 @@ test('shared-child leader lookup survives the strict execution gate', () => {
|
||||
assert.deepEqual(validateStandardOperation(operation), []);
|
||||
});
|
||||
|
||||
test('shared-child batch dispatch is Program-only, resolves all parents, and enters the guarded batch adapter', () => {
|
||||
const parsed = {
|
||||
action: 'shared_child_order_batch_create',
|
||||
order_nature: 'formal',
|
||||
submit_mode: 'dry_run',
|
||||
data: {
|
||||
customer: { name: '合成客户', keyword: '合成客户' },
|
||||
product: { name: '老挝行程--广东', keyword: '老挝行程--广东' },
|
||||
leader: { name: '张三', keyword: '张三' },
|
||||
recurrence: { start_date: '2026-10-06', end_date: '2026-11-03' },
|
||||
passenger_counts: { adult: 20, leader: 1 }
|
||||
}
|
||||
};
|
||||
const dispatch = plans.validateDispatchOperation(parsed);
|
||||
assert.equal(dispatch.ok, true, dispatch.blockers.join('; '));
|
||||
assert.equal(dispatch.execution, 'browser_live_split_child_batch');
|
||||
assert.equal(dispatch.requiresErpResolution, true);
|
||||
assert.equal(plans.validateOperation(parsed).ok, false, 'execution gate requires ERP-resolved batch targets');
|
||||
|
||||
const resolved = {
|
||||
...parsed,
|
||||
data: {
|
||||
...parsed.data,
|
||||
batch_targets: [
|
||||
{ parent_group_no: 'LW-261006-A', tid: '61006', departure_date: '2026-10-06' },
|
||||
{ parent_group_no: 'LW-261103-A', tid: '61103', departure_date: '2026-11-03' }
|
||||
],
|
||||
resolution: {
|
||||
stage: 'erp_readonly', target: 'batch', candidate_count: 2,
|
||||
target_kind: 'shared_plan', write_attempted: false
|
||||
}
|
||||
}
|
||||
};
|
||||
const execution = plans.validateOperation(resolved);
|
||||
assert.equal(execution.ok, true, execution.blockers.join('; '));
|
||||
assert.equal(execution.execution, 'browser_live_split_child_batch');
|
||||
assert.equal(execution.noErpWrite, false);
|
||||
assert.deepEqual(validateStandardOperation(resolved), []);
|
||||
});
|
||||
|
||||
test('shared-child batch execution rejects duplicate or out-of-range resolved parents', () => {
|
||||
const operation = {
|
||||
action: 'shared_child_order_batch_create',
|
||||
order_nature: 'formal',
|
||||
submit_mode: 'dry_run',
|
||||
data: {
|
||||
customer: { name: '合成客户' },
|
||||
product: { name: '合成产品' },
|
||||
recurrence: { start_date: '2026-10-06', end_date: '2026-11-03' },
|
||||
passenger_counts: { adult: 20, leader: 1 },
|
||||
batch_targets: [
|
||||
{ parent_group_no: 'LW-261006-A', tid: '61006', departure_date: '2026-10-06' },
|
||||
{ parent_group_no: 'LW-261006-A', tid: '61006', departure_date: '2026-11-04' }
|
||||
],
|
||||
resolution: {
|
||||
stage: 'erp_readonly', target: 'batch', candidate_count: 2,
|
||||
target_kind: 'shared_plan', write_attempted: false
|
||||
}
|
||||
}
|
||||
};
|
||||
const result = plans.validateOperation(operation);
|
||||
assert.equal(result.ok, false);
|
||||
assert.match(result.blockers.join('\n'), /重复母团目标|不在请求日期范围/);
|
||||
});
|
||||
|
||||
test('blocks a marked split-child parent when the child remark loses the test marker', () => {
|
||||
const result = plans.validateOperation({
|
||||
action: 'shared_child_order_create',
|
||||
|
||||
@@ -153,6 +153,7 @@ test('background timing hook covers every executor through the shared result and
|
||||
'verifyLifecycleOperation',
|
||||
'createSplitParentLive',
|
||||
'createSplitChildLive',
|
||||
'createSplitChildBatchLive',
|
||||
'exportConfirmationSources',
|
||||
'preflightRawInstruction',
|
||||
'liveSubmitApproved',
|
||||
|
||||
Reference in new issue
Block a user