安排大交通问题修复
This commit is contained in:
@@ -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 项目记忆本任务不更新。
|
||||
@@ -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 现有结果';
|
||||
|
||||
@@ -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 执行或用户指令。
|
||||
|
||||
@@ -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)。
|
||||
|
||||
5
archive/evidence/2026-09-22/README.md
Normal file
5
archive/evidence/2026-09-22/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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)。
|
||||
5
archive/releases/2026-09-22/README.md
Normal file
5
archive/releases/2026-09-22/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# 2026-09-22 历史发布物
|
||||
|
||||
以下仅用于追溯和恢复,不是当前规则或当前交付入口。
|
||||
|
||||
- [默认日期空行修复前 0.5.188](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)。
|
||||
Binary file not shown.
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 验收待部署方完成。
|
||||
|
||||
|
||||
@@ -6677,7 +6677,7 @@
|
||||
}
|
||||
|
||||
window.LTJTOrderAssistant = {
|
||||
version: '0.5.188',
|
||||
version: '0.5.189',
|
||||
resolveNativeListSearchValues,
|
||||
lookupKeywordMatchesText,
|
||||
inspectLifecycleSearchCriteria: lifecycleSearchCriteria,
|
||||
|
||||
@@ -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));
|
||||
}) : [];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "联泰下单助手",
|
||||
"version": "0.5.188",
|
||||
"version": "0.5.189",
|
||||
"description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。",
|
||||
"permissions": [
|
||||
"activeTab",
|
||||
|
||||
@@ -944,7 +944,7 @@
|
||||
|
||||
window.LTJTOrderAssistant = {
|
||||
...(window.LTJTOrderAssistant || {}),
|
||||
version: '0.5.188',
|
||||
version: '0.5.189',
|
||||
openTeamBatchForm,
|
||||
pingTeamBatchFrame,
|
||||
preflightTeamBatchNative,
|
||||
|
||||
10
dist/release-manifest.json
vendored
10
dist/release-manifest.json
vendored
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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; })()
|
||||
];
|
||||
|
||||
@@ -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'\)\)/);
|
||||
|
||||
Reference in New Issue
Block a user