From 5b54b56482220edade384c4ff432cfdd2eb053f5 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 10:42:04 -0700 Subject: [PATCH] =?UTF-8?q?feat(scripts):=20=E6=BC=94=E7=A4=BA=E6=A1=A3?= =?UTF-8?q?=E6=A1=88=E8=A1=A5=E4=B8=89=E5=90=8D=E5=88=86=E5=85=AC=E5=8F=B8?= =?UTF-8?q?=E5=A1=AB=E6=8A=A5=E4=BA=BA=E5=91=98,=E5=88=86=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E5=A1=AB=E6=8A=A5=E5=8D=95=E7=94=B1=E6=9C=AC=E4=BA=BA?= =?UTF-8?q?=E5=A1=AB(#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 分公司在方案里既是被考核主体、又是核对方,而核对人员按《设计方案》§3 表 1 只能 「确认无误 / 提出争议」、改不了数值 —— 演示档案此前只为三家分公司建了核对人员, 分公司自己那张填报单于是在界面上无人可填,只能管理员代填。 - software-people.mjs:华东 / 华南 / 华北各补 1 名「分公司填报人员」(岗位 kpi_dept_reporter,组织归属为本分公司),幂等,账号清单输出同步; - software-flow.mjs:三张分公司填报单改由本分公司的填报人员登录后填报并提交, 新增 T11b 断言「不是管理员代填」,原有断言一条未放松(54 → 55 全绿); - README:演示种子档案一节补充三家分公司「填报 + 核对」两名账号的说明。 数据范围无需任何元数据改动:方案发布时按参与主体写入的共享规则已把本主体的填报单 放宽到本单元成员,分公司本身就是参与主体。实测(见工作项测试报告)分公司填报人员 只见本分公司的单与明细、填数出分提交成功、越权访问他司记录 404;分公司核对人员 仍改不了实际值。 Co-Authored-By: Claude Fable 5.1 --- README.md | 10 ++++++- scripts/software-flow.mjs | 56 +++++++++++++++++++++++++++++++++---- scripts/software-people.mjs | 14 +++++++++- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index da9f830..c0bbdf2 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ pnpm dev # http://localhost:3000 ;Console: /_console/ ;管理员 adm rm -rf dist rm -f .objectstack/software.db* # 或换一个没用过的文件名 OS_SEED_PROFILE=software OS_DATABASE_URL=file:./.objectstack/software.db pnpm dev -node scripts/software-people.mjs # 15 个岗位账号、到人分工、个人承接项、分管领导(用户不能种子,只能运行期建) +node scripts/software-people.mjs # 18 个岗位账号、到人分工、个人承接项、分管领导(用户不能种子,只能运行期建) node scripts/software-flow.mjs [结果.json] # 发布 → 填报 → 核对 → 审核 → 加减分 → 调整 → 汇总 → 归档 → 数据范围 # 换回默认档案:同样是删 dist + 另指定(或清空)数据库文件 @@ -57,6 +57,14 @@ OS_DATABASE_URL=file:./.objectstack/default.db pnpm dev 两个脚本都读 `KPI_BASE_URL`(缺省 `http://localhost:${OS_PORT:-3000}`);`software-people.mjs` 可重复执行, 且必须在方案仍是草稿时运行 —— 到人分工与分管领导随方案发布冻结;`software-flow.mjs` 要求空库(它会先故意把方案改坏来验发布拦截)。 +`software-people.mjs` 建的 18 个账号里,三家分公司**各有两名**:一名「分公司填报人员」(沈月 / +黄鹤 / 秦朗,岗位 `kpi_dept_reporter`)、一名「分公司核对人员」(陈东 / 林南 / 高北,岗位 +`kpi_branch_checker`)。分公司在方案里既是被考核主体、又是核对方,而核对人员按《设计方案》 +§3 表 1 只能「确认无误 / 提出争议」、改不了数值 —— 分公司自己那张填报单必须由分公司填报 +人员来填,不是管理员代填(`software-flow.mjs` 的 T11b 就断言这一条)。他们的数据范围与部门 +填报人员同源:方案发布时按参与主体写入的共享规则把本主体的填报单放宽到本单元成员,分公司 +本身就是参与主体,不需要任何额外的元数据。 + > ⚠️ 演示夹具的租户对齐(临时):种子写入的组织单元 `organization_id` 为空,而管理员在 > Setup 里新建的单元会被引擎盖上当前组织;共享规则的收件方展开对这一列做等值比较,所以 > 只有种子单元展开不出人。`src/data/align-demo-units.ts` 在 `kernel:bootstrapped` 时把这几个 diff --git a/scripts/software-flow.mjs b/scripts/software-flow.mjs index 43d4b90..755a4e2 100644 --- a/scripts/software-flow.mjs +++ b/scripts/software-flow.mjs @@ -28,6 +28,9 @@ const ACCOUNT = { east: 'east.checker@kpi.demo', south: 'south.checker@kpi.demo', north: 'north.checker@kpi.demo', + eastReporter: 'east.reporter@kpi.demo', + southReporter: 'south.reporter@kpi.demo', + northReporter: 'north.reporter@kpi.demo', hr: 'hr.reviewer@kpi.demo', head: 'hr.head@kpi.demo', leaderTech: 'leader.tech@kpi.demo', @@ -267,14 +270,52 @@ asAdmin(); sheet = await get('kpi_entry_sheet', salesSheet.id); log('T10', '分管领导审批通过 → 已通过并盖章', r.status < 300 && sheet.status === 'approved' && !!sheet.approved_at && !!sheet.approved_by, `status=${sheet.status}`); -// ── 其余 10 张填报单:管理员批量推到已通过,让四维汇总有完整数据 ────────── +/** + * 分公司主体的填报人员 —— 分公司自己那张填报单由**本分公司的填报人员**填,不由管理员代填。 + * + * 分公司在本方案里既是核对方、又是被考核主体,两件事是两个岗位:核对人员只能确认或提出 + * 争议、改不了数值,所以分公司填报单必须另有填报人员。这三个账号由 software-people.mjs + * 建立(岗位 `kpi_dept_reporter`,组织归属为本分公司)。 + */ +const BRANCH_REPORTER = { + bu_sw_east: ACCOUNT.eastReporter, + bu_sw_south: ACCOUNT.southReporter, + bu_sw_north: ACCOUNT.northReporter, +}; +/** 由分公司填报人员本人填报并提交的分公司主体(供 T11b 断言,不是管理员代填)。 */ +const filledByBranchReporter = []; + +// ── 其余 10 张填报单:部门单由管理员批量推,分公司单由分公司填报人员本人填 ────── async function pushToApproved(target) { - const lines = await list('kpi_entry_line', `?sheet=${target.id}&limit=50`); - for (const l of lines) { - const code = [...byCode.entries()].find(([, ind]) => String(ind.id) === String(l.indicator))?.[0]; - await patch('kpi_entry_line', l.id, { actual_value: ACTUALS[String(target.subject)][code] }); + const subject = String(target.subject); + const reporter = BRANCH_REPORTER[subject]; + if (reporter) { + // 填报与提交这两步换成分公司填报人员的登录态:能填、能提交本身就是数据范围的断言, + // 填不了或提交不了会让这里直接抛错(signIn 失败即终止),不会静默退回管理员代填。 + await signIn(reporter); + // 共享规则展开成逐人记录共享行是异步的:等到本分公司的填报单真的可见再动手, + // 否则明细列表会返回空数组,而「空数组每一行都填好了」会静默变成一条假通过。 + await waitUntil(`${subject} 填报单共享给分公司填报人员`, async () => + (await list('kpi_entry_line', `?sheet=${target.id}&limit=50`)).length > 0); + const own = await list('kpi_entry_line', `?sheet=${target.id}&limit=50`); + for (const l of own) { + const code = [...byCode.entries()].find(([, ind]) => String(ind.id) === String(l.indicator))?.[0]; + await patch('kpi_entry_line', l.id, { actual_value: ACTUALS[subject][code] }); + } + const submitted = await patch('kpi_entry_sheet', target.id, { pending_action: 'submit' }); + asAdmin(); + const after = await get('kpi_entry_sheet', target.id); + const reread = await list('kpi_entry_line', `?sheet=${target.id}&limit=50`); + const filledAll = reread.length > 0 && reread.every((l) => l.actual_value !== null && l.actual_value !== undefined); + if (own.length > 0 && submitted.status < 300 && filledAll && after.status !== 'draft') filledByBranchReporter.push(subject); + } else { + const lines = await list('kpi_entry_line', `?sheet=${target.id}&limit=50`); + for (const l of lines) { + const code = [...byCode.entries()].find(([, ind]) => String(ind.id) === String(l.indicator))?.[0]; + await patch('kpi_entry_line', l.id, { actual_value: ACTUALS[subject][code] }); + } + await patch('kpi_entry_sheet', target.id, { pending_action: 'submit' }); } - await patch('kpi_entry_sheet', target.id, { pending_action: 'submit' }); let s = await get('kpi_entry_sheet', target.id); if (s.status === 'branch_checking') { for (const t of await list('kpi_check_task', `?sheet=${target.id}&limit=20`)) { @@ -297,6 +338,9 @@ for (const s of sheets) { } log('T11', '其余 10 张填报单全部推进到已通过', pushed.every((s) => s.status === 'approved'), pushed.map((s) => `${s.name.split(' · ')[1]}:${s.status}/${s.total_score}`).join(' | ')); +log('T11b', '三张分公司填报单由本分公司的填报人员本人填报并提交(不是管理员代填)', + filledByBranchReporter.length === 3 && ['bu_sw_east', 'bu_sw_south', 'bu_sw_north'].every((u) => filledByBranchReporter.includes(u)), + `由分公司填报人员完成:${filledByBranchReporter.join(', ') || '(无)'}`); const csSheet = await get('kpi_entry_sheet', sheetOf('bu_sw_cs').id); const csLines = await list('kpi_entry_line', `?sheet=${csSheet.id}&limit=50`); diff --git a/scripts/software-people.mjs b/scripts/software-people.mjs index 0c0c796..663a087 100644 --- a/scripts/software-people.mjs +++ b/scripts/software-people.mjs @@ -25,8 +25,20 @@ const STAFF = [ { name: '郑洁', email: 'fin.specialist@kpi.demo', job: '财务专员', unit: 'bu_sw_fin', position: 'kpi_dept_reporter', weight: 100, coefficient: 0.95, item: 'SWFI02', itemWeight: 20 }, ]; -/** 流程岗位人员:分公司核对、人力审核、人力负责人、分管领导(不建到人分工)。 */ +/** + * 流程岗位人员:分公司填报、分公司核对、人力审核、人力负责人、分管领导(不建到人分工)。 + * + * 分公司同时是**被考核主体**和**核对方**,两件事是两个岗位:核对人员只能「确认无误 / + * 提出争议」、改不了数值(《设计方案》§3 表 1),所以分公司自己那张填报单必须另有填报 + * 人员来填。三家分公司各配 1 名「部门填报人员」(岗位机器名 `kpi_dept_reporter`,组织 + * 归属为本分公司),数据范围与部门填报人员同源:方案发布时按参与主体写入的共享规则把 + * 本主体的填报单放宽到本单元成员(services/sharing-service.ts),分公司本身就是参与 + * 主体,不需要任何额外的元数据。缺了他们,三张分公司填报单在界面上无人可填。 + */ const OFFICERS = [ + { name: '沈月', email: 'east.reporter@kpi.demo', job: '华东分公司填报人员', unit: 'bu_sw_east', position: 'kpi_dept_reporter' }, + { name: '黄鹤', email: 'south.reporter@kpi.demo', job: '华南分公司填报人员', unit: 'bu_sw_south', position: 'kpi_dept_reporter' }, + { name: '秦朗', email: 'north.reporter@kpi.demo', job: '华北分公司填报人员', unit: 'bu_sw_north', position: 'kpi_dept_reporter' }, { name: '陈东', email: 'east.checker@kpi.demo', job: '华东分公司核对人员', unit: 'bu_sw_east', position: 'kpi_branch_checker' }, { name: '林南', email: 'south.checker@kpi.demo', job: '华南分公司核对人员', unit: 'bu_sw_south', position: 'kpi_branch_checker' }, { name: '高北', email: 'north.checker@kpi.demo', job: '华北分公司核对人员', unit: 'bu_sw_north', position: 'kpi_branch_checker' },