From 269f6b5d0fe25063b1cca59b48c9ec6017e467ed Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 21 Mar 2026 20:32:00 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=AE=8C=E6=88=90=20R17=20=E5=B0=8F?= =?UTF-8?q?=E5=9B=A2=E9=98=9F=E4=B8=8A=E7=BA=BF=E6=94=B6=E5=8F=A3=E4=B8=8E?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/trialrun/.env.example | 14 ++++ deploy/trialrun/checklist.md | 12 +++ deploy/trialrun/runbook.md | 34 ++++++++ docs/admin-handbook.md | 76 +++++++++++++++++ docs/employee-quickstart.md | 47 +++++++++++ docs/feature-matrix.md | 10 +++ server/.env.example | 9 ++ server/prisma.config.ts | 2 +- server/src/scripts/seed-config.js | 28 +++++++ server/src/scripts/seed-trialrun.js | 122 ++++++++++++++++++++++++++++ 10 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 docs/admin-handbook.md create mode 100644 docs/employee-quickstart.md diff --git a/deploy/trialrun/.env.example b/deploy/trialrun/.env.example index 7ae9ba04..f89515a5 100644 --- a/deploy/trialrun/.env.example +++ b/deploy/trialrun/.env.example @@ -94,3 +94,17 @@ TRIALRUN_ADMIN_EMAIL="admin@hyinda.com" TRIALRUN_ADMIN_PHONE="13800000000" # 留空则首次 seed 自动生成并打印一次性口令 TRIALRUN_ADMIN_PASSWORD="" + +# ===== Prisma Migration Diff(本地维护 migration 可选) ===== +# SHADOW_DATABASE_URL="postgresql://hyboa_user:hyboa_password@db:5432/hyb_oa_shadow" + +# ===== R13-R17 小团队内部管理默认上线建议 ===== +PORTAL_HOME_ENABLED="true" +GLOBAL_SEARCH_ENABLED="true" +ANNOUNCEMENTS_ENABLED="true" +PRESENCE_BOARD_ENABLED="true" +WEEKLY_REPORT_ENABLED="true" +EMPLOYEE_LIFECYCLE_ENABLED="true" +TODO_BOARD_ENABLED="true" +PAYMENT_BATCH_ENABLED="false" +CUTOVER_CONSOLE_ENABLED="false" diff --git a/deploy/trialrun/checklist.md b/deploy/trialrun/checklist.md index 7a9a8986..b8122cb0 100644 --- a/deploy/trialrun/checklist.md +++ b/deploy/trialrun/checklist.md @@ -54,3 +54,15 @@ - [ ] 先开 `portal`,再逐域灰度打开业务入口 - [ ] Portal、待办、预算预校验、支付批次、公告、搜索已在试点部门可用 - [ ] 回退方案已演练:关闭 `CutoverScope` + 恢复旧入口 + +## F. R17 小团队上线检查 +- [ ] 工作区模式已确认为 `small_team_internal` +- [ ] 主导航已收敛为 `Portal / 团队 / 请假 / 报销 / 资产 / 周报 / 待办 / 公告` +- [ ] 采购 / 差旅 / 预算 / 支付批次 / AP 运营已降级为管理员备用入口 +- [ ] 已打开 `portal_home_v1`、`announcements_v1`、`presence_board_v1`、`weekly_report_v1`、`employee_lifecycle_v1`、`todo_board_v1` +- [ ] 员工账号、档案、部门、直属上级已补齐 +- [ ] 资产台账与员工领用关系已核对 +- [ ] 至少 1 条公告已发布并在 `Portal` 可见 +- [ ] 至少 1 次入职/离职/资产交接已演练 +- [ ] 全员已知晓每周周报和今日状态使用要求 +- [ ] 已建立连续 4 周观测记录方式 diff --git a/deploy/trialrun/runbook.md b/deploy/trialrun/runbook.md index 297e5a7f..fb35cfb3 100644 --- a/deploy/trialrun/runbook.md +++ b/deploy/trialrun/runbook.md @@ -191,3 +191,37 @@ docker logs -f hyb-oa-trialrun-server 2. 恢复旧 OA 对应入口并重新开放新单。 3. 若需要代码/数据回退,按本文第 3 节恢复到最近备份。 4. 新系统中试点期间新建单据保留审计,不做 destructive 回写到旧 OA。 + +## 7. R17 小团队内部管理上线 Runbook + +### 7.1 默认目标形态 +- 工作区模式:`small_team_internal` +- 主导航:`Portal`、`团队`、`请假`、`报销`、`资产`、`周报`、`待办`、`公告` +- 入口策略:账号密码主入口,站内通知主通道,桌面优先 + +### 7.2 推荐环境开关 +- 建议打开:`PORTAL_HOME_ENABLED=true`、`GLOBAL_SEARCH_ENABLED=true`、`ANNOUNCEMENTS_ENABLED=true`、`PRESENCE_BOARD_ENABLED=true`、`WEEKLY_REPORT_ENABLED=true`、`EMPLOYEE_LIFECYCLE_ENABLED=true`、`TODO_BOARD_ENABLED=true` +- 建议保留备用:`PAYMENT_BATCH_ENABLED=false`、`CUTOVER_CONSOLE_ENABLED=false` +- OCR 回归环境建议:`OCR_PROVIDER=mock` + +### 7.3 上线前整理事项 +1. 补齐员工账号、部门、直属上级与员工档案。 +2. 核对资产卡片与实际领用关系。 +3. 为老板 / 管理员 / 主管 / 员工分配推荐角色预设。 +4. 在 `工作区设置` 确认模块显隐,收敛主导航。 +5. 准备一条公告,验证全员登录后 `Portal` 可见。 + +### 7.4 首周启用顺序 +1. 打开 `Portal + 公告 + 团队状态` +2. 第二天开始要求全员更新今日状态 +3. 当周启用 `周报 + 待办` +4. 同步启用 `入职 / 离职 / 资产交接` +5. 报销和请假保持可用,但不再作为首页主叙事 + +### 7.5 4 周观测建议 +- 每周记录: + - 周报提交人数 + - 团队状态更新人数 + - 公告阅读反馈 + - 入职/离职/资产交接演练情况 +- 若连续两周无人使用某入口,考虑继续降级模块可见性。 diff --git a/docs/admin-handbook.md b/docs/admin-handbook.md new file mode 100644 index 00000000..f523bea4 --- /dev/null +++ b/docs/admin-handbook.md @@ -0,0 +1,76 @@ +# hyb-oa 管理员手册(10 人以下团队版) + +## 1. 适用范围 +- 适用于 `small_team_internal` 工作区模式。 +- 适用于单公司、少部门、全员账号密码登录的小团队。 +- 目标不是替代飞书/钉钉等 SaaS,而是把内部高频管理动作收口到一个系统。 + +## 2. 管理员每周固定动作 +- 周一:查看 `Portal` 的团队状态、上周周报汇总、入离职待办、资产待归还。 +- 周中:处理公告、请假审批、员工档案更新、离职阻塞项。 +- 周五:催交周报、查看高优先级待办、确认下周请假和资产交接。 + +## 3. 首次上线最小配置 +1. 进入 `工作区设置`,确认产品模式为 `small_team_internal`。 +2. 仅保留主导航:`Portal`、`团队`、`请假`、`报销`、`资产`、`周报`、`待办`、`公告`。 +3. 将 `采购`、`差旅`、`预算`、`支付批次`、`AP 运营` 等模块降级为管理员备用。 +4. 在 `用户管理` 中按推荐预设配置:老板 / 管理员 / 主管 / 员工。 +5. 检查功能开关: + - 建议开启:`portal_home_v1`、`announcements_v1`、`presence_board_v1`、`weekly_report_v1`、`employee_lifecycle_v1`、`todo_board_v1`、`global_search_v1` + - 建议保留备用:`payment_batch_v1`、`cutover_console_v1` + +## 4. 人员生命周期管理 +### 4.1 入职 +1. 在 `人员管理` 创建员工账号。 +2. 打开员工详情页,补齐部门、职位、直属上级、试用期结束日、工作地点、紧急联系人。 +3. 发起 `入职`,系统会自动生成 checklist:账号初始化、资产发放、制度确认。 +4. checklist 全部完成后,员工状态自动从 `onboarding` 进入 `probation`。 + +### 4.2 转正 / 调岗 +- `转正`:记录事件并将员工状态改为 `active`。 +- `调岗`:记录部门、直属上级、职位调整,同时生成调岗交接 checklist。 +- 这两个动作只做管理留痕,不引入复杂审批流。 + +### 4.3 离职 +1. 发起 `离职` 后,系统自动生成离职 checklist:资产归还、权限回收、费用与审批清理、交接说明。 +2. 若员工仍有名下资产或待处理审批任务,系统不会自动完成离职。 +3. checklist 完成且阻塞清零后,账号会被停用,主业务入口对该账号不可再用。 + +## 5. 团队状态管理 +- 员工每天可在 `Portal` 或 `团队` 页更新状态:在岗 / 居家 / 外出 / 出差。 +- 已批准请假会自动覆盖当天状态为 `请假`。 +- 管理员只需要看当天分布,不做打卡、迟到、排班和月度统计。 + +## 6. 周报与待办 +- 周报固定三段:本周完成 / 当前阻塞 / 下周计划。 +- 建议每周五下班前提交,周一老板看团队汇总。 +- 需要跟进的事项可从周报沉淀为待办。 +- 高优先级待办建议由老板或管理员统一看板跟进。 + +## 7. 公告与 Portal 使用建议 +- 公告只发公司级通知、制度更新、假期提醒,不做复杂 CMS。 +- `Portal` 首屏建议重点看: + - 今日团队状态 + - 未提交周报人数 + - 需协助员工 + - 入/离职待办 + - 资产待归还 + - 审批待办 + +## 8. 备用模块治理 +- 采购、差旅、预算、支付批次、AP 运营保留代码和历史数据,但不作为全员主路径。 +- 若短期完全不用,保持管理员可见即可,不必删除。 +- 只有出现真实场景时,再恢复到主导航或单独扩展。 + +## 9. 推荐上线顺序 +1. 先开 `Portal + 公告 + 团队状态` +2. 再推 `周报 + 待办` +3. 同步启用 `入职/离职/资产交接` +4. 最后根据实际需要保留轻量报销与请假 + +## 10. 试点验收口径 +- 全员都能登录并进入 `Portal` +- 至少 1 次真实入职或模拟入职跑通 +- 至少 1 次真实离职或模拟离职跑通 +- 全员连续 4 周提交周报 +- 管理员不再额外维护“今日在岗表”或“离职交接表” diff --git a/docs/employee-quickstart.md b/docs/employee-quickstart.md new file mode 100644 index 00000000..8cd97566 --- /dev/null +++ b/docs/employee-quickstart.md @@ -0,0 +1,47 @@ +# hyb-oa 员工快速上手(10 人以下团队版) + +## 1. 你每周主要会用到什么 +- `Portal`:看公告、我的待办、我的资产、本周周报入口。 +- `团队`:更新今天是在岗、居家、外出还是出差。 +- `请假`:提交并查看请假记录。 +- `报销`:继续提交和跟踪报销。 +- `周报`:每周写一次三段式周报。 +- `待办`:记录和跟进个人事项。 + +## 2. 第一次登录后建议先做三件事 +1. 检查个人资料是否正确。 +2. 打开 `团队` 更新今天状态。 +3. 打开 `周报` 页面,熟悉本周周报入口。 + +## 3. 今日状态怎么填 +- `在岗`:今天主要在办公室。 +- `居家`:今天远程办公。 +- `外出`:今天外勤、不在办公室。 +- `出差`:今天在外地差旅。 +- 如果你已经请假获批,系统会自动显示 `请假`,不用手工改。 + +## 4. 周报怎么写 +- 本周完成:写已经完成的主要工作。 +- 当前阻塞:写你卡住的事、需要谁协助。 +- 下周计划:写下周打算做什么。 +- 如果确实需要支持,勾选“需要协助”。 + +## 5. 待办怎么用 +- 记录自己要跟进的事项。 +- 可填写优先级和截止日期。 +- 完成后及时点完成,避免 Portal 一直堆积。 + +## 6. 请假和报销 +- 继续按现有流程提交。 +- 批准后的请假会自动反映到团队状态。 +- 报销状态仍可在系统内查看,不需要额外问行政。 + +## 7. 入职 / 离职时你会看到什么 +- 入职时会有账号初始化、资产发放、制度确认等事项。 +- 离职前需要归还资产并完成交接,系统会提示阻塞项。 + +## 8. 使用建议 +- 每天登录先看 `Portal` +- 每周五提交周报 +- 状态有变化时及时更新 `团队` +- 有通知先看站内消息,IM 只是补充提醒 diff --git a/docs/feature-matrix.md b/docs/feature-matrix.md index 461370de..de373265 100644 --- a/docs/feature-matrix.md +++ b/docs/feature-matrix.md @@ -336,3 +336,13 @@ | R11-3 | 请假采购资产规格补齐 | §4.7-§4.9,§13.2.7-§13.2.9 | 2026-03-13_10-30-00-r11-contract-reconciliation-and-spec-refresh.csv | 2026-03-13_10-30-00-r11-contract-reconciliation-and-spec-refresh.csv | 2026-03-13 | DONE | manual | 规格书已按当前代码与完成合同补齐请假 / 采购 / 资产的范围、权限、状态机、核心对象与显式不做项 | | R11-4 | 差旅集成规格补齐与 cross-cutting 同步 | §4.10-§4.11,§13.2.10-§13.2.11,§15-§16 | 2026-03-13_10-30-00-r11-contract-reconciliation-and-spec-refresh.csv | 2026-03-13_10-30-00-r11-contract-reconciliation-and-spec-refresh.csv | 2026-03-13 | DONE | manual | 规格书已补齐 TRAVEL-1~3、IM-1、ERP-1~3 的已实现边界,并同步数据模型概览与迭代计划 | | R11-5 | R11矩阵事实源与文档校验收口 | §16 迭代计划(执行合同收口) | 2026-03-13_10-30-00-r11-contract-reconciliation-and-spec-refresh.csv | 2026-03-13_10-30-00-r11-contract-reconciliation-and-spec-refresh.csv | 2026-03-13 | DONE | CSV 校验 + validate-feature-matrix + git diff --check | Feature Matrix、事实源校验器、R11 合同与文档变更已同步收口,历史 TODO 清账口径可追溯 | + +## R13-R17 小团队内部管理增量(2026-03-21) + +- **事实源**:`issues/2026-03-21_12-00-00-r13-small-team-profile.csv`、`issues/2026-03-21_12-10-00-r14-people-lifecycle.csv`、`issues/2026-03-21_12-20-00-r15-team-presence.csv`、`issues/2026-03-21_12-30-00-r16-weekly-report-todo.csv`、`issues/2026-03-21_12-40-00-r17-small-team-go-live.csv` +- **R13 小团队模式收敛**:新增 `WorkspaceSetting` 单例配置、`/api/system/workspace` 与 `/api/system/navigation`,默认主导航收敛为 `Portal / 团队 / 请假 / 报销 / 资产 / 周报 / 待办 / 公告`,采购/差旅/预算/AP 等模块降级为管理员备用入口。 +- **R14 入转离与资产闭环**:扩展 `Employee` 就业状态字段,新增 `EmployeeLifecycleEvent`、`EmployeeChecklistTask`,支持入职、转正、调岗、离职、资产阻塞校验与账号停用闭环。 +- **R15 团队状态**:新增 `EmployeePresence` 与团队在岗视图,支持员工手工更新今日状态,请假自动覆盖为 `leave`。 +- **R16 周报与待办**:新增 `WeeklyReport` 与 `TodoItem`,支持周报懒创建、提交、团队汇总、待办创建/完成/取消,并接入 `Portal` 与搜索。 +- **R17 真可用收口**:新增小团队管理员手册、员工快速上手文档、trialrun 运行手册与检查项补充,支持以 `small_team_internal` 为默认上线模式。 +- **默认上线建议**:开启 `portal_home_v1`、`announcements_v1`、`presence_board_v1`、`weekly_report_v1`、`employee_lifecycle_v1`、`todo_board_v1`、`global_search_v1`;保留 `payment_batch_v1`、`cutover_console_v1` 为管理员备用能力。 diff --git a/server/.env.example b/server/.env.example index 4b885156..80877c90 100644 --- a/server/.env.example +++ b/server/.env.example @@ -108,3 +108,12 @@ ANNOUNCEMENTS_ENABLED=false WORKFLOW_DEFINITION_ADMIN_ENABLED=false CUTOVER_CONSOLE_ENABLED=false OIDC_PROVIDER_LABEL=企业统一登录 + +# ===== Prisma Migration Diff(本地生成 migration 用,可选) ===== +# SHADOW_DATABASE_URL="postgresql://hyboa_user:hyboa_password@127.0.0.1:5432/hyb_oa_shadow" + +# ===== R13-R17 小团队内部管理 ===== +PRESENCE_BOARD_ENABLED=false +WEEKLY_REPORT_ENABLED=false +EMPLOYEE_LIFECYCLE_ENABLED=false +TODO_BOARD_ENABLED=false diff --git a/server/prisma.config.ts b/server/prisma.config.ts index 52c5d1d5..7334a842 100644 --- a/server/prisma.config.ts +++ b/server/prisma.config.ts @@ -8,6 +8,6 @@ export default defineConfig({ }, datasource: { url: process.env["DATABASE_URL"], + shadowDatabaseUrl: process.env["SHADOW_DATABASE_URL"], }, }); - diff --git a/server/src/scripts/seed-config.js b/server/src/scripts/seed-config.js index 6dd880d1..b98940f7 100644 --- a/server/src/scripts/seed-config.js +++ b/server/src/scripts/seed-config.js @@ -282,6 +282,34 @@ const DEFAULT_FEATURE_FLAGS = [ department_ids: [], note: "bootstrap default: force_off", }, + { + flag_key: "presence_board_v1", + override_mode: "force_off", + rollout_type: "global", + department_ids: [], + note: "bootstrap default: force_off", + }, + { + flag_key: "weekly_report_v1", + override_mode: "force_off", + rollout_type: "global", + department_ids: [], + note: "bootstrap default: force_off", + }, + { + flag_key: "employee_lifecycle_v1", + override_mode: "force_off", + rollout_type: "global", + department_ids: [], + note: "bootstrap default: force_off", + }, + { + flag_key: "todo_board_v1", + override_mode: "force_off", + rollout_type: "global", + department_ids: [], + note: "bootstrap default: force_off", + }, ]; const DEFAULT_BUDGET_CONTROL_RULES = [ diff --git a/server/src/scripts/seed-trialrun.js b/server/src/scripts/seed-trialrun.js index 1422f572..42d649ca 100644 --- a/server/src/scripts/seed-trialrun.js +++ b/server/src/scripts/seed-trialrun.js @@ -53,6 +53,108 @@ function generateOneTimePassword() { return `TrialrunA1${suffix}`; } +async function ensureFeatureFlag(prisma, payload) { + const now = new Date(); + const existing = await prisma.featureFlag.findUnique({ + where: { flag_key: payload.flag_key }, + select: { id: true, version: true }, + }); + + if (existing) { + return prisma.featureFlag.update({ + where: { id: existing.id }, + data: { + override_mode: payload.override_mode, + rollout_type: payload.rollout_type, + department_ids: payload.department_ids, + note: payload.note, + version: existing.version + 1, + updated_at: now, + }, + }); + } + + return prisma.featureFlag.create({ + data: { + ...payload, + created_at: now, + updated_at: now, + }, + }); +} + +async function ensureWorkspaceSetting(prisma) { + const current = await prisma.workspaceSetting.findFirst({ select: { id: true } }); + if (current) { + return prisma.workspaceSetting.update({ + where: { id: current.id }, + data: { + profile: "small_team_internal", + org_mode: "single_company", + primary_entry_mode: "password", + notification_strategy: "in_app", + device_priority: "desktop", + module_visibility_json: { + portal: "main", + team: "main", + leaveRequests: "main", + reimbursements: "main", + assets: "main", + weeklyReports: "main", + todos: "main", + announcements: "main", + workspace: "admin", + people: "admin", + workflowDefinitions: "admin", + systemSettings: "admin", + featureFlags: "admin", + auditLogs: "admin", + purchaseRequests: "admin", + travelRequests: "admin", + budgets: "admin", + paymentBatches: "admin", + apOperations: "admin", + cutover: "admin", + erpIntegrations: "admin", + }, + }, + }); + } + + return prisma.workspaceSetting.create({ + data: { + profile: "small_team_internal", + org_mode: "single_company", + primary_entry_mode: "password", + notification_strategy: "in_app", + device_priority: "desktop", + module_visibility_json: { + portal: "main", + team: "main", + leaveRequests: "main", + reimbursements: "main", + assets: "main", + weeklyReports: "main", + todos: "main", + announcements: "main", + workspace: "admin", + people: "admin", + workflowDefinitions: "admin", + systemSettings: "admin", + featureFlags: "admin", + auditLogs: "admin", + purchaseRequests: "admin", + travelRequests: "admin", + budgets: "admin", + paymentBatches: "admin", + apOperations: "admin", + cutover: "admin", + erpIntegrations: "admin", + }, + }, + }); +} + async function ensureAdminEmployee(prisma, params) { const now = new Date(); @@ -151,6 +253,26 @@ async function main() { hireDate: new Date(process.env.TRIALRUN_ADMIN_HIRE_DATE ?? "2020-01-01"), }); + await ensureWorkspaceSetting(prisma); + + for (const flagKey of [ + "portal_home_v1", + "announcements_v1", + "global_search_v1", + "presence_board_v1", + "weekly_report_v1", + "employee_lifecycle_v1", + "todo_board_v1", + ]) { + await ensureFeatureFlag(prisma, { + flag_key: flagKey, + override_mode: "force_on", + rollout_type: "global", + department_ids: [], + note: "trialrun default: force_on", + }); + } + // eslint-disable-next-line no-console console.log("Seeded trialrun admin:"); // eslint-disable-next-line no-console