From b27d4410df813d30be3b332a670b3c5b8da52c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=85=E5=91=A8=E6=B6=9B?= Date: Sun, 6 Sep 2026 12:53:27 -0700 Subject: [PATCH] =?UTF-8?q?feat(views,hooks):=20=E6=A0=B8=E5=AF=B9?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E6=89=B9=E9=87=8F=E7=A1=AE=E8=AE=A4;?= =?UTF-8?q?=E8=B0=83=E6=95=B4/=E5=8A=A0=E5=87=8F=E5=88=86=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E5=8F=AA=E7=95=99=E4=B8=9A=E5=8A=A1=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E3=80=81=E7=8A=B6=E6=80=81=E9=BB=98=E8=AE=A4=E8=8D=89=E7=A8=BF?= =?UTF-8?q?(#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 分公司核对人员清空「待我核对」原来要逐条走「更多操作 → 确认无误 → 确认」, 10 条 30 击(UI 实测);数据调整与加减分的新建表单把状态与审批字段摆给填报人员, 「状态」必填、无默认值,下拉里还列着会被状态机拒掉的值,要试错才建得出草稿。 - CheckTaskViews.pending 开多选并挂 bulkActions: ['kpi_check_confirm']。裸字符串 形式是逐记录派发,动作体一个字没改,岗位校验 / 填报单状态校验 / 盖章 / 留痕 / 全部确认后自动推进仍全部落在 check-task.hook.ts。10 条实测 30 击 → 4 击。 「提出争议」故意不进批量:争议内容必填且逐条不同,保持行内单条。 - AdjustmentViews / BonusViews 的表单各收敛到五个业务字段,状态与审批字段移出。 移出 ≠ 删掉:记录详情页的字段清单来自对象定义,这些字段在记录页照常只读可见。 「审批意见」原来在申请表单上可编辑,一并移出——审批意见只由审批按钮写。 - 两个 hook 的 beforeInsert 各加一行「输入未带 status 时置草稿」。只补空缺: 带了状态的写入行为不变,状态机 transitions / initialStates 与岗位规则一个字没动 (API 实测带 status=approved 直接新建仍被 invalid_initial_state 拒)。 新建成功后仍会离开列表(字段数 < 12 弹记录抽屉、≥ 12 跳记录页):控制台这条路径 不读 form view 的 submitBehavior,声明 { kind: 'continue' } 实测无效,去向只由对象 字段数算出。不绕行、不改平台包,已上报 objectstack-ai/objectstack#16381。 pnpm verify 绿;scripts/software-flow.mjs 55/55、scripts/e2e-flow.mjs 74/74 全 PASS。 Co-Authored-By: Claude Fable 5.1 --- src/hooks/adjustment.hook.ts | 4 ++ src/hooks/bonus.hook.ts | 4 ++ src/translations/zh-CN.objects.generated.ts | 3 -- src/views/index.ts | 57 +++++++++++++++++++-- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/hooks/adjustment.hook.ts b/src/hooks/adjustment.hook.ts index 1c412da..648fe8c 100644 --- a/src/hooks/adjustment.hook.ts +++ b/src/hooks/adjustment.hook.ts @@ -55,6 +55,10 @@ export const AdjustmentHook: Hook = { input.subject = sheet?.subject ?? null; input.old_value = row.adjust_type === 'result' ? toNumber(line.final_score) : toNumber(line.actual_value); if (!input.requested_by) input.requested_by = actorId(ctx); + // 状态默认「草稿」(#37):申请表单不再让填报人员选状态,新建一律从草稿起步,再点「提交审批」。 + // 只在**输入未带 status** 时补 —— 带了状态的写入(按钮、脚本、导入)行为一个字不变, + // 合法性仍由对象上的状态机(`initialStates: ['draft']`)判定,这里不替它放行任何值。 + if (!input.status) input.status = 'draft'; return; } diff --git a/src/hooks/bonus.hook.ts b/src/hooks/bonus.hook.ts index 8f83ef7..8ed8cc1 100644 --- a/src/hooks/bonus.hook.ts +++ b/src/hooks/bonus.hook.ts @@ -73,6 +73,10 @@ export const BonusHook: Hook = { } const points = toNumber(row.points) ?? 0; input.signed_points = row.bonus_type === 'deduct' ? -points : points; + // 状态默认「待审批」(#37):登记表单不再让人力审核选状态,新登记一律从待审批起步。 + // 只在**输入未带 status** 时补 —— 带了状态的写入(批准 / 否决按钮、脚本、导入)行为一个字不变, + // 合法性仍由对象上的状态机(`initialStates: ['draft']`)与下面的岗位分离判定。 + if (ctx.event === 'beforeInsert' && !input.status) input.status = 'draft'; const rule = requiredBonusPosition(ctx.event, input, prev); if (rule) { diff --git a/src/translations/zh-CN.objects.generated.ts b/src/translations/zh-CN.objects.generated.ts index 04fdfa9..4a0c341 100644 --- a/src/translations/zh-CN.objects.generated.ts +++ b/src/translations/zh-CN.objects.generated.ts @@ -113,9 +113,6 @@ export const zhCNTranslations: TranslationData = { _sections: { request: { label: "调整申请" - }, - decision: { - label: "审批" } } }, diff --git a/src/views/index.ts b/src/views/index.ts index 76eb144..32851ce 100644 --- a/src/views/index.ts +++ b/src/views/index.ts @@ -185,7 +185,29 @@ export const EntryLineViews = defineView({ export const CheckTaskViews = defineView({ list: { label: '全部核对任务', type: 'grid', data: obj('kpi_check_task'), columns: cols('name', 'sheet', 'branch', 'status', 'decided_by', 'decided_at', 'comment'), exportOptions: XLSX }, listViews: { - pending: { label: '待我核对', type: 'grid', data: obj('kpi_check_task'), columns: cols('name', 'sheet', 'branch', 'status'), filter: [{ field: 'status', operator: 'equals', value: 'pending' }] }, + /** + * 「待我核对」开多选 + 批量「确认无误」(#37)。 + * + * 分公司核对人员清空队列原来要逐条走「更多操作 → 确认无误 → 确认」,10 条 30 击(UI 实测)。 + * `bulkActions` 的裸字符串形式是**逐记录派发**:渲染器按名字取到对象上的 `kpi_check_confirm`, + * 连同它自己的 label、`params`(核对意见,选填)与 `visible` 谓词一起提升成批量按钮,对勾选的 + * 每条记录各发一次,`recordId` 由该行记录带出。所以状态推进仍然一条不落地走 + * `hooks/check-task.hook.ts`(岗位校验、填报单状态校验、盖章、留痕、全部确认后自动推进), + * 批量只省点击、不省规则 —— 不用 `bulkActionDefs`,那是给「一次调用覆盖整个选区」的聚合动作 + * 与纯数据面批改准备的,会绕开逐条 hook。 + * + * 「提出争议」**故意不进** `bulkActions`:争议内容必填且逐条不同,批量填一份意见套给多条记录 + * 就是伪造留痕(《设计方案》§5.5「提出争议必须写意见」)。它保持行内单条。 + */ + pending: { + label: '待我核对', + type: 'grid', + data: obj('kpi_check_task'), + columns: cols('name', 'sheet', 'branch', 'status'), + filter: [{ field: 'status', operator: 'equals', value: 'pending' }], + selection: { type: 'multiple' }, + bulkActions: ['kpi_check_confirm'], + }, disputed: { label: '有争议', type: 'grid', data: obj('kpi_check_task'), columns: cols('name', 'sheet', 'branch', 'comment', 'decided_by'), filter: [{ field: 'status', operator: 'equals', value: 'disputed' }] }, }, formViews: { form: { type: 'simple', data: obj('kpi_check_task'), sections: [{ name: 'main', label: '核对任务', columns: 2, fields: [{ field: 'name' }, { field: 'sheet' }, { field: 'branch' }, { field: 'status' }, { field: 'comment' }, { field: 'decided_by' }, { field: 'decided_at' }] }] } }, @@ -205,7 +227,19 @@ export const BonusViews = defineView({ listViews: { pending: { label: '待审批', type: 'grid', data: obj('kpi_bonus'), columns: cols('sheet', 'title', 'bonus_type', 'points', 'reason'), filter: [{ field: 'status', operator: 'equals', value: 'draft' }] }, }, - formViews: { form: { type: 'simple', data: obj('kpi_bonus'), sections: [{ name: 'main', label: '加减分', columns: 2, fields: [{ field: 'sheet', required: true }, { field: 'title', required: true }, { field: 'bonus_type', required: true }, { field: 'points', required: true }, { field: 'reason', required: true }, { field: 'status' }, { field: 'signed_points' }, { field: 'approved_by' }, { field: 'approved_at' }] }] } }, + /** + * 登记表单只留五个业务字段(#37)。 + * + * 原表单把 状态 / 计入分值 / 审批人 / 审批时间 一并摆给登记人:「状态」是必填下拉、没有默认值, + * 而下拉里的「已批准」「已否决」都会被对象上的状态机(`initialStates: ['draft']`)与 + * `hooks/bonus.hook.ts` 的岗位分离拒掉 —— 人力审核要试错才登记得出一条待审批的加减分。 + * 现在状态由 hook 的 `beforeInsert` 补「待审批」,表单上不再出现;计入分值由 hook 按类型算符号; + * 审批人 / 审批时间由「批准 / 否决」按钮盖章。 + * + * 移出 ≠ 删掉:记录详情页的字段清单来自对象定义、不来自表单视图(与 #34 收敛填报明细表单同一条路), + * 这四项在记录页照常只读可见。 + */ + formViews: { form: { type: 'simple', data: obj('kpi_bonus'), sections: [{ name: 'main', label: '加减分', columns: 2, fields: [{ field: 'sheet', required: true }, { field: 'title', required: true }, { field: 'bonus_type', required: true }, { field: 'points', required: true }, { field: 'reason', required: true }] }] } }, }); export const AdjustmentViews = defineView({ @@ -213,9 +247,24 @@ export const AdjustmentViews = defineView({ listViews: { pending: { label: '待审批', type: 'grid', data: obj('kpi_adjustment'), columns: cols('name', 'sheet', 'line', 'adjust_type', 'old_value', 'new_value', 'reason', 'requested_by'), filter: [{ field: 'status', operator: 'equals', value: 'submitted' }] }, }, + /** + * 申请表单只留五个业务字段(#37)。 + * + * 原表单把 调整前值 / 申请人 / 状态 / 审批人 / 审批时间 / 审批意见 / 落地时间 一并摆给填报人员, + * 其中两项是实打实的坑:「状态」必填、无默认值,下拉里的「已批准」「已否决」会被状态机 + * (`initialStates: ['draft']`)拒掉;「审批意见」在表单上**可编辑**,等于让申请人替审批人写意见。 + * 现在状态由 `hooks/adjustment.hook.ts` 的 `beforeInsert` 补「草稿」,审批意见只由 + * 「批准并落地 / 否决」两个按钮写。 + * + * 「调整前值」也移出表单:它由 hook 在保存时按所选明细回填。控制台表单没有「按 lookup 选中值 + * 回查另一对象的字段、即时回填本表单」的联动能力(实测选中明细后该框仍为空,改动前后一致), + * ObjectStack 表达这类派生值的正规做法是对象上的公式 / 汇总字段,而本单不新增字段 —— + * 所以维持「保存时回填、记录页只读展示」,不在表单上留一个永远空着的框骗人。 + * + * 移出 ≠ 删掉:记录详情页的字段清单来自对象定义、不来自表单视图,这七项在记录页照常只读可见。 + */ formViews: { form: { type: 'simple', data: obj('kpi_adjustment'), sections: [ - { name: 'request', label: '调整申请', columns: 2, fields: [{ field: 'sheet', required: true }, { field: 'line', required: true }, { field: 'adjust_type', required: true }, { field: 'old_value' }, { field: 'new_value', required: true }, { field: 'reason', required: true }, { field: 'requested_by' }, { field: 'status' }] }, - { name: 'decision', label: '审批', columns: 2, fields: [{ field: 'decided_by' }, { field: 'decided_at' }, { field: 'decision_reason' }, { field: 'applied_at' }] }, + { name: 'request', label: '调整申请', columns: 2, fields: [{ field: 'sheet', required: true }, { field: 'line', required: true }, { field: 'adjust_type', required: true }, { field: 'new_value', required: true }, { field: 'reason', required: true }] }, ] } }, });