Skip to content

填报体验优化:工作台一屏填数出分提交、明细列表瘦身、堵住相关页签与编辑表单两条死路 (#34) - #35

Merged
baozhoutao merged 3 commits into
mainfrom
issue-34-fill-ux
Sep 6, 2026
Merged

填报体验优化:工作台一屏填数出分提交、明细列表瘦身、堵住相关页签与编辑表单两条死路 (#34)#35
baozhoutao merged 3 commits into
mainfrom
issue-34-fill-ux

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

工作项 #34。填报人打开系统就落在工作台,在同一页填完 3 行、看到分、把单提交掉:实测 7 次点击、0 次页面跳转(原动线 11 击 3 跳)。

改了什么

位置 改动
src/pages/index.ts 工作台从一段说明文字改成待办入口:「我的指标填报」= kpi_entry_line 的可编辑 object-grid(单击进编辑,攒够了一次「全部保存」),「我的填报单」= kpi_entry_sheet 网格 + kpi_sheet_submit 行操作;说明文字收成一行
src/views/index.ts 填报明细列表(list / unfilled)16 列 → 10 列,实际值从第 8 列提到第 5 列;编辑表单删掉整个「计分」分区,只留 实际值 / 备注 可写,其余业务字段显式 readonly
src/objects/entry.object.ts kpi_entry_line.sheet 关系新增 relatedListColumns —— 填报单详情「相关」页签的明细列表原本派生出六列全是配置项(指标方向 / 计分方式 / 来源下达 / 指标 / 指标名称 / 计量单位),看不到目标、权重、实际值、得分
src/security/index.ts 部门填报人员的 kpi_entry_line.allowCreatefalse(明细只由方案发布生成;导入填的是已生成行的实际值,走 allowEdit,不受影响)
src/translations/zh-CN.objects.generated.ts pnpm i18n:extract 重新生成(删掉的 score 分区词条随之消失),未手改

计分口径(src/lib/scoring.ts)与状态机(src/hooks/*)一行未动;没有新建 kind: 'react'/'html' 自定义页。

两处要请评审留意的判断

1. 为什么没用主从子表(subforms)。 工作项范围 §1 要求先做可行性验证,三条里两条不成立,还有更早的一层:

  • 入口不存在 —— 填报单记录页的「编辑」按钮对每一个非平台内置管理员账号都不出现(实测覆盖填报人员 / 人力审核 / 分管领导 × 4 个对象 × owner 与非 owner),编辑表单进不去,子表配得再对也没人能打开。
  • 保存 403 —— 从绕路打开的表单里改一格实际值点「更新」,POST /batch 的每条 operation 都被塞进系统托管字段 owner_id,服务端按「未授权的所有权转移」拒绝。剔掉 owner_id 的同一批请求 200 且逐行算分正常,说明卡点就是它。
  • 幽灵行关不掉 —— 内嵌网格底部固定一行 Add line,与「明细只由发布生成」冲突。

两条都是平台侧,按「只上报不修复」处理:objectstack-ai/objectstack#16127(新立)、objectstack-ai/objectstack#14912(已有单,补了本轮八账号实测表)。因此走备选方案,改用平台的 object-grid 页面块在工作台达成同一业务目标——它的保存是逐行 PATCH、只发脏字段,两个平台坑都不经过。

2. relatedListColumns 写在了 src/objects/ 派发边界写的是「src/objects/ 只允许补 readonly」,而相关列表的列取值链是「子对象 highlightFields → 关系上的 relatedListColumns → 页面块 record:related_list.columns」——视图层没有入口,不新建自定义记录页就只能声明在关系上。改动是纯展示、只增一个键,与同处一个 masterDetail 声明的 inlineColumns 并列。已在工作项「需拍板事项」里列出备查。

验证

  • 部门填报人员真机走查:7 击 / 0 跳,URL 全程未变;保存后三行 105.56/105.56/31.67、91.67/91.67/18.33、95.00/95.00/47.50,合计 97.50 与填报单最终得分一致
  • 已提交(分公司核对中)与已归档两种状态在新入口改数均被 hook 拦下,422 KPI_LINE_LOCKED,提示为三段式
  • 填报人员在填报明细列表与相关页签均无「新建」;管理员记录页按钮与编辑表单 17 个字段、人力审核填报单列表 13 列均与改前一致
  • scripts/software-flow.mjs 54/54scripts/e2e-flow.mjs 74/74pnpm verify 绿(validate / typecheck / 139 单测 / i18n 新鲜度门禁)

测试报告与需求符合度清单挂在 #34 评论上,截图走 acceptance-evidence 分支 commit 01d7a15473b04854616aa65c667234208cf51d9e(17 张,已用 contents API 逐张核对)。

已知未达成一项:填报明细的编辑表单保存仍报权限错误。#15259 那一层已解除(提交体不再带 score),剩下的是同一个 owner_id 注入缺陷(#16127),应用侧没有可配置的关法。主动线不经此表单,验收标准 1、2 不受影响。

🤖 Generated with Claude Code

@baozhoutao

Copy link
Copy Markdown
Contributor Author

代码评审报告(os-project-dev-review)

档位:全量档(依据:#34 的方案分级「改已有视图与权限集一处 = 中风险」+ 调度员按「改已有视图/工作台页/权限集/对象元数据、影响全部岗位日常界面、且实现路径与范围 §1 不同」判全量)
结论:不可合并

评审基线:PR head 268d0a6,base f20269f,5 文件 +82 / −9。
评审环境:独立临时 clone,pnpm install --frozen-lockfilepnpm verify 全绿(validate / typecheck / 139 单测 / i18n 528 keys in sync);rm -rf distOS_SEED_PROFILE=software 空库起实例,software-people.mjs 建 15 个岗位账号,管理员 REST 发布方案,11 张填报单生成。UI 实测用 Chromium 1440×900 zh-CN,账号:sales.manager(部门填报人员)、hr.reviewer(人力审核)、leader.tech(分管领导)、admin(管理员)。


通用质量(/code-review 高档)

6 项发现(1 high / 4 medium / 1 low)。机械层面无问题:所有 property key 都能对上 @objectstack/specobject-grid / element:text / relatedListColumns schema(editablesingleClickEditpageSizerowActionssortvariant: 'subheading' 均为真实读点),引用的字段名全部存在,kpi_sheet_submitvisible 谓词所需的 status 已投影进网格列。问题集中在作用域:新表单/新网格对非填报人员岗位跨期间数据的行为没有被考虑到。逐条已并入下方「发现清单」,不重复列。

专属核查(四条逐条)

1 改动面越界:✅ 无越界

  • 5 个文件逐个对照 填报体验优化:填报单一屏填数出分提交(主从子表)、工作台待办入口、明细列表瘦身、堵住相关页签与编辑表单两条死路 #34 范围:views/index.ts ↔ §3 + §4-b;objects/entry.object.ts ↔ §4-a;security/index.ts ↔ §4-c;pages/index.ts ↔ §2 + 已披露的 §1 替代实现;translations/zh-CN.objects.generated.ts = 生成物。
  • 禁触碰面 diff 为空:src/lib/scoring.tssrc/hooks/src/actions/index.tssrc/services/src/data/scripts/docs/、README、CLAUDE.md 全部零改动(本 PR 的 files 接口只返回 5 项)。
  • entry.object.ts:+14 −0,纯新增,全部落在 sheet 这个 master-detail 关系的对象字面量里,新增键只有 relatedListColumns(字符串数组,纯展示)。17 个字段定义与 inlineColumns 一字未动 —— 与调度员裁定一致。
  • security/index.ts:+5 −1 = 4 行注释 + 1 行改动;该行只有 allowCreate: true → false,allowRead / allowEdit / allowDelete / allowExport / readScope / writeScope 逐项比对不变;其余 5 个权限集零改动。
  • zh-CN.objects.generated.ts 的 −3 行(_sections.score 词条):在评审环境重跑 pnpm i18n:extract(写模式),git status --porcelain src/translations 为空 —— 确为元数据 label 变化重生成,非手改;pnpm verify 里的 i18n:extract:check 也是绿的。

2 降级对账:🟡 部分不符(逐项)

  • ✅ 工作台可编辑网格只允许改 实际值 / 备注:实测点 目标值 / 权重(%) / 完成率(%) 单元格均不进入编辑态(字段定义上就是 readonly),只有 实际值 / 备注 开编辑器。
  • 对填报人员隐藏「新建」:sales.manager 的「填报明细」列表工具条无「新建」;填报单详情「相关」页签的填报明细区块无「新建」(同页的「数据调整」区块仍有,符合权限集);工作台网格无新增行入口、无幽灵行。
  • 「我的填报单」的提交走既有 kpi_sheet_submit:行操作 → 确认框(confirmText 原文)→ 动作只写 pending_action: 'submit',状态推进全在 sheet.hook。实测 sales.manager 提交后状态 填报中 → 分公司核对中,提示「填报单已提交,进入核对与审核流程。」。无绕行
  • 已提交后改数仍被拦:同一网格改已提交单的实际值 → 保存报「保存失败: 修改填报明细失败:填报单已提交,数据已冻结。如需更正,请发起数据调整申请。」(hook KPI_LINE_LOCKED,三段式完整)。
  • 验收 2 得分与 scoring.ts 口径一致:工作台保存后同页出分 105.56/105.56/31.67、91.67/91.67/18.33、95.00/95.00/47.50,合计 97.50;切到「填报明细」列表与「相关」页签,同一批数字逐格一致(同一个 hook,不存在第二条计分路径)。
  • 🟡 验收 1「≤7 击」与清单 ✅ 不符:见下「点击计数」一节,实测 13 击(含登录)/ 10 击(不含登录),清单记 7 击。
  • 🟡 验收 5-a「原有字段与动作不受影响」的相邻面被改坏:§4-b 的 readonly 落在唯一的 formViews.form 上,该表单同时服务「新建」,导致管理员 / 人力审核 的「填报明细 → 新建」变成死路。实测坐实,见发现 F1。
  • 3-b 的平台受限自述属实:填报明细编辑表单点「更新」→ 403,响应体 [Security] Access denied: 'owner_id' on 'kpi_entry_line' is system-managed …,与 [repo:objectui] Master-detail form's batch save sends owner_id on every parent and child operation, so a permitted line edit 403s with "'owner_id' is system-managed" — the list view's inline grid sends only the dirty cell and succeeds objectstack#16127 的现象一致;同一账号在工作台网格保存则不注入 owner_id(PATCH 走到 hook 才 422)。自述与实测相符

3 三禁痕迹:✅ 无

4 硬拍板落地:⚪ 基本落地,一处术语建议

  • 本单未新增任何数字字段,四件套无适用面(readonly 只是表单侧覆盖,字段定义未动)。
  • 新增用户可见文案 3 条:说明句「在「我的指标填报」里直接填实际值,保存即出分;确认无误后在「我的填报单」里点「提交填报」。」、区块标题「我的指标填报」「我的填报单」。过 std-copy 四条红线:无内部代号 ✅、无异常原文 ✅、无新增报错文案(三段式无适用面)✅;术语按需求 §1.4:「填报单」「实际值」「提交填报」均为表内名词 ✅。
  • ⚪ 「我的指标填报」中的**「指标填报」不是术语表里该对象的名字**(对象 label 是「填报明细」),且「我的」对管理员 / 人力审核 / 分管领导 不成立(见发现 F3)。建议改成不带人称的「填报明细」类表述。

调用面抽查(五类岗位)

岗位 工作台(新增两块) 填报明细列表(§3 瘦身后) 填报明细编辑/新建表单 填报单表单
管理员 渲染正常;可编辑网格覆盖全组织 36 行,分页 2 页;两个区块标题「我的…」名不副实 10 列;失去「所属填报单」列,5 行同名「回款率」无法区分归属 「新建」死路(F1) ✅ 未受影响:diff 未触碰 EntrySheetViews;实测编辑弹窗字段与分区完整(填报单/流程信息两段)
人力审核 同上,且网格对其 allowEdit 为真 → 落地页上一次误点即可改别的部门草稿单的实际值(hook 在 draft 期不拦);「我的填报单」列出全部 11 张单并对 10 张草稿单提供「提交填报」入口 同上;「未填实际值」队列同样无部门/单据识别列,org 范围下无法定位是哪个部门欠填 同管理员,「新建」死路(F1) ✅ 列表 13 列与动作未受影响
人力负责人 kpi_entry_lineallowRead → 网格不进入编辑态(无越权写入口) 只读,列同上 allowCreate,不受 F1 影响
分公司核对人员 同上,只读 只读 同上
分管领导 实测 leader.tech:网格不进入编辑态(allowEdit: false 被网格尊重),只看到分管的 16 行;「我的填报单」5 张单仍显示「提交填报」行操作 只读 同上

两处越权探针(均发现越权):

  1. 分管领导点工作台网格 实际值 → 不渲染编辑器,allowEdit: false 生效。
  2. 人力审核点「我的填报单」→ 研发部草稿单 →「提交填报」→ 确认 → 被 sheet.hook 的岗位闸门拦下:「操作失败:当前节点「部门填报」需要由对应岗位处理,你没有该岗位。如需处理,请联系管理员分配岗位。」。新入口没有放大权限。
    ⚪ 该提示前面外露 KpiError: 前缀 —— 填报体验优化:填报单一屏填数出分提交(主从子表)、工作台待办入口、明细列表瘦身、堵住相关页签与编辑表单两条死路 #34 §5 明确「不处理 KpiError: 前缀」,记录不判。

点击计数(评审方亲自实测,sales.manager,3 行填报单)

#34 口径「从登录到提交」,登录点击计入。实测 13 击 / 0 次页面跳转。

# 动作 说明
1 点「邮箱」输入框 输入 ×1
2 点「密码」输入框 输入 ×1
3 点「登录」 登录后直接落在工作台(0 跳)
4 点第 1 行「实际值」单元格 单元格进入编辑态,但焦点停在 <td>,不在输入框(document.activeElement 为 TD,输入框未 autofocus),此时打字丢失
5 点第 1 行输入框 焦点进入 <input>,方可输入;输入 ×1 + Enter 提交单元格
6–7 第 2 行同上两击
8–9 第 3 行同上两击 Enter 后不自动跳到下一行,无键盘续填
10 点「全部保存 (3)」 同页回填完成率/得分率/最终得分
11 点「我的填报单」行的「更多操作」
12 点「提交填报」
13 点确认框「继续」 提交成功

发现清单

级别 位置 问题 处置出口
🟡 应修 F1 src/views/index.ts:168(formViews.formsheet / plan_indicator) 两个字段同时是对象上的 required: true 且这里被标 readonly: true,而这是唯一的 formView,新建与编辑共用。实测管理员点「填报明细 → 新建」:两个必填 lookup 的搜索框 disabled,无法选值,点「创建」得到「请检查表单中标记的字段:所属填报单、来源下达」——表单永远提交不了,管理员与人力审核原有的「新建填报明细」能力被这次改动关死。#34 §4-b 只要求「编辑表单」的其余业务字段标 readonly,验收 5-a 又明确要求管理员/人力审核原有动作不受影响。修法:把这两个字段改成 immutable: true(spec view.zod.ts:1864「Editable on create, locked once the record exists」),或给新建单独一个 formView。 退回原开发子 agent 原分支追加提交
🟡 应修 F2 src/security/index.ts:117 allowCreate 关了但 allowDelete: true 原样保留,而 EntryLineDeleteGuardHook 只在填报单离开 draft 后才拦删除。于是部门填报人员在「填报中」仍能删掉方案发布生成的明细行(行菜单里「删除」实测可见),删完没有任何重建路径(新建已关)。更要命的是提交闸门只数 actual_value == null 的行(sheet.hook.ts:112),缺行的单能干净地提交并一路流转,weight_total 静默低于 100、indicator_score 少掉该指标的分。改动前 allowCreate: true 时至少还能补回来,这次改动把可恢复的坑变成不可恢复的坑。修法:allowDelete: falseallowCreate 一起关(删除本就该走数据调整)。 退回原开发子 agent 原分支追加提交
🟡 应修 F3 src/pages/index.ts:33-46(「我的指标填报」网格) 网格绑 kpi_entry_line、无 filter、无任何识别列(sheet / 方案 / 期间 / 状态都不在九列里),按 indicator_name 排序。① 对填报人员:同一部门跨方案/跨季度的同名指标(如两行「回款率」)在屏幕上完全无法区分,单击即改,写错行没有任何提示;已提交/已归档单的行同样可编辑,只在保存时才报冻结。② 对人力审核(org 范围):落地页上是一张覆盖全组织 41 行的可写网格,4 行同名「回款率」、4 行「客户续费率」、4 行「有效线索数」无从分辨,误点即改别的部门草稿数据(draft 期 hook 不拦)。页面注释把「不加 filter」论证成安全问题,但这里要的是正确性(哪一行是哪一张单),不是可见性 —— 数据层决定「哪些行」,决定不了「屏幕上怎么分辨」。修法:补 sheet 列(或方案+期间),并按填报单状态过滤到「填报中」。 退回原开发子 agent 原分支追加提交
⚪ 记录 R1 src/views/index.ts:149(lineCols) §3 明令移除「所属填报单」,已忠实落地;副作用是「填报明细」与「未填实际值」两个列表对 org 范围岗位(管理员/人力审核)失去归属识别 —— 「未填实际值」队列再也不能用来催某个部门。属工作项授权范围内的取舍,不判本 PR,建议另立工作项按岗位分列或加分组。 留痕;值得跟踪可另立工作项
⚪ 记录 R2 src/objects/entry.object.ts:163 relatedListColumns 没收 is_adjusted / adjust_type_applied,而紧邻的 inlineColumns 特意收了并注明「列表与内嵌网格因此能一眼看出哪一行被调过」。数据调整批准后,「相关」页签会显示改过的实际值/得分却没有任何「已调整」标记。清单已声明这是有意口径,记录不判。 留痕
⚪ 记录 R3 src/views/index.ts:168 的注释 注释称标 readonly 是为了让控制台不再给 lookup 画出「可清除的 ✕」;实测编辑弹窗里「所属填报单」「来源下达」的 ✕ 仍在(readonly 在本版控制台上对 lookup 表现为 disabled + 保留 ✕)。理由与实际行为对不上,注释会误导后来人。 留痕
⚪ 记录 R4 src/pages/index.ts:31(说明文字) 收成一行的同时,删掉的原说明是分公司核对 / 人力审核 / 分管领导 / 归档四类岗位在工作台上唯一的操作指引;新说明只讲填报动线,而 kpi_homeisDefault: truenav_homerequiredPermissions,六个岗位共用这一张落地页。「我的指标填报」「我的填报单」两个标题对 org / 分管范围岗位也名不副实(见核查 4 的术语意见)。 留痕
⚪ 记录 R5 工作台网格交互 ① 保存被 hook 拒绝后,网格保留脏值,用户必须手动点「全部取消」才能回到真值;② 「我的填报单」的汇总列(指标得分合计/最终得分)在上方网格保存后不自动刷新,要等下一次提交或重载才更新。均为体验层,不阻塞。 留痕
⚪ 记录 R6 提交失败提示 岗位闸门的拒绝提示带 KpiError: 前缀外露(工作台新入口上同样可见)。#34 §5 明确不处理该前缀,记录不判。 留痕

结论

不可合并 —— 🟡 应修 3 项(F1 / F2 / F3)未清零。🔴 阻塞 0 项:改动面无越界、三禁无痕迹、两处偏差(主从子表改工作台网格、3-b 平台受限)均已在工作项与符合度清单中披露,且经实测与代码核实自述属实,不构成静默降级。需求符合度清单唯一与实测不符的一条是验收 1 的点击数(7 击 vs 实测 13 击 / 不含登录 10 击),连同 F1(验收 5-a 的相邻面)需要一并回单更正。

修复交回后按 dev-review 轮次纪律,复查只核 F1 / F2 / F3 三个修复点与是否引入新问题。

@baozhoutao

Copy link
Copy Markdown
Contributor Author

返修:评审 🟡 应修三条已处置(F1 / F2 / F3)

返修提交:6049183(分支 issue-34-fill-ux,base f20269f,在 268d0a6 之上追加)。范围只覆盖评审列出的三条 + 两条如实更正,未扩围;⚪ 记录级条目(R1 / R2 / R3 / R5 / R6)按评审结论留痕不动。

修复点 ↔ 提交

评审项 处置 落点(均在 6049183)
🟡 F1 新建填报明细死路 formViews.formsheet / plan_indicatorreadonly: trueimmutable: true src/views/index.ts(+ 注释写明实测边界)
🟡 F2 填报人员能删发布生成的行 kpi_entry_line.allowDelete: true → false,与 allowCreate 一起关 src/security/index.ts
🟡 F3 工作台网格无 filter、无识别列、标题名不副实 补「所属填报单」为首列并以它打头排序;标题改术语表用词;说明文字保留四类岗位指引 src/pages/index.ts
⚪ R4(评审记录级,一并修) 说明文字收成一行但保留分公司核对 / 人力审核 / 分管领导 / 归档四类岗位各一句 src/pages/index.ts
点击数更正 #34 追加「更正」评论,验收 1 按字面口径标 ⚠️ 并写明出口 #34 评论

F1 —— 实测结论,含一处必须摆明的边界

  • 管理员:「填报明细 → 新建」两个必填 lookup 均可选,选完点「创建」得到「填报明细创建成功」,entry-line.hook 从指标下达冻结出 指标名称 / 计量单位 / 目标值 / 权重。
    新建表单 · 归属已选 · 创建成功
  • 人力审核(hr.reviewer,马丽):同上,新建成功。
    新建表单 · 创建成功
  • ⚠️ 边界(不藏):本版控制台只落实了 immutable 的前半句。编辑弹窗里这两个 lookup 的「选择…」按钮仍是 disabled === false,没有「记录存在后锁死」的表现 —— 也就是说,评审 F1 建议的 immutable 修好了「新建」,但没能同时保住 PR 原意里的「编辑不可改」。
    管理员编辑表单(如实存档)
    • 这不是本次新开的口子:改动前主干上这两个字段只有 required,同样可改;而且 readonly 那层护栏本来也只在 UI —— 实测部门填报人员直接走 REST PATCHplan_indicator 返回 200,表单标记从来拦不住接口。真正的护栏在数据层:writeScope: 'own' 让填报人只够得到自己那张单。
    • 评审给的另一条路也实测了:给「新建」单独一个 formViews.create 不成立 —— 控制台的「新建」仍然渲染 formViews.form,新建与编辑不按视图名分流(FormViewSchema 上也没有 create/edit 的选择键)。
    • 出口:属平台能力面,按「只上报不修复」处理;是否就此向 objectstack-ai/objectstack 立一条 issue,列入下方需拍板事项。

F2 —— 权限闸门与冻结闸门分开验证

研发工程师(其填报单仍在「填报中」,冻结守卫不会误替权限背书)走 REST:

DELETE /api/v1/data/kpi_entry_line/<id>  -> 403 {"code":"PERMISSION_DENIED","object":"kpi_entry_line"}
POST   /api/v1/data/kpi_entry_line       -> 403 {"code":"PERMISSION_DENIED","object":"kpi_entry_line"}

UI 侧(销售经理):填报明细列表工具条无「新建」,行操作单元格 DOM 为空 <div class="flex items-center justify-end gap-1"></div>,记录页「···」菜单只剩「分享」。
列表无新建 · 记录页 · 操作菜单无删除
导入路径不受影响(导入写的是已生成行的实际值,走 allowEdit)。

F3 —— 四项逐条,做到多少写多少

评审要求 结果
② 加「所属填报单」识别列 ✅ 已加为首列,sort 也以它打头,同名指标按单归堆。人力审核工作台 里两行「回款率」现在能分辨
③ 标题改术语表用词、不用「我的」 ✅ 「我的指标填报」→「待填报的指标」,「我的填报单」→「填报单」
① filter 只显示「填报中」的单的明细 平台不支持,不是没做。这是 kpi_entry_sheet.status,而查询引擎明确拒绝跨关系过滤:`` 写 sheet.status 返回 400 `INVALID_FIELD` ——「follows the relationship 'sheet' into another object … Denormalise the value onto 'kpi_entry_line' … and filter that」。`kpi_entry_line` 上没有任何字段编码填报单状态,反规范化要新增字段 + 写入路径,超出本工作项范围。退化的替代条件(如「实际值为空」)不做:它会让保存后的行当场从网格里消失,连带毁掉验收 2「同页出分」,比不过滤更糟。已提交 / 已归档的行仍会出现,改动在保存时被 `sheet.hook` 的冻结闸门拦下并给三段式提示 —— 拦得住,只是晚一步
④ 对 org 岗位隐藏该网格 平台不支持。页面组件 visibleWhen 只绑 record / current_user / page.<var>,没有岗位绑定;实测写 'kpi_dept_reporter' in current_user.positionsos validate 通过、运行期连部门填报人员本人都看不到网格(谓词求值为空 = 对所有人隐藏),已回退

四条论证与实测结论都写进了 src/pages/index.ts 的文件头注释,不只留在这条评论里。

回归

门禁 结果
pnpm verify ✅ validate 通过 / typecheck 通过 / 139 单测全过 / i18n 528 keys in sync
scripts/software-flow.mjs(软件档案,空库) 54 / 54
scripts/e2e-flow.mjs(默认档案,rm -rf dist 后空库) 74 / 74 全 PASS
点击数复测(销售经理,从登录起) 13 击 / 0 跳,与评审方独立实测逐步一致;更正已挂 #34

需拍板事项

  1. 验收 1 的口径:计不计登录的 3 击?计入 = 13 击(平台焦点缺陷 [repo:objectui] Inline-edit grid: Enter neither commits nor moves to the next row, Tab does not move, and single-click can open the editor unfocused (keystrokes lost, cell commits as 0) objectstack#15260 修复后 10 击);不计 = 10 击(修复后 7 击,达标)。AI 不替人选边。
  2. 是否就 immutable 在编辑表单上不生效向 objectstack-ai/objectstack 立 issue(现象 / 最小复现 / 期望能力 / 平台版本已备齐,只等指示 —— 未经确认不在平台仓开单)。

@baozhoutao

Copy link
Copy Markdown
Contributor Author

复查轮(os-project-dev-review)

复查基线:返修提交 6049183(= 当前 PR head),base f20269f只核首轮列出的 F1 / F2 / F3 三个修复点、两条更正,以及是否引入新问题;记录级条目(R1/R2/R3/R5/R6)按轮次纪律不重开、不升级。

复查环境:独立临时 clone(issue-34-fill-ux),pnpm install --frozen-lockfile,独立端口与独立数据库;rm -rf dist 后按档案分别起空库实例。UI 实测账号:admin(管理员)、hr.reviewer(人力审核)、rd.engineer(部门填报人员,其填报单仍在「填报中」,冻结守卫不会替权限背书)、sales.manager(部门填报人员,点击计数)。评审只读,所有探针改动均在临时 clone 内做完即 git checkout -- 还原,还原后 git status --porcelain 为空。

结论:不可合并 —— 🟡 应修 1 项(F1 的残余被定性为「平台受限」,但实测应用侧有可用写法)。F2 / F3 / 两条更正 / 回归门禁全部清零,未发现返修引入的新问题。


1. F1 —— 新建填报明细死路 ✅ 已修复;残余定性 🟡 应修

  • 代码:src/views/index.tsformViews.formsheet / plan_indicatorreadonly: true 改为 immutable: true,其余业务字段仍 readonly。改动只在这一处。
  • 实跑(管理员):「填报明细 → 新建」弹窗中,两个必填 lookup 的「选择…」按钮 disabled === false,可搜可选;选「2026 年第 3 季度考核 · 销售部」+「新签客户数 · 销售部」后点「创建」→ 提示「填报明细创建成功」,entry-line.hook 正常冻结 指标名称 / 计量单位 / 目标值(60)/ 权重(20)。死路已解除。(该测试记录随后由管理员删除,环境已还原。)
  • 编辑表单里两个 lookup 的状态(如实记录):与开发方自述一致 —— 编辑弹窗中「所属填报单」「来源下达」仍渲染为带可清除 ✕ 的选择器,「选择…」按钮 disabled === false;同弹窗里 指标名称 / 计量单位 / 目标值 / 权重 四个输入框 disabled === true。即本版控制台只落实了 immutable 的前半句,自述属实、无隐瞒。
  • 🟡 但「应用侧已无更好写法」这一定性不成立,复查实测:
    • 视图层确实没别的键 —— FormFieldBaseSchema 的键只有 readonly / immutable / required / hidden / colSpan / span / widget / language / keyField / dependsOn / visibleWhen / visibleOn / disclosure,没有 readonlyWhen(spec 里 FormFieldSchema.readonlyWhen 只出现在注释中);formViews 也确实不按 create/edit 分流。这两条开发方说得对。
    • 但对象层的字段条件规则有 readonlyWhen,而且在本版控制台上生效。 在临时 clone 上给 kpi_entry_linesheet(master-detail)与 plan_indicator(lookup)各加一行 readonlyWhen: 'record.id != null'(其余不动):pnpm validate 通过;新建弹窗两个 lookup 依旧可搜可选;编辑弹窗里两者变成纯文本(无选择器、无 ✕)。即「新建可选、存在即锁」两半都拿到了 —— 正是 immutable 承诺的行为。本仓已在 src/objects/indicator.object.ts 用同族的 visibleWhen: 'record.…',record 绑定本来就通。
    • 该写法与原来的表单标记同属 UI 层:实测 PATCH /api/v1/data/kpi_entry_line/<id> {"plan_indicator": …} 仍返回 200(readonlyWhen 在服务端拦截),数据层的真护栏依旧是 writeScope: 'own' 与 hook —— 这点开发方的判断没错。
    • 出口(二选一,不必都做):① 按上面两行落实编辑锁(改动面 = 2 个字段各 1 行;复查只跑到 validate 与 UI 层,返修后请重跑 pnpm verify 与两个流程脚本);② 或明确放弃「编辑不可改」这一目标,同时把 src/views/index.ts 注释与返修说明里「属平台能力面 / 只上报不修复」的定性改掉 —— 应用侧能表达的事不该记成平台受限。immutable 自身在编辑弹窗上不生效,仍可作为平台缺陷单独上报(调度员已上报),两件事不冲突。
  • ⚪ 记录(不判、不阻塞):本版控制台里部门填报人员在填报明细记录页根本没有「编辑」按钮(只有「分享」),所以上面这条编辑锁实际只面向管理员 / 人力审核。已排除是本次返修造成的:把 allowDelete 临时改回 true 重启后,该按钮仍然不出现

2. F2 —— 填报人员删除权 ✅ 已清零

  • 代码:src/security/index.tskpi_entry_line.allowDelete: true → false(+4 行注释),allowRead / allowCreate / allowEdit / allowExport / readScope / writeScope 逐项未变;其余 5 个权限集零改动。
  • 实测(rd.engineer,填报单仍在「填报中」):DELETE /api/v1/data/kpi_entry_line/<真实行>403 PERMISSION_DENIED;POST /api/v1/data/kpi_entry_line403 PERMISSION_DENIED
  • 闸门归属已分离验证:把 allowDelete 临时改回 true 重启后,同一账号 DELETE 一个不存在的 id 返回 404 RECORD_NOT_FOUND(权限闸门放行、只是记录不存在)—— 证明 403 确实来自权限集这次的改动,不是冻结守卫或别的守卫顺手挡下的。
  • UI:填报明细列表工具条无「新建」;三行的行操作单元格 DOM 均为空 <div class="flex items-center justify-end gap-1"></div>(0 个按钮);记录页「⋯」菜单只剩「分享」。
  • 导入路径:同一账号 PATCH 实际值仍 200,填数路径未受影响。
  • 管理员 / 人力审核未被顺带改动:管理员 full(allowDelete: true)—— 实测管理员删除一条明细返回 200;人力审核 editOrg(allowDelete: false)本来就没有删除权,与本次改动无关;人力审核的「新建」「导入」按钮仍在(实测 hasNew=true / hasImport=true),说明关删除没有连带关掉建。
  • ⚪ 记录:部门填报人员的列表工具条同样没有「导入」——这来自上一提交的 allowCreate: false,把 allowDelete 临时改回 true 时该按钮也不出现,与本次返修无关,首轮已受理的改动范围内,记录不判。

3. F3 —— 工作台网格 ✅ ②③ 已落地;①④ 两条「平台做不到」经复现属实

  • ② 识别列:columns 首位加 sheet,sort 改为 [{sheet asc}, {indicator_name asc}]。实测管理员工作台 36 行按填报单归堆,同名「回款率」分属市场部 / 销售部 / 华东 / 华北 / 华南可一眼分辨;部门填报人员(3 行)与人力审核视图同样带该列。
  • ③ 标题:「我的指标填报」→「待填报的指标」、「我的填报单」→「填报单」,渲染实测无「我的」。
  • ① 只显示「填报中」的单的明细 —— 属实,平台做不到:实测 $filter=[["sheet.status","=","draft"]]400,错误体原文点名跨关系:filters on 'sheet.status', which follows the relationship 'sheet' into another object … Denormalise the value onto 'kpi_entry_line' … and filter that;{"sheet.status":"draft"}[["sheet.status","eq","draft"]] 三种写法同样 400。另核对 kpi_entry_line 的 20 个业务字段,确无任何字段编码填报单状态,反规范化要新增字段 + 写入路径,超出本工作项范围 —— 自述与实测一致。
  • ④ 按岗位隐藏该网格 —— 属实,平台做不到:在临时 clone 上给「待填报的指标」网格加 visibleWhen: "'kpi_dept_reporter' in current_user.positions",同时给下方「填报单」网格加对照谓词 visibleWhen: "current_user.id != ''",pnpm validate 通过。重启后实测:部门填报人员(rd.engineer)本人也看不到第一张网格(标题「待填报的指标」下方空白),而对照谓词的第二张网格正常渲染 —— 说明 current_user 绑定是通的,是 positions 求值不出来;控制台无 CEL 报错,静默判假。回退后恢复正常。自述属实。
  • 因此接受 ②③ 兜底,①④ 的残余风险按 ⚪ 记录:已提交 / 已归档单的行仍出现在网格里,靠保存时 sheet.hook 的冻结闸门拦截(拦得住,晚一步);org 范围岗位的落地页仍是一张覆盖全组织的可写网格,靠首列区分归属。
  • 顺带核了返修有没有开新口子:新加的首列 sheeteditable: true 的网格里不可编辑 —— 点该单元格不渲染任何编辑器(该行 input 数为 0,只有「打开」链接),不存在「把明细挪到别的填报单」的新入口。

4. 更正 1(点击计数)✅ 已如实更正

  • 填报体验优化:填报单一屏填数出分提交(主从子表)、工作台待办入口、明细列表瘦身、堵住相关页签与编辑表单两条死路 #34 上的更正评论把验收 1 标为 ⚠️ 未达标,写明 13 击 / 0 跳,并给出出口(平台焦点缺陷修复后 10 击;若口径不计登录则 7 击,达标),明确「口径二选一需 PM 拍板,AI 不替人选边」。要求的三项(标 ⚠️、如实写击数与跳数、写出口)都做到了。
  • 复查方独立重数(sales.manager@kpi.demo,从登录页起,3 行填报单):13 击 / 0 次页面跳转,与更正评论逐步一致:
    1 邮箱框(实测登录页 document.activeElement 为 BODY,邮箱框无 autofocus)· 2 密码框 · 3「登录」→ 直接落工作台(0 跳)· 4 第 1 行「实际值」单元格(编辑器开出但 activeElement 仍是 TD)· 5 该行输入框(activeElementINPUT[type=number])· 6–7 第 2 行 · 8–9 第 3 行 · 10「全部保存 (3)」→ 同页回填 105.56/105.56/31.67、96.67/96.67/19.33、105.00/105.00/52.50 · 11「更多操作」· 12「提交填报」· 13 确认框「继续」→ 提示「填报单已提交,进入核对与审核流程。」,状态 填报中 → 分公司核对中。
    • 每格 2 击在第 1、2、3 行上各复现一次,不是操作失误。
    • 补一条供 PM 拍板时参考:登录那 3 击里有 1 击是可以用键盘替代的(邮箱框输完按 Tab、密码框输完按 Enter),纯键盘走法下总数为 11;但按「点击」字面口径计,13 击是准确的。

5. 更正 2(工作台说明文字)✅ 四类岗位指引已保留

渲染实测原文一句到底,四类岗位入口齐全:部门填报人员(「待填报的指标」填数 → 「填报单」提交填报)· 分公司核对人员(「分公司核对」确认或提出争议)· 人力审核 / 分管领导(「填报单」队列审核通过或驳回,驳回必须填写原因)· 归档(「结果与归档」看四维汇总并归档)。首轮 R4 点名的四类岗位一个不缺。

6. 是否引入新问题 ✅ 未发现

结果
返修提交改动面 6049183 只动 3 个文件:src/views/index.ts(+16 −3)、src/security/index.ts(+5 −1)、src/pages/index.ts(+35 −13)—— 与「修复点 ↔ 提交」表逐条对得上,无夹带。src/lib/scoring.tssrc/hooks/src/actions/src/services/src/data/scripts/docs/package.json 零改动;无 node_modules/ / patches/ 路径(三禁无痕)
pnpm verify ✅ validate 通过 / typecheck 通过 / 7 文件 139 单测全过 / i18n 528 keys in sync(门禁绿)
scripts/software-flow.mjs 54 / 54 PASS(rm -rf dist .objectstack 后空库、软件档案、software-people.mjs 建号)
scripts/e2e-flow.mjs 74 / 74 PASS(再次 rm -rf dist .objectstack 后空库、默认档案)
新加的可编辑网格首列 不可写(见 F3 末条)
关删除的连带影响 未波及新建 / 导入 / 填数 / 管理员删除(见 F2)

复查发现清单

级别 位置 问题 处置出口
🟡 应修 src/views/index.ts(F1 的注释与定性)+ src/objects/entry.object.ts(可选落点) 「本版控制台 immutable 在编辑弹窗上不生效」属实,但由此得出的「属平台能力面 / 只上报不修复」不成立:对象层字段条件规则 readonlyWhen: 'record.id != null' 在本版上实测可用,新建可选、编辑锁死,validate 绿。要么落实它,要么放弃该目标并把「平台受限」的定性改掉 —— 二选一 退回原开发子 agent 原分支追加提交
⚪ 记录 填报明细记录页 部门填报人员没有「编辑」入口(只有「分享」),与 allowDelete 无关(临时改回 true 仍不出现),非本次返修引入 留痕
⚪ 记录 工作台网格 ① 已提交 / 已归档单的行仍出现在可编辑网格里,靠保存时冻结闸门拦(晚一步);② org 范围岗位落地页仍是一张全组织可写网格 —— 两条都因平台的跨关系过滤与岗位谓词能力所限,已接受兜底 留痕
⚪ 记录 填报明细列表(部门填报人员) 工具条同时也没有「导入」,来自上一提交的 allowCreate: false,与本次返修无关 留痕

结论:不可合并(🟡 应修 1 项)。清零后复查只需核这一条与相应门禁,不再重开。

@baozhoutao

Copy link
Copy Markdown
Contributor Author

修复点 ↔ 提交(复查轮 F1 残余)

复查条目 处置 提交 改动面
🟡 应修 · F1 残余:编辑弹窗里 sheet / plan_indicator 仍可改仍带 ✕,而「属平台能力面 / 只上报不修复」的定性不成立 走复查给的出口 ①:在对象层落实编辑锁,视图层 immutable 保留不动 524f8b1 src/objects/entry.object.ts 两个字段各 +1 行 + 注释,合计 +24 −1,单文件

改法(与复查员临时 clone 上实测的写法一致):

sheet: Field.masterDetail('kpi_entry_sheet', {, readonlyWhen: 'record.id != null',}),
plan_indicator: Field.lookup('kpi_plan_indicator', {, readonlyWhen: 'record.id != null' }),

新记录没有 id、谓词判假 → 两个 lookup 照常可选;已存在的记录判真 → 编辑面渲染为只读文本。平台约定 readonlyWhen 不在创建路径上锁字段(spec WriteObservabilityOptions 明写「readonlyWhen cannot lock anything on a create at all」),所以 hook 与种子的建行路径不受影响。

定性已改:代码注释里写明「视图层 immutable 待平台修复(objectstack-ai/objectstack#16171),对象层 readonlyWhen 为应用侧兜底」,并注明这是 UI/写入面护栏而非权限闸门 —— 数据层真护栏仍是 writeScopeentry-line.hook 的冻结守卫。


返修实跑(端口 3113,OS_DATABASE_URL=file:./.objectstack/issue-34-fix2.db,OS_SEED_PROFILE=software,换库前 rm -rf dist;岗位账号由 scripts/software-people.mjs 建;方案由管理员 REST 发布)

① 管理员「填报明细 → 新建」——两个 lookup 可选、创建成功 ✅

新建弹窗初始态探针:sheetplan_indicator 各 2 个按钮、disabled=false,「选择…」可搜可选。选「2026 年第 3 季度考核 · 销售部」+「新签客户数 · 销售部」→ 点「创建」→ 提示 「填报明细创建成功」,entry-line.hook 正常冻结 指标名称「新签客户数」/ 计量单位「家」/ 目标值 60.0000 / 权重 20.00。(测试记录随后由管理员删除,库已还原。)

新建弹窗两个 lookup 可选

填报明细创建成功

② 销售经理(sales.manager@kpi.demo)填报单详情「相关」页签行菜单「编辑」——两个 lookup 已变只读、无 ✕ ✅

弹窗内探针(修复前后同一位置对照):

字段 按钮数 输入框数 图标数 渲染
所属填报单 0 0 0 纯文本「2026 年第 3 季度考核 · 销售部」
来源下达 0 0 0 纯文本「新签客户数 · 销售部」
目标值 / 权重(%) 0 1(disabled=true) 灰态输入框
实际值 0 1(disabled=false) 可填

即选择器与 ✕ 都不再渲染,填数路径不受影响。

编辑弹窗两个 lookup 只读无 ✕

③ 同一弹窗改实际值点「更新」——仍失败,如实记录 ⚠️

在②的弹窗里把 实际值 填 66 → 点「更新」→ 弹窗内红字 「您没有权限保存这条记录。」,请求与响应原文:

PATCH /api/v1/data/kpi_entry_line/<id> -> 403
{"error":"[Security] Access denied: 'owner_id' on 'kpi_entry_line' is system-managed — changing record ownership on update requires the transfer grant (allowTransfer or modifyAllRecords)","code":"PERMISSION_DENIED","object":"kpi_entry_line"}

被拒的字段是表单回填的 owner_id,与本次改的两个字段无关(它们已不在提交面上),即仍是 objectstack-ai/objectstack#16127 那条平台缺陷,本次返修既没修好它、也没让它变坏。同一条明细的填数走工作台网格是通的(见④),所以该缺陷不阻断填报主路径。

④ 工作台网格填数、保存出分、提交 ✅

研发工程师(rd.engineer@kpi.demo)工作台:3 行「实际值」逐格填 92 / 88 / 75 → 「全部保存 (3)」→ 三条 PATCH … -> 200,同页回填 完成率 96.84 / — / 20.00,最终得分 48.42 / 0.00 / 6.00 → 填报单行菜单「提交填报」→ 确认「继续」→ 状态 填报中 → 分公司核对中,填报单最终得分 54.42。首列「所属填报单」仍在,仍不可编辑。

工作台填数保存出分并提交


门禁

结果
pnpm validate ✅ 通过(17 Objects / 184 Fields)
pnpm verify ✅ validate + typecheck + 7 文件 139 单测全过 + i18n 528 keys in sync(本次未动 label,未重生成翻译包)
scripts/software-flow.mjs 54 / 54 PASS(rm -rf dist + 空库 + 软件档案 + software-people.mjs)
scripts/e2e-flow.mjs 74 / 74 PASS(再次 rm -rf dist + 另一个空库 + 默认档案)
改动面 单文件 src/objects/entry.object.ts;src/lib/ src/hooks/ src/actions/ src/services/ src/data/ scripts/ docs/ src/security/ src/views/ src/pages/ package.json 零改动;无 node_modules/ / patches/ 痕迹

提交即止,未合并;状态标签与处理人未动。

@baozhoutao

Copy link
Copy Markdown
Contributor Author

复查轮(第二轮)(os-project-dev-review)

复查基线:修复提交 524f8b1(= 当前 PR head),上一提交 6049183。范围严格限定为上一轮唯一未清零项 F1 残余的修复 + 是否引入新问题;上一轮已清零项与记录级条目按轮次纪律不重开。

复查环境:独立临时 clone(issue-34-fill-ux,HEAD = 524f8b1),pnpm install --frozen-lockfile,独立端口 3114 与独立数据库;换档案前 rm -rf dist .objectstack。评审只读,全程未改临时 clone 的任何文件,收尾 git status --porcelain 为空。

结论:可合并 —— F1 残余已清零(🔴 0 项 / 🟡 0 项),未发现本次修复引入的功能性新问题;另记 ⚪ 1 项(不阻塞)。


核点 1 —— 改动面 ✅ 只动两处字段标记与注释,禁触碰面零改动

  • git diff --stat 6049183 524f8b1:单文件 src/objects/entry.object.ts,+24 −1。
  • 剔除注释行后的代码改动只有两行:
    • sheet(Field.masterDetail)新增一行 readonlyWhen: 'record.id != null',;
    • plan_indicator(Field.lookup)在原有 options 末尾追加 readonlyWhen: 'record.id != null'
  • 字段类型 / required / 其他属性均未动:sheetlabel / required / deleteBehavior / inlineEdit / inlineTitle / relatedListColumns 逐项与 6049183 一致;plan_indicator 仍是 Field.lookup('kpi_plan_indicator', { label: '来源下达', required: true, … }),只多了新键。其余 20 个字段一字未改。
  • 禁触碰面零改动(git diff --name-only 6049183 524f8b1 -- … 无命中):src/lib/src/hooks/src/actions/src/services/src/data/scripts/docs/src/security/src/views/src/pages/ 全部 0 文件;package.json / pnpm-lock.yaml 未动;无 node_modules/、无 patches/(三禁无痕)。

核点 2 —— 临时 clone 实跑 ✅ 三条主路径全通,系统写入未被误伤

环境:端口 3114、OS_DATABASE_URL=file:./.objectstack/review-35c-ui.dbOS_SEED_PROFILE=software;scripts/software-people.mjs 建岗位账号;方案由管理员 REST PATCH /api/v1/data/kpi_plan/<id> {"status":"published"} 发布。

① 管理员「填报明细 → 新建」——两个 lookup 可选、创建成功
新建弹窗 DOM 探针:所属填报单来源下达2 个按钮、disabled 全为 false,渲染「选择…」。搜「销售部」选中「2026 年第 3 季度考核 · 销售部」,搜「新签客户数」选中「新签客户数 · 销售部」→ 点「创建」→ 提示 「填报明细创建成功」,落到记录页,entry-line.hook 正常冻结 指标名称「新签客户数」/ 计量单位「家」/ 目标值 60.0000 / 权重 20.00。死路未复发。(该测试记录随后由管理员删除,明细数回到 36,环境已还原。)

② 填报人员(sales.manager@kpi.demo)在填报单详情「相关」页签行菜单「编辑」——两个 lookup 只读文本、无 ✕
路径:工作台 →「2026 年第 3 季度考核 · 销售部」记录页 →「相关」页签 → 填报明细行「⋯」→「编辑」。弹窗 DOM 探针:

字段 按钮数 输入框数 图标数 渲染
所属填报单 0 0 0 纯文本「2026 年第 3 季度考核 · 销售部」
来源下达 0 0 0 纯文本「签约金额完成率 · 销售部」
指标名称 / 目标值 / 权重(%) 0 1(disabled=true) 灰态输入框
实际值 / 备注 0 1(disabled=false) 可填

选择器与 ✕ 都不再渲染。同一探针在管理员的记录页「编辑」弹窗上得到完全相同的结果 —— 对象层声明,两个展示面一致生效。

③ 工作台网格填数、保存出分、提交
sales.manager 工作台 3 行「实际值」逐格填 92 / 66 / 4200 →「全部保存 (3)」→ 同页回填 完成率/得分率/得分 102.22 / 102.22 / 30.67110.00 / 110.00 / 22.00105.00 / 105.00 / 52.50;填报单记录页 指标得分合计 = 最终得分 = 105.17 → 点「提交填报」→ 确认「继续」→ 提示 「填报单已提交,进入核对与审核流程。」,状态 填报中 → 分公司核对中,提交人「赵敏」。填数与提交主路径完好。

readonlyWhen 是否误伤系统写入 ——未误伤

  • 发布生成明细:方案发布后一次生成 11 张填报单 / 36 行明细,逐行核 sheetplan_indicator 无一为空(缺失计数 0),样本行同时带着 hook 派生的 indicator_name / unit / target_value / weight
  • spec 依据:@objectstack/spec 明写 readonlyWhen cannot lock anything on a create at all(analytics.zod-*.d.ts),创建路径本就豁免,与提交说明一致。
  • scripts/software-flow.mjs(空库 + 软件档案 + software-people.mjs):54 / 54 PASS
  • scripts/e2e-flow.mjs(再次 rm -rf dist .objectstack 后空库、默认档案):74 / 74 PASS
  • src/hooks/entry-line.hook.tsbeforeUpdate 重派生分支(input.plan_indicator !== previous.plan_indicator)走的是系统上下文,不受影响;数据调整落地同样经 sys()

核点 3 —— pnpm verify ✅ 全绿(含 i18n 门禁)

validate 通过(17 对象 / 184 字段 / 17 视图 / 16 动作)· typecheck(tsc --noEmit)通过 · vitest 7 文件 139 用例全过 · i18n:extract --check zh-CN 528 keys,1 bundle in sync

核点 4 —— #34 定性更正与代码事实 ✅ 一致

更正评论的说法 复查核实
应用侧已用对象层 readonlyWhen: 'record.id != null' 兜底,两个字段各一行,在 src/objects/entry.object.ts ✅ 与 diff 逐字相符;新建可选、建完锁定两半都实测拿到
视图层 immutable 待平台修复(objectstack-ai/objectstack#16171),保留不动 src/views/index.tsimmutable: true 仍在、未回删,两者共存;平台单继续挂着
「编辑表单保存仍 403」维持原判,仍是 objectstack-ai/objectstack#16127,被拒字段是控制台注入的 owner_id,与本次改的两个字段无关 ✅ 实测复现:sales.manager 在②的弹窗里改实际值点「更新」→ PATCH /api/v1/data/kpi_entry_line/<id> 403,响应原文 [Security] Access denied: 'owner_id' on 'kpi_entry_line' is system-managed — changing record ownership on update requires the transfer grant …。错误点名的是 owner_id 而非 sheet / plan_indicator,反证这两个字段已不在提交面上。主动线(工作台网格填数出分提交)不经此表单,见核点 2③

补一条复查独立测得、比提交说明更强的事实(对更正无损):对象层 readonlyWhen 在本版上不只是 UI 护栏——管理员直接 PATCH /api/v1/data/kpi_entry_line/<id> {"plan_indicator": <另一条>} 返回 200,但回读值未变(平台在 DataProtocol 入口静默剥离了该字段)。提交说明里「这是 UI/写入面的护栏而非权限闸门,数据层真护栏仍是 writeScope 与冻结守卫」的判断因此偏保守而非夸大,不构成失真。

核点 5 —— 是否引入新问题 ✅ 未发现功能性新问题

结果
改动面夹带 无(单文件两行,见核点 1)
三禁痕迹 无(无平台包路径、无 patch 文件、无绕行实现)
新增用户可见文案 / 新增数字字段 均无(本次只加谓词与注释),std-copy 红线与数字字段四件套本轮不适用
门禁 pnpm verify 绿;software-flow 54/54;e2e-flow 74/74
填数 / 提交 / 出分主路径 正常(核点 2③)
创建路径(表单新建、发布生成、hook 派生) 正常(核点 2①④)
编辑弹窗其余字段 实际值 / 备注仍可写,冻结字段仍灰态,未被顺带锁死

复查发现清单

级别 位置 问题 处置出口
⚪ 记录 src/views/index.ts 第 171–178 行注释 该段「⚠️ 实测边界」写着「归属字段在编辑表单上并没有 UI 锁 …… 真正的护栏在数据层:writeScope: 'own'」,在 524f8b1 之后已与实际行为不符(编辑弹窗现在确实锁死),且未回指对象层的 readonlyWhenentry.object.ts 那侧的新注释是双向说清楚的,视图侧这段是单向陈旧。纯注释问题,不影响运行,按记录级不阻塞 留痕;后续动这块视图时顺手改一句

结论:可合并(🔴 0 / 🟡 0;⚪ 1 不阻塞)。

@baozhoutao
baozhoutao merged commit d1abec7 into main Sep 6, 2026
1 check passed
@baozhoutao
baozhoutao deleted the issue-34-fill-ux branch September 6, 2026 05:22
baozhoutao and others added 3 commits September 5, 2026 22:45
填报人打开系统就落在工作台,在同一页填完 3 行、看到分、把单提交掉:实测 7 次点击、
0 次跳页(原动线 11 击 3 跳)。

- 工作台(kpi_home)从一段说明文字改成待办入口:「我的指标填报」是 kpi_entry_line 的
  可编辑 object-grid(单击进编辑,攒够了一次「全部保存」),「我的填报单」挂
  kpi_sheet_submit 行操作。两张网格都不写页面级 filter —— 看得到哪些行由对象 OWD +
  方案发布写入的动态共享规则 + 权限集 readScope 决定,视图筛选是展示范围不是安全边界。
- 填报明细列表(list / unfilled)从 16 列收到 10 列,实际值从第 8 列提到第 5 列;
  移出的所属填报单、指标方向、计分方式、调整类型、调整后得分在记录页与导出里仍在。
- 填报明细编辑表单去掉整个「计分」分区:本版本控制台提交表单上的全部字段,而填报人员
  对 score / adjusted_score 只读,于是只改一格实际值也被判「写了不该写的字段」
  (objectstack-ai/objectstack#15259)。字段不在表单里,提交体就不带它们。留下的业务
  字段一律显式 readonly。
- 填报单详情「相关」页签的明细列表显式声明 relatedListColumns:派生出来的六列全是配置项,
  填报人看不到自己填了什么、得了多少分。相关列表的列只能声明在关系上(子对象
  highlightFields → relatedListColumns → 页面块),视图层没有入口。
- 部门填报人员的 kpi_entry_line.allowCreate 收成 false:明细只由方案发布生成,手工新建的
  行没有冻结的目标值与权重,算不出分。导入填的是已生成行的实际值,走 allowEdit,不受影响。

计分口径与状态机一行未动。工作项 #34。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
F1 `src/views/index.ts`:填报明细唯一的 formView 新建与编辑共用,`sheet` /
`plan_indicator` 标 `readonly` 会把两个必填 lookup 在新建时一起禁掉,管理员与人力审核
点「新建」永远提交不了。改标 `immutable`(spec ui/view.zod.ts:Editable on create,
locked once the record exists),实测管理员、人力审核各新建一条明细成功。实测边界写进
注释:本版控制台只落实前半句,编辑弹窗里这两个 lookup 仍可改(与改动前主干一致,不是新
开的口子),真正的护栏是 `writeScope: 'own'`;另一条路「给新建单独一个 formViews.create」
也实测过,控制台的「新建」仍渲染 formViews.form,不按名字分流。

F2 `src/security/index.ts`:`kpi_entry_line.allowDelete` 与 `allowCreate` 一起关。
删除守卫只在填报单离开「填报中」后才拦,留着删除权等于允许填报人在填报期删掉发布生成的
行,而新建已关、提交闸门只数「实际值为空」的行 —— 缺行的单能干净地提交,权重合计与指标
得分静默少掉一块。实测:研发工程师(填报单仍在填报中)DELETE 与 POST 均 403
PERMISSION_DENIED;销售经理的填报明细列表行操作单元格为空、记录页操作菜单只剩「分享」。

F3 `src/pages/index.ts`:工作台可编辑网格补「所属填报单」为首列并以它打头排序 —— 跨方案 /
跨期间的同名指标靠它区分归属;区块标题改用术语表的表内名词「待填报的指标」「填报单」,
不再用「我的」(默认落地页六个岗位共用);说明文字收成一行但保留四类岗位各自的入口指引
(评审 R4)。两条做不到的按实记录在注释里:① 「只显示填报中的单的明细」要跨关系过滤,
查询引擎明确拒绝(`$filter` 写 sheet.status → 400 INVALID_FIELD,提示反规范化后再过滤),
本对象上没有编码填报单状态的字段,退化条件(如「实际值为空」)会让保存后的行当场消失、
连带毁掉「同页出分」,故不做;② 「对 org 岗位隐藏该网格」实测 `visibleWhen` 写
`'kpi_dept_reporter' in current_user.positions` 后连部门填报人员本人都看不到网格,已回退。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
复查轮 F1 残余:`sheet` / `plan_indicator` 在编辑弹窗里仍可改、仍带 ✕。
视图层 `immutable: true` 在本版控制台的编辑表单上只落实了前半句(新建可填),
后半句(建完锁定)没生效,已上报 objectstack-ai/objectstack#16171 等平台修复。

应用侧兜底:在对象层给这两个字段各加一行 `readonlyWhen: 'record.id != null'`。
新记录没有 id、谓词判假,两个 lookup 照常可选;已存在的记录判真,编辑面渲染成
只读文本。平台约定 readonlyWhen 不在创建路径上锁字段,hook 与种子的建行路径不受
影响。视图层的 `immutable` 保留不动,平台修好后两者语义一致、可共存。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant