判断修改
This commit is contained in:
1 parent
e06afc8c33
commit
4194a200c1
65 files changed
+9617
-109
No files matched your search
@@ -11,9 +11,13 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过
|
||||
|
||||
## 当前版本
|
||||
|
||||
当前源码版本为 `0.5.177`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。
|
||||
当前源码版本为 `0.5.178`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。
|
||||
|
||||
0.5.177 当前重点:
|
||||
0.5.178 当前重点:
|
||||
|
||||
- 酒店新增取消固定 150 个可序列化字段门槛,改为检查当前已连接表单、有效 MaxI、连续酒店行及每行必要控件;控件唯一且可用、页面加载完成、无 loading/未完成 Ajax,并实际观察结构稳定至少 1 秒后才通过。76 字段两行与较长表单采用同一结构检查;字段总数只作诊断。资源匹配、空槽、付款审核、身份归属及保存回查仍执行。酒店修改/清空和其他业务的门槛不变。真实 ERP 验收待部署方完成;本项运行修复只需替换插件,无迁移。
|
||||
|
||||
继续保留 0.5.177 的车票说明过滤:
|
||||
|
||||
- 程序模式“安排大交通 / 安排大交通变更”新增选填车票说明(别名:车票说明搜索)。与结算单位搜索共同筛选同一条原生候选,填写后仅接受唯一结果;省略沿用首条匹配行为。完整说明与价格等字段仍由 ERP 原生选择联动。原有“车票/控位说明”兼容字段不变。需同步更新服务端,无新增迁移。
|
||||
|
||||
@@ -26,9 +30,7 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过
|
||||
|
||||
- 名单 TSV 在派发校验、领队提取和原生名单合并时保留末尾空列的制表符,只统一换行并移除首尾空行。最后一条电话或备注为空时仍按完整 13 列处理;真正少列、多列及领队不一致仍阻断。本次需同步部署服务端的统一 operation 校验修复,不涉及数据库变更。
|
||||
|
||||
继续保留 0.5.172 的酒店门槛修正:
|
||||
|
||||
- 按维护方要求,将酒店新增 `DoInfo_jiudian` 的最少可序列化字段数从 195 调整为 150,恢复原有页面预检流程。154 字段页面可以通过数量检查;关键字段、loading、稳定 1 秒、资源、空槽、财务状态、归属和保存后回查检查保持原样。酒店修改/清空仍为 150,其他业务阈值不变。低于 150 的页面仍会阻断;真实 ERP 验收由部署方完成。
|
||||
0.5.172 的酒店新增 195 → 150 门槛修正已由 0.5.178 的结构检查取代;酒店修改/清空仍保留原门槛。
|
||||
|
||||
继续保留 0.5.170 的批量子单执行能力:
|
||||
|
||||
|
||||
@@ -6518,7 +6518,7 @@
|
||||
}
|
||||
|
||||
window.LTJTOrderAssistant = {
|
||||
version: '0.5.177',
|
||||
version: '0.5.178',
|
||||
resolveNativeListSearchValues,
|
||||
lookupKeywordMatchesText,
|
||||
inspectLifecycleSearchCriteria: lifecycleSearchCriteria,
|
||||
|
||||
@@ -155,7 +155,6 @@
|
||||
const FORM_READINESS_MINIMUMS = Object.freeze({
|
||||
DoInfo_daoyou: 12,
|
||||
DoInfo_cheliang: 90,
|
||||
DoInfo_jiudian: 150,
|
||||
DoInfo_piao: 120,
|
||||
DoInfo_qita: 120,
|
||||
DoInfoJH: 800,
|
||||
@@ -688,7 +687,102 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Hotel pages vary with the number of native slots. A successful-control
|
||||
// total cannot prove completeness: short complete forms and large partial
|
||||
// forms can fall on either side of the same threshold.
|
||||
function hotelFormStructure(form) {
|
||||
const required = FORM_READINESS_REQUIRED_FIELDS.DoInfo_jiudian;
|
||||
const rowPrefixes = required.filter((name) => name.endsWith('0')).map((name) => name.slice(0, -1));
|
||||
const controls = Array.from(form?.elements || []);
|
||||
const byName = new Map();
|
||||
const indexes = new Set();
|
||||
const disabled = (control) => control.disabled === true || control.matches?.(':disabled') === true;
|
||||
for (const control of controls) {
|
||||
for (const key of new Set([control.name, control.id].filter(Boolean))) {
|
||||
if (!byName.has(key)) byName.set(key, []);
|
||||
byName.get(key).push(control);
|
||||
const match = key.match(/^([a-zA-Z_]+)(\d+)$/);
|
||||
if (match && rowPrefixes.includes(match[1])) indexes.add(Number(match[2]));
|
||||
}
|
||||
}
|
||||
const maxIText = controlValue(getControl(form, 'MaxI'));
|
||||
const maxI = /^[1-9]\d*$/.test(maxIText) && Number.isSafeInteger(Number(maxIText)) ? Number(maxIText) : null;
|
||||
const slots = [...indexes].sort((a, b) => a - b);
|
||||
const blockers = [];
|
||||
const missing = [];
|
||||
const unavailable = [];
|
||||
if (!form || form.isConnected !== true || findForm(CONTRACTS.arrangement_hotel, form.ownerDocument) !== form) {
|
||||
blockers.push('lifecycle_complete_form_missing');
|
||||
}
|
||||
if (form?.ownerDocument?.readyState !== 'complete') blockers.push('lifecycle_document_not_ready');
|
||||
if (maxI === null) blockers.push('lifecycle_hotel_max_i_invalid');
|
||||
// Native MaxI is the slot count (zero-based slots 0..MaxI-1). Enumerate
|
||||
// actual slots, never loop up to an untrusted marker value.
|
||||
if (maxI !== null && (slots.length !== maxI || slots.some((slot, index) => slot !== index))) {
|
||||
blockers.push(`lifecycle_hotel_rows_incomplete:${slots.length}:${maxI}`);
|
||||
}
|
||||
const names = [...new Set([...required, ...slots.flatMap((slot) => rowPrefixes.map((prefix) => `${prefix}${slot}`))])];
|
||||
for (const name of names) {
|
||||
const matches = byName.get(name) || [];
|
||||
if (!matches.length) { missing.push(name); continue; }
|
||||
const control = matches[0];
|
||||
const submit = name === 'SubmitButton';
|
||||
const tag = String(control.tagName || '').toLowerCase();
|
||||
const type = String(control.type || '').toLowerCase();
|
||||
if (matches.length !== 1 || control.isConnected === false || disabled(control)
|
||||
|| (!submit && (control.name !== name || !['input', 'select', 'textarea'].includes(tag)
|
||||
|| NON_SERIALIZABLE_CONTROL_TYPES.has(type)))
|
||||
|| (submit && !['input', 'button'].includes(tag))) unavailable.push(name);
|
||||
}
|
||||
if (missing.length) blockers.push(`lifecycle_required_fields_missing:${missing.join(',')}`);
|
||||
if (unavailable.length) blockers.push(`lifecycle_required_fields_unavailable:${unavailable.join(',')}`);
|
||||
const signature = JSON.stringify([maxIText, controls.map((control) => [
|
||||
control.name || '', control.id || '', control.tagName || '', control.type || '',
|
||||
disabled(control), control.readOnly === true, control.options?.length ?? null
|
||||
])]);
|
||||
return { controls, signature, blockers, missing_required_fields: missing,
|
||||
unavailable_required_fields: unavailable, native_max_i: maxI, hotel_row_count: slots.length };
|
||||
}
|
||||
|
||||
async function waitForHotelCreateReadiness(form, timeoutMs) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
let previous = null;
|
||||
let stableSince = null;
|
||||
let latest;
|
||||
let stable = false;
|
||||
do {
|
||||
const structure = hotelFormStructure(form);
|
||||
const loading = form ? visibleLoadingSignals(form.ownerDocument.defaultView) : [];
|
||||
const count = form ? serializedFieldCount(form) : 0;
|
||||
const ajaxActive = Number(form?.ownerDocument?.defaultView?.jQuery?.active || 0) > 0;
|
||||
const blockers = [...structure.blockers];
|
||||
if (loading.length || ajaxActive) blockers.push('lifecycle_loading_overlay_visible');
|
||||
const signature = JSON.stringify([structure.signature, count, loading, ajaxActive]);
|
||||
const sameNodes = previous && structure.controls.length === previous.controls.length
|
||||
&& structure.controls.every((control, index) => control === previous.controls[index]);
|
||||
const now = Date.now();
|
||||
if (blockers.length) stableSince = null;
|
||||
else if (!previous || signature !== previous.signature || !sameNodes || stableSince === null) stableSince = now;
|
||||
stable = !blockers.length && now - stableSince >= 1000;
|
||||
latest = { ready: stable, strategy: 'native_hotel_structure', native_action: 'DoInfo_jiudian',
|
||||
minimum_successful_controls: null, required_fields: FORM_READINESS_REQUIRED_FIELDS.DoInfo_jiudian,
|
||||
successful_control_count: count, native_max_i: structure.native_max_i,
|
||||
hotel_row_count: structure.hotel_row_count, missing_required_fields: structure.missing_required_fields,
|
||||
unavailable_required_fields: structure.unavailable_required_fields, visible_loading_signals: loading,
|
||||
native_ajax_active: ajaxActive, stable_ms: stableSince === null ? 0 : now - stableSince,
|
||||
blockers, value_redacted: true };
|
||||
previous = { signature, controls: structure.controls };
|
||||
if (stable || now >= deadline) break;
|
||||
await sleep(Math.min(250, deadline - now));
|
||||
} while (true);
|
||||
if (!stable && !latest.blockers.length) latest.blockers.push('lifecycle_form_not_stable');
|
||||
return latest;
|
||||
}
|
||||
|
||||
async function waitForFormReadiness(operation, contract, form, timeoutMs = 12000) {
|
||||
if (operation.action === 'arrangement_hotel' && operation.data?.arrangement?.mode === 'create') {
|
||||
return waitForHotelCreateReadiness(form, timeoutMs);
|
||||
}
|
||||
const target = formReadinessTarget(operation, contract, form);
|
||||
const profile = readinessProfile(operation, contract);
|
||||
if (!profile.minimum_successful_controls) return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "联泰下单助手",
|
||||
"version": "0.5.177",
|
||||
"version": "0.5.178",
|
||||
"description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。",
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
|
||||
@@ -944,7 +944,7 @@
|
||||
|
||||
window.LTJTOrderAssistant = {
|
||||
...(window.LTJTOrderAssistant || {}),
|
||||
version: '0.5.177',
|
||||
version: '0.5.178',
|
||||
openTeamBatchForm,
|
||||
pingTeamBatchFrame,
|
||||
preflightTeamBatchNative,
|
||||
|
||||
Reference in new issue
Block a user