修复一个插件bug
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# Hotel form readiness threshold correction
|
||||
|
||||
## Scope and authorization
|
||||
|
||||
- User authorized local code changes and a deployable plugin package; deployment and live ERP validation will be performed by the user.
|
||||
- Correct hotel-create preflight rejecting an otherwise complete dynamic form with `lifecycle_form_incomplete:154:195`; preserve other business/write gates.
|
||||
- Latest user direction: use `DoInfo_jiudian: 150` as the minimal correction, replacing the previously delivered structural readiness solution.
|
||||
- Feature mode: this task owns its record, relevant extension implementation/regressions, and synchronized release files. No canonical memory edits.
|
||||
- No subagents: repository instructions require explicit user consent, which has not been provided.
|
||||
|
||||
## Worktree safety
|
||||
|
||||
- Branch: `main`
|
||||
- Worktree: `/Users/andy/IdeaProjects/LWLT-AIBOT`
|
||||
- Base commit: `275564d1bf00b36bf2ea64edeff3d04a6a401bd0`
|
||||
- Existing changes: untracked `.idea/`, unrelated and preserved.
|
||||
- Other worktrees: none.
|
||||
- Overlap result: Clear for source/release scope; `.idea/` excluded.
|
||||
|
||||
## Current status — 0.5.172 minimal threshold correction
|
||||
|
||||
- Relative to the original base, the only lifecycle-adapter implementation change is `DoInfo_jiudian: 195` → `DoInfo_jiudian: 150`.
|
||||
- Restored the original readiness flow, removing this task's 0.5.171 structural/MaxI changes. The source was first verified byte-identical to the previously delivered 0.5.171 ZIP before replacing these known task-owned edits.
|
||||
- Hotel create now accepts 150 or more serialized fields when original required-field/loading/stability and subsequent business gates pass. Update/clear stay at 150; other actions unchanged. Complete pages below 150 can still be blocked.
|
||||
- Version synchronization advanced to 0.5.172; 0.5.171 ZIP and its manifest archived alongside 0.5.170. Deployment and live ERP acceptance remain the user's next step.
|
||||
|
||||
## Current 0.5.172 verification
|
||||
|
||||
- Targeted regressions PASS, 9/9: 149 blocked; 150/154/195/442 pass the quantity gate; required fields, loading, stability, identity, resource, slot finance/audit and ownership still enforced.
|
||||
- Full verification PASS on `/private/tmp/ltjt-hotel-150-verify-ho763clc`, an otherwise identical copy excluding local secrets, `.idea/`, Finder files and generated output, with dependencies reused through a symlink. Original local files preserved.
|
||||
- `node --run check:repo`: 10/10 PASS; `node --run check`: PASS; `node --run test:control-plane`: 183/183 PASS; `node --run test:legacy`: 287/287 PASS; `node --run build`: PASS.
|
||||
- The 9 targeted tests are included in the 287 legacy tests. Temporary localhost mock HTTP tests ran within the already authorized validation scope; no ERP/runtime deployment access.
|
||||
- `git diff --check`: PASS. ZIP entry set and all 20 source files match byte-for-byte, with release-manifest SHA-256 verified. Prior 0.5.171 archive retains its original manifest/hash.
|
||||
- Final local review confirms the adapter differs from the committed base only in the requested 195 → 150 constant. Source and package are ready for the user's deployment test.
|
||||
|
||||
## Superseded 0.5.171 implementation
|
||||
|
||||
- Implemented hotel-create structural readiness: valid current positive MaxI, existing/enabled required slot-0 controls, connected form, no visible loading, and an observed stable interval of at least one second. Serialized count remains diagnostic; no replacement fixed minimum.
|
||||
- MaxI, control names/ids/types/disabled state and select option counts participate in stability detection, including same-count structural changes. Timeout cannot substitute for an observed stable sample.
|
||||
- Hotel update/clear and other actions retain their prior minimum counts. Full preflight still applies identity, resource, slot, finance/audit and ownership gates; native submit and post-save evidence paths are unchanged.
|
||||
- Extension/source handshake, platform minimum, mapping, version assertions and release gate synchronized to `0.5.171`; previous `0.5.170` package/manifest preserved in dated release archive.
|
||||
|
||||
## Previous 0.5.171 verification
|
||||
|
||||
- Regression-first check reproduced `lifecycle_form_incomplete:154:195` on the original code; missing-field and update/clear baseline tests already passed.
|
||||
- Targeted polling/structure and full-preflight regressions pass after the fix; fixtures are synthetic and do not represent a live page capture.
|
||||
- ZIP has 20 files, with the complete entry set and every file (including README) byte-identical to current source.
|
||||
- Initial full-check attempt exposed absent local dependencies (`tsc`, `tsx`, etc.). Installed locked dependencies with scripts disabled; the first network-restricted attempt was stopped, then installation succeeded with network permission. Lockfile unchanged.
|
||||
- Root hygiene also reports pre-existing/unrelated `.idea/` and Finder `.DS_Store` files; these are preserved and excluded from the plugin package.
|
||||
- `node --run check`: PASS; `node --run build`: PASS.
|
||||
- `node --run test:control-plane`: PASS, 183/183.
|
||||
- `node --run check:repo`: the original checkout reports two unrelated IDE/Finder hygiene failures; all 10 checks PASS on an otherwise identical validation copy excluding `.idea/`, `.DS_Store`, local secrets, and generated output.
|
||||
- `node --run test:legacy`: PASS, 290/290, on that validation copy. Includes 12 new readiness/preflight regression tests. Localhost-only mock HTTP tests required permission outside the filesystem/network sandbox; no real application services or ERP were contacted.
|
||||
- Initial legacy regression also found four escaped old-version assertions; these were synchronized to `0.5.171` before the final green run.
|
||||
- Validation copy: `/private/tmp/ltjt-hotel-readiness-01a0858b-verify-96e49iaz`; dependencies reused through a symlink without reinstalling them during validation. Command outputs remain task-prefixed temporary files in `/private/tmp`, not repository evidence or deployed runtime logs.
|
||||
- Final local review: scope limited to hotel-create readiness plus synchronized release metadata; native submit, resource selection, identity/ownership, slot finance/audit and post-save verification implementations unchanged. No subagent review claimed.
|
||||
- `git diff --check`: PASS. Final package verified against every source file, including README; archived previous ZIP verified against its archived release manifest.
|
||||
- No live ERP access, writes, service restarts, or deployment.
|
||||
|
||||
## Follow-ups and promotion candidates
|
||||
|
||||
- User deployment and live hotel-task acceptance pending; local 0.5.172 implementation, validation and package complete.
|
||||
- Promotion candidate: canonical current state may advance the extension baseline to `0.5.172` after integration; the accepted implementation is the fixed 150 hotel-create threshold, not the superseded structural solution. No product capability or parser contract expansion. Evidence: this task's focused regressions and release verification. Human runtime validation remains required before asserting deployed ERP success.
|
||||
@@ -100,7 +100,7 @@ const persistedExtensionResultVersions = new Map();
|
||||
let taskCreateInProgress = false;
|
||||
const AUTO_HANDOFF_RETRY_MS = 30_000;
|
||||
|
||||
const REQUIRED_EXTENSION_VERSION = '0.5.170';
|
||||
const REQUIRED_EXTENSION_VERSION = '0.5.172';
|
||||
const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件';
|
||||
const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件';
|
||||
const RECONCILE_LABEL = '只读回查 ERP 现有结果';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 当前基线
|
||||
|
||||
- Chrome 插件:`0.5.170`
|
||||
- Chrome 插件:`0.5.172`
|
||||
- Agent Prompt:`ltjt-agent-prompt-v1.8-independent-headcount-categories`
|
||||
- 生命周期契约:`ltjt-lifecycle-v2.9-roster-leader-contact-2026-08`
|
||||
- 五个 Skill:`0.5.126`;运营 DOCX:`0.5.127`
|
||||
@@ -12,6 +12,7 @@
|
||||
- `0.5.167` 保留插件侧的空闲证明和受控重载安全协议,并继续包含 `0.5.166` 的散拼入口自适应等待。中央服务私有 OSS/ECS 云助手编排及迁移 019 已按用户决定从主线撤回,因此这些插件更新消息在当前主线没有服务端触发方,属于休眠能力;插件仍按现有人工发布/加载流程使用。
|
||||
- `0.5.168` 将 ERP 身份门禁从整页子串搜索收紧为唯一 `#Lable_UserName` 登录节点的规范化精确全等;缺失、重复、空白、子串或其他页面文字命中均不再让云电脑进入可执行状态,观察到的账号文本不离开扩展。
|
||||
- `0.5.170` 保留全部执行门禁,并新增 Program-only 散拼批量子单的“完整只读枚举后逐单执行、首错停止、不自动重试”门禁;ERP 写入不确定后的人工只读核验责任仍明确绑定到任务归属账号,管理员仍完全不进入任务数据面。
|
||||
- `0.5.172` 按维护方要求将酒店新增 `DoInfo_jiudian` 的固定字段门槛从 195 调整为 150,恢复原来的预检流程;`0.5.171` 的结构判定方案已撤回并归档。其他模式/业务原门槛及后续执行检查保留,低于 150 的页面仍会阻断。尚未部署或进行真实 ERP 写入验收;验证范围见[任务记录](../../../.project-docs/30-worklog/tasks/20260909-hotel-form-readiness-01a0858b.md)。
|
||||
- 运营输入/DOCX `0.5.127` 补齐第 19 项`散拼团多个新增子单`的可复制模板与示例;该入口仍固定为 Program-only,不进入 Agent Prompt 或 Skill。
|
||||
- 名单工作簿 normalizer `v1.4.0`、Skill `0.5.126` 与业务输入 `0.5.127` 继续只选择 12 个必需源字段;身份证、年龄、源证件类型及其他普通额外列不参与行识别、字段校验或 canonical 输出。必需字段完整性、宏/外链、全局超链接和外部公式等安全门槛保持失败关闭。
|
||||
|
||||
|
||||
@@ -4,3 +4,5 @@
|
||||
|
||||
- `release-manifest-0.5.170-skills-0.5.126-docx-0.5.126.json`:补齐散拼团多个新增子单运营模板前的历史清单快照。
|
||||
- `老挝联泰AI指令表-0.5.126.docx`:被 `0.5.127` 的 19 项完整运营输入表替代;旧版缺少`散拼团多个新增子单`。
|
||||
- `ltjt-order-assistant-0.5.170.zip` 与 `release-manifest-0.5.170-skills-0.5.126-docx-0.5.127.json`:酒店新增表单就绪修复前的插件和清单快照,由插件 `0.5.171` 替代。
|
||||
- `ltjt-order-assistant-0.5.171.zip` 与 `release-manifest-0.5.171-skills-0.5.126-docx-0.5.127.json`:酒店新增结构判定方案及清单快照;维护方随后选择最小改动,由固定门槛 150 的 `0.5.172` 替代。
|
||||
|
||||
BIN
archive/releases/2026-09-09/ltjt-order-assistant-0.5.171.zip
Normal file
BIN
archive/releases/2026-09-09/ltjt-order-assistant-0.5.171.zip
Normal file
Binary file not shown.
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"generated_on": "2026-09-09",
|
||||
"baselines": {
|
||||
"chrome_extension": "0.5.170",
|
||||
"skills": "0.5.126",
|
||||
"business_instruction_docx": "0.5.127",
|
||||
"agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories"
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "chrome_extension",
|
||||
"version": "0.5.170",
|
||||
"path": "dist/ltjt-order-assistant-0.5.170.zip",
|
||||
"source": "chrome-extension/ltjt-order-assistant",
|
||||
"sha256": "c041d13f468f46f1130668ae67701342b0dd920c5be20c5b47b97732384f48d8"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-arrangement",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-arrangement-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-arrangement",
|
||||
"sha256": "83770befe3a8e4048eacaeacb567c5139bf8a0b3a49f2b1513c26383422f2a9c"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-confirmation",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-confirmation-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-confirmation",
|
||||
"sha256": "543507ed8b68c183d2df6722061b0e927ebb728de4bf254d15f83311c9264018"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-lifecycle",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-lifecycle-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-lifecycle",
|
||||
"sha256": "fff3879d9a4143c34ae51e091b91411e55382ad3f5d77c99d6202a005b1ddbd9"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-newbooking",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-newbooking-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-newbooking",
|
||||
"sha256": "5eb6a7bf5649a0fcee3fbee488a124e1b1d018a642a3a49ebb5439ccf20ccc2f"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-updating",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-updating-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-updating",
|
||||
"sha256": "3bd665207649cbb8e0e5ebb012c916720201fe160eea62bccdf1c3a1f0275d1d"
|
||||
},
|
||||
{
|
||||
"kind": "business_instruction_docx",
|
||||
"version": "0.5.127",
|
||||
"path": "dist/老挝联泰AI指令表-0.5.127.docx",
|
||||
"source": "agent设计规范/templates/business-input-templates.md",
|
||||
"builder": "tools/build_business_instruction_docx.py",
|
||||
"sha256": "e1b8ae5a98d550ab0785692a01cf1c87b99e4edfaea39e4edbbab7341e585f29",
|
||||
"source_sha256": "652af8ef645ae56af2dd9990f74b8078de230cb9b87acd13c289d1f4dae0738a",
|
||||
"builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"generated_on": "2026-09-09",
|
||||
"baselines": {
|
||||
"chrome_extension": "0.5.171",
|
||||
"skills": "0.5.126",
|
||||
"business_instruction_docx": "0.5.127",
|
||||
"agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories"
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "chrome_extension",
|
||||
"version": "0.5.171",
|
||||
"path": "dist/ltjt-order-assistant-0.5.171.zip",
|
||||
"source": "chrome-extension/ltjt-order-assistant",
|
||||
"sha256": "ecd358cc1dc9cb79cc72b5a85789a6dceeba7646b421646aa61e3a848607f761"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-arrangement",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-arrangement-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-arrangement",
|
||||
"sha256": "83770befe3a8e4048eacaeacb567c5139bf8a0b3a49f2b1513c26383422f2a9c"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-confirmation",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-confirmation-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-confirmation",
|
||||
"sha256": "543507ed8b68c183d2df6722061b0e927ebb728de4bf254d15f83311c9264018"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-lifecycle",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-lifecycle-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-lifecycle",
|
||||
"sha256": "fff3879d9a4143c34ae51e091b91411e55382ad3f5d77c99d6202a005b1ddbd9"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-newbooking",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-newbooking-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-newbooking",
|
||||
"sha256": "5eb6a7bf5649a0fcee3fbee488a124e1b1d018a642a3a49ebb5439ccf20ccc2f"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
"name": "lwlt-updating",
|
||||
"version": "0.5.126",
|
||||
"path": "dist/lwlt-updating-0.5.126.skill",
|
||||
"source": "agent设计规范/skills/lwlt-updating",
|
||||
"sha256": "3bd665207649cbb8e0e5ebb012c916720201fe160eea62bccdf1c3a1f0275d1d"
|
||||
},
|
||||
{
|
||||
"kind": "business_instruction_docx",
|
||||
"version": "0.5.127",
|
||||
"path": "dist/老挝联泰AI指令表-0.5.127.docx",
|
||||
"source": "agent设计规范/templates/business-input-templates.md",
|
||||
"builder": "tools/build_business_instruction_docx.py",
|
||||
"sha256": "e1b8ae5a98d550ab0785692a01cf1c87b99e4edfaea39e4edbbab7341e585f29",
|
||||
"source_sha256": "652af8ef645ae56af2dd9990f74b8078de230cb9b87acd13c289d1f4dae0738a",
|
||||
"builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,9 +11,13 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过
|
||||
|
||||
## 当前版本
|
||||
|
||||
当前源码版本为 `0.5.170`。版本化 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.172`。版本化 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.170 当前重点:
|
||||
0.5.172 当前重点:
|
||||
|
||||
- 按维护方要求,将酒店新增 `DoInfo_jiudian` 的最少可序列化字段数从 195 调整为 150,恢复原有页面预检流程。154 字段页面可以通过数量检查;关键字段、loading、稳定 1 秒、资源、空槽、财务状态、归属和保存后回查检查保持原样。酒店修改/清空仍为 150,其他业务阈值不变。低于 150 的页面仍会阻断;真实 ERP 验收由部署方完成。
|
||||
|
||||
继续保留 0.5.170 的批量子单执行能力:
|
||||
|
||||
- 新增 Program-only `shared_child_order_batch_create`:先逐日只读枚举日期段内所有产品匹配母团并锁定完整 `tid` 清单,再复用单母团子单路径顺序创建;首个失败或不确定结果立即停止且不自动重试。
|
||||
|
||||
|
||||
@@ -6403,7 +6403,7 @@
|
||||
}
|
||||
|
||||
window.LTJTOrderAssistant = {
|
||||
version: '0.5.170',
|
||||
version: '0.5.172',
|
||||
resolveNativeListSearchValues,
|
||||
lookupKeywordMatchesText,
|
||||
inspectLifecycleSearchCriteria: lifecycleSearchCriteria,
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
const FORM_READINESS_MINIMUMS = Object.freeze({
|
||||
DoInfo_daoyou: 12,
|
||||
DoInfo_cheliang: 90,
|
||||
DoInfo_jiudian: 195,
|
||||
DoInfo_jiudian: 150,
|
||||
DoInfo_piao: 120,
|
||||
DoInfo_qita: 120,
|
||||
DoInfoJH: 800,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "联泰下单助手",
|
||||
"version": "0.5.170",
|
||||
"version": "0.5.172",
|
||||
"description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。",
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
|
||||
@@ -944,7 +944,7 @@
|
||||
|
||||
window.LTJTOrderAssistant = {
|
||||
...(window.LTJTOrderAssistant || {}),
|
||||
version: '0.5.170',
|
||||
version: '0.5.172',
|
||||
openTeamBatchForm,
|
||||
pingTeamBatchFrame,
|
||||
preflightTeamBatchNative,
|
||||
|
||||
BIN
dist/ltjt-order-assistant-0.5.172.zip
vendored
Normal file
BIN
dist/ltjt-order-assistant-0.5.172.zip
vendored
Normal file
Binary file not shown.
8
dist/release-manifest.json
vendored
8
dist/release-manifest.json
vendored
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"generated_on": "2026-09-09",
|
||||
"baselines": {
|
||||
"chrome_extension": "0.5.170",
|
||||
"chrome_extension": "0.5.172",
|
||||
"skills": "0.5.126",
|
||||
"business_instruction_docx": "0.5.127",
|
||||
"agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories"
|
||||
@@ -10,10 +10,10 @@
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "chrome_extension",
|
||||
"version": "0.5.170",
|
||||
"path": "dist/ltjt-order-assistant-0.5.170.zip",
|
||||
"version": "0.5.172",
|
||||
"path": "dist/ltjt-order-assistant-0.5.172.zip",
|
||||
"source": "chrome-extension/ltjt-order-assistant",
|
||||
"sha256": "c041d13f468f46f1130668ae67701342b0dd920c5be20c5b47b97732384f48d8"
|
||||
"sha256": "5ded1ac1e5ed137f0997d1220ac3c76b66a7443f4fcda6c824e482f122d0c198"
|
||||
},
|
||||
{
|
||||
"kind": "skill",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"contract_version": "ltjt-lifecycle-v2.9-roster-leader-contact-2026-08",
|
||||
"current_agent_parse_prompt_version": "ltjt-agent-prompt-v1.8-independent-headcount-categories",
|
||||
"current_extension_version": "0.5.170",
|
||||
"current_extension_version": "0.5.172",
|
||||
"historical_test_marker": "TEST-202609",
|
||||
"scope": "/System/Business/",
|
||||
"erp_session_keepalive": {
|
||||
@@ -306,6 +306,14 @@
|
||||
"arrangement_hotel": {
|
||||
"page": "/System/Business/teams_jiudian.asp",
|
||||
"action": "DoInfo_jiudian",
|
||||
"create_form_readiness": {
|
||||
"strategy": "serialized_control_minimum",
|
||||
"minimum_successful_controls": 150,
|
||||
"required_fields": ["riqi0", "riqis0", "danwei0", "danweiid0", "fangxing0", "jianshu0", "zt0", "beizhu0", "id0", "yf0", "sh0", "MaxI", "tdID", "ddID", "chufari", "SubmitButton"],
|
||||
"stable_ms": 1000,
|
||||
"stability": "retain the original serialized count, passenger row count, required field presence and visible loading checks",
|
||||
"preserved_gates": ["resource matching", "empty slot 0", "payment and audit state", "exact ERP identity and ownership", "native success response and fresh requery"]
|
||||
},
|
||||
"live_validated_update": {
|
||||
"mode": "update",
|
||||
"target": "exact slot_index=0 plus native row_id",
|
||||
|
||||
176
tools/hotel-form-readiness.test.mjs
Normal file
176
tools/hotel-form-readiness.test.mjs
Normal file
@@ -0,0 +1,176 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import vm from 'node:vm';
|
||||
|
||||
const adapterSource = readFileSync(new URL('../chrome-extension/ltjt-order-assistant/lifecycle-adapters.js', import.meta.url), 'utf8');
|
||||
// Exercise the actual polling function without adding a production message/API.
|
||||
const testSource = adapterSource.replace(
|
||||
'assistant.preflightLifecycleOperation = preflightLifecycleOperation;',
|
||||
'assistant.testReadiness = (operation, form, timeout) => waitForFormReadiness(operation, contractFor(operation), form, timeout);\n'
|
||||
+ 'assistant.testSlot = arrangementSlotEvidence;\n'
|
||||
+ 'if (window.testProofs) { arrangementResourceProof = window.testProofs.resource; ownershipListProof = window.testProofs.ownership; }\n'
|
||||
+ 'assistant.preflightLifecycleOperation = preflightLifecycleOperation;'
|
||||
);
|
||||
const hotelFields = ['riqi0', 'riqis0', 'danwei0', 'danweiid0', 'fangxing0', 'jianshu0', 'zt0', 'beizhu0', 'id0', 'yf0', 'sh0', 'MaxI', 'tdID', 'ddID', 'chufari', 'SubmitButton'];
|
||||
|
||||
function fixture({ count = 154, maxI = '6', mode = 'create', tick = () => {}, proofs } = {}) {
|
||||
let now = 10000;
|
||||
const initialTime = now;
|
||||
const values = { MaxI: maxI, tdID: '123', ddID: '0', chufari: '2026-09-15' };
|
||||
const field = (name) => ({
|
||||
name, id: name, tagName: 'INPUT', type: name === 'SubmitButton' ? 'submit' : 'text',
|
||||
disabled: false, value: values[name] ?? ''
|
||||
});
|
||||
const form = { id: 'InfoForm1', isConnected: true, elements: hotelFields.map(field) };
|
||||
for (let i = hotelFields.length - 1; i < count; i += 1) form.elements.push(field(`extra${i}`));
|
||||
const state = { loading: false };
|
||||
const loadingElement = {
|
||||
className: 'datagrid-mask',
|
||||
getBoundingClientRect: () => ({ width: 50, height: 50, left: 0, top: 0, right: 50, bottom: 50 }),
|
||||
getClientRects: () => [{}]
|
||||
};
|
||||
const document = {
|
||||
querySelectorAll: (selector) => selector.startsWith('.datagrid-mask') && state.loading ? [loadingElement] : [],
|
||||
querySelector: () => form,
|
||||
readyState: 'complete'
|
||||
};
|
||||
const window = {
|
||||
document, innerWidth: 1024, innerHeight: 768,
|
||||
location: { pathname: '/System/Business/teams_jiudian.asp', href: 'https://erp.invalid/System/Business/teams_jiudian.asp?tdID=123' },
|
||||
testProofs: proofs,
|
||||
getComputedStyle: () => ({ display: 'block', visibility: 'visible', opacity: '1' })
|
||||
};
|
||||
document.defaultView = window;
|
||||
window.parent = window;
|
||||
form.ownerDocument = document;
|
||||
const sandbox = {
|
||||
window, document, URL, URLSearchParams, TextEncoder, location: window.location,
|
||||
Date: class extends Date { static now() { return now; } },
|
||||
setTimeout(callback, ms) { now += ms; tick({ elapsed: now - initialTime, form, state }); callback(); }
|
||||
};
|
||||
window.setTimeout = sandbox.setTimeout;
|
||||
vm.runInNewContext(testSource, sandbox);
|
||||
const operation = { action: 'arrangement_hotel', data: { arrangement: { mode } } };
|
||||
return {
|
||||
form, state, operation, api: window.LTJTOrderAssistant,
|
||||
elapsed: () => now - initialTime,
|
||||
run: (timeout = 2500) => window.LTJTOrderAssistant.testReadiness(operation, form, timeout)
|
||||
};
|
||||
}
|
||||
|
||||
test('hotel create accepts the reported complete 154-field form after one stable second', async () => {
|
||||
const page = fixture();
|
||||
const result = await page.run();
|
||||
assert.equal(result.successful_control_count, 154);
|
||||
assert.equal(result.ready, true, JSON.stringify(result.blockers));
|
||||
assert.ok(page.elapsed() >= 1000 && page.elapsed() < 2000);
|
||||
});
|
||||
|
||||
test('hotel create fails closed without a form', async () => {
|
||||
const page = fixture();
|
||||
const missing = await page.api.testReadiness(page.operation, null, 2500);
|
||||
assert.equal(missing.ready, false);
|
||||
assert.ok(missing.blockers.includes('lifecycle_complete_form_missing'));
|
||||
});
|
||||
|
||||
test('hotel create uses the requested 150-field boundary', async () => {
|
||||
for (const count of [149, 150, 154, 195, 442]) {
|
||||
const result = await fixture({ count }).run();
|
||||
assert.equal(result.successful_control_count, count);
|
||||
assert.equal(result.minimum_successful_controls, 150);
|
||||
assert.equal(result.ready, count >= 150, `${count}: ${JSON.stringify(result.blockers)}`);
|
||||
if (count < 150) assert.ok(result.blockers.includes(`lifecycle_form_incomplete:${count}:150`));
|
||||
}
|
||||
});
|
||||
|
||||
test('hotel create rejects every missing required field even with more than 195 fields', async () => {
|
||||
for (const name of hotelFields) {
|
||||
const page = fixture({ count: 442 });
|
||||
page.form.elements = page.form.elements.filter((control) => control.name !== name);
|
||||
const result = await page.run();
|
||||
assert.equal(result.ready, false, name);
|
||||
assert.ok(result.missing_required_fields.includes(name), name);
|
||||
}
|
||||
});
|
||||
|
||||
test('hotel create waits for visible loading to disappear and then stabilize', async () => {
|
||||
const page = fixture({ tick: ({ elapsed, state }) => { if (elapsed >= 750) state.loading = false; } });
|
||||
page.state.loading = true;
|
||||
assert.equal((await page.run()).ready, true);
|
||||
assert.equal(page.elapsed(), 1750);
|
||||
const stuck = fixture();
|
||||
stuck.state.loading = true;
|
||||
const result = await stuck.run();
|
||||
assert.equal(result.ready, false);
|
||||
assert.ok(result.blockers.includes('lifecycle_loading_overlay_visible'));
|
||||
});
|
||||
|
||||
test('hotel create retains the existing one-second count stability gate', async () => {
|
||||
const grow = (form) => form.elements.push({ name: `extra${form.elements.length}`, tagName: 'INPUT', type: 'text', value: '' });
|
||||
const settling = fixture({ tick: ({ elapsed, form }) => { if (elapsed === 750) grow(form); } });
|
||||
assert.equal((await settling.run()).ready, true);
|
||||
assert.equal(settling.elapsed(), 1750);
|
||||
const changing = fixture({ tick: ({ form }) => grow(form) });
|
||||
const result = await changing.run();
|
||||
assert.equal(result.ready, false);
|
||||
assert.ok(result.blockers.includes('lifecycle_form_not_stable'));
|
||||
});
|
||||
|
||||
test('hotel update/clear and unrelated lifecycle actions retain their current count gates', async () => {
|
||||
for (const mode of ['update', 'clear']) {
|
||||
const result = await fixture({ count: 149, mode }).run();
|
||||
assert.equal(result.ready, false);
|
||||
assert.ok(result.blockers.includes('lifecycle_form_incomplete:149:150'));
|
||||
assert.equal((await fixture({ count: 154, mode }).run()).ready, true);
|
||||
}
|
||||
const page = fixture();
|
||||
page.operation.action = 'arrangement_vehicle';
|
||||
page.form.elements = page.form.elements.slice(0, 70);
|
||||
const result = await page.run();
|
||||
assert.equal(result.ready, false);
|
||||
assert.equal(result.minimum_successful_controls, 90);
|
||||
});
|
||||
|
||||
test('154-field readiness still leaves occupied slots and payment/audit state blocked', async () => {
|
||||
for (const [name, value, blocker] of [
|
||||
['id0', '1234', 'arrangement_slot_occupied:id0'],
|
||||
['danweiid0', '5678', 'arrangement_slot_occupied:danweiid0'],
|
||||
['yf0', '1', 'arrangement_slot_financial_or_audit_state:yf0'],
|
||||
['sh0', '1', 'arrangement_slot_financial_or_audit_state:sh0']
|
||||
]) {
|
||||
const page = fixture();
|
||||
page.form.elements.find((item) => item.name === name).value = value;
|
||||
assert.equal((await page.run()).ready, true);
|
||||
const slot = page.api.testSlot(page.form, page.operation);
|
||||
assert.equal(slot.ready, false);
|
||||
assert.ok(slot.blockers.includes(blocker), JSON.stringify(slot.blockers));
|
||||
}
|
||||
});
|
||||
|
||||
test('full hotel preflight preserves identity, resource and ownership gates after the count check', async () => {
|
||||
for (const failure of ['', 'identity', 'resource', 'ownership', 'slot']) {
|
||||
const calls = [];
|
||||
const page = fixture({ proofs: {
|
||||
resource: () => { calls.push('resource'); return { matched: failure !== 'resource', blockers: ['resource_mismatch'] }; },
|
||||
ownership: async () => { calls.push('ownership'); return { matched: failure !== 'ownership', blockers: ['account_mismatch'] }; }
|
||||
} });
|
||||
page.operation.data.existing_refs = {
|
||||
kind: 'independent_order', identifier: 'TEST-HOTEL-READINESS', tid: failure === 'identity' ? '999' : '123', ddid: '456',
|
||||
resolved: true, resolution_source: 'erp_unique_match'
|
||||
};
|
||||
Object.assign(page.operation.data.arrangement, {
|
||||
start_date: '2026-09-16', end_date: '2026-09-18', room_count: 9,
|
||||
resource: { id: '789', name: '测试酒店' }, status: '未安排', side_effect_policy: 'no_external'
|
||||
});
|
||||
if (failure === 'slot') page.form.elements.find((item) => item.name === 'yf0').value = '1';
|
||||
const result = await page.api.preflightLifecycleOperation(page.operation);
|
||||
assert.equal(result.preflight.form_readiness.ready, true);
|
||||
assert.equal(result.status, failure ? 'lifecycle_preflight_blocked' : 'lifecycle_preflight_ready', JSON.stringify(result.blockers));
|
||||
const expected = { identity: 'form_tid_mismatch:', resource: 'resource_mismatch', ownership: 'ownership_proof:account_mismatch', slot: 'arrangement_slot_financial_or_audit_state:yf0' };
|
||||
if (failure) assert.ok(result.blockers.some((blocker) => blocker.startsWith(expected[failure])), JSON.stringify(result.blockers));
|
||||
assert.deepEqual(calls, failure === 'identity' ? [] : ['resource', ...(['', 'ownership'].includes(failure) ? ['ownership'] : [])]);
|
||||
assert.equal(result.no_erp_write, true);
|
||||
assert.equal(result.write_attempted, false);
|
||||
}
|
||||
});
|
||||
@@ -1374,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\.170'/);
|
||||
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.172'/);
|
||||
});
|
||||
|
||||
test('uncertain lifecycle writes can only converge through a read-only plugin requery', async () => {
|
||||
@@ -1584,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.170 keeps reload dormant unless its durable execution state is fully idle', async () => {
|
||||
test('extension 0.5.172 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')
|
||||
@@ -2023,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.170');
|
||||
assert.equal(mapping.current_extension_version, '0.5.172');
|
||||
assert.equal(
|
||||
mapping.updates.order_update_independent.field_mapped_pending_live_validation['pax.child_no_bed'],
|
||||
'ertrenshu'
|
||||
@@ -2407,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.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.equal(extensionManifest.version, '0.5.172');
|
||||
assert.match(inpage, /version: '0\.5\.172'/);
|
||||
assert.match(teamBatchInpage, /version: '0\.5\.172'/);
|
||||
assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.172'/);
|
||||
assert.match(inpage, /function strictIsoDate\(value\)/);
|
||||
assert.match(inpage, /const startDate = strictIsoDate\(controlCanonicalValue\(form, 'riqi0'\)\)/);
|
||||
assert.match(inpage, /const endDate = strictIsoDate\(controlCanonicalValue\(form, 'riqis0'\)\)/);
|
||||
|
||||
Reference in New Issue
Block a user