填报体验优化:工作台一屏填数出分提交、明细列表瘦身、堵住相关页签与编辑表单两条死路 (#34) - #35
Conversation
代码评审报告(os-project-dev-review)档位:全量档(依据:#34 的方案分级「改已有视图与权限集一处 = 中风险」+ 调度员按「改已有视图/工作台页/权限集/对象元数据、影响全部岗位日常界面、且实现路径与范围 §1 不同」判全量) 评审基线:PR head 通用质量(/code-review 高档)6 项发现(1 high / 4 medium / 1 low)。机械层面无问题:所有 property key 都能对上 专属核查(四条逐条)1 改动面越界:✅ 无越界
2 降级对账:🟡 部分不符(逐项)
3 三禁痕迹:✅ 无
4 硬拍板落地:⚪ 基本落地,一处术语建议
调用面抽查(五类岗位)
两处越权探针(均未发现越权):
点击计数(评审方亲自实测,sales.manager,3 行填报单)按 #34 口径「从登录到提交」,登录点击计入。实测 13 击 / 0 次页面跳转。
发现清单
结论不可合并 —— 🟡 应修 3 项(F1 / F2 / F3)未清零。🔴 阻塞 0 项:改动面无越界、三禁无痕迹、两处偏差(主从子表改工作台网格、3-b 平台受限)均已在工作项与符合度清单中披露,且经实测与代码核实自述属实,不构成静默降级。需求符合度清单唯一与实测不符的一条是验收 1 的点击数(7 击 vs 实测 13 击 / 不含登录 10 击),连同 F1(验收 5-a 的相邻面)需要一并回单更正。 修复交回后按 dev-review 轮次纪律,复查只核 F1 / F2 / F3 三个修复点与是否引入新问题。 |
返修:评审 🟡 应修三条已处置(F1 / F2 / F3)返修提交: 修复点 ↔ 提交
F1 —— 实测结论,含一处必须摆明的边界
F2 —— 权限闸门与冻结闸门分开验证用研发工程师(其填报单仍在「填报中」,冻结守卫不会误替权限背书)走 REST: UI 侧(销售经理):填报明细列表工具条无「新建」,行操作单元格 DOM 为空 F3 —— 四项逐条,做到多少写多少
四条论证与实测结论都写进了 回归
需拍板事项
|
复查轮(os-project-dev-review)复查基线:返修提交 复查环境:独立临时 clone( 结论:不可合并 —— 🟡 应修 1 项(F1 的残余被定性为「平台受限」,但实测应用侧有可用写法)。F2 / F3 / 两条更正 / 回归门禁全部清零,未发现返修引入的新问题。 1. F1 —— 新建填报明细死路 ✅ 已修复;残余定性 🟡 应修
2. F2 —— 填报人员删除权 ✅ 已清零
3. F3 —— 工作台网格 ✅ ②③ 已落地;①④ 两条「平台做不到」经复现属实
4. 更正 1(点击计数)✅ 已如实更正
5. 更正 2(工作台说明文字)✅ 四类岗位指引已保留渲染实测原文一句到底,四类岗位入口齐全:部门填报人员(「待填报的指标」填数 → 「填报单」提交填报)· 分公司核对人员(「分公司核对」确认或提出争议)· 人力审核 / 分管领导(「填报单」队列审核通过或驳回,驳回必须填写原因)· 归档(「结果与归档」看四维汇总并归档)。首轮 R4 点名的四类岗位一个不缺。 6. 是否引入新问题 ✅ 未发现
复查发现清单
结论:不可合并(🟡 应修 1 项)。清零后复查只需核这一条与相应门禁,不再重开。 |
修复点 ↔ 提交(复查轮 F1 残余)
改法(与复查员临时 clone 上实测的写法一致): sheet: Field.masterDetail('kpi_entry_sheet', { …, readonlyWhen: 'record.id != null', … }),
plan_indicator: Field.lookup('kpi_plan_indicator', { …, readonlyWhen: 'record.id != null' }),新记录没有 id、谓词判假 → 两个 lookup 照常可选;已存在的记录判真 → 编辑面渲染为只读文本。平台约定 定性已改:代码注释里写明「视图层 返修实跑(端口 3113,
|
| 字段 | 按钮数 | 输入框数 | 图标数 | 渲染 |
|---|---|---|---|---|
| 所属填报单 | 0 | 0 | 0 | 纯文本「2026 年第 3 季度考核 · 销售部」 |
| 来源下达 | 0 | 0 | 0 | 纯文本「新签客户数 · 销售部」 |
| 目标值 / 权重(%) | 0 | 1(disabled=true) |
— | 灰态输入框 |
| 实际值 | 0 | 1(disabled=false) |
— | 可填 |
即选择器与 ✕ 都不再渲染,填数路径不受影响。
③ 同一弹窗改实际值点「更新」——仍失败,如实记录 ⚠️
在②的弹窗里把 实际值 填 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/ 痕迹 |
提交即止,未合并;状态标签与处理人未动。
复查轮(第二轮)(os-project-dev-review)复查基线:修复提交 复查环境:独立临时 clone( 结论:可合并 —— F1 残余已清零(🔴 0 项 / 🟡 0 项),未发现本次修复引入的功能性新问题;另记 ⚪ 1 项(不阻塞)。 核点 1 —— 改动面 ✅ 只动两处字段标记与注释,禁触碰面零改动
核点 2 —— 临时 clone 实跑 ✅ 三条主路径全通,系统写入未被误伤环境:端口 3114、 ① 管理员「填报明细 → 新建」——两个 lookup 可选、创建成功 ✅ ② 填报人员(sales.manager@kpi.demo)在填报单详情「相关」页签行菜单「编辑」——两个 lookup 只读文本、无 ✕ ✅
选择器与 ✕ 都不再渲染。同一探针在管理员的记录页「编辑」弹窗上得到完全相同的结果 —— 对象层声明,两个展示面一致生效。 ③ 工作台网格填数、保存出分、提交 ✅ ④
核点 3 ——
|
| 更正评论的说法 | 复查核实 |
|---|---|
应用侧已用对象层 readonlyWhen: 'record.id != null' 兜底,两个字段各一行,在 src/objects/entry.object.ts |
✅ 与 diff 逐字相符;新建可选、建完锁定两半都实测拿到 |
视图层 immutable 待平台修复(objectstack-ai/objectstack#16171),保留不动 |
✅ src/views/index.ts 的 immutable: 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 行注释 |
该段「writeScope: 'own'」,在 524f8b1 之后已与实际行为不符(编辑弹窗现在确实锁死),且未回指对象层的 readonlyWhen。entry.object.ts 那侧的新注释是双向说清楚的,视图侧这段是单向陈旧。纯注释问题,不影响运行,按记录级不阻塞 |
留痕;后续动这块视图时顺手改一句 |
结论:可合并(🔴 0 / 🟡 0;⚪ 1 不阻塞)。
填报人打开系统就落在工作台,在同一页填完 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>




工作项 #34。填报人打开系统就落在工作台,在同一页填完 3 行、看到分、把单提交掉:实测 7 次点击、0 次页面跳转(原动线 11 击 3 跳)。
改了什么
src/pages/index.tskpi_entry_line的可编辑object-grid(单击进编辑,攒够了一次「全部保存」),「我的填报单」=kpi_entry_sheet网格 +kpi_sheet_submit行操作;说明文字收成一行src/views/index.tslist/unfilled)16 列 → 10 列,实际值从第 8 列提到第 5 列;编辑表单删掉整个「计分」分区,只留 实际值 / 备注 可写,其余业务字段显式readonlysrc/objects/entry.object.tskpi_entry_line.sheet关系新增relatedListColumns—— 填报单详情「相关」页签的明细列表原本派生出六列全是配置项(指标方向 / 计分方式 / 来源下达 / 指标 / 指标名称 / 计量单位),看不到目标、权重、实际值、得分src/security/index.tskpi_entry_line.allowCreate→false(明细只由方案发布生成;导入填的是已生成行的实际值,走allowEdit,不受影响)src/translations/zh-CN.objects.generated.tspnpm i18n:extract重新生成(删掉的score分区词条随之消失),未手改计分口径(
src/lib/scoring.ts)与状态机(src/hooks/*)一行未动;没有新建kind: 'react'/'html'自定义页。两处要请评审留意的判断
1. 为什么没用主从子表(
subforms)。 工作项范围 §1 要求先做可行性验证,三条里两条不成立,还有更早的一层: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并列。已在工作项「需拍板事项」里列出备查。验证
422 KPI_LINE_LOCKED,提示为三段式scripts/software-flow.mjs54/54、scripts/e2e-flow.mjs74/74、pnpm verify绿(validate / typecheck / 139 单测 / i18n 新鲜度门禁)测试报告与需求符合度清单挂在 #34 评论上,截图走
acceptance-evidence分支 commit01d7a15473b04854616aa65c667234208cf51d9e(17 张,已用 contents API 逐张核对)。已知未达成一项:填报明细的编辑表单保存仍报权限错误。#15259 那一层已解除(提交体不再带
score),剩下的是同一个owner_id注入缺陷(#16127),应用侧没有可配置的关法。主动线不经此表单,验收标准 1、2 不受影响。🤖 Generated with Claude Code