diff --git a/.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md b/.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md new file mode 100644 index 0000000..1d4d0d9 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md @@ -0,0 +1,41 @@ +# 大交通与其他/备案默认日期空行修复 + +## Scope / 授权 + +- 用户确认只有大交通与其他/备案的备用空行带日期,并明确要求修复。限定这两类 create 空行判断;酒店、用车、导游及 update/clear 保持原规则。 +- 用户截图显示 6 条已有安排及 5 条仅带团队发团日期的备用行;日志只有 `riqi0` 占用,并以索引 6–10 的草稿和无空行阻断。修复前已用真实预检入口的合成表单完整复现。 +- 不处理独立的线路横线、行程天数格式问题;不自动重放失败任务、不执行真实 ERP 写入、部署、重启或插件重载。 + +## Worktree gate / Context + +- Branch: `main`;worktree: `/Users/andy/IdeaProjects/LWLT-AIBOT`;base: `40230ce33d6df77b1c5eb5b52a4556f7feb5e1dc`。 +- 仅一个本地 worktree。既有未跟踪 `.idea/` 原样保留;`dist/ltjt-order-assistant-0.5.188.zip` 已验证 SHA-256 符合现有清单,全部 20 文件逐字节等于当前源码,可按原字节归档。 +- Overlap: Clear;Feature 模式,仅写本任务项目记忆。遵守用户约束,不启用子 Agent。 +- 已读取入口、memory index、项目定位、current-state 集成快照、决策索引、架构、文档策略、成功标准、业务规则、业务登记、安排 Skill/契约、Schema/mapping、适配器与现有回归。前序空行与产品任务记录仅用于范围和实现追溯。 +- 当前源码/清单为插件 0.5.188;canonical 旧快照不作为当前版本依据。ARCH-001/SAFETY-001/RELEASE-001 无冲突,Planning Gate passed。 + +## Plan + +1. 大交通及其他/备案新增时,仅将等于页面有效 `chufari` 且行数量为空/0/1 的日期视作默认值;保留所有其他占用、财务、审核和备案保护。非默认/无有效来源的日期或非默认数量仍阻断。 +2. 合成测试覆盖 6 条已有 + 5 条默认日期备用行、不同下标、目标日期覆盖、其他行/备案保留,以及草稿、异常日期、回调变动和酒店/用车不放宽。 +3. 插件递增至 0.5.189,同步平台最低版本、mapping、版本测试、说明、ZIP/清单,原字节归档 0.5.188。 +4. 执行五项仓库检查、逐文件包校验与最终差异审查,记录本地证据及真实 ERP 验收边界。 + +## Progress / Validation + +- 已完成:仅在这两类 create 的占用判断中豁免经过有效日历校验、等于页面 `chufari` 的默认日期;兼容补零格式。数量为空/0/1 才可使用该例外;未清空任何字段,所有其他守卫保留。 +- 新增 8 项行为回归,并扩充酒店、其他省略数量的既有回归。定向 40/40;覆盖选中索引 0/1/6/10、数量 0/1、6 条已有 + 5 条备用行、真实组装的提交体和排序回查。预检不修改表单,用户目标日期只进入选中行;其他行、独立备案及省略数量保持。异常来源/日期、草稿、财务/审核、原生回调改变备用行均在 POST 前阻断。酒店/用车及既有导游/变更/清除回归通过。 +- 完整源码快照(不读取/复制 `.env`,排除既有 `.idea/` 和可重建输出,复用 node_modules)通过五项检查:仓库治理 10/10、类型检查、控制面 293/293、legacy 439/439、构建。原工作区 `.idea/` 原样保留;没有改动忽略/检查规则。 +- 首次 legacy 仅两项本机 HTTP 监听被 sandbox EPERM 拦截;允许 127.0.0.1 测试端口后完整重跑通过,未跳过测试,未访问真实 ERP。 +- 发布 0.5.189:插件/API 版本、平台最低版本、mapping、测试和文档已同步;ZIP 20 个文件逐字节匹配源码,当前全部 7 个交付物哈希有效。0.5.188 原字节归档;活动交付引用已更新,旧任务记录中的历史路径保持。 +- 单线程完成代码与差异复核;`git diff --check` 通过。无子 Agent,canonical 项目记忆未修改。 +- 证据:[本地合成验证](../../../archive/evidence/2026-09-22/arrangement-default-date-01a0b77e/README.md)。 + +## Delivery / 后续 + +- 当前包:[0.5.189](../../../dist/ltjt-order-assistant-0.5.189.zip)。需要更新插件和平台最低版本并刷新 ERP 页面;无服务端运行逻辑变更、无数据库迁移。Skill、输入模板/DOCX、解析器和 Schema 结构不变。 +- 未提交/推送、未部署/重启/重载插件、未操作真实 ERP、未重放失败任务。真实 ERP 验收待部署方完成;截图中的已有安排不能凭此次失败自动重做。 + +## Promotion Candidates + +- 将这两类默认日期空行规则及本地验证边界纳入下次 Integration 的 business-rules/current-state。依据为用户页面证据、修复前完整复现和修复后行为回归;不宣称生产已验收。canonical 项目记忆本任务不更新。 diff --git a/LianSyn-platform/app.js b/LianSyn-platform/app.js index 96a2881..a67e118 100644 --- a/LianSyn-platform/app.js +++ b/LianSyn-platform/app.js @@ -103,7 +103,7 @@ const persistedExtensionResultVersions = new Map(); let taskCreateInProgress = false; const AUTO_HANDOFF_RETRY_MS = 30_000; -const REQUIRED_EXTENSION_VERSION = '0.5.188'; +const REQUIRED_EXTENSION_VERSION = '0.5.189'; const MANUAL_HANDOFF_LABEL = '确认并提交到 ERP 插件'; const RETRY_HANDOFF_LABEL = '继续提交到 ERP 插件'; const RECONCILE_LABEL = '只读回查 ERP 现有结果'; diff --git a/agent设计规范/business-adaptation-registry.md b/agent设计规范/business-adaptation-registry.md index 52d2c08..eb14407 100644 --- a/agent设计规范/business-adaptation-registry.md +++ b/agent设计规范/business-adaptation-registry.md @@ -30,6 +30,10 @@ 测试对象的 `order_delete` 回收能力仍保留在 Schema、插件和生命周期测试夹具中,但不属于生产运营业务,不登记为用户输入入口。 +## 大交通与其他/备案新增的默认日期空行 + +插件 `0.5.189` 起,这两类新增仅允许备用行的日期等于页面有效发团日期 `chufari`(兼容月份/日期补零差异),且数量为空/0/1 时,将该日期视为默认值;结算单位、资源 ID、说明、备注、费用、付款和审核等原有保护仍全部生效。其他日期、缺失/无效默认日期来源或非默认数量继续阻断;酒店、用车、导游及变更/清除不放宽。选中行填写用户指定日期,其余已有/备用行及独立备案字段保持原样。用户截图与本地合成回归支持此修复,真实 ERP 验收待部署方完成。见[任务记录](../.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md)。 + ## 跨业务成功回执 各业务成功回执追加当前账号、完整团号范围内的团队办理进度及五类当前有效安排明细;散拼子单分别记录,名单只展示最近成功导入人数,文件保留导出事实。该汇总只来源于本系统成功记录,强制删除任务不删除独立业务历史,旧回执保持生成时快照。具体规则见 [AgentBus 用户回复契约](agentbus-reply-contract.md#全业务团队进度与安排明细);实现位于 `control-plane/src/team-progress.ts`、`arrangement-ledger.ts` 与 `task-service.ts`,不改变 ERP 执行或用户指令。 diff --git a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md index 125eff9..1e4c22e 100644 --- a/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md +++ b/agent设计规范/test-fixtures/lwlt-lifecycle/release-gate.md @@ -2,7 +2,8 @@ ## 当前基线 -- Chrome 插件:`0.5.188` +- Chrome 插件:`0.5.189` +- `0.5.189` 仅修复大交通与其他/备案新增的默认日期空行误判:行日期等于页面有效发团日期且数量为原生空/0/1 时允许进入原有空行校验,资源、说明、备注、费用、付款、审核与独立备案保护仍保留;其他安排、非默认日期和变更/清除不放宽。需更新插件、平台最低版本并刷新 ERP 页面,无服务端逻辑/迁移变更;真实 ERP 验收待部署方完成。见[本地验证](../../../archive/evidence/2026-09-22/arrangement-default-date-01a0b77e/README.md)及[任务记录](../../../.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md)。 - `0.5.188` 与程序解析器 `v1.0.14` 修复单个下单的产品格式匹配:比较双方统一 NFKC/空白,保留标点及业务限定词,唯一候选使用 ERP 原名联动;keyword 保留输入原始格式。初始失败跳过后续依赖校验,零/多产品候选提供独立中文原因。真实公共预检入口的合成回归通过,生产 ERP 验收待部署方完成;需同步服务端、插件和平台,无迁移。见[验证证据](../../../archive/evidence/2026-09-19/product-name-normalization-01a0b77e/README.md)及[任务记录](../../../.project-docs/30-worklog/tasks/20260919-product-name-normalization-01a0b77e.md)。 - `0.5.187` 与程序解析器 `v1.0.13` 支持独立团“房间数更改为 12 标+2 大”,DBL 映射 `frenshu3` 并参与原生合计及保存后 fresh GET 核验;语法、保存前阻断和保存后不一致的本地合成回归通过,真实 ERP 实写待部署方验收。服务端及插件需同步更新,无迁移。见[任务记录](../../../.project-docs/30-worklog/tasks/20260916-independent-dbl-update-01a0858b.md)。 - `0.5.186` 修复车票说明中车次与时刻的数字边界,明确供应商与说明联合唯一匹配及多候选提示;新增/变更本地合成回归通过,真实 ERP 验收待部署方完成。见[任务记录](../../../.project-docs/30-worklog/tasks/20260916-transport-description-match-01a0858b.md)。 diff --git a/archive/evidence/2026-09-22/README.md b/archive/evidence/2026-09-22/README.md new file mode 100644 index 0000000..2181688 --- /dev/null +++ b/archive/evidence/2026-09-22/README.md @@ -0,0 +1,5 @@ +# 2026-09-22 验证证据 + +此目录为历史验证记录,不能替代当前源码及业务契约。 + +- [大交通与其他/备案默认日期空行](arrangement-default-date-01a0b77e/README.md) diff --git a/archive/evidence/2026-09-22/arrangement-default-date-01a0b77e/README.md b/archive/evidence/2026-09-22/arrangement-default-date-01a0b77e/README.md new file mode 100644 index 0000000..084c415 --- /dev/null +++ b/archive/evidence/2026-09-22/arrangement-default-date-01a0b77e/README.md @@ -0,0 +1,13 @@ +# 默认日期空行修复验证 + +仅记录本任务本地合成验证,不代表真实 ERP 验收,也不定义当前业务规则。用户截图/日志未复制进仓库。 + +- 修复前:真实预检入口的合成 6 条已保存 + 5 条仅带日期空行,复现索引 6–10 全部占用及无空行的原始 blocker 组合。 +- 修复后定向回归:40/40,通过大交通/其他默认日期新增、数量 0/1、下标 0/1/6/10、实际提交体、已有/备用行及全局备案保留、异常日期来源、草稿/付款/审核、回调变动、酒店/用车隔离和回查保护。 +- 发布:0.5.189 ZIP 全部 20 文件逐字节与源码一致;0.5.188 按原字节归档。 +- 五项完整检查:`check:repo` 10/10、`check`、`test:control-plane` 293/293、`test:legacy` 439/439、`build` 全部通过。最终 `git diff --check` 通过。 +- 验证在完整源码/发布物临时快照中执行,排除原工作区既有 `.idea/`、可重建输出及 `.env`,不读取或复制秘密。首次 legacy 只有两项本机 HTTP 监听因 sandbox EPERM 失败;允许 127.0.0.1 测试端口后完整重跑,零失败、零跳过。 +- 主线程完成代码复核,无子 Agent;只豁免大交通和其他/备案 create 中等于有效 `chufari` 的默认日期,数量限空/0/1;其他占用/安全/唯一候选校验未放宽。全部 7 个交付物哈希有效,旧包原哈希仍符合归档清单。 +- 未运行真实 ERP 操作、重试任务、部署、重启、扩展重载或外部发送。 + +任务:[20260922-arrangement-default-date-01a0b77e](../../../../.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md)。 diff --git a/archive/releases/2026-09-22/README.md b/archive/releases/2026-09-22/README.md new file mode 100644 index 0000000..74e499c --- /dev/null +++ b/archive/releases/2026-09-22/README.md @@ -0,0 +1,5 @@ +# 2026-09-22 历史发布物 + +以下仅用于追溯和恢复,不是当前规则或当前交付入口。 + +- [默认日期空行修复前 0.5.188](arrangement-default-date-01a0b77e/README.md) diff --git a/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/README.md b/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/README.md new file mode 100644 index 0000000..5ee3d36 --- /dev/null +++ b/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/README.md @@ -0,0 +1,7 @@ +# 0.5.188 发布物归档 + +此目录保存默认日期空行修复前的 0.5.188 ZIP 与当时发布清单,仅供追溯,不定义当前业务规则。 + +归档前 ZIP 的 SHA-256 已与旧清单核对,20 个文件与修复前源码逐字节一致;归档保留原字节。活动交付引用仅在当前清单,已切换到新包;旧任务记录中的历史路径只作上下文,保持原文。 + +当前交付物以 [dist 清单](../../../../dist/release-manifest.json) 为准。任务:[默认日期空行修复](../../../../.project-docs/30-worklog/tasks/20260922-arrangement-default-date-01a0b77e.md)。 diff --git a/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/ltjt-order-assistant-0.5.188.zip b/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/ltjt-order-assistant-0.5.188.zip new file mode 100644 index 0000000..5f23833 Binary files /dev/null and b/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/ltjt-order-assistant-0.5.188.zip differ diff --git a/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/release-manifest.json b/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/release-manifest.json new file mode 100644 index 0000000..f2f5924 --- /dev/null +++ b/archive/releases/2026-09-22/arrangement-default-date-01a0b77e/release-manifest.json @@ -0,0 +1,69 @@ +{ + "manifest_version": 1, + "generated_on": "2026-09-19", + "baselines": { + "chrome_extension": "0.5.188", + "skills": "0.5.129", + "business_instruction_docx": "0.5.136", + "agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories" + }, + "artifacts": [ + { + "kind": "chrome_extension", + "version": "0.5.188", + "path": "dist/ltjt-order-assistant-0.5.188.zip", + "source": "chrome-extension/ltjt-order-assistant", + "sha256": "e8288c03a3e16bffadecb00c53c92e2ee23cd5a18ecb638ca5c8d5ccb5157072" + }, + { + "kind": "skill", + "name": "lwlt-arrangement", + "version": "0.5.129", + "path": "dist/lwlt-arrangement-0.5.129.skill", + "source": "agent设计规范/skills/lwlt-arrangement", + "sha256": "ae3fc66965d2a7968aeaa2ecf371d75e5365ef4cc4dfd8ef080f9343e0d0f09c" + }, + { + "kind": "skill", + "name": "lwlt-confirmation", + "version": "0.5.129", + "path": "dist/lwlt-confirmation-0.5.129.skill", + "source": "agent设计规范/skills/lwlt-confirmation", + "sha256": "c2f3689d8a0b4d6b250bb2cfce572fe3a9fca3f5519ea5fae5ef0d382b44dae4" + }, + { + "kind": "skill", + "name": "lwlt-lifecycle", + "version": "0.5.129", + "path": "dist/lwlt-lifecycle-0.5.129.skill", + "source": "agent设计规范/skills/lwlt-lifecycle", + "sha256": "fcf23bb0e12e7026f03dc3670fe35db77c740a9902a7a415e2efa464d4eceb1e" + }, + { + "kind": "skill", + "name": "lwlt-newbooking", + "version": "0.5.129", + "path": "dist/lwlt-newbooking-0.5.129.skill", + "source": "agent设计规范/skills/lwlt-newbooking", + "sha256": "b64593d88145966cd4e702d2532ffe3ea85052e8d56fbe75d503a19ababa1715" + }, + { + "kind": "skill", + "name": "lwlt-updating", + "version": "0.5.129", + "path": "dist/lwlt-updating-0.5.129.skill", + "source": "agent设计规范/skills/lwlt-updating", + "sha256": "c57e137c358fb6332af488d5985e52e88e5cf7439fa069a34edfdcb0d0b47336" + }, + { + "kind": "business_instruction_docx", + "version": "0.5.136", + "path": "dist/老挝联泰AI指令表-0.5.136.docx", + "source": "agent设计规范/templates/business-input-templates.md", + "builder": "tools/build_business_instruction_docx.py", + "sha256": "e6a669ef6f9a5b2acb00fba20988869d2c7da2663bb24527cc813853470a622f", + "source_sha256": "ecd51e09b75f56f64773b43a9305fb0ad62fbb3c8d2a0f1ec289eea3d074bdb5", + "builder_sha256": "c162884210da22e3b78368749b66f1f177df5e9fc6155f4fd9954bd516d187f1" + } + ] +} diff --git a/chrome-extension/ltjt-order-assistant/README.md b/chrome-extension/ltjt-order-assistant/README.md index 24cf22e..aea6758 100644 --- a/chrome-extension/ltjt-order-assistant/README.md +++ b/chrome-extension/ltjt-order-assistant/README.md @@ -11,9 +11,13 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过 ## 当前版本 -当前源码版本为 `0.5.188`。版本化 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.189`。版本化 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.188 当前重点: +0.5.189 当前重点: + +- 大交通、其他/备案新增支持带团队默认日期的备用空行:仅允许行日期等于页面有效发团日期 chufari(兼容补零格式),数量为空/0/1,且原有资源、备注、费用、付款、审核保护全部通过。其他日期或无法验证默认值仍阻断。只改选中行的目标日期,保留其他行与全局备案信息;酒店/用车/导游和变更/清除规则保持。本地合成回归通过,真实 ERP 验收待部署方完成;需更新插件至 0.5.189、同步平台最低版本并刷新 ERP 页面,无服务端逻辑变更或数据库迁移。 + +继续保留 0.5.188 的产品名称匹配修复: - 独立团单个下单的产品名比较统一输入与 ERP 候选的 NFKC 和空白格式,精确优先、唯一包含兜底,保留其他标点及业务限定词;归一后多候选仍阻断。选中后以 ERP 原名填写及触发原生联动。初始匹配/校验失败保持表单未填写,后续阶段标记跳过,产品零/多候选分别给出中文原因。程序解析器 v1.0.14 保留 keyword 的原始格式,name 沿用既有归一化。需同步更新服务端、插件和平台最低版本,无迁移;本地合成验证通过,真实 ERP 验收待部署方完成。 diff --git a/chrome-extension/ltjt-order-assistant/inpage.js b/chrome-extension/ltjt-order-assistant/inpage.js index 00f14a1..b52a09a 100644 --- a/chrome-extension/ltjt-order-assistant/inpage.js +++ b/chrome-extension/ltjt-order-assistant/inpage.js @@ -6677,7 +6677,7 @@ } window.LTJTOrderAssistant = { - version: '0.5.188', + version: '0.5.189', resolveNativeListSearchValues, lookupKeywordMatchesText, inspectLifecycleSearchCriteria: lifecycleSearchCriteria, diff --git a/chrome-extension/ltjt-order-assistant/lifecycle-adapters.js b/chrome-extension/ltjt-order-assistant/lifecycle-adapters.js index 323b1eb..824ec0a 100644 --- a/chrome-extension/ltjt-order-assistant/lifecycle-adapters.js +++ b/chrome-extension/ltjt-order-assistant/lifecycle-adapters.js @@ -1253,6 +1253,23 @@ }; } + function hasDefaultArrangementCreateDate(form, operation) { + if (operation.data?.arrangement?.mode !== 'create' + || !['arrangement_transport', 'arrangement_other'].includes(operation.action)) return false; + // These two native forms prefill unused rows with the team's departure + // date. Only accept that page-derived default, never an arbitrary draft date. + const departure = controlValue(getControl(form, 'chufari')); + const parts = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(departure); + if (!parts || !validDate(`${parts[1]}-${parts[2].padStart(2, '0')}-${parts[3].padStart(2, '0')}`)) return false; + if (dateYyyyMD(controlValue(getArrangementControl(form, operation, 'riqi0'))) !== dateYyyyMD(departure)) return false; + // Native unused quantities are blank/zero/one. Do not make a row with an + // entered quantity eligible merely because its date matches the default. + return ['shuliang0', 'tianshu0'].every((name) => { + const control = getArrangementControl(form, operation, name); + return !control || /^(?:|0|1)$/.test(controlValue(control)); + }); + } + function arrangementSlotEvidence(form, operation) { const action = operation.action; const arrangement = operation.data?.arrangement || {}; @@ -1290,11 +1307,13 @@ occupiedFields.push('riqi0', 'riqis0', 'zt0', 'diqu0', 'dianhua0', 'danjia0', 'jine0', 'qita0', 'qitajia0'); if (action === 'arrangement_hotel') occupiedFields.push('jianshu0', 'wanshu0'); // Native cost rows may have a default quantity of 1 even when unused. - // A resource/date/remark or financial value still identifies a draft. + // A resource/non-default date/remark or financial value identifies a draft. } const financialFields = action === 'arrangement_guide' ? [] : ['yf0', 'sh0', 'shenhe0']; const missingFields = required.filter((name) => !getArrangementControl(form, operation, name)); + const defaultDate = hasDefaultArrangementCreateDate(form, operation); const blockingFields = mode === 'create' ? occupiedFields.filter((name) => { + if (name === 'riqi0' && defaultDate) return false; const control = getArrangementControl(form, operation, name); return control && !emptySlotValue(controlValue(control)); }) : []; diff --git a/chrome-extension/ltjt-order-assistant/manifest.json b/chrome-extension/ltjt-order-assistant/manifest.json index 130994c..13b0db3 100644 --- a/chrome-extension/ltjt-order-assistant/manifest.json +++ b/chrome-extension/ltjt-order-assistant/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "联泰下单助手", - "version": "0.5.188", + "version": "0.5.189", "description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。", "permissions": [ "activeTab", diff --git a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js index 3bbc3e7..88dc35e 100644 --- a/chrome-extension/ltjt-order-assistant/team-batch-inpage.js +++ b/chrome-extension/ltjt-order-assistant/team-batch-inpage.js @@ -944,7 +944,7 @@ window.LTJTOrderAssistant = { ...(window.LTJTOrderAssistant || {}), - version: '0.5.188', + version: '0.5.189', openTeamBatchForm, pingTeamBatchFrame, preflightTeamBatchNative, diff --git a/dist/release-manifest.json b/dist/release-manifest.json index f2f5924..ed179c2 100644 --- a/dist/release-manifest.json +++ b/dist/release-manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 1, - "generated_on": "2026-09-19", + "generated_on": "2026-09-22", "baselines": { - "chrome_extension": "0.5.188", + "chrome_extension": "0.5.189", "skills": "0.5.129", "business_instruction_docx": "0.5.136", "agent_prompt": "ltjt-agent-prompt-v1.8-independent-headcount-categories" @@ -10,10 +10,10 @@ "artifacts": [ { "kind": "chrome_extension", - "version": "0.5.188", - "path": "dist/ltjt-order-assistant-0.5.188.zip", + "version": "0.5.189", + "path": "dist/ltjt-order-assistant-0.5.189.zip", "source": "chrome-extension/ltjt-order-assistant", - "sha256": "e8288c03a3e16bffadecb00c53c92e2ee23cd5a18ecb638ca5c8d5ccb5157072" + "sha256": "06a44b11e53c0c563ab11edb9e055e8a8e893c1bb804f4afd5b988f89a90db5b" }, { "kind": "skill", diff --git a/mappings/lifecycle.mapping.json b/mappings/lifecycle.mapping.json index 67a085b..8350977 100644 --- a/mappings/lifecycle.mapping.json +++ b/mappings/lifecycle.mapping.json @@ -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.188", + "current_extension_version": "0.5.189", "original_arrangement_date_selection": "Hotel, vehicle, transport and other/filing update selection accepts MM-DD or YYYY-MM-DD; omitted year matches original ERP month/day within the exact group only. Require exactly one persisted row, then freeze its actual full dates and id. Full year is exact; mixed/cross-year endpoints are matched independently with actual range order checked. No match or ambiguity blocks before mutation; show up to five original candidates and ask to resubmit complete dates. Creation and legacy target-date changes retain existing semantics.", "historical_test_marker": "TEST-202609", "scope": "/System/Business/", @@ -276,6 +276,7 @@ "indexed_create": { "actions": ["arrangement_hotel", "arrangement_vehicle", "arrangement_transport", "arrangement_other"], "selection": "first complete empty native row after form readiness; public target is forbidden; drafts or unavailable rows block before write", + "default_date_empty_rows": "Only transport/other create may retain riqi equal to the valid page chufari (YYYY-M-D or YYYY-MM-DD) as an empty-row default, with blank/0/1 native quantity. All resource/id/item/remark/financial/audit guards still apply. Missing/invalid departure, another date, or entered quantity blocks. Hotel/vehicle/guide and update/clear are unchanged. Only the selected row receives the requested date; other rows and global filing are preserved.", "write_guard": "bind lookup/date/quantity/status/remark to the selected row; preserve other row values and nodes; check selected row id and payment/audit state after native validation immediately before POST", "requery": "preserved records identified by native row id and field digests; exactly one new id and all requested values; support row sorting", "evidence": "hotel_create remains compatible with 0.5.179; other cost actions use arrangement_create, including stored evidence in read-only reconciliation", diff --git a/tools/arrangement-create-empty-slot.test.mjs b/tools/arrangement-create-empty-slot.test.mjs index c26eafa..c5ac854 100644 --- a/tools/arrangement-create-empty-slot.test.mjs +++ b/tools/arrangement-create-empty-slot.test.mjs @@ -65,7 +65,7 @@ function fixture(type, { occupied = 1, rows = occupied + 2, onSelection = () => const match = /^([a-zA-Z_]+)(\d+)$/.exec(control.name); if (match && [0, occupied].includes(Number(match[2]))) control.name = control.id = `${match[1]}${Number(match[2]) === 0 ? occupied : 0}`; } - onPost(savedForm); + onPost(savedForm, body); return { completed: true, http_status: 200 }; }; window.testFreshPage = () => ({ document: { querySelector: (selector) => { @@ -168,8 +168,86 @@ for (const type of ['vehicle', 'transport', 'other']) { }); } +for (const type of ['transport', 'other']) { + test(`${type} creates in a departure-default row and preserves existing rows, spare rows and filing`, async () => { + for (const occupied of [0, 1, 6, 10]) for (const quantity of ['0', '1']) { + let submitted; + const f = fixture(type, { occupied, rows: occupied + 5, sort: true, onPost(_form, body) { submitted = body; } }); + for (let index = occupied; index < occupied + 5; index++) { + f.fields.get(`riqi${index}`).value = index % 2 ? '2026-09-01' : '2026-9-1'; + f.fields.get(`shuliang${index}`).value = quantity; + } + const preserved = f.form.elements.filter(c => !new RegExp(`^[a-zA-Z_]+${occupied}$`).test(c.name)) + .map(c => [c.name, c.value]); + const before = f.form.elements.map(c => c.value); + const preflight = await f.api.preflightLifecycleOperation(f.operation); + assert.equal(preflight.status, 'lifecycle_preflight_ready', JSON.stringify(preflight)); + assert.deepEqual(f.form.elements.map(c => c.value), before, 'preflight must not clear default dates'); + const result = await f.api.liveSubmitLifecycleOperation(f.operation); + assert.equal(result.status, 'lifecycle_completed', JSON.stringify(result)); + assert.equal(f.posts(), 1); assert.equal(f.selections(), 1); + assert.equal(f.fields.get(`riqi${occupied}`).value, '2026-09-05'); + assert.equal(f.fields.get(`shuliang${occupied}`).value, '12'); + for (const [name, value] of preserved) assert.equal(f.fields.get(name).value, value, name); + const body = new URLSearchParams(submitted); + assert.equal(body.get(`riqi${occupied}`), '2026-09-05'); + for (const [name, value] of preserved) { + if (!['SubmitButton', 'ap_qt'].includes(name)) assert.equal(body.get(name), value, `submitted ${name}`); + } + } + }); + + test(`${type} rejects drafts and unverified dates even with other default-date rows available`, async () => { + const changes = [ + f => { f.fields.get('riqi2').value = '2026-09-02'; }, + f => { f.fields.get('riqi2').value = 'invalid'; }, + f => { f.fields.get('chufari').value = ''; }, + f => { f.form.elements = f.form.elements.filter(c => c.name !== 'chufari'); }, + f => { for (const name of ['chufari', 'riqi1', 'riqi2']) f.fields.get(name).value = '2026-2-30'; }, + ...['danwei2', 'danweiid2', 'shuoming2', 'beizhu2', 'danjia2', 'jine2', 'yf2', 'sh2', 'shenhe2', 'shuliang2'].map(name => + f => { f.fields.get(name).value = '19'; }) + ]; + for (const change of changes) { + const f = fixture(type); + f.fields.get('riqi1').value = f.fields.get('riqi2').value = '2026-9-1'; + change(f); + const before = f.form.elements.map(c => c.value); + const result = await f.api.liveSubmitLifecycleOperation(f.operation); + assert.equal(result.status, 'lifecycle_preflight_blocked', JSON.stringify(result)); + assert.equal(f.posts(), 0); assert.equal(f.selections(), 0); + assert.deepEqual(f.form.elements.map(c => c.value), before); + } + }); + + test(`${type} guards other default dates through native selection, validation and preflight changes`, async () => { + for (const callback of ['onSelection', 'onValidate']) { + const f = fixture(type, { [callback](fields) { fields.get('riqi2').value = '2026-09-02'; } }); + f.fields.get('riqi1').value = f.fields.get('riqi2').value = '2026-9-1'; + const result = await f.api.liveSubmitLifecycleOperation(f.operation); + assert.notEqual(result.status, 'lifecycle_completed', JSON.stringify(result)); + assert.equal(f.posts(), 0); + } + const f = fixture(type); + f.fields.get('riqi1').value = f.fields.get('riqi2').value = '2026-9-1'; + const preflight = await f.api.preflightLifecycleOperation(f.operation); + f.fields.get('riqi2').value = '2026-09-02'; + const result = await f.api.liveSubmitLifecycleOperation(f.operation, { expected_before_hash: preflight.preflight.form_payload_sha256 }); + assert.notEqual(result.status, 'lifecycle_completed'); + assert.equal(f.posts(), 0); assert.equal(f.selections(), 0); + }); +} + +test('vehicle still rejects an unsaved date equal to departure', async () => { + const f = fixture('vehicle'); f.fields.get('riqi1').value = '2026-9-1'; + const result = await f.api.liveSubmitLifecycleOperation(f.operation); + assert.equal(result.status, 'lifecycle_preflight_blocked'); + assert.ok(result.blockers.includes('arrangement_slot_occupied:riqi0')); + assert.equal(f.posts(), 0); assert.equal(f.selections(), 0); +}); + test('other create keeps omitted quantity and shared filing, and verifies them after sorted requery', async () => { const f = fixture('other', { sort: true }); delete f.operation.data.arrangement.quantity; + f.fields.get('riqi1').value = f.fields.get('riqi2').value = '2026-9-1'; const result = await f.api.liveSubmitLifecycleOperation(f.operation); assert.equal(result.status, 'lifecycle_completed', JSON.stringify(result)); assert.equal(f.fields.get('shuliang1').value, '1'); @@ -182,6 +260,16 @@ test('other create keeps omitted quantity and shared filing, and verifies them a } }); +test('other preserves omitted zero quantity in a default-date row', async () => { + const f = fixture('other'); delete f.operation.data.arrangement.quantity; + f.fields.get('riqi1').value = f.fields.get('riqi2').value = '2026-9-1'; + f.fields.get('shuliang1').value = '0'; + const result = await f.api.liveSubmitLifecycleOperation(f.operation); + assert.equal(result.status, 'lifecycle_completed', JSON.stringify(result)); + assert.equal(f.fields.get('shuliang1').value, '0'); + assert.equal(f.fields.get('riqi1').value, '2026-09-05'); +}); + test('other create may fill blank filing fields, blocks conflicting filing and preserves realized state', async () => { const f = fixture('other'); f.fields.get('beianhao').value = ''; f.operation.data.arrangement.filing = { number: 'NEW' }; assert.equal((await f.api.liveSubmitLifecycleOperation(f.operation)).status, 'lifecycle_completed'); diff --git a/tools/hotel-create-empty-slot.test.mjs b/tools/hotel-create-empty-slot.test.mjs index 85dd8bf..141dd71 100644 --- a/tools/hotel-create-empty-slot.test.mjs +++ b/tools/hotel-create-empty-slot.test.mjs @@ -151,6 +151,7 @@ test('hotel create matches the new record after ERP sorts rows and uses persiste test('no free row, unsaved drafts, financial state and incomplete rows fail before any POST', async () => { const cases = [ fixture({ occupied: 3, rows: 3 }), + (() => { const f = fixture(); f.fields.get('riqi1').value = f.fields.get('chufari').value; return f; })(), ...['danwei1', 'riqi1', 'jianshu1', 'yf1', 'sh1', 'beizhu1', 'danjia1'].map((name) => { const f = fixture(); f.fields.get(name).value = 'unexpected'; return f; }), (() => { const f = fixture(); f.form.elements = f.form.elements.filter((control) => control.name !== 'id1'); return f; })() ]; diff --git a/tools/lifecycle-contract.test.mjs b/tools/lifecycle-contract.test.mjs index c83546d..fb5ecee 100644 --- a/tools/lifecycle-contract.test.mjs +++ b/tools/lifecycle-contract.test.mjs @@ -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\.188'/); + assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.189'/); }); 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.188 keeps reload dormant unless its durable execution state is fully idle', async () => { +test('extension 0.5.189 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') @@ -2037,7 +2037,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.188'); + assert.equal(mapping.current_extension_version, '0.5.189'); assert.equal( mapping.updates.order_update_independent.field_mapped_pending_live_validation['pax.child_no_bed'], 'ertrenshu' @@ -2421,10 +2421,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.188'); - assert.match(inpage, /version: '0\.5\.188'/); - assert.match(teamBatchInpage, /version: '0\.5\.188'/); - assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.188'/); + assert.equal(extensionManifest.version, '0.5.189'); + assert.match(inpage, /version: '0\.5\.189'/); + assert.match(teamBatchInpage, /version: '0\.5\.189'/); + assert.match(platformApp, /REQUIRED_EXTENSION_VERSION = '0\.5\.189'/); assert.match(inpage, /function strictIsoDate\(value\)/); assert.match(inpage, /const startDate = strictIsoDate\(current\('riqi'\)\)/); assert.match(inpage, /const endDate = strictIsoDate\(current\('riqis'\)\)/);