Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 另指定(或清空)数据库文件
Expand All @@ -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` 时把这几个
Expand Down
56 changes: 50 additions & 6 deletions scripts/software-flow.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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`)) {
Expand All @@ -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`);
Expand Down
14 changes: 13 additions & 1 deletion scripts/software-people.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
Loading