diff --git a/docs/requirements-specification.md b/docs/requirements-specification.md index ce493135..a640ba86 100644 --- a/docs/requirements-specification.md +++ b/docs/requirements-specification.md @@ -1,1563 +1,349 @@ -# 海音达 OA 系统 - 需求规格说明书 +# 海音达 OA 系统 - MVP 需求规格说明书 -**版本**:1.4 -**日期**:2026-02-06 -**范围**:报销系统及其依赖模块(员工/权限/工作流/附件) +**版本**:2.0 +**日期**:2026-05-09 +**范围**:10 人以下科技公司试运行 MVP 需求基线 +**状态**:当前 PRD 主事实源 **相关文档**: -- [系统架构概览](./architecture.md) -- [技术栈与复用报告](./tech_stack_report.md) -- [实施计划](./implementation_plan.md) +- [产品口径文档](./product-decisions.md):关键产品决策与边界。 +- [功能状态矩阵](./feature-matrix.md):历史实现状态、覆盖证据和完成追踪,不作为 PRD 扩写入口。 +- [管理员手册](./admin-handbook.md):试运行配置、企业微信入口和上线操作。 +- [员工快速开始](./employee-quickstart.md):员工侧使用说明。 +- [报销与发票规则](./reimbursement-invoice-rules.md):报销、发票、OCR、附件的细则。 +- `issues/*.csv`:每轮开发执行合同;需求确认后必须拆 Issue CSV 再开发。 --- -## 1. 项目概述 +## 1. 项目目标与当前路线 ### 1.1 背景 -海音达科技需要一套独立的 OA 系统,用于内部办公管理。Phase 1 聚焦**报销系统**及其依赖(员工管理、权限、工作流、附件/发票)。 -### 1.2 目标(Phase 1) -- 实现完整的费用报销流程:草稿 → 提交 → 审批(工作流任务)→ 通过 → 付款标记 → 完成 -- 审批规则可配置(金额区间 → 多级审批角色),P0 预置 3 条默认规则 -- 审批引擎采用通用 4 表式工作流(Definition/Instance/Task/History),后续扩展到请假/采购无需重构 -- 支持行政代提交(需授权)、发票附件上传、双策略判重、离线打款标记 -- 响应式 Web(PC/移动端) +海音达需要一套适合 10 人以下科技公司的轻量 OA,用来把报销、请假、采购、费用、合同、项目资料、审批和基础行政动作集中到一个可审计、可回归、可持续迭代的系统中。系统不追求一次性替代所有办公平台,而是先解决公司内部最容易割裂、最需要留痕的业务流程。 -### 1.3 技术栈(定案) -| 层级 | 选型 | -| :--- | :--- | -| 前端(OA) | React + Vite + Tailwind | -| 后端(OA API) | Fastify + Prisma | -| 数据库 | PostgreSQL | +### 1.2 总目标 -### 1.4 复用资产(来自 `shenzhou-business-suite`) -> 目的:减少踩坑与重复造轮子。本文档的表结构/枚举/约束均以该项目的已验证实现为基线,再按 hyb-oa 目标做裁剪与补齐。 +- `hyb-oa` 是唯一业务主系统和唯一正式审批中心。 +- 所有正式审批在 OA 内提交、审批、留痕、归档。 +- 企业微信只作为基础设施:手机工作台入口、免登/绑定、通讯录身份、消息通知、打卡原始采集。 +- PC 用户可直接访问 OA;手机端优先从企业微信工作台进入 OA 移动端。 +- 后续开发必须先有需求事实源和 Issue CSV 执行合同,避免边想边做。 -| 资产 | 来源路径(参考) | 在 hyb-oa 的复用方式 | -| :--- | :--- | :--- | -| 工作流 4 表结构 + `approval_rules`/`approval_transfers`/`document_sequences` | `shenzhou-business-suite/supabase/schema/70_workflow/01_workflow_02_tables.sql` | 转为 Prisma schema;服务端实现工作流事务与权限校验 | -| 工作流类型定义(assignee_rule 等) | `shenzhou-business-suite/src/types/workflow.ts` | 作为工作流 JSON 配置与服务层契约参考 | -| 权限码体系 | `shenzhou-business-suite/src/types/permissions.ts` | 复用命名与粒度;补齐行政代提交权限码 | -| 报销表结构参考(forms/items/attachments) | `shenzhou-business-suite/supabase/schema/20_finance/06_reimbursement_01_tables.sql` | 字段设计参考;按 hyb-oa 简化并补齐 `file_sha256` 等字段 | -| 默认费用类型 seed(35 启用 + 3 legacy 禁用) | `shenzhou-business-suite/supabase/seed.sql` | 作为系统默认费用类型基线;hyb-oa 额外启用 `utilities/rent` 形成 37 启用 | -| 存储安全基线思路(类型/大小/访问控制) | `shenzhou-business-suite/supabase/migrations/20260131175000_storage_security_baseline.sql` | 落到本地存储 + 签名 URL 的约束与鉴权规则 | +### 1.3 当前平台边界 + +| 平台 | 定位 | 首批使用方式 | 不承担 | +| --- | --- | --- | --- | +| `hyb-oa` | 唯一业务主系统和审批中心 | 审批、报销、发票、OCR、请假、采购、合同台账、项目资料、审计归档 | 聊天、会议、在线文档协作 | +| 企业微信 | 基础设施 | 手机入口、免登/绑定、通知 deeplink、通讯录身份、打卡原始采集 | 正式审批流、财务附件主库、业务单据主库 | +| 飞书 | 已废弃路线 | 不进入首批实现 | 不接入身份、审批、通知、文档或项目主线 | --- -## 2. 用户角色与权限模型 +## 2. 软件开发项目流程 -### 2.1 角色(role_code)与职责边界(P0) -| 角色(展示) | role_code | 职责/范围 | -| :--- | :--- | :--- | -| 普通员工 | `employee` | 创建/提交报销,查看/编辑本人草稿与记录 | -| 部门经理 | `manager` | 审批直属下属报销(工作流任务);可查看直属下属报销(P0:不递归) | -| 总经理 | `ceo` | 审批大额报销(工作流任务),查看全公司数据(只读) | -| 财务专员 | `finance` | 财务复核(工作流任务)、付款标记、票据管理(全量) | -| 财务主管 | `finance_mgr` | 大额复核、发票判重例外、审批规则维护(P1 UI) | -| 行政 | `administration` | 代提交(授权由 principal 本人或 `hr/admin` 维护;不含审批/付款) | -| 人事 | `hr` | 员工与组织维护(不含财务/审批) | -| 系统管理员 | `admin` | 系统设置、用户、审计(可授予 `*`) | +后续所有非微小改动按以下流程推进: -> 说明:一个员工可同时拥有多个 `role_code`(例如 `finance` + `admin`),权限按并集生效。 - -### 2.2 权限码(当前实现) -> 命名规则:`domain.action[_scope]`。后端接口必须声明所需权限码并统一校验;前端仅用于菜单与按钮显隐,不作为最终授权依据。 - -#### 2.2.1 报销 / 票据 / 系统基础权限 - -| permission | 说明 | -| :--- | :--- | -| `expense.create` | 创建报销单(草稿) | -| `expense.view_own` | 查看自己的报销单 | -| `expense.view_all` | 查看所有报销单(数据范围由角色决定) | -| `expense.edit_own` | 编辑自己的报销单(仅草稿/驳回) | -| `expense.delete_own` | 删除自己的报销单(仅草稿) | -| `expense.submit` | 提交报销单(发起工作流) | -| `expense.withdraw` | 撤回报销单(将流程取消并回到草稿) | -| `expense.approve` | 审批报销单(处理工作流任务) | -| `expense.transfer` | 转审(转交当前待办任务) | -| `expense.finance.pay` | 付款标记(`approved -> paid`) | -| `expense.finance.override_duplicate` | 发票重复例外(需填原单号 + 原因) | -| `expense.proxy_submit` | 行政代提交 | -| `expense.proxy_manage` | 代提交授权管理(principal 自管;`hr/admin` 可代维护) | -| `invoice.upload` | 上传发票 / 附件 | -| `invoice.view_own` | 查看自己的发票 / 附件 | -| `invoice.manage` | 票据中心管理(可查看全量) | -| `invoice.verify` | 票据验真与重试 | -| `system.settings` | 系统设置(审批规则 / 文档序列 / 通知渠道等配置入口) | -| `system.users` | 用户与组织管理 | -| `system.users_sensitive` | 查看或维护员工敏感字段 | -| `system.audit.view` | 审计日志查看 | -| `report.dashboard.view` | 查看经营 / 审批看板 | -| `budget.view` | 查看预算池与预算余额 | -| `budget.manage` | 维护预算池与预算规则 | -| `masterdata.view` | 查看法体 / 成本中心 / 供应商等共享主数据 | -| `masterdata.manage` | 维护共享主数据 | -| `notification.channel.manage` | 通知渠道与模板配置 | -| `integration.manage` | 集成轨道管理、补发、重试、主数据同步 | -| `ai.assist` | 调用只读 AI 建议能力 | -| `*` | 超级权限(仅 `admin` 可授予) | - -#### 2.2.2 已扩展业务域权限(P15-P22 当前实现) - -- `leave.*`:`leave.create`、`leave.view_own`、`leave.view_all`、`leave.submit`、`leave.approve`、`leave.manage` -- `procurement.*`:`procurement.create`、`procurement.view_own`、`procurement.view_all`、`procurement.submit`、`procurement.approve`、`procurement.manage` -- `travel.*`:`travel.create`、`travel.view_own`、`travel.view_all`、`travel.submit`、`travel.approve`、`travel.manage`、`travel.payment.manage` -- `asset.*`:`asset.view_own`、`asset.view_all`、`asset.manage`、`asset.transfer`、`asset.inventory` - -### 2.3 角色默认权限映射(当前实现) -> 默认角色权限以 `server/src/constants/permissions.js` 为单一事实源;下表仅描述当前主干中的默认映射与职责边界。 - -- `employee`:报销自助权限 + 请假自助权限 + 采购自助权限 + 差旅自助权限 + `asset.view_own` -- `manager`:在 `employee` 基础上 + `expense.approve/transfer/view_all` + `leave.approve` + `procurement.approve/view_all` + `travel.approve/view_all` + `asset.view_all` + `report.dashboard.view` + `budget.view/manage` -- `ceo`:在经理审批视角基础上 + `leave.view_all` + `masterdata.view` + 公司级只读视角 -- `finance`:`invoice.manage/verify` + `expense.approve/finance.pay/view_all` + `procurement.view_all` + `travel.view_all/payment.manage` + `asset.view_all` + `report.dashboard.view` + `budget.view` + `masterdata.view` -- `finance_mgr`:在 `finance` 基础上 + `expense.finance.override_duplicate` + `procurement.manage` + `travel.manage/payment.manage` + `asset.manage/inventory` + `integration.manage` + `system.settings` + `system.audit.view` + `budget.manage` + `masterdata.manage` -- `administration`:`expense.proxy_submit` + 员工基础自助权限 + `procurement.view_all/manage` + `travel.view_all/manage/payment.manage` + `asset.view_all/manage/transfer` -- `hr`:请假自助权限 + `leave.view_all/manage` + `system.users/system.users_sensitive` + `expense.view_all` + `report.dashboard.view` -- `admin`:`*` - -### 2.4 权限校验原则(实现约束) -- 后端为单一事实源:所有关键动作必须在服务端校验权限码与数据范围;前端只负责界面显隐。 -- `*_own` 语义: - - 对报销单(reimbursement),`own` 定义为:`owner_id = me OR submitter_id = me`(代提交场景) - - 对采购单(purchase_request)与差旅申请(travel_request),`own` 定义为:`applicant/applicant_employee_id = me OR submitter_id = me` - - 对请假单(leave_request),`own` 定义为:`owner_id = me` - - 对资产卡片(asset_card),`own` 定义为:`assignee_employee_id = me` -- `view_all` / `manage` 不是前端列表参数,而是服务端数据范围收敛规则:许多列表 / 详情接口只做 `authenticate`,实际权限在 handler 内按 `view_own/view_all/manage` 做二次判断。 -- 工作流审批范围由 `workflow_tasks.assignee_id/assignee_role` 决定:只有当前待办处理人 / 处理角色可执行 approve/reject/return;当前前端仅对报销任务暴露 `transfer` 入口。 -- `travel.payment.manage` 控制差旅支付运营面板(授权、交易、`mark-paid`、冻结建议、对账确认);`integration.manage` 控制主数据同步、ERP/IM 轨道查看增强、重试与补发。 -- 所有例外操作(判重例外、预算释放、主数据重试、集成补发等)都必须进入 `audit_logs`,并保留最小必要上下文。 - -## 3. 组织架构与部门模型 - -### 3.1 组织架构示例 -``` -总经理 -├── 部门经理 A -│ ├── 员工 A1 -│ └── 员工 A2 -└── 部门经理 B - ├── 员工 B1 - └── 员工 B2 -``` - -- 普通员工的直属上级 = 部门经理 -- 部门经理的直属上级 = 总经理 - -### 3.2 部门模型(P0) -- 支持多级部门(`parent_id`)。 -- 部门归档约束(禁止硬删除): - - 有子部门:禁止归档 - - 有在职员工:禁止归档 - - 归档后不可再分配新员工;组织树默认隐藏归档部门(可筛选查看) +1. 需求收集与边界确认:明确目标用户、业务流程、必须做、暂不做、验收标准。 +2. 需求文档冻结:更新本文件或对应规则文档,形成产品、开发、测试共同事实源。 +3. 执行合同拆分:新建 `issues/YYYY-MM-DD_HH-mm-ss-.csv`,写清范围、验收、测试、依赖。 +4. 技术方案设计:明确接口、数据模型、权限、状态机、前端入口、回滚策略。 +5. 开发实现:按 Issue 分后端、前端、QA 阶段推进,不顺手做无关改动。 +6. 测试与回归:先单点验证,再业务流程回归,最后做 PC/移动/企业微信入口验证。 +7. 部署与验收:部署后检查 `/ready`、前端资源、关键路径、企业微信真机入口。 +8. 复盘与下一轮规划:把试运行反馈转成新 Issue CSV,不直接插入开发。 --- -## 4. 功能需求 +## 3. 用户角色与典型场景 -### 4.1 员工管理 - -#### 4.1.1 员工信息字段(核心) -| 字段 | 类型 | 必填 | 说明 | -| :--- | :--- | :---: | :--- | -| 工号 | String | ✅ | 唯一标识 | -| 姓名 | String | ✅ | | -| 手机号 | String | ✅ | 登录标识(identifier 之一) | -| 邮箱 | String | ✅ | 登录标识(identifier 之一)/通知 | -| 部门 | FK | ✅ | 关联部门表 | -| 职位 | String | ✅ | | -| 直属上级 `direct_manager_id` | FK | ✅(顶层可空) | 审批链与 `department_manager` 解析依赖;顶层角色(如 CEO)可为空 | -| 入职日期 | Date | ✅ | | -| 状态 | Enum | ✅ | 在职/离职 | -| 角色集合 | M2M | ✅ | 通过 `employee_roles(employee_id, role_code)` 维护;权限按并集生效 | -| 银行卡号 | String | ✅ | 敏感字段:加密存储/脱敏展示(见第 12 章) | -| 开户行 | String | ✅ | | -| 身份证号 | String | ❌ | 敏感字段:加密存储/脱敏展示(见第 12 章) | - -#### 4.1.2 功能点 -- 员工 CRUD(`system.users`,通常由 `hr/admin` 执行) -- 组织架构树展示(含归档部门过滤) -- 离职员工归档:离职后禁止登录并强制下线(见第 11 章 tokenVersion) +| 角色 | 主要目标 | 高频动作 | 关键要求 | +| --- | --- | --- | --- | +| 老板/总经理 | 看清公司待办、费用、项目风险和审批瓶颈 | 审批大额事项、看项目和费用摘要、处理关键待办 | 少打扰、信息集中、审批记录可信 | +| 管理员/HR | 维护人员、入口、制度、请假和基础行政 | 员工管理、请假/补卡审核、公告、入离职、权限配置 | 配置清晰、可回退、有审计 | +| 部门主管/项目负责人 | 管团队交付和日常审批 | 审批报销/请假/采购、查看团队状态、跟进项目事项 | 待办清楚、移动端可处理 | +| 普通员工 | 快速提交申请和查状态 | 报销、上传发票、请假、补卡、外出、加班、采购申请、周报 | 手机好用、字段少、失败提示明确 | +| 财务 | 管报销、发票、付款和导出 | 发票查重、OCR 修正、付款标记、财务导出 | 数据完整、附件可追溯、重复票据可控 | +| 系统管理员 | 保障系统可用和可维护 | 部署、备份、恢复、日志、功能开关、企业微信配置 | 不泄露密钥、可定位问题、可回滚 | --- -### 4.2 报销系统 +## 4. MVP 模块优先级 -#### 4.2.1 报销单(Reimbursement Form) -| 字段 | 说明 | -| :--- | :--- | -| 报销单号 `form_number` | 由单号规则生成(见第 8 章) | -| 申请人 `submitter_id` | 当前登录用户或代提交人 | -| 实际报销人 `owner_id` | 费用归属人 | -| 部门 `department_id` | 默认取 `owner_id` 所属部门 | -| 费用明细 `items[]` | 多条,见下表 | -| 总金额 `total_amount` | 自动汇总 | -| 报销日期 `reimbursement_date` | 业务发生日期(整单) | -| 状态 `status` | 固定 6 状态(见 4.3.3) | -| 备注 `description` | 可选 | - -#### 4.2.2 报销明细(Reimbursement Item) -| 字段 | 说明 | -| :--- | :--- | -| 费用类型 `expense_type_id` | 关联 `expense_types`(见第 9 章) | -| 发生日期 `expense_date` | 日期 | -| 金额 `amount` | 数值 | -| 说明 `description` | 必填(用于审计与复核) | - -> 附件(发票池条目)与明细的关联由 `invoice_pool.linked_item_id` 实现(可选),详见第 10 章。 - -> 费用类型不再是“21 种硬编码”,改为动态 `expense_types`;系统默认启用 37 项(见附录 A)。 - -#### 4.2.3 代提交(Proxy Submit,P0) -- 允许角色:`administration`(需权限 `expense.proxy_submit`) -- 约束:代提交必须存在有效授权(由 `delegation_grants` 控制;授权由本人或 `hr/admin` 维护),否则拒绝提交/创建 -- 记录要求: - - 报销单必须同时记录 `submitter_id`(代提交人)与 `owner_id`(实际报销人) - - 审批与通知以 `owner_id` 为主,同时抄送 `submitter_id` - -#### 4.2.4 代提交授权(Delegation Grants,P0) -> 用于控制“行政可以为哪些员工代提交”,避免权限过大。P0 默认“创建即生效”,无需被授权人二次确认。 - -**`delegation_grants` 表(建议字段)**: -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| principal_employee_id | UUID | 被代提交人(员工) | -| delegate_employee_id | UUID | 代办人(行政) | -| scope | String | `reimbursement_submit \| all`(P0;P1+ 可扩展其他 scope) | -| valid_from/valid_to | DateTime | 生效/失效时间(`valid_to` 可空表示长期) | -| revoked_at | DateTime | 撤销时间(可空) | -| created_at/created_by | DateTime/UUID | 创建时间/操作者 | - -**授权规则(P0 定案)**: -- `delegate_employee_id` 必须包含 `administration` 角色 -- 授权生效条件:`revoked_at is null` 且当前时间在 `[valid_from, valid_to]` 范围内 -- 授权管理权限: - - 员工本人可对“授予给行政的授权”执行创建/撤销(UI:个人设置 → 授权管理) - - `hr/admin` 可代维护(用于补录/紧急处理),所有变更必须进入 `audit_logs` +| 优先级 | 模块 | 目标 | 主入口 | 验收口径 | +| --- | --- | --- | --- | --- | +| P0 | 统一审批中心 | 所有正式审批在 OA 内处理 | PC 审批待办、移动审批 | 能提交、审批、驳回、退回、转审、留痕 | +| P0 | 企业微信基础设施 | 手机入口、免登、通知、打卡原始采集 | 企业微信自建应用 `海音达工作台` | 手机进入 `/mobile`,通知 deeplink 回 OA | +| P0 | 报销/发票/OCR | 员工自助报销,财务可查重和导出 | 报销、移动报销、票据中心 | 上传、OCR、查重、绑定、审批、付款闭环 | +| P0 | 请假与考勤修正审批 | 请假、补卡、外出、加班等修正都在 OA 内审批 | 请假、移动审批、团队状态 | 审批后团队状态和考勤口径不破坏 | +| P1 | 采购/费用审批 | 小团队采购和费用事项留痕 | 采购、审批待办 | 申请、附件、审批、状态更新可用 | +| P1 | 合同与附件归档 | 管合同编号、责任人、归档位置和非涉密附件 | 合同/项目资料或项目页 | 能登记、查询、关联项目和审批记录 | +| P1 | 项目轻管理 | 把费用、采购、差旅、周报挂到项目 | 项目、Portal、周报 | 能看项目事项、风险、费用摘要,不做重型排产 | +| P2 | 预算/支付/ERP/AI | 在试运行稳定后增强运营能力 | 管理员备用入口 | 另立 Issue CSV,不进入首批验收 | --- -### 4.3 审批与流程(业务视角) +## 5. 统一审批中心需求 -#### 4.3.1 默认审批链(P0 预置规则) -审批链不再硬编码在业务代码里,而是由 `approval_rules` 匹配得到(见第 6 章)。P0 预置 3 条规则: -- 小额:`[0, 500)` → `manager` → `finance` -- 中额:`[500, 5000)` → `manager` → `finance_mgr` -- 大额:`[5000, +∞)` → `manager` → `ceo` → `finance_mgr` +### 5.1 范围 -#### 4.3.2 审批操作(P0) -| 操作 | 触发者 | 结果 | -| :--- | :--- | :--- | -| 提交 `submit` | 申请人/代提交人 | 生成单号,发起工作流实例与首个任务 | -| 通过 `approve` | 当前任务处理人 | 完成当前任务并推进到下一任务/流程结束 | -| 驳回 `reject` | 当前任务处理人 | 流程终止(instance=`rejected`),报销单状态变为 `rejected`(可编辑后再次 submit 进入新一轮) | -| 退回 `return` | 当前任务处理人 | 终止当前流程实例(instance=`terminated`),报销单回到 `draft`(可修改后重新 submit) | -| 撤回 `withdraw` | 申请人 | 取消当前流程实例(instance=`cancelled`),报销单回到 `draft`(仅在审批人未处理前) | -| 转审 `transfer` | 当前任务处理人 | 将当前任务转交他人;需记录原因(≥10 字) | -| 付款标记 `pay` | 财务 | 流程通过后,将 `approved` 标记为 `paid` | +OA 是公司唯一正式审批中心。以下审批必须在 OA 内提交、审批、留痕、归档: -#### 4.3.3 报销单数据状态机(固定 6 状态) -> “待部门审批/待财务复核”等是任务节点文案,由工作流当前待办任务决定,不作为数据状态写入 DB。 +- 报销、费用、采购。 +- 合同相关审批和合同台账维护。 +- 请假、补卡、外出、加班、出差。 +- 资产领用、归还、调拨、盘点中的需要留痕动作。 +- 后续新增业务审批,默认也进入 OA 审批中心。 -``` -draft - └─ submit → reviewing -reviewing - ├─ approve(最终) → approved - ├─ reject → rejected - ├─ return → draft - └─ withdraw → draft -approved - └─ pay → paid -paid (终态) -rejected (可由申请人编辑后再 submit 进入新一轮) -cancelled (作废/终止,终态) -``` +企业微信原生审批不作为公司正式审批入口,不允许形成“一部分审批在企业微信、一部分审批在 OA”的双轨口径。 -#### 4.3.4 `return` vs `reject` vs `withdraw`(P0 定案) -- `return`(退回):用于“需要申请人修改后再提交”的场景。动作发生后: - - 当前 `workflow_instances` 必须结束并标记为 `terminated` - - 报销单状态回到 `draft`;申请人修改后需再次 `submit` 才会进入新一轮审批 - - 所有未完成任务必须被取消(`workflow_tasks.status=cancelled`),并写入 `workflow_history(action=return)` -- `reject`(驳回):用于“本轮不通过”的场景。动作发生后: - - 当前 `workflow_instances` 必须结束并标记为 `rejected` - - 报销单状态变为 `rejected`(保留驳回意见);申请人可编辑后再次 `submit` 进入新一轮审批 - - 写入 `workflow_history(action=reject)` -- `withdraw`(撤回):由申请人发起的“主动取消”。动作发生后: - - 当前 `workflow_instances` 必须结束并标记为 `cancelled` - - 报销单状态回到 `draft` - - 约束:仅允许在**审批人尚未处理**前撤回(即当前实例内不存在已完成的审批任务) - - 写入 `workflow_history(action=withdraw)` +### 5.2 审批能力 + +- 支持草稿、提交、审批中、通过、驳回、退回、撤回、转审、完成等状态语义。 +- 审批链优先采用串行多节点;当前阶段不做并行会签。 +- 大额或高风险单据通过审批规则配置多级审批,不通过硬编码角色分叉。 +- 当前审批人只能处理分配给自己的待办,或者处理分配给自己角色且权限匹配的待办。 +- 转审必须记录原因,转给具备对应审批权限的人,并保留转审历史。 +- 单据编号由 OA 生成和保存;报销单、采购单、合同编号等应使用统一编号规则或对应业务编号规则。 + +### 5.3 数据与留痕 + +- 所有审批动作写入工作流历史。 +- 关键例外动作写入审计日志,包括重复发票例外、付款标记、权限变更、附件下载、通知重试。 +- 企业微信消息只包含脱敏标题、业务编号、状态和 deeplink,不包含涉密正文、完整发票、合同正文或敏感报价。 + +### 5.4 验收 + +- 审批人可在 PC 和移动端看到待办。 +- 审批动作完成后,业务单据状态、审批历史、通知和审计记录一致。 +- 企业微信提醒点击后回到 OA 页面处理,不能在企业微信原生审批里完成正式审批。 --- -### 4.4 发票池(Invoice Pool)与 OCR(P0) -> 目的:把“票据文件 + OCR 结果 + 发票元数据 + 绑定关系 + 生命周期状态”从报销单附件中解耦出来,形成可复用、可审计、可判重的单一事实源。 +## 6. 报销、发票、OCR、附件与财务导出 -#### 4.4.1 模块定位(P0) -- `invoice_pool` 是票据/附件的单一事实源(含发票、行程单、火车票、出租车票等图片/PDF)。 -- 报销单不再直接管理“附件文件”,而是**绑定** `invoice_pool` 条目(见 4.4.6)。 -- 判重与例外属于“使用/报销”层面的合规约束:P0 允许先入池,**在绑定/提交阶段**做硬校验(见 4.4.7)。 +### 6.1 用户流程 -#### 4.4.2 生命周期状态机(5 态,P0) -`invoice_pool.status`: -- `pending_ocr`:已上传,OCR 未完成(或排队中) -- `ocr_failed`:OCR 失败(可重试,或人工修正后转为 `available`) -- `available`:可绑定(OCR 成功或人工确认/修正后) -- `bound`:已绑定到某报销单(1:1) -- `archived`:已归档(不可再绑定;P0 不提供恢复) +1. 员工在 PC 或手机端创建报销草稿。 +2. 上传发票图片、PDF 或必要附件。 +3. 系统执行 OCR,识别发票代码、号码、金额、日期、销方、购方等关键字段。 +4. 员工确认或修正识别结果,补充费用明细和说明。 +5. 系统做发票查重和基础校验。 +6. 员工提交报销,进入 OA 审批流。 +7. 审批通过后,财务复核并标记付款。 +8. 财务按期间、状态、项目、人员导出报销和发票数据。 -**不变量(P0,必须由 DB + 服务端共同保证)**: -- `status = bound` ⇒ `linked_reimbursement_id is not null` -- `status in (pending_ocr, ocr_failed, available)` ⇒ `linked_reimbursement_id is null` -- `status = archived` ⇒ 不允许再执行 bind -- `status = archived AND archived_reason = used_complete` ⇒ `linked_reimbursement_id is not null`(用于追溯) -- `status = archived AND archived_reason in (unused_timeout, manual)` ⇒ `linked_reimbursement_id is null` +### 6.2 发票与附件管理 -#### 4.4.3 归档原因(用于判重口径,P0) -`invoice_pool.archived_reason`(仅当 `status=archived` 时有意义): -- `used_complete`:已被报销使用且流程完成后归档 -- `unused_timeout`:90 天未绑定自动归档(从未被使用) -- `manual`:人工归档(清理/隐藏;从未被使用或不再使用) +- 发票和附件原始文件以 OA/对象存储为主,不依赖企业微信长期保存。 +- 同一发票不得被重复报销;如确需例外,必须由授权财务角色填写原单号和原因。 +- OCR 失败时允许重试和人工修正,但必须保留失败原因和修正痕迹。 +- 报销完成后,相关发票进入已使用或归档状态,不能被普通员工再次绑定到新单据。 +- 普通员工只看到“添加发票、上传附件、提交审批、查看状态”等业务语言,不暴露内部发票池概念。 -**判重口径(P0 定案)**: -- 只有 `status=bound` 与 `status=archived AND archived_reason=used_complete` 的记录参与“全局判重历史”。 -- `unused_timeout/manual` 的归档记录不参与全局判重(避免“未使用的旧票据”阻塞新票据入库)。 +### 6.3 财务导出 -#### 4.4.4 90 天未绑定自动归档(P0) -- 目标:控制“孤儿发票/票据”堆积。 -- 规则:对 `status in (available, ocr_failed)` 且未绑定的条目,超过 90 天自动转 `archived` 且 `archived_reason=unused_timeout`。 -- `pending_ocr` 超时策略(建议):先转 `ocr_failed` 再进入自动归档规则(超时阈值由实现配置)。 +- 财务可按报销状态、付款状态、员工、部门、项目、期间筛选导出。 +- 导出数据应包含业务编号、申请人、实际报销人、金额、费用类型、发票摘要、审批状态和付款状态。 +- 导出不得包含未授权敏感字段明文。 -#### 4.4.5 OCR(腾讯云,P0) -- OCR 提供方:腾讯云(参考既有项目实现)。 -- OCR 结果必须持久化(用于复核/搜索/审计/复用);P0 不落库保存腾讯云 rawResponse(仅日志)。 -- `ocr_type`(P0 枚举): - - `vat`(增值税发票;覆盖专票/普票/数电票,细分类型体现在标准化结果中) - - `train`(火车票) - - `flight`(机票行程单) - - `taxi`(出租车票) - - `general`(其他通用票据,默认值) -- 交互策略:用户可选 `ocr_type`;默认 `general`。 -- 提交不阻塞:OCR 失败/未完成时,允许用户人工补齐关键字段后继续(UI 提示即可,不让流程卡死)。 -- 发票验真(税局/验真接口):**P2**,P0/P1 不做。 +### 6.4 验收 -#### 4.4.6 绑定/解绑规则(P0) -- 绑定关系:一张 `invoice_pool` 条目只能绑定一个报销单(1:1)。 -- 绑定前置条件:`status=available` 且未绑定。 -- 绑定后:`status=bound`,写入 `linked_reimbursement_id`(可选 `linked_item_id`)。 -- 解绑后:清空 `linked_reimbursement_id/linked_item_id`,回到 `available`(P0 不自动恢复到 `ocr_failed`)。 - -**与报销流程的联动(P0 定案)**: -- 报销草稿被删除(仅 `draft`):必须自动解绑其绑定的发票池条目并回到 `available`。 -- 报销单进入 `paid`(完成节点):其绑定的发票池条目自动归档为 `archived` 且 `archived_reason=used_complete`。 - -#### 4.4.7 判重与例外(P0) -- 全局双策略判重(公司级): - 1) `invoice_code + invoice_number`(齐全时) - 2) `file_sha256` -- 生效点:不阻塞“入池上传”;在 **bind/submit** 阶段做硬校验。 -- 例外(允许重复):仅 `finance_mgr`,必填原单号 + 原因(≥10 字),并写入审计;当原单处于 `approved` 或 `paid` 时禁止例外(见错误码 `VALID_004`)。 - -> 存储与签名 URL 访问规则见第 10 章;接口契约见第 13 章。 +- 手机拍照或上传发票后,能完成 OCR、查重、绑定、提交。 +- 报销审批通过后,财务能付款标记并导出。 +- 重复发票、OCR 失败、附件缺失、金额不一致等异常有明确提示。 --- -### 4.5 支付管理 -- 打款方式:线下打款,系统仅做“付款标记” -- 付款状态:`approved` → `paid` -- 付款记录:付款时间、操作人、银行流水号(可选)、备注 +## 7. 请假、补卡、外出、加班与考勤口径 + +### 7.1 边界 + +- 企业微信打卡用于原始采集,保留其定位、人脸、Wi-Fi、排班和设备能力。 +- OA 不在首批重造打卡规则、定位、人脸识别或排班引擎。 +- 请假、补卡、外出、加班等考勤修正申请在 OA 内提交和审批。 +- 后续可同步企业微信打卡原始记录,再结合 OA 审批形成正式考勤口径。 + +### 7.2 用户流程 + +- 员工在 OA 提交请假、补卡、外出或加班申请。 +- 主管或 HR 在 OA 审批。 +- 审批通过后,团队状态、日历和后续考勤统计使用 OA 的审批结果。 +- 企业微信打卡异常不直接等于最终考勤异常,最终口径以 OA 汇总为准。 + +### 7.3 验收 + +- 请假提交、审批、撤回、驳回后状态正确。 +- 已批准请假能影响团队状态或日历展示。 +- 文档和界面不引导员工去企业微信原生审批提交请假或补卡。 --- -### 4.6 通知系统 -| 场景 | 通知方式 | -| :--- | :--- | -| 待审批提醒 | 站内 + 邮件 + 企微群机器人(已实现);飞书 / 钉钉继续 backlog | -| 审批结果 | 站内 + 邮件 + 企微群机器人(按渠道配置) | -| 付款完成 | 站内 + 邮件 + 企微群机器人(按渠道配置) | +## 8. 采购、费用、合同与项目资料 + +### 8.1 采购和费用审批 + +- 员工可提交采购或费用申请,填写用途、金额、项目、供应商、附件和期望时间。 +- 主管、财务或老板按规则审批。 +- 审批通过后形成可追溯记录,可关联预算、项目、付款和合同资料。 +- 首批优先支持轻量申请和审批,不做复杂招采流程。 + +### 8.2 合同与附件归档 + +- 合同管理首批以台账为主:合同编号、名称、客户或供应商、项目、金额、负责人、状态、归档位置。 +- 非涉密附件可进入 OA;涉密合同正文、敏感报价、成本细节和客户敏感资料不得进入公网 OA。 +- 合同编号应由 OA 统一生成或由管理员登记后校验唯一性。 +- 合同相关审批和变更记录应回到 OA 留痕。 + +### 8.3 项目轻管理 + +- 项目页只做轻量管理:成员、里程碑、任务、风险、交付记录、费用/采购/差旅关联。 +- 不做重型甘特图、资源排产、CRM 或完整项目组合管理。 +- 员工的报销、采购、差旅、周报和待办应尽量关联项目,减少另建表格。 + +### 8.4 验收 + +- 采购申请可提交、审批和查看状态。 +- 合同台账能记录编号、责任人、归档位置和项目关联。 +- 项目视图能汇总关键事项,不要求替代专业项目管理工具。 --- -### 4.7 请假管理 +## 9. 企业微信基础设施需求 -#### 4.7.1 范围与边界 -- 当前请假域以 `LeaveType`、`LeavePolicy`、`LeaveLedger`、`LeaveRequest` 四层模型为核心,支持余额查询、列表/详情、草稿创建、提交、审批与撤回闭环。 -- 当前实现支持半天粒度(`am/pm`)请假、同人同时间段冲突阻断、余额强设闸、附件规则校验与审批后账本消耗。 -- 假期类型与年度额度由 HR 维护;法定节假日、调休日等共享主数据能力归入主数据域,不在本节单独展开日历算法。 +### 9.1 自建应用 -#### 4.7.2 角色与权限 -- `employee`:`leave.create/view_own/submit`,可发起、查看、提交和撤回本人请假。 -- `manager`、`ceo`:`leave.approve`,通过通用 workflow 待办处理请假审批。 -- `hr`:`leave.view_all/manage`,可维护假期类型、年度配额与全量查看请假单。 +- 公司只维护一个企业微信自建应用,建议命名为 `海音达工作台`。 +- 手机端默认入口进入 `/mobile`。 +- PC 浏览器和普通账号密码登录默认进入 `/portal`。 +- 企业微信入口失败时必须保留账号密码登录回退路径。 -#### 4.7.3 状态机 / 工作流语义 -- 请假单状态:`draft -> reviewing -> approved/rejected`;申请人 `withdraw` 或审批人 `return` 均回到 `draft`。 -- `submit`:创建 workflow 实例与首条待办,同时写入 `LeaveLedger(entry_type=reserve)` 冻结额度。 -- `approved`:消费冻结额度(`consume`);`rejected`、`return`、`withdraw`:释放冻结额度(`release`)。 -- 当前 workflow 复用通用 `workflow_definitions / instances / tasks / history` 四表,`entity_type=leave`,不引入独立请假审批引擎。 +### 9.2 免登与绑定 -#### 4.7.4 核心数据对象 -| 对象 | 作用 | -| :--- | :--- | -| `LeaveType` | 假别定义,含是否必传附件、附件 MIME 白名单、排序等配置 | -| `LeavePolicy` | 假别对应年度额度与启用开关 | -| `LeaveRequest` | 请假申请主单,包含日期范围、半天粒度、附件信息与状态 | -| `LeaveLedger` | 假期余额账本,记录 `grant/reserve/consume/release/adjust` 五类流水 | +- 支持企业微信 OAuth 或免登后识别员工身份。 +- 首次未绑定时,员工可使用账号密码登录后在个人设置中完成绑定。 +- 一个企业微信身份不得绑定多个员工。 +- 员工离职或账号停用后,企业微信入口也不得继续进入业务系统。 -#### 4.7.5 显式不做项 -- 不在请假域内单独实现复杂排班、倒班、加班换休或跨法域假期累计规则。 -- 不单独开放“请假转审”专用 UI;当前仅复用通用 workflow 的 approve/reject/return 能力。 -- 不把日历主数据、法定节假日策略和跨年结转算法写死到请假申请 API 中。 +### 9.3 消息通知 -### 4.8 采购管理 +- 企业微信消息只做提醒,审批动作必须回到 OA 页面完成。 +- 消息内容最小化:标题、业务编号、状态、处理入口。 +- 发送失败应可重试,不阻断主业务提交或审批。 +- 不在消息中发送合同正文、完整发票、敏感报价、银行卡、身份证等敏感内容。 -#### 4.8.1 范围与边界 -- 当前采购域覆盖 `PurchaseRequest` 草稿 / 提交 / 审批 / 撤回主流程,以及 `PurchaseOrderArchive` 的 PO / 合同归档闭环。 -- 采购二期已补齐预算 `reserve/confirm/release` 语义,并允许在 approved 采购单详情内完成归档留痕。 -- 采购域当前不进入收货、入库、三单匹配、供应商门户或发票核销。 +### 9.4 打卡原始采集 -#### 4.8.2 角色与权限 -- `employee`:`procurement.create/view_own/submit`,可创建和提交本人的采购申请。 -- `manager`、`ceo`:`procurement.approve`,处理采购审批待办;具备 `procurement.view_all` 的角色可查看全量。 -- `administration`、`finance_mgr`:当前默认具备 `procurement.manage`,可执行归档留痕等运营动作。 -- `integration.manage`:用于查看或补发采购 ERP 轨道,不参与采购主业务编辑。 - -#### 4.8.3 状态机 / 工作流语义 -- 采购申请状态:`draft -> reviewing -> approved/rejected/cancelled`。 -- `submit`:校验法体、成本中心、供应商、预算池等必填项,创建 workflow 并写预算预留。 -- `approved`:确认预算占用;`rejected`、`return`、`withdraw`:释放预算预留并回到草稿或终止实例。 -- PO / 合同归档仅允许在 `approved` 状态下执行,且归档不触发新的审批实例。 - -#### 4.8.4 核心数据对象 -| 对象 | 作用 | -| :--- | :--- | -| `PurchaseRequest` | 采购申请主单,承载申请人、法体、成本中心、供应商、预算池、总金额与状态 | -| `PurchaseRequestItem` | 采购明细,记录规格、数量、单价、金额与资产候选标记 | -| `PurchaseAttachment` | 采购附件,支持上传、删除、签名 URL 预览 | -| `PurchaseOrderArchive` | 已审批采购单的 PO / 合同归档记录 | - -#### 4.8.5 显式不做项 -- 不实现收货、入库、应付、发票核销、三单匹配或供应商协同门户。 -- 不把采购归档拆成独立后台模块;当前固定在采购详情内完成。 -- 不在核心采购表注入 ERP 私有字段;ERP 外部编号统一走集成层 binding 维护。 - -### 4.9 资产管理 - -#### 4.9.1 范围与边界 -- 当前资产域覆盖分类、卡片、领用归还、人员 / 位置调拨、盘点任务与资产 ERP 摘要轨道。 -- 资产卡片可关联采购来源(approved 采购单),并维护法体、成本中心、位置、领用人、状态等台账信息。 -- 资产域当前不引入独立审批流;所有领用、归还、调拨、盘点都属于受控运营动作。 - -#### 4.9.2 角色与权限 -- `employee`:`asset.view_own`,查看本人名下资产。 -- `manager`、`ceo`、`finance`:通常具备 `asset.view_all`,查看全量资产台账。 -- `administration`、`finance_mgr`:通常具备 `asset.manage` / `asset.transfer`,负责建卡、变更、领用归还与调拨。 -- `asset.inventory`:专用于盘点任务创建、启动、录入与完成;`integration.manage` 负责资产 ERP 轨道重试。 - -#### 4.9.3 状态机 / 工作流语义 -- 资产卡片状态:`in_stock`、`in_use`、`under_repair`、`retired`、`archived`;状态变化由运营动作直接驱动,不经审批流。 -- 领用与归还通过 `AssetAssignment` 维护当前持有人;调拨通过 `AssetTransfer` 维护人员 / 位置变更历史。 -- 盘点任务状态:`draft -> counting -> completed`;盘点明细以 `inventory_check_id + asset_id` 唯一键幂等 upsert。 -- 资产 ERP 一期只在建卡和财务维度关键变更时触发摘要出站;领用、归还、位置调拨不触发 ERP 导出。 - -#### 4.9.4 核心数据对象 -| 对象 | 作用 | -| :--- | :--- | -| `AssetCategory` | 资产分类,绑定默认文档分类并控制是否启用 | -| `AssetCard` | 资产主卡片,记录编号、法体、成本中心、位置、领用人、状态等台账字段 | -| `AssetAssignment` | 资产领用 / 归还流水,维护当前 open assignment | -| `AssetTransfer` | 资产人员 / 位置调拨历史 | -| `InventoryCheck` / `InventoryCheckItem` | 盘点任务与盘点结果明细 | - -#### 4.9.5 显式不做项 -- 不做折旧、净值、总账过账、会计科目自动分录等资产财务深化能力。 -- 不引入资产专属 workflow 审批实例;资产流转保持受控操作模型。 -- 不做原生扫码壳或仓储 WMS 深度集成;移动扫码与盘点增强由 PWA 路线承接。 - -### 4.10 差旅管理 - -#### 4.10.1 范围与边界 -- 当前差旅域按三阶段交付:`TRAVEL-1` 申请与行程入站、`TRAVEL-2` 对公支付与发票绑定、`TRAVEL-3` 对账批次与 ERP 摘要桥。 -- 差旅申请主单承载出差目的、法体、成本中心、预算池、起止日期与行程段;支付域承接订单导入、企业卡授权 / 交易导入、`mark-paid` 与发票绑定;对账域承接批次快照、异常确认与 ERP 摘要桥接。 -- 当前差旅域不接真实商旅供应商 API,也不自动改写订单、申请或预算原值。 - -#### 4.10.2 角色与权限 -- `employee`:`travel.create/view_own/submit`,创建和提交差旅申请。 -- `manager`、`ceo`:`travel.approve`,处理差旅审批待办;具备 `travel.view_all` 的角色可查看全量。 -- `administration`、`finance_mgr`:可持有 `travel.manage` 与 `travel.payment.manage`,用于支付运营、异常处理和对账确认。 -- `integration.manage`:负责 ERP 轨道、requeue 与主数据同步控制,不参与差旅主单编辑。 - -#### 4.10.3 状态机 / 工作流语义 -- 差旅申请状态:`draft -> reviewing -> approved/rejected`;`return` 与 `withdraw` 均回到 `draft`。 -- `submit`:创建 workflow,并写预算预留;`approved`:确认预算;`rejected` / `return` / `withdraw`:释放预算。 -- `TravelOrder` 当前采用运行时字符串状态(如 `imported/confirmed/paid`)承载支付轨道,不作为独立公共枚举对外承诺;`mark-paid` 会写入付款时间、渠道、操作人并触发订单支付闭环。 -- `TravelReconciliationBatch` 状态固定为 `draft/reconciled`;异常明细必须人工 `confirm` 或 `reopen`,`finalize` 后只允许新建下一批,不回刷历史批次。 - -#### 4.10.4 核心数据对象 -| 对象 | 作用 | -| :--- | :--- | -| `TravelRequest` | 差旅申请主单,含申请人、法体、成本中心、预算池、目的与预计金额 | -| `TravelRequestSegment` | 行程段明细,含往返 / 中转角色、交通方式、起讫城市与时间 | -| `TravelOrder` | 导入或运营维护的差旅订单,承载支付确认与发票绑定 | -| `CardAuthorization` / `CardTransaction` | 企业卡授权与交易轨道,用于 `authorize/settle/reverse` 预算语义与支付明细 | -| `TravelReconciliationBatch` / `TravelReconciliationItem` | 差旅对账批次与异常明细快照 | - -#### 4.10.5 显式不做项 -- 不接真实商旅供应商出票、改签、退票、拒付、退款回调或银行清结算。 -- 不把 `card_authorization` / `card_transaction` 导入事实提升为长期稳定公共业务事件名。 -- 不做自动修账、自动改申请、自动冲销预算;对账只做差异发现与人工接受。 - -### 4.11 集成与外部适配 - -#### 4.11.1 范围与边界 -- 当前集成层只通过边界适配核心域,不向报销、采购、资产、差旅等事务主表注入 ERP / IM 私有字段。 -- 已实现范围包括:共享主数据同步、IM-1 通知渠道、IM-2 企微 `wecom_webhook` 真实出站生产化、ERP-1 报销付款凭证切片、ERP-2 采购 commitment/archive、ERP-3 资产摘要、TRAVEL-3 批次级 `erp.travel.export`。 -- `docs/integration-boundaries.md` 与 `docs/domain-events.md` 是本章的边界与事件语义事实源;本规格只吸收已实现部分。 - -#### 4.11.2 角色与权限 -- `integration.manage`:主数据同步、ERP / IM 轨道查看增强、补发、重试、手工触发。 -- 业务可读角色可以查看与自身实体相关的轨道摘要,但外部编号、重试与补发动作继续由 `integration.manage` 控制。 -- `notification.channel.manage`:维护通知渠道与模板;当前默认由 `finance_mgr` 持有。 - -#### 4.11.3 状态机 / 工作流语义 -- 集成统一采用“业务事务提交成功 -> Outbox 异步派发 -> Binding / Track 回写”的交付模型;外部失败不回滚业务主事务。 -- `IntegrationOutbox` 负责 `pending/processed/dead-letter` 等投递状态、重试窗口与 `dedupe_key` 幂等;`IntegrationBinding` 负责本地实体与外部编号映射;`IntegrationSyncState` 负责主数据资源级同步状态。 -- 公开业务事件名保持最小集;`erp.travel.export`、`erp.journal.export` 等属于集成事件,不等同于新的业务领域事件名。 -- 当前后置 backlog 固定包括 `IM-2` 的飞书 / 钉钉 provider 子项、`IM-3/IM-4`、`P22+VEH-1`、`P22+ASSET-FIN-1`,不进入本轮已实现范围。 - -#### 4.11.4 核心数据对象 -| 对象 | 作用 | -| :--- | :--- | -| `IntegrationOutbox` | 统一出站事件队列,负责重试、死信与去重 | -| `IntegrationBinding` | provider / entity / localId / externalId 绑定关系 | -| `IntegrationSyncState` | 主数据同步状态、上次结果与错误摘要 | -| `NotificationChannel` | IM / 邮件等通知渠道配置与模板 | - -#### 4.11.5 显式不做项 -- 不直接在核心事务表写入第三方私有字段或回调状态列。 -- 不在本轮实现飞书 / 钉钉 provider、`IM-3/IM-4`、车辆管理、资产财务深化,也不把这些方向提前写入已完成事实源。 -- 不把 AI、MCP、PWA、生产化治理并入本章主体;这些能力继续由边界文档和运维文档承载。 - -## 5. 工作流引擎(Workflow,P0 采用) -> 采用 4 表式通用工作流,审批任务由 `workflow_tasks` 表驱动。P0 仅用线性审批链,但表结构支持后续扩展为条件分支/动作节点。 - -### 5.1 表结构(核心四表) - -#### 5.1.1 `workflow_definitions`(流程定义) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| name | String | 流程名称(如“报销 / 请假 / 采购 / 差旅审批流程”) | -| entity_type | String | 实体类型(如 `reimbursement` / `leave` / `purchase_request` / `travel_request`) | -| definition_json | JSON | 流程配置(见 5.2) | -| version | Int | 版本号(递增) | -| is_active | Bool | 是否启用 | - -#### 5.1.2 `workflow_instances`(流程实例) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| workflow_id | UUID | 关联 `workflow_definitions` | -| entity_type | String | 实体类型(如 `reimbursement` / `leave` / `purchase_request` / `travel_request`) | -| entity_id | UUID | 业务实体 ID(报销单 ID) | -| submitter_id | UUID | 发起人(提交者) | -| current_status | String | `pending/approved/rejected/cancelled/terminated` | -| current_step_id | String | 当前步骤 ID(与任务 step_id 对齐) | -| context_data | JSON | 实体快照(**必须包含 `owner_id`、`department_id`**;如 `total_amount`、命中 rule_id、审批链等) | -| started_at/completed_at | DateTime | 开始/结束时间 | - -#### 5.1.3 `workflow_tasks`(审批任务) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| instance_id | UUID | 关联 `workflow_instances` | -| step_id/step_name | String | 步骤标识与名称 | -| assignee_id | UUID | 指派到具体人(可空) | -| assignee_role | String | 指派到角色(可空) | -| status | String | `pending/completed/rejected/cancelled/skipped` | -| outcome | String | `approved/rejected/returned/start/transfer/skipped` | -| comment | String | 审批意见/原因 | -| due_date | DateTime | 截止时间(Phase 2 可启用超时) | -| created_at/completed_at | DateTime | 创建/完成时间 | -| completed_by | UUID | 完成人 | - -#### 5.1.4 `workflow_history`(流程历史) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| instance_id | UUID | 关联实例(必填) | -| task_id | UUID | 关联任务(可空) | -| actor_id | UUID | 操作人(必填) | -| action | String | 动作(approve/reject/return/transfer/start/withdraw 等) | -| comment | String | 说明 | -| payload | JSON | 扩展信息(如转审 from/to、判重例外引用原单号等) | -| created_at | DateTime | 时间戳(高精度) | - -### 5.2 流程定义 JSON 规范(WorkflowConfig) -> 参考 `shenzhou-business-suite/src/types/workflow.ts`。 - -```json -{ - "steps": [ - { - "id": "dept_manager", - "name": "部门经理审批", - "type": "approval", - "assignee_rule": { "type": "department_manager" } - }, - { - "id": "ceo", - "name": "总经理审批", - "type": "approval", - "assignee_rule": { "type": "role", "value": "ceo" } - }, - { - "id": "finance", - "name": "财务复核", - "type": "approval", - "assignee_rule": { "type": "role", "value": "finance" } - }, - { - "id": "finance_mgr", - "name": "财务主管复核", - "type": "approval", - "assignee_rule": { "type": "role", "value": "finance_mgr" } - } - ] -} -``` - -### 5.3 指派规则(assignee_rule)解析(P0) -支持类型: -- `specific_user`:指派到指定用户 ID -- `role`:指派到指定 `role_code` -- `department_manager`:对报销 / 请假 / 采购 / 差旅四类审批实体,统一基于 `owner_id` 或 `applicant` 映射出的直属上级链向上寻找经理(最多 10 层;若经理=本人则继续向上;找不到则该步骤可被跳过并写入 history) -- `submitter_manager`:基于 `submitter_id` 的直属上级链向上寻找经理(为未来其他流程预留;报销流程默认不使用该规则) - -### 5.4 一致性与并发要求(实现约束) -- 任务处理必须加锁:完成 `workflow_tasks` 时必须对该任务行做行级锁(防止重复审批)。 -- 单实例串行:同一个 `workflow_instances` 在任意时刻最多只有 1 条 `pending` 任务。 -- 幂等提交:同一 `entity_type+entity_id` 若存在未结束实例(非 approved/rejected/cancelled/terminated),再次提交应返回同一实例或拒绝重复提交。 -- 状态同步: - - 创建首个任务后,报销单状态应进入 `reviewing` - - 实例 approved → 报销单状态 `approved` - - 实例 rejected → 报销单状态 `rejected` - - 实例 cancelled(撤回)→ 报销单状态回到 `draft` - - 实例 terminated(退回)→ 报销单状态回到 `draft` - -### 5.5 动作→状态对照表(消歧义) -> 用于避免 `workflow_tasks.status` / `outcome` / `workflow_instances.current_status` / 报销 `status` 之间的实现分歧。 - -| 动作 | `workflow_tasks.status` | `workflow_tasks.outcome` | `workflow_instances.current_status` | 报销 `status` | -| :--- | :--- | :--- | :--- | :--- | -| 提交 `submit` | 首个 task=`pending` | `start` | `pending` | `reviewing` | -| 通过 `approve`(非最终) | 当前=`completed`;下一=`pending` | `approved` | `pending` | 保持 `reviewing` | -| 通过 `approve`(最终) | 当前=`completed` | `approved` | `approved` | `approved` | -| 驳回 `reject` | 当前=`rejected` | `rejected` | `rejected` | `rejected` | -| 退回 `return` | 当前=`completed`;其余 pending=`cancelled` | `returned` | `terminated` | `draft` | -| 撤回 `withdraw` | 所有 pending=`cancelled` | N/A | `cancelled` | `draft` | -| 转审 `transfer` | 保持 `pending`;`assignee_id` 更新 | `transfer` | 保持 `pending` | 保持 `reviewing` | -| 跳过(无审批人) | 当前=`skipped` | `skipped` | `pending`(推进到下一) | 保持 `reviewing` | +- 企业微信打卡数据可作为后续同步来源。 +- 首批不新增企业微信打卡 API 同步实现。 +- 正式考勤统计必须结合 OA 的请假、补卡、外出、加班审批结果。 --- -## 6. 审批规则(Approval Rules,P0 可配置) -> 用于决定报销审批链的层级与角色,不在业务代码中硬编码金额阈值。 +## 10. 权限、审计、数据安全与非功能要求 -### 6.1 `approval_rules` 表(字段) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| name | String | 规则名(如“小额/中额/大额”) | -| min_amount | Decimal | 最小金额(含) | -| max_amount | Decimal | 最大金额(不含;NULL 表示无上限) | -| level1_role | String | 一级审批角色(role_code) | -| level2_role | String | 二级审批角色(可空) | -| level3_role | String | 三级审批角色(可空) | -| enabled | Bool | 启用 | -| priority | Int | 优先级(越小越优先) | +### 10.1 权限原则 -### 6.2 匹配算法(P0 定案) -- `amount >= min_amount` -- `max_amount is null OR amount < max_amount` -- `ORDER BY priority ASC, min_amount ASC LIMIT 1` -- 若无匹配:fallback 到“默认小额规则” +- 后端是权限单一事实源;前端按钮显隐不等于授权。 +- 普通员工只能看和处理自己的单据。 +- 审批人只能处理分配给自己的待办或自己角色范围内的待办。 +- 财务可处理发票、付款和导出,但不默认拥有系统管理权限。 +- HR/管理员可维护人员、组织和基础配置,但敏感操作必须审计。 +- 老板/总经理可查看公司级摘要和处理关键审批,但仍应保留操作留痕。 -### 6.3 P0 预置 3 条规则(默认值) -| name | min_amount | max_amount | level1_role | level2_role | level3_role | priority | -| :--- | ---: | ---: | :--- | :--- | :--- | ---: | -| 小额 | 0 | 500 | `manager` | `finance` | NULL | 10 | -| 中额 | 500 | 5000 | `manager` | `finance_mgr` | NULL | 20 | -| 大额 | 5000 | NULL | `manager` | `ceo` | `finance_mgr` | 30 | +### 10.2 审计要求 -### 6.4 审批角色 → 指派规则映射(消歧义,P0 定案) -> `approval_rules.level*_role` 存的是 **role_code**;工作流落到 `workflow_tasks` 时,需要把部分 role_code 映射为“解析规则”(assignee_rule),避免把 `manager` 误解为“一个静态角色池”。 +以下动作必须可追溯: -| `approval_rules.level*_role`(role_code) | 生成的工作流指派规则 | `workflow_tasks` 落地形态 | -| :--- | :--- | :--- | -| `manager` | `assignee_rule.type=department_manager`(以 `owner_id` 为基准解析直属经理) | 写入 `assignee_id`(解析到具体人);若找不到则该步骤可跳过并写入 history | -| 其他 role_code(如 `finance/finance_mgr/ceo`) | `assignee_rule.type=role`,`value=role_code` | 写入 `assignee_role=role_code`(允许该角色下任一成员处理;行级锁保证只完成一次) | +- 登录、退出、绑定、解绑、账号停用。 +- 单据提交、审批、驳回、退回、撤回、转审。 +- 发票上传、OCR 重试、人工修正、重复例外、附件下载。 +- 付款标记、财务导出、合同归档、权限变更。 +- 企业微信通知发送失败、重试和配置异常。 + +### 10.3 数据安全 + +- 密码、密钥、令牌不得写入文档、日志或前端包。 +- 银行卡、身份证、手机号等敏感字段对外响应应脱敏。 +- 附件下载应有鉴权和有效期控制。 +- 涉密合同正文、详细报价、成本细节、敏感客户资料不进入公网 OA。 +- 生产环境必须有数据库和文件存储备份,并定期做恢复演练。 + +### 10.4 非功能要求 + +- PC 和移动端都应覆盖高频流程。 +- 10 人以下团队的常用列表和表单应保持快速响应。 +- 关键错误需要给出可操作提示,例如未绑定、权限不足、审批人缺失、OCR 失败、重复发票。 +- 部署后必须能通过 `/ready` 检查服务状态。 +- 新功能应有可复现测试步骤,无法自动化时写清人工验收步骤。 --- -## 7. 转审(Transfer,P0) +## 11. 暂不做范围 -### 7.1 `approval_transfers` 表(字段) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| reimbursement_id | UUID | 关联报销单 | -| from_approver_id | UUID | 原审批人 | -| to_approver_id | UUID | 新审批人 | -| reason | String | 转审原因(至少 10 字) | -| status | String | `active/cancelled` | -| created_at/created_by | DateTime/UUID | 创建时间/操作者 | +首批 MVP 不做以下事项: -### 7.2 业务规则(P0 定案) -- 仅当前 `workflow_tasks.status=pending` 的实际处理人可转审 -- 转审后原审批人失去操作权(只读),新审批人成为任务 `assignee_id` -- 转审链最大次数:3(超过需 `admin` 介入) -- 转审必须落 3 处证据: - 1) 更新 `workflow_tasks.assignee_id` - 2) 插入 `approval_transfers` - 3) 插入 `workflow_history`,`action=transfer`,payload 含 from/to/reason +- 不接入飞书,不保留飞书登录、通知、审批、文档或项目管理路线。 +- 不使用企业微信原生审批作为正式审批流。 +- 不开发复杂 BPM 流程设计器,不做并行会签建模。 +- 不依赖付费低代码平台、付费第三方应用或付费项目管理套件。 +- 不重造企业微信打卡的定位、人脸识别、Wi-Fi、排班规则。 +- 不做完整 CRM、ERP、PLM、甘特排产或项目组合管理。 +- 不把聊天记录、会议纪要、涉密合同正文或敏感报价长期保存在 OA。 --- -## 8. 单据编号规则(Document Sequences,P0) +## 12. MVP 验收标准 -### 8.1 `document_sequences` 表(字段) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| document_type | String | 文档类型(如 `reimbursement`) | -| prefix | String | 前缀(如 `BX`) | -| date_format | String | 日期格式(如 `YYYYMMDD`) | -| separator | String | 分隔符(如 `-`) | -| number_length | Int | 流水号长度(默认 4) | -| reset_period | String | `none/daily/monthly/yearly` | -| current_number | Int | 当前序号(原子递增) | -| last_reset_date | DateTime | 上次重置时间 | +### 12.1 产品验收 -> 约束:`document_type` 必须唯一(避免多条序列配置导致重复单号)。 +- 老板能在 OA 中看到审批待办、项目/费用摘要和关键风险。 +- 员工能在 PC 或手机端完成报销、请假、采购等核心申请。 +- 审批人能在 PC 和移动端处理待办。 +- 财务能完成发票查重、付款标记和导出。 +- 管理员能维护人员、权限、企业微信入口和基础配置。 +- 企业微信只作为入口和提醒,不产生正式审批分叉。 -### 8.2 P0 默认报销单号格式 -- document_type=`reimbursement` -- prefix=`BX` -- date_format=`YYYYMMDD` -- separator=`-` -- number_length=`4` -- reset_period=`daily` +### 12.2 流程验收 -示例:`BX-20260205-0001` +| 场景 | 验收步骤 | +| --- | --- | +| 报销主流程 | 上传发票 -> OCR -> 绑定明细 -> 提交 -> 审批 -> 付款 -> 导出 | +| 重复发票 | 上传已使用发票 -> 系统阻断 -> 财务授权例外需原因和原单号 | +| 请假流程 | 员工提交请假 -> 主管审批 -> 团队状态或日历更新 | +| 采购流程 | 员工提交采购 -> 审批通过或驳回 -> 状态和附件可追溯 | +| 企业微信入口 | 手机企业微信进入 `/mobile` -> 账号已绑定则直达,未绑定可回退账号密码 | +| 企业微信通知 | 收到审批提醒 -> 点击 deeplink -> 回到 OA 审批页处理 | +| 文档门禁 | 新需求先更新 PRD 或规则文档,再拆 Issue CSV,不直接开发 | -### 8.3 并发安全要求(实现约束) -- 编号生成必须数据库原子递增(等价于 `UPDATE ... RETURNING`),并在同一事务内写入报销单 -- `form_number` 必须全局唯一;失败必须回滚,禁止出现重复编号或“跳号后写入失败但编号已占用”的不可追溯状态 -- `daily reset` 的日期边界以公司本地时区为准(默认 `Asia/Shanghai`);实现时应统一使用显式时区计算,禁止依赖数据库/容器时区的隐式行为 +### 12.3 工程验收 + +- Issue CSV 校验通过。 +- 文档 diff 检查通过。 +- 涉及后端时执行 Prisma、语法和相关回归脚本。 +- 涉及前端时执行 typecheck、build 和相关 Playwright。 +- 涉及公网或企业微信真机时,必须部署后检查 `/ready`、前端资源和真实入口。 --- -## 9. 费用类型与科目映射(Expense Types,P0) +## 13. 后续 Backlog -### 9.1 `expense_types`(动态费用类型) -字段至少包含: -- `code`(全局唯一、不可修改) -- `name` -- `description` -- `category`(用于 UI 分组;系统默认使用 `travel/rd/production/office/business/assets/other`) -- `sort_order` -- `account_code_default`(默认会计科目,可配置) -- `is_system_default`(系统内置:不可删除、仅可禁用) -- `enabled`(软禁用:不在录入下拉显示,但历史可展示) +后续事项必须另立 Issue CSV,不直接进入当前 MVP: -### 9.2 系统默认费用类型(P0:37 项启用) -- 基线:复用 `shenzhou-business-suite/supabase/seed.sql` 中已启用的 35 项 -- 补齐:启用 `utilities`(水电费)与 `rent`(房租租赁费)两项高频类型 -- 兼容预留:保留 3 个 legacy 类型(`transport/meal/rd`)为禁用状态,不在 P0 下拉显示 - -详见附录 A(37 项启用)与附录 B(legacy 禁用)。 +- 企业微信打卡 API 同步和考勤汇总报表。 +- 补卡、外出、加班的完整移动端体验强化。 +- 合同台账、合同编号、合同审批和离线档案号的细化。 +- 预算控制、支付批次、AP 运营和 ERP 出站凭证增强。 +- 项目轻管理与周报、待办、费用、采购、差旅的进一步联动。 +- 全量 E2E 稳定性清理和移动视觉回归。 +- 生产备份恢复演练、并发试运行和正式上线复核。 --- -## 10. 发票池与文件存储(P0:本地 + 签名 URL) -> 关键原则:文件与票据元数据以 `invoice_pool` 为单一事实源;报销单通过“绑定”使用发票池条目(见 4.4)。 - -### 10.1 P0 存储方案(本地存储) -- 存储介质:本地磁盘(Docker volume) -- DB 仅保存 `storage_path`,不保存公网 URL -- 访问方式:后端生成签名 URL(TTL=10 分钟)用于浏览器预览/下载 - -### 10.2 安全基线(P0) -- 文件类型白名单:`application/pdf`、`image/jpeg`、`image/png`、`image/webp` -- 单文件大小:≤ 10MB -- 权限: - - 未绑定时:`owner_employee_id`、`uploaded_by_employee_id`、`finance/finance_mgr`、`admin` 可访问 - - 绑定后:除以上人员外,报销相关人员(owner/submitter/审批链/财务/admin)可访问 -- 文件元数据(用于判重/审计): - - `file_sha256`(必填) - - `invoice_number`/`invoice_code`(可选;齐全时参与判重) - -### 10.3 P2 迁移(MinIO/OSS) -- 替换为对象存储(S3 pre-signed URL) -- 接口与 DB 字段保持不变(`storage_path` 语义不变) - -### 10.4 `invoice_pool` 表结构(P0) -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| owner_employee_id | UUID | 费用归属人(决定可见范围与绑定归属) | -| uploaded_by_employee_id | UUID | 上传人(可能是行政代上传) | -| storage_path | String | 本地存储路径(相对路径) | -| original_name | String | 原始文件名 | -| mime_type | String | MIME 类型 | -| file_size | Int | 文件大小(字节) | -| file_sha256 | String | 文件哈希(必填;用于判重与 OCR 去重) | -| status | String | `pending_ocr/ocr_failed/available/bound/archived` | -| archived_reason | String | `used_complete/unused_timeout/manual`(仅 `archived` 有意义) | -| archived_at | DateTime | 归档时间(可空) | -| linked_reimbursement_id | UUID | 绑定到报销单(可空;`status=bound` 必填) | -| linked_item_id | UUID | 绑定到费用明细(可空) | -| remarks | String | 备注/人工说明(可空) | -| ocr_provider | String | OCR 提供方(P0 固定 `tencent`) | -| ocr_type | String | `vat/train/flight/taxi/general` | -| ocr_result_id | UUID | 关联 `ocr_results`(可空) | -| ocr_attempts | Int | OCR 重试次数(默认 0) | -| ocr_last_error | String | OCR 最近一次错误(可空) | -| ocr_completed_at | DateTime | OCR 完成时间(可空) | -| invoice_code | String | 发票代码(可空) | -| invoice_number | String | 发票号码(可空) | -| invoice_date | Date | 开票日期(可空) | -| seller_name | String | 销售方名称(可空) | -| buyer_name | String | 购买方名称(可空) | -| amount | Decimal | 不含税金额(可空) | -| tax_amount | Decimal | 税额(可空) | -| total_amount | Decimal | 价税合计(可空) | -| is_duplicate_override | Bool | 是否为判重例外(默认 false) | -| override_ref_form_number | String | 例外引用的原单号(`is_duplicate_override=true` 时必填) | -| override_reason | String | 例外原因(`is_duplicate_override=true` 时必填,≥10 字) | -| overridden_by | UUID | 谁批准的例外(`finance_mgr`) | -| created_at/created_by | DateTime/UUID | 创建时间/操作者 | -| updated_at/updated_by | DateTime/UUID | 更新时间/操作者 | - -### 10.5 `ocr_results`(去重 OCR 结果表,P0) -> 目的:避免同一文件重复计费调用 OCR,同时持久化标准化结果用于复核与复用。 - -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| file_sha256 | String | 文件哈希(与 `invoice_pool.file_sha256` 对齐) | -| ocr_provider | String | 提供方(P0 固定 `tencent`) | -| ocr_type | String | `vat/train/flight/taxi/general` | -| result_json | JSON | 标准化 OCR 结果(必须保存) | -| confidence | Int | 置信度(可空) | -| created_at | DateTime | 创建时间 | - -**唯一约束(P0 定案)**: -- `UNIQUE (file_sha256, ocr_provider, ocr_type)` - -### 10.6 索引与约束(P0 定案) -> Prisma Schema 不能直接表达 partial unique index,需要通过 **自定义 SQL migration** 创建(`prisma migrate dev --create-only` 后编辑迁移 SQL)。 - -**并发绑定保护(实现约束)**: -- `POST /api/invoice-pool/:id/bind` 必须对目标 `invoice_pool` 行加行级锁(`SELECT ... FOR UPDATE`),避免并发双绑。 - -**全局双策略判重(只对已使用历史生效,P0 定案)**: -- `UNIQUE (file_sha256) WHERE (status = 'bound' OR (status = 'archived' AND archived_reason = 'used_complete')) AND is_duplicate_override = false` -- `UNIQUE (invoice_code, invoice_number) WHERE invoice_code IS NOT NULL AND invoice_number IS NOT NULL AND (status = 'bound' OR (status = 'archived' AND archived_reason = 'used_complete')) AND is_duplicate_override = false` - -**例外字段完整性(DB check,防漏填)**: -- 当 `is_duplicate_override = true` 时,`override_ref_form_number`、`override_reason`、`overridden_by` 必填,且 `char_length(override_reason) >= 10` - -### 10.7 API 约定(P0) -- 上传:`POST /api/invoice-pool/upload`(multipart;入参 `file` + 可选 `owner_employee_id` + 可选 `ocr_type`;默认 `ocr_type=general`) - - 上传即创建 `invoice_pool` 记录并返回 `invoice_pool_id` - - OCR 结果命中 `ocr_results` 时应复用(不重复计费) -- 签名 URL:`POST /api/invoice-pool/:id/signed-url`(TTL=10m;服务端按 10.2 的规则鉴权) - ---- - -## 11. 认证与会话(P0) - -### 11.1 登录方式(P0) -- 支持 `手机号/邮箱 + 密码` 登录 -- 验证码仅用于“找回/重置密码”(P0:邮件验证码;短信验证码/SSO 放 P1/P2) - -### 11.2 密码策略(P0) -- 长度:≥12 -- 复杂度:必须同时包含 **大写 + 小写 + 数字**(允许特殊字符但不强制) -- 弱密码拦截:禁止命中弱密码库(Top 10k) -- 错误次数锁定:15 分钟内失败 5 次 → 锁定 30 分钟;管理员可解锁 -- 首次登录:必须修改初始密码 - -### 11.3 Token 与会话(JWT + Refresh Rotation) -- Access Token: - - 过期:15 分钟 - - 传输:`Authorization: Bearer ` - - 存储:仅内存(禁止 localStorage/sessionStorage) -- Refresh Token: - - 过期:7 天 - - 客户端存储:`HttpOnly + Secure + SameSite=Strict` Cookie - - 轮换:每次 refresh 都签发新 refresh,旧 refresh 立即失效 - - 服务端存储:数据库保存 refresh 的哈希(不可明文),并绑定设备信息(deviceFingerprint/ipAddress/userAgent) -- 强制下线:员工 `tokenVersion + 1`(密码重置/离职/管理员强制下线)使旧 access 立即失效 - -**实现约束(P0 定案)**: -- `tokenVersion` 立即失效机制: - - Access Token 载荷必须包含 `tokenVersion` - - 每次请求服务端必须校验 `tokenVersion`(从 DB 或缓存读取当前值) - - 若 Token 中 `tokenVersion < DB tokenVersion`,立即返回 `AUTH_004` -- Refresh 重放检测(防止 refresh 泄漏后被持续利用): - - 每次 refresh 成功后,旧 refresh 的 hash 必须标记为已使用或直接删除 - - 若检测到"已使用的旧 refresh 被重放",说明可能存在泄漏: - - 立即撤销该员工所有有效会话(`revoked_at = now()`) - - 员工 `tokenVersion + 1` - - 写入 `audit_logs`(`action=REFRESH_REUSE_DETECTED`) - - 返回 `AUTH_005` 并要求用户重新登录 -- Refresh 同站校验(额外防线): - - `POST /api/auth/refresh` 若请求携带 `Origin`/`Referer`,则必须与本站域名匹配(按服务端配置的 `APP_ORIGIN`) - - 不匹配则拒绝该请求并返回 `AUTH_005`,同时写入 `audit_logs`(`action=AUTH_REFRESH_ORIGIN_MISMATCH`) - -### 11.4 Auth API(P0) -- `POST /api/auth/login` → 返回 access;refresh 通过 `Set-Cookie` -- `POST /api/auth/refresh` → 从 cookie 轮换 refresh,返回新 access -- `POST /api/auth/logout` → 撤销当前会话并清 cookie -- `POST /api/auth/forgot-password`(邮件验证码) -- `POST /api/auth/reset-password` - -### 11.5 `auth_sessions`(Refresh 会话表,P0) -> 目的:服务端可撤销 refresh、限制并发会话数、绑定设备信息以支持 Phase 2 异常登录检测。 - -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| employee_id | UUID | 所属员工 | -| refresh_token_hash | String | refresh 的哈希(不可明文) | -| device_fingerprint | String | 设备指纹(必填) | -| ip_address | String | 登录 IP(必填) | -| user_agent | String | UA(必填) | -| created_at | DateTime | 创建时间 | -| last_seen_at | DateTime | 最近活跃时间(refresh 时更新) | -| expires_at | DateTime | 过期时间(= 7 天) | -| revoked_at | DateTime | 撤销时间(可空) | - -**会话规则(P0 定案)**: -- 同一员工最多 5 个有效会话(`revoked_at is null AND expires_at > now()`);超过时撤销最旧会话 -- 退出登录:撤销当前会话(写 `revoked_at`) -- 密码重置/离职/管理员强制下线:撤销该员工所有有效会话 + `tokenVersion + 1` - ---- - -## 12. 数据安全与审计(P0) - -### 12.1 敏感字段清单 -- 员工:银行卡号、身份证号 -- 报销:发票/附件文件(可能包含敏感信息) - -### 12.2 脱敏展示规则 -- 银行卡:仅显示后 4 位(例:`**** **** **** 1234`) -- 身份证:前 3 位 + 后 4 位(例:`310***********1234`) - -### 12.3 加密存储(应用层加密) -- 算法:AES-256-GCM -- 每条记录随机 nonce -- 密钥来自服务端环境变量(P0);后续可迁移 KMS -- 明确:加密字段不支持按明文检索(禁止为“搜索”而明文存储) - -### 12.4 审计分层(必须写清楚) -- `workflow_history`:审批链路与流程动作的权威记录(谁在何时对哪个任务做了什么) -- `audit_logs`(系统审计):登录/锁定/重置/敏感字段明文查看/权限配置变更/判重例外等安全关键事件 - -**`audit_logs` 表(建议字段)**: -| 字段 | 类型 | 说明 | -| :--- | :--- | :--- | -| id | UUID | 主键 | -| request_id | String | 关联请求 ID | -| actor_id | UUID | 操作人 | -| action | String | 动作码(如 `AUTH_LOGIN_SUCCESS`/`INVOICE_DUPLICATE_OVERRIDE`) | -| target_type | String | 目标类型(如 `employee`/`reimbursement`/`attachment`) | -| target_id | UUID | 目标 ID(可空) | -| ip/user_agent | String | 来源信息 | -| extra | JSON | 扩展信息(含变更快照) | -| created_at | DateTime | 时间戳 | - -对“敏感字段变更/授权变更/关键配置变更”,`extra.changes` 必须包含脱敏前后快照: -`{ changes: { field: { before: masked, after: masked } } }`(禁止记录明文) - -保留期:至少 1 年。 - -### 12.5 审计动作码(P0 最小集) -> 目的:把“高风险动作”与“关键链路”落到可检索的审计事件上,便于排查与合规追溯。 - -**OCR(发票识别)** -- `OCR_REQUESTED`:发起 OCR 请求(含 `ocr_type`、file_sha256) -- `OCR_SUCCEEDED`:OCR 成功(含 `ocr_result_id`、confidence 摘要) -- `OCR_FAILED`:OCR 失败(含错误摘要;禁止记录 rawResponse) -- `OCR_RETRIED`:OCR 重试(含 attempts 与节流结果) - -**发票池(Invoice Pool)** -- `INVOICE_POOL_UPLOADED`:文件入池(owner/uploaded_by、mime/size/hash) -- `INVOICE_POOL_EDITED`:人工修正关键字段(`extra.changes` 记录脱敏前后快照) -- `INVOICE_POOL_BOUND`:绑定到报销单 -- `INVOICE_POOL_UNBOUND`:从报销单解绑 -- `INVOICE_POOL_ARCHIVED`:人工归档(`archived_reason=manual`) -- `INVOICE_POOL_AUTO_ARCHIVED`:系统自动归档(`archived_reason=unused_timeout/used_complete`) - -**判重与例外** -- `INVOICE_DUPLICATE_OVERRIDE`:判重例外(必须包含引用原单号 + 原因摘要) - ---- - -## 13. API 契约与错误码(P0) -> 本章为 API 契约唯一权威(P0 定案)。目的:前后端联调与测试可直接以此为准,不在实现阶段临场补协议。 - -### 13.1 通用响应结构(P0) -- 所有响应必须返回 `requestId`(可放在响应头 `x-request-id`,并在 body 同步返回) -- 失败响应结构: -```json -{ - "requestId": "01J....", - "error": { - "code": "AUTH_001", - "message": "用户名或密码错误", - "details": {} - } -} -``` - -- 成功响应结构(对象): -```json -{ - "requestId": "01J....", - "data": {} -} -``` - -- 成功响应结构(列表 + 分页): -```json -{ - "requestId": "01J....", - "data": [], - "pageInfo": { - "page": 1, - "pageSize": 20, - "total": 0, - "totalPages": 0 - } -} -``` - -#### 13.1.1 分页 / 排序 / 筛选约定(P0) -> 目的:前后端统一口径,避免“边开发边发明 query 参数”。 - -- `page`:页码(从 1 开始;默认 1) -- `pageSize`:每页数量(默认 20;最大 100) -- `sort`:排序(格式:`field:asc|desc`;多个排序用逗号分隔,例如 `sort=created_at:desc,total_amount:desc`) -- `q`:可选的关键字搜索(各接口自行决定是否支持) -- 过滤参数:按接口逐项定义(P0 不引入通用 filter DSL) - -#### 13.1.2 字段类型约定(P0) -- `UUID`:JSON 中一律为 string -- `Date`:`YYYY-MM-DD`(业务日期) -- `DateTime`:ISO 8601(建议带时区,例如 `2026-02-06T09:00:00+08:00`) -- `Decimal/Money`:JSON 中一律为 string(禁止用 number,避免精度丢失) - -### 13.2 核心业务 API(P0) - -#### 13.2.1 报销单(Reimbursements)CRUD -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| POST | `/api/reimbursements` | `expense.create` | 创建报销草稿(默认 `owner_id=me, submitter_id=me`;代提交场景可指定 owner,但必须存在有效授权) | -| GET | `/api/reimbursements` | `expense.view_own`(或 `expense.view_all`) | 报销列表(分页/筛选/排序);服务端按权限自动收敛数据范围 | -| GET | `/api/reimbursements/:id` | `expense.view_own`(或 `expense.view_all`) | 报销详情(含 items + 已绑定的发票池条目摘要;可包含当前 pending task 用于“节点文案”展示) | -| PATCH | `/api/reimbursements/:id` | `expense.edit_own` | 更新草稿/驳回单(仅允许 `status in (draft, rejected)`) | -| DELETE | `/api/reimbursements/:id` | `expense.delete_own` | 删除草稿(仅允许 `status=draft`;需删除 items;并自动解绑其绑定的发票池条目(回到 `available`;不删除发票池文件)) | - -**列表筛选建议(P0)**: -- `status`:`draft/reviewing/approved/paid/rejected/cancelled` -- `formNumber`:精确匹配 -- `ownerId` / `submitterId` / `departmentId` -- `reimbursementDateFrom` / `reimbursementDateTo`(业务日期) -- `createdAtFrom` / `createdAtTo`(时间戳) - -#### 13.2.2 审批待办(Workflow Tasks) -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/workflow/tasks` | 登录即可(服务端鉴权) | 待办/已办列表(分页/筛选/排序);默认只返回“我可处理”的任务(`assignee_id=me` 或 `assignee_role in myRoles`) | - -**列表筛选建议(P0)**: -- `status`:默认 `pending`;可选 `completed/rejected/cancelled/skipped` -- `entityType`:默认 `reimbursement` -- `reimbursementId`(便于从报销详情页拉取当前任务/历史) - -#### 13.2.3 流程动作(Workflow / Reimbursement Actions) -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| POST | `/api/reimbursements/:id/submit` | `expense.submit` | 提交报销并发起工作流(生成单号、创建 instance/task/history) | -| POST | `/api/reimbursements/:id/withdraw` | `expense.withdraw` | 撤回:取消流程实例并回到草稿(仅审批人未处理前) | -| POST | `/api/workflow/tasks/:taskId/approve` | `expense.approve` | 通过当前任务并推进 | -| POST | `/api/workflow/tasks/:taskId/reject` | `expense.approve` | 驳回并终止流程 | -| POST | `/api/workflow/tasks/:taskId/return` | `expense.approve` | 退回:终止当前实例(instance=`terminated`)并回到草稿;申请人修改后需重新 submit 才会进入新一轮 | -| POST | `/api/workflow/tasks/:taskId/transfer` | `expense.transfer` | 转审(reason≥10,链≤3) | -| POST | `/api/reimbursements/:id/pay` | `expense.finance.pay` | 付款标记(approved → paid) | - -#### 13.2.4 员工与组织(Employees / Departments,P0) -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/me` | 登录即可 | 获取当前登录员工信息(含 role_codes;前端用于菜单与数据范围判断) | -| GET | `/api/employees` | `system.users` | 员工列表(分页/筛选/排序) | -| GET | `/api/employees/:id` | `system.users`(或 `expense.view_own` 仅本人) | 员工详情 | -| POST | `/api/employees` | `system.users` | 创建员工(初始密码策略见第 11 章;首次登录强制改密) | -| PATCH | `/api/employees/:id` | `system.users` | 更新员工信息(敏感字段加密/脱敏与审计见第 12 章) | -| POST | `/api/employees/:id/archive` | `system.users` | 员工离职/归档(禁止登录并强制下线,见 4.1.2/11.3) | -| GET | `/api/departments` | 登录即可 | 部门树/列表(默认隐藏归档部门,可通过 query 开启) | -| POST | `/api/departments` | `system.users` | 创建部门 | -| PATCH | `/api/departments/:id` | `system.users` | 更新部门 | -| POST | `/api/departments/:id/archive` | `system.users` | 归档部门(归档约束见 3.2) | - -#### 13.2.5 认证(Auth,P0) -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| POST | `/api/auth/login` | N/A | 登录:返回 access token;refresh token 通过 `Set-Cookie` 写入 `HttpOnly` cookie(见 11.3) | -| POST | `/api/auth/refresh` | N/A | 刷新:轮换 refresh cookie,返回新 access token(refresh rotation + 重放检测见 11.3) | -| POST | `/api/auth/logout` | 登录即可 | 退出:撤销当前会话并清 cookie | -| POST | `/api/auth/forgot-password` | N/A | 忘记密码:发送邮件验证码(P0) | -| POST | `/api/auth/reset-password` | N/A | 重置密码:验证码校验 + `tokenVersion + 1` 强制下线(见 11.3) | - -#### 13.2.6 发票池(Invoice Pool)+ OCR(P0) -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| POST | `/api/invoice-pool/upload` | `invoice.upload` | 上传票据文件入池(multipart;入参 `file` + 可选 `owner_employee_id` + 可选 `ocr_type`;默认 `ocr_type=general`;返回 `invoice_pool_id` + 初始 `status`) | -| GET | `/api/invoice-pool` | 登录即可(服务端鉴权) | 发票池列表(分页/筛选/排序);默认只返回与本人相关(owner 或 uploaded_by),`finance/admin` 可查看全量 | -| GET | `/api/invoice-pool/:id` | 登录即可(服务端鉴权) | 发票池详情(含 OCR 摘要字段) | -| PATCH | `/api/invoice-pool/:id` | 登录即可(服务端鉴权) | 更新备注/人工修正关键字段(如 `invoice_code/number/date/amount`);允许将 `ocr_failed` 条目“人工确认”转为 `available` | -| POST | `/api/invoice-pool/:id/retry-ocr` | 登录即可(服务端鉴权) | 重试 OCR(仅 `status=ocr_failed`;次数/频控见下) | -| POST | `/api/invoice-pool/:id/bind` | 登录即可(服务端鉴权) | 绑定到报销单(1:1;并发与判重硬校验在此发生) | -| POST | `/api/invoice-pool/:id/unbind` | 登录即可(服务端鉴权) | 从报销单解绑(仅草稿/驳回可解绑;解绑后回到 `available`) | -| POST | `/api/invoice-pool/:id/archive` | 登录即可(服务端鉴权) | 手工归档(仅未绑定;归档为 `archived_reason=manual`) | -| POST | `/api/invoice-pool/:id/signed-url` | 登录即可(服务端鉴权) | 获取票据文件签名 URL(TTL=10m;服务端按第 10 章规则鉴权) | - -**上传归属权限链(P0 定案)**: -- 当 `owner_employee_id != 当前登录用户` 时: - - 若当前登录用户持有 `system.users`(hr/admin)→ 允许 - - 若当前登录用户持有 `expense.proxy_submit` 且角色包含 `administration` → 必须存在有效 `delegation_grants`(principal=owner, delegate=当前用户, scope 包含 `reimbursement_submit`) - - 否则拒绝(`PERM_001`) - -**绑定并发语义(P0 定案)**: -- `POST /api/invoice-pool/:id/bind` 必须在事务内对目标 `invoice_pool` 行执行 `SELECT ... FOR UPDATE`(行级锁)。 -- 若已是 `bound` 或 `linked_reimbursement_id` 非空:返回 409 `VALID_005`(发票已被其他报销单占用/绑定)。 -- 若命中全局判重唯一约束:返回 409 `VALID_003`(发票判重命中)。 - -**retry-ocr 次数与频控(P0 固定值)**: -- 次数:`ocr_attempts <= 3`;超过返回 429 `VALID_006`(OCR 重试次数超限)。 -- 间隔:同一条目两次重试间隔 `>= 60s`;过于频繁返回 429 `VALID_007`(OCR 重试过于频繁)。 - -#### 13.2.7 请假 API -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/leave-types` | `leave.view_own` | 列出启用假别;`leave.manage` 可查看禁用项与配额配置 | -| POST | `/api/leave-types` | `leave.manage` | 创建假别与年度配额策略 | -| PATCH | `/api/leave-types/:id` | `leave.manage` | 更新假别启用状态、附件要求与年度额度 | -| GET | `/api/leave-balances` | `leave.view_own` | 查看当前员工请假余额;查询他人需 `leave.view_all/leave.manage` | -| GET | `/api/leave-requests` | `leave.view_own`(或 `leave.view_all/leave.manage`) | 请假列表;服务端按本人或全量视角收敛 | -| POST | `/api/leave-requests` | `leave.create` | 创建请假草稿并校验基础日期 / 附件规则 | -| GET | `/api/leave-requests/:id` | 登录即可(服务端鉴权) | 获取请假详情;仅本人或具备全量权限的角色可读 | -| POST | `/api/leave-requests/:id/submit` | `leave.submit` | 提交请假申请,触发冲突校验、额度强设闸与 workflow 启动 | -| POST | `/api/leave-requests/:id/withdraw` | `leave.submit` | 在审批人未处理前撤回,请假状态回到 `draft` 并释放冻结额度 | - -> 请假的 approve / reject / return 继续复用通用 workflow API,审批权限码为 `leave.approve`。 - -#### 13.2.8 采购 API -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/purchase-requests` | 登录即可(服务端鉴权) | 采购列表;按 `procurement.view_own/view_all/manage` 收敛 `mine` 视角 | -| POST | `/api/purchase-requests` | `procurement.create` | 创建采购草稿 | -| GET | `/api/purchase-requests/:id` | 登录即可(服务端鉴权) | 获取采购详情;仅申请人 / 提交人或可读角色可读 | -| PATCH | `/api/purchase-requests/:id` | `procurement.create` | 更新采购草稿;仅 `draft` 可编辑 | -| POST | `/api/purchase-requests/:id/submit` | `procurement.submit` | 提交采购申请并写预算预留 | -| POST | `/api/purchase-requests/:id/withdraw` | `procurement.submit` | 在审批人未处理前撤回,释放预算并回到 `draft` | -| GET | `/api/purchase-requests/:id/archive-order` | 登录即可(服务端鉴权) | 查看 PO / 合同归档信息 | -| POST | `/api/purchase-requests/:id/archive-order` | `procurement.manage` | 在 approved 采购单上创建或更新归档记录 | -| POST | `/api/purchase-requests/:id/attachments` | `procurement.create` | 上传采购附件 | -| DELETE | `/api/purchase-requests/:id/attachments/:attachmentId` | `procurement.create` | 删除采购附件 | -| POST | `/api/purchase-requests/:id/attachments/:attachmentId/signed-url` | 登录即可(服务端鉴权) | 获取附件签名 URL | -| GET | `/api/purchase-requests/:id/attachments/:attachmentId/file` | 登录即可(服务端鉴权) | 下载附件文件 | - -> 采购 approve / reject / return 继续复用通用 workflow API,审批权限码为 `procurement.approve`。 - -#### 13.2.9 资产与盘点 API -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/asset-categories` | 登录即可(服务端鉴权) | 资产分类列表;按 `asset.view_own/view_all/manage` 收敛 | -| POST | `/api/asset-categories` | `asset.manage` | 创建资产分类 | -| PATCH | `/api/asset-categories/:id` | `asset.manage` | 更新资产分类 | -| GET | `/api/assets/assignees` | 登录即可(服务端鉴权) | 资产领用人候选查询;内部要求具备 `asset.manage` | -| GET | `/api/assets` | 登录即可(服务端鉴权) | 资产卡片列表;按 `asset.view_own/view_all/manage/transfer/inventory` 收敛 | -| POST | `/api/assets` | `asset.manage` | 创建资产卡片;可在建卡时初始化领用状态 | -| GET | `/api/assets/:id` | 登录即可(服务端鉴权) | 资产详情 | -| PATCH | `/api/assets/:id` | `asset.manage` | 更新资产卡片;当前领用 / 归还 / 调拨语义在此受控落账 | -| GET | `/api/inventory-checks` | `asset.inventory` | 盘点任务列表 | -| POST | `/api/inventory-checks` | `asset.inventory` | 创建盘点任务 | -| POST | `/api/inventory-checks/:id/start` | `asset.inventory` | 启动盘点 | -| POST | `/api/inventory-checks/:id/items` | `asset.inventory` | 录入或覆盖盘点明细 | -| POST | `/api/inventory-checks/:id/complete` | `asset.inventory` | 完成盘点任务 | - -#### 13.2.10 差旅 API -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/travel-requests` | 登录即可(服务端鉴权) | 差旅申请列表;按 `travel.view_own/view_all/manage` 收敛 | -| POST | `/api/travel-requests` | `travel.create` | 创建差旅草稿 | -| GET | `/api/travel-requests/:id` | 登录即可(服务端鉴权) | 差旅申请详情 | -| PATCH | `/api/travel-requests/:id` | `travel.create` | 更新差旅草稿 | -| POST | `/api/travel-requests/:id/submit` | `travel.submit` | 提交差旅申请并写预算预留 | -| POST | `/api/travel-requests/:id/withdraw` | `travel.submit` | 撤回差旅申请并释放预算 | -| GET | `/api/travel-requests/:id/payment-track` | 登录即可(服务端鉴权) | 查看订单支付轨道摘要 | -| POST | `/api/travel/orders/import` | `integration.manage` | 导入差旅订单 | -| POST | `/api/travel-orders/:id/mark-paid` | `travel.payment.manage` | 手工结算差旅订单并写支付凭证字段 | -| GET | `/api/travel/authorizations` | `travel.payment.manage` | 查看企业卡授权记录 | -| POST | `/api/travel/authorizations/import` | `integration.manage` | 导入企业卡授权 | -| GET | `/api/travel/transactions` | `travel.payment.manage` | 查看企业卡交易 | -| POST | `/api/travel/transactions/import` | `integration.manage` | 导入企业卡交易 | -| POST | `/api/travel/transactions/:id/freeze-suggestion` | `travel.payment.manage` | 标记冻结建议 | -| GET | `/api/travel/reconciliation/batches` | 登录即可(服务端鉴权) | 查看对账批次列表;服务端按差旅 / 集成读权限过滤 | -| POST | `/api/travel/reconciliation/batches` | `travel.payment.manage` | 创建对账批次快照 | -| GET | `/api/travel/reconciliation/batches/:batchId` | 登录即可(服务端鉴权) | 获取批次详情与 KPI | -| POST | `/api/travel/reconciliation/items/:itemId/confirm` | `travel.payment.manage` | 确认异常明细 | -| POST | `/api/travel/reconciliation/items/:itemId/reopen` | `travel.payment.manage` | 重开异常明细 | -| POST | `/api/travel/reconciliation/batches/:batchId/finalize` | `travel.payment.manage` | 将批次从 `draft` 收口到 `reconciled` | - -> 差旅 approve / reject / return 继续复用通用 workflow API,审批权限码为 `travel.approve`。 - -#### 13.2.11 集成 API -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/integrations/erp/master-data/status` | `integration.manage` | 查看 ERP 主数据同步状态 | -| POST | `/api/integrations/erp/master-data/sync` | `integration.manage` | 手工触发主数据同步 | -| GET | `/api/integrations/erp/journals/reimbursements/:reimbursementId/preview` | `integration.manage` | 查看报销凭证切片预览 | -| POST | `/api/integrations/erp/journals/reimbursements/:reimbursementId/export` | `integration.manage` | 手工触发报销 ERP 导出 | -| GET | `/api/integrations/erp/procurement/purchase-requests/:purchaseRequestId/track` | 登录即可(服务端鉴权) | 查看采购 ERP 轨道;仅具备该采购单读权限的用户可见 | -| POST | `/api/integrations/erp/procurement/purchase-requests/:purchaseRequestId/requeue` | `integration.manage` | 补发或重试采购 ERP 轨道 | -| GET | `/api/integrations/erp/assets/:assetId/track` | 登录即可(服务端鉴权) | 查看资产 ERP 轨道 | -| POST | `/api/integrations/erp/assets/:assetId/requeue` | `integration.manage` | 补发或重试资产 ERP 轨道 | -| GET | `/api/integrations/erp/travel/reconciliation-batches/:batchId/track` | 登录即可(服务端鉴权) | 查看差旅对账批次 ERP 轨道 | -| POST | `/api/integrations/erp/travel/reconciliation-batches/:batchId/requeue` | `integration.manage` | 补发或重试差旅 ERP 摘要桥 | -| POST | `/api/integrations/erp/sync/:reimbursementId` | `integration.manage` | 兼容性手工同步入口 | -| GET | `/api/notifications/channels` | `notification.channel.manage` | 查看通知渠道列表;返回 `secretConfigured` 与 `secretMasked`,不回显明文 secret | -| POST | `/api/notifications/channels` | `notification.channel.manage` | 创建通知渠道;`configJson.secret` 为只写字段,服务端以 `secretEncrypted` 落库 | -| PATCH | `/api/notifications/channels/:id` | `notification.channel.manage` | 更新通知渠道;支持保留 / 替换 / 清空 Webhook Secret | -| POST | `/api/notifications/channels/:id/test` | `notification.channel.manage` | 同步测试企微通道,返回 `code`、`message`、`retryable` 与消息预览 | -### 13.3 配置读取 API(P0 只读) -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/expense-types?enabled=true` | 登录即可 | 获取启用费用类型(下拉选项) | -| GET | `/api/approval-rules?enabled=true` | 登录即可 | 获取启用审批规则(便于运维/排查) | -| GET | `/api/document-sequences/reimbursement` | `system.settings`(可选) | 查看报销单号规则配置 | - -### 13.4 文件与签名 URL(P0) -> 上传与文件预览/下载统一归口到“发票池”模块,避免 `files` 与 `invoice_pool` 双源。 - -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| POST | `/api/invoice-pool/upload` | `invoice.upload` | 上传票据文件入池(见 13.2.6) | -| POST | `/api/invoice-pool/:id/signed-url` | 登录即可(服务端鉴权) | 获取签名 URL(TTL=10m;服务端校验“是否与票据相关/是否有权限”) | - -### 13.5 Delegation Grants API(P0) -| 方法 | 路径 | 权限码 | 说明 | -| :--- | :--- | :--- | :--- | -| GET | `/api/delegation-grants` | 登录即可 | 默认返回与本人相关(principal/delegate);`system.users` 可查询任意 | -| POST | `/api/delegation-grants` | 登录即可 | 本人创建:principal=自己;代维护需 `system.users`;所有变更写入 `audit_logs` | -| POST | `/api/delegation-grants/:id/revoke` | 登录即可 | 本人撤销:principal=自己;代维护需 `system.users`;所有变更写入 `audit_logs` | - -**实现约定(P0)**: -- `GET /api/delegation-grants` 响应建议内嵌 `principal`/`delegate` 的员工摘要(如 `id/employee_no/name/department_id`),用于“行政代提交选择人员”场景,避免前端额外拉取员工目录。 -- 默认只返回“当前仍有效”的授权(`revoked_at is null` 且时间在有效期内);如需查看历史可通过 query 显式开启(例如 `includeInactive=true`)。 - -### 13.6 错误码(P0 最小集) -#### 13.6.1 认证(Auth) -- `AUTH_001`:用户名或密码错误(401) -- `AUTH_002`:账号已锁定(423) -- `AUTH_003`:账号已离职/禁用(403) -- `AUTH_004`:Access Token 已过期/无效(401) -- `AUTH_005`:Refresh Token 无效/已撤销(401) - -#### 13.6.2 权限(Permission) -- `PERM_001`:无权限(403) -- `PERM_002`:不在审批范围/非当前处理人(403) - -#### 13.6.3 校验(Validation) -- `VALID_001`:参数校验失败(400) -- `VALID_002`:转审原因不足 10 字(400) -- `VALID_003`:发票判重命中(409) -- `VALID_004`:发票重复例外被禁止(原单已打款/待打款)(409) -- `VALID_005`:发票已被其他报销单占用/绑定(409) -- `VALID_006`:OCR 重试次数超限(429) -- `VALID_007`:OCR 重试过于频繁(429) - ---- - -## 14. 非功能需求 -| 类别 | 要求 | -| :--- | :--- | -| 响应式 | PC + 移动端(响应式 Web) | -| 性能 | 页面加载 <2s,API 响应 <500ms(P95) | -| 安全 | JWT(access+refresh rotation)、RBAC 权限码、审计日志、敏感字段加密与脱敏 | -| 部署 | Docker + Caddy/Nginx(反向代理),域名 oa.hyinda.com | - ---- - -## 15. 数据模型概览(高层) -```mermaid -erDiagram - departments ||--o{ employees : contains - employees ||--o{ employee_roles : assigns - employees ||--o{ delegation_grants : principal - employees ||--o{ delegation_grants : delegate - employees ||--o{ auth_sessions : has - employees ||--o{ audit_logs : writes - - employees ||--o{ reimbursement_forms : submits - employees ||--o{ reimbursement_forms : owns - reimbursement_forms ||--|{ reimbursement_items : has - reimbursement_items }o--|| expense_types : uses - reimbursement_forms ||--o{ invoice_pool : binds - - leave_types ||--o{ leave_policies : configures - leave_types ||--o{ leave_requests : classifies - employees ||--o{ leave_requests : owns - leave_requests ||--o{ leave_ledgers : anchors - employees ||--o{ leave_ledgers : accrues - - employees ||--o{ purchase_requests : applies - purchase_requests ||--|{ purchase_request_items : has - purchase_requests ||--o{ purchase_attachments : stores - purchase_requests ||--o| purchase_order_archives : archives - - asset_categories ||--o{ asset_cards : classifies - purchase_requests ||--o{ asset_cards : sources - asset_cards ||--o{ asset_assignments : assigns - asset_cards ||--o{ asset_transfers : transfers - inventory_checks ||--o{ inventory_check_items : records - asset_cards ||--o{ inventory_check_items : counted - - employees ||--o{ travel_requests : applies - travel_requests ||--|{ travel_request_segments : has - travel_requests ||--o{ travel_orders : imports - travel_requests ||--o{ card_authorizations : authorizes - travel_requests ||--o{ card_transactions : settles - travel_reconciliation_batches ||--o{ travel_reconciliation_items : includes - travel_orders ||--o{ travel_reconciliation_items : snapshots - card_transactions ||--o{ travel_reconciliation_items : snapshots - - workflow_definitions ||--o{ workflow_instances : defines - reimbursement_forms ||--o{ workflow_instances : starts - leave_requests ||--o{ workflow_instances : starts - purchase_requests ||--o{ workflow_instances : starts - travel_requests ||--o{ workflow_instances : starts - workflow_instances ||--o{ workflow_tasks : has - workflow_instances ||--o{ workflow_history : logs -``` - -> 说明: -> - 上图为概念关系图,具体字段与索引以各章节表结构和 `server/prisma/schema.prisma` 为准。 -> - `IntegrationOutbox`、`IntegrationBinding`、`IntegrationSyncState`、`NotificationChannel` 构成集成层私有模型,不回写核心事务表。 - ---- - -## 16. 迭代计划 - -> 状态口径说明(2026-03-13): -> - 以 `docs/feature-matrix.md`、`docs/todo-reconciliation.md` 与各已完成执行合同为实现状态事实源。 -> - 本节用于规格分期表达;具体执行状态仍以 Issue CSV 和实现状态矩阵为准。 -> - 本轮规格书正文新增覆盖请假 / 采购 / 资产 / 差旅 / 集成五大业务域;AI / MCP / PWA / 生产化治理继续以边界与运维文档承载。 - -### Phase 1(核心 / P0) -- [x] 技术栈确定(Fastify + Prisma + PostgreSQL) -- [x] 员工与组织(含归档约束、角色集合) -- [x] 权限码体系与服务端校验(RBAC) -- [x] 工作流引擎(4 表)落库与服务层事务 -- [x] `approval_rules`(预置 3 条)+ 审批链生效 -- [x] 转审(reason≥10,链路≤3) -- [x] `document_sequences` 单号规则(BX-YYYYMMDD-0001) -- [x] 费用类型 `expense_types`(默认启用 37 项)+ 下拉动态加载 -- [x] 发票池上传(本地存储 + 签名 URL)+ SHA256 + 腾讯云 OCR(结果持久化 / 去重 / 重试;不阻塞提交)+ 双策略判重(bind / submit 生效)+ 例外审计 -- [x] 报销单 CRUD + 提交 / 撤回 / 审批 / 付款标记 -- [x] 站内通知 + 邮件 - -### Phase 2(基础增强 / P3-P14) -- [x] 审计回归、OCR mock 回归、全量回归入口与最小 CI 门禁 -- [x] 验真、预算、看板、对象存储、通知渠道、集成框架等基础能力 -- [x] 权限解耦、主数据(法体 / 成本中心 / 供应商)、假期日历与路线治理收口 - -### Phase 3(请假 / P15B) -- [x] 请假类型、年度配额、余额账本与半天粒度模型 -- [x] 请假申请列表 / 详情 / 提交 / 撤回与 workflow 审批闭环 -- [x] 冲突日期阻断、余额强设闸、审批后 consume / release 台账语义 - -### Phase 4(采购 / P17A-P17B) -- [x] 采购申请主流程、明细、附件与员工端页面 -- [x] 采购预算 `reserve/confirm/release` 闭环 -- [x] PO / 合同归档、详情区运营入口与 ERP 轨道挂件 - -### Phase 5(资产 / P18A-P18B) -- [x] 资产分类、卡片、建卡与采购来源关联 -- [x] 资产领用 / 归还 / 调拨与盘点任务闭环 -- [x] 资产 ERP 摘要桥、详情轨道与只读同步状态 - -### Phase 6(集成 / P19 + IM-1 + ERP-1/2/3) -- [x] 集成边界、领域事件、MCP 只读基线与路线治理冻结 -- [x] IM-1 通知渠道、Outbox 派发与业务事件接入 -- [x] ERP-1 报销凭证切片、ERP-2 采购 commitment / archive、ERP-3 资产摘要桥 - -### Phase 7(差旅 / P20TRAVEL1-3) -- [x] `TRAVEL-1`:差旅申请、行程段、订单入站与预算语义 -- [x] `TRAVEL-2`:企业卡授权 / 交易导入、订单 `mark-paid`、发票绑定与支付轨道 -- [x] `TRAVEL-3`:对账批次快照、异常确认、Finalize 与 `erp.travel.export` 摘要桥 - -### Phase 8(移动与生产化 / P21-P22) -- [x] 浏览器 PWA 资产扫码、盘点录入、移动审批 deep link 与离线草稿 -- [x] 路由懒加载、E2E 补齐、benchmark 基线与生产化治理收口 -- [ ] 后置 backlog:`IM-2/IM-3/IM-4`、`P22+VEH-1`、`P22+ASSET-FIN-1` - -## 附录 A:系统默认费用类型(P0:37 项启用) -> 来源:`shenzhou-business-suite/supabase/seed.sql`(启用 35 项)+ hyb-oa 补齐启用 `utilities/rent`(2 项)。 - -| code | name | category | sort_order | account_code_default | description | -| :--- | :--- | :--- | ---: | :--- | :--- | -| `transport_long` | 长途交通费 | `travel` | 101 | `5102-01` | 飞机、火车、长途大巴等城市间交通费用 | -| `accommodation` | 住宿费 | `travel` | 102 | `5102-01` | 出差期间酒店、宾馆住宿费 | -| `meal_allowance` | 伙食补贴 | `travel` | 103 | `5102-01` | 出差期间伙食补助 | -| `transport_city` | 市内交通费 | `travel` | 104 | `5102-01` | 出差地或日常公事打车、公交费 | -| `rd_material` | 研发直接材料 | `rd` | 201 | `5102-07` | 研发专用的元器件、耗材、PCB打样等 | -| `rd_test` | 研发测试试验费 | `rd` | 202 | `5102-07` | 第三方检测、环境试验费 | -| `rd_service` | 研发技术服务费 | `rd` | 203 | `5102-07` | 外包开发、技术咨询服务 | -| `rd_sample` | 研发样品/样机费 | `rd` | 204 | `5102-07` | 原型机、模型制作费用 | -| `hydro_calibration` | 水声设备标定费 | `rd` | 205 | `5102-07` | 水听器、换能器等核心设备标定 | -| `test_site_rental` | 试验场地租赁费 | `rd` | 206 | `5102-07` | 水池、消声室、湖试场地租赁 | -| `rd_software` | 研发软件订阅 | `rd` | 207 | `5102-07` | 水声仿真、CAD设计软件(≤5000元) | -| `rd_patent` | 专利申请/维护费 | `rd` | 208 | `5102-07` | 发明专利代理费、年费 | -| `raw_material` | 原材料 | `production` | 301 | `1403` | 生产用主材、辅材 | -| `prod_direct_material` | 生产成本-直接材料 | `production` | 302 | `5001-01` | 生产过程直接消耗的材料 | -| `mfg_consumable` | 制造费用-物料消耗 | `production` | 303 | `5101-02` | 车间消耗性物料 | -| `outsourcing` | 外协加工费 | `production` | 304 | `5001-03` | 外壳加工、组件代工等 | -| `mfg_repair` | 生产设备维修费 | `production` | 305 | `5101-03` | 车间仪器设备维修保养 | -| `office` | 办公费 | `office` | 401 | `5102-02` | 文具、纸张、打印耗材等 | -| `communication` | 通讯费 | `office` | 402 | `5102-03` | 话费、宽带、网络服务费 | -| `property` | 物业费 | `office` | 403 | `5102-15` | 物业管理、停车费 | -| `vehicle` | 车辆费 | `office` | 404 | `5102-10` | 油费、过路费、保养(私车公用需附协议) | -| `welfare` | 福利费 | `office` | 405 | `5102-19` | 下午茶、团建、礼品(不超工资14%) | -| `maintenance` | 维修费 | `office` | 406 | `5102-09` | 办公电脑、空调、设施维修 | -| `training` | 培训费 | `office` | 407 | `5102-05` | 员工外部培训、购书 | -| `software` | 软件服务费 | `office` | 408 | `5102-02` | Office/Adobe/SaaS订阅(非研发) | -| `express` | 快递物流费 | `office` | 409 | `5102-02` | 样品寄送、文件快递 | -| `utilities` | 水电费 | `office` | 410 | `5102-14` | 办公场地水电费、能耗费 | -| `rent` | 房租租赁费 | `office` | 411 | `5102-06` | 办公室/设备租赁费 | -| `entertainment` | 业务招待费 | `business` | 501 | `5102-04` | 客户宴请、礼品(注意限额) | -| `meeting` | 会议费 | `business` | 502 | `5102-13` | 会议场地租赁、茶歇 | -| `marketing` | 市场推广费 | `business` | 503 | `5101-01` | 展会、画册制作、广告 | -| `consulting` | 咨询服务费 | `business` | 504 | `5102-08` | 法律顾问、咨询费 | -| `consulting_audit` | 代账/审计费 | `business` | 505 | `5102-08` | 记账报税、年度审计费 | -| `fixed_assets` | 固定资产 | `assets` | 601 | `1601` | 单价≥2000元 + 使用年限≥1年 | -| `low_value_consumables` | 低值易耗品 | `assets` | 602 | `1411` | 工具、小电器(<2000) | -| `bank_fees` | 银行手续费 | `other` | 701 | `5103-01` | 转账手续费、账户维护费 | -| `other` | 其他合规费用 | `other` | 901 | `5102-99` | 无法归类的小额费用 | - -## 附录 B:Legacy 费用类型(禁用,迁移预留) -| code | name | enabled | note | -| :--- | :--- | :---: | :--- | -| `meal` | 旧-餐费 | ❌ | 兼容历史/迁移预留,P0 不在下拉显示 | -| `rd` | 旧-研发费 | ❌ | 兼容历史/迁移预留,P0 不在下拉显示 | -| `transport` | 旧-交通费 | ❌ | 兼容历史/迁移预留,P0 不在下拉显示 | - -## 附录 C:P0 验收用例(最小集) -> 每条用例需覆盖:输入 → 状态流转 → 权限结果 → 日志记录点(workflow_history / audit_logs)。 - -1) **审批规则匹配(3 档)** - - 输入:`total_amount=100/1000/6000` - - 预期:分别命中“小额/中额/大额”规则,生成对应审批链(manager→finance / manager→finance_mgr / manager→ceo→finance_mgr) - -2) **工作流实例与任务创建** - - 输入:员工提交草稿 - - 预期:创建 `workflow_instances`(pending)+ 首个 `workflow_tasks`(pending)+ `workflow_history`(start);报销状态进入 `reviewing` - -3) **任务推进(approve)** - - 输入:审批人执行 approve - - 预期:当前 task completed;创建下一 task(若存在);或实例 approved 并将报销状态置为 `approved` - -4) **驳回(reject) vs 退回(return)** - - 输入:审批人执行 reject/return(带 comment) - - 预期(reject):实例 rejected;报销状态 `rejected`;history 记录 reject;申请人可编辑后再次 submit - - 预期(return):实例 terminated;报销状态回到 `draft`;所有 pending task cancelled;history 记录 return;申请人修改后再次 submit - -5) **撤回(withdraw)** - - 输入:申请人在审批人未处理前撤回 - - 预期:实例 cancelled;所有 pending task cancelled;报销状态回到 `draft`;history 记录 withdraw - -6) **转审(transfer)约束与证据** - - 输入:当前审批人转审(reason 少于/不少于 10 字) - - 预期:不足 10 字返回 `VALID_002`;满足时: - - `workflow_tasks.assignee_id` 更新 - - 插入 `approval_transfers` - - 插入 `workflow_history`(action=transfer,payload 含 from/to/reason) - -7) **状态 vs 节点一致性** - - 输入:报销单处于 `reviewing` - - 预期:页面“当前节点文案”来自 `workflow_tasks` 的 pending 任务(而非 DB status 细分) - -8) **费用类型启用/禁用语义** - - 输入:禁用某费用类型 - - 预期:新建报销下拉不再显示;历史报销仍可展示该类型名称(不回退为 code) - -9) **发票池入池 + OCR 结果持久化 + 去重复用(`ocr_results`)** - - 输入:上传同一文件两次(同 `file_sha256`),OCR 类型相同 - - 预期: - - 第二次上传命中 `ocr_results`(不重复计费调用 OCR) - - `invoice_pool` 记录复用 `ocr_result_id` 并写入关键字段摘要(invoice_code/number/date/amount 等) - - 日志:写 `INVOICE_POOL_UPLOADED`;如触发 OCR,写 `OCR_REQUESTED/OCR_SUCCEEDED`(禁止落库 rawResponse) - -10) **OCR 失败与重试(次数/频控)** - - 输入:制造 OCR 失败 → 多次调用 `POST /api/invoice-pool/:id/retry-ocr` - - 预期: - - `ocr_attempts` 递增;失败条目保持 `status=ocr_failed` - - 超过 3 次返回 `VALID_006`(429) - - 60 秒内重复重试返回 `VALID_007`(429) - - 日志:写 `OCR_FAILED/OCR_RETRIED`(错误摘要即可) - -11) **发票池绑定并发冲突(行锁 + 409)** - - 输入:两个报销草稿同时 bind 同一张发票池条目 - - 预期:一个成功,一个返回 `VALID_005`(409);服务端在 bind 事务内对 `invoice_pool` 行 `SELECT ... FOR UPDATE` - -12) **全局判重与例外(绑定/提交阶段硬校验)** - - 输入:已有“使用完成并归档”的发票(`status=archived, archived_reason=used_complete`),再尝试用相同 `file_sha256` 或相同 `invoice_code+invoice_number` 绑定/提交 - - 预期: - - 普通用户返回 `VALID_003`(409) - - `finance_mgr` 可执行判重例外,必填原单号+原因(≥10);若原单处于 `approved/paid` 则返回 `VALID_004` - - 日志:例外必须写 `audit_logs`(`INVOICE_DUPLICATE_OVERRIDE`) - -13) **90 天游离自动归档不参与全局判重** - - 输入:一张发票池条目 90 天未绑定自动归档(`archived_reason=unused_timeout`),再上传/绑定同一张文件 - - 预期:不因“未使用的超期归档”触发全局判重拦截;仅当新条目被使用并进入 `bound/used_complete` 后才进入判重历史 - -14) **签名 URL 鉴权与过期** - - 输入:非票据相关人员请求 `POST /api/invoice-pool/:id/signed-url`;或使用过期 URL - - 预期:无权获取(403/`PERM_001`);过期访问失败;必要时写入 `audit_logs` - -15) **行政代上传授权链(owner ≠ uploaded_by)** - - 输入:行政为员工 A 上传票据(`owner_employee_id=A, uploaded_by=行政`) - - 预期: - - 有有效 `delegation_grants`(principal=A, delegate=行政, scope 含 `reimbursement_submit`)时允许上传 - - 无授权时拒绝(403/`PERM_001`) - - 补充:hr/admin(持 `system.users`)可在无 delegation 的情况下代维护上传;所有代维护必须写入 `audit_logs` - -16) **敏感字段脱敏(masking)** - - 输入:读取/变更员工敏感字段(银行卡/身份证)并查看相关响应与审计 - - 预期:对外响应仅返回脱敏格式;`audit_logs.extra.changes` 仅包含脱敏前后快照(禁止明文) - -17) **代提交审批链解析(owner 基准)** - - 输入:行政为员工 A 代提交报销单(submitter=行政,owner=A) - - 预期:首节点审批人按 **A 的直属经理** 解析(而非行政的直属经理);`workflow_instances.context_data` 包含 `owner_id/department_id` - -18) **`*_own` 语义(owner 或 submitter)** - - 输入:行政查看/协助处理其代提交的单据;owner 编辑草稿/驳回单 - - 预期:当 `owner_id=me OR submitter_id=me` 时允许 `view_own`;`draft/rejected` 允许 `edit_own`;仅 `draft` 允许 `delete_own`;`withdraw` 仍需满足“审批人未处理前”等状态约束 +## 14. 文档维护规则 + +- 本文件回答“要做什么、为什么做、先做什么、不做什么、如何验收”。 +- `docs/product-decisions.md` 记录已经拍板的产品口径。 +- `docs/feature-matrix.md` 记录已经实现了什么和证据在哪里。 +- `issues/*.csv` 是每轮开发执行合同。 +- 需求变化必须先更新本文件或对应规则文档,再创建新的 Issue CSV。 +- 不批量回改旧 Issue CSV 来伪造一致性;历史计划保留为历史快照。 diff --git a/issues/2026-05-09_15-39-29-mvp-requirements-baseline.csv b/issues/2026-05-09_15-39-29-mvp-requirements-baseline.csv new file mode 100644 index 00000000..54562d5a --- /dev/null +++ b/issues/2026-05-09_15-39-29-mvp-requirements-baseline.csv @@ -0,0 +1,6 @@ +ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes +A1,需求事实源盘点与旧文档定位,"盘点现有需求、产品口径、功能矩阵和管理员/员工文档,明确哪些是当前 PRD,哪些是历史证据或实现状态。","1) docs/requirements-specification.md 明确自己是当前 PRD 主事实源;2) docs/feature-matrix.md 被定位为实现状态矩阵,不再作为 PRD 扩写;3) docs/product-decisions.md 继续作为关键产品口径来源;4) 不回改历史 Issue CSV。","人工步骤:阅读 docs/requirements-specification.md 的文档定位章节,确认能区分当前 PRD、产品决策、实现矩阵和执行合同。",manual,DONE,DONE,DONE,docs/requirements-specification.md,"[]","仅整理事实源,不修改业务代码。" +A2,重写 MVP 需求总纲 v2.0,"将 docs/requirements-specification.md 从历史 Phase 1 报销规格重写为面向 10 人以下科技公司的 MVP 需求总纲。","1) 文档版本更新到 v2.0;2) 包含项目目标、用户角色、MVP 模块、统一审批中心、企微边界、权限审计、非功能、验收和 backlog;3) 不继续堆历史 Phase 1 实施细节。","人工步骤:检查 docs/requirements-specification.md 是否能回答我们做什么、先做什么、不做什么、谁使用、如何验收。",manual,DONE,DONE,DONE,docs/requirements-specification.md,"[""A1""]","旧 v1.4 内容通过 git 历史保留;如需恢复可按路径回滚该文档。" +A3,固化 MVP 模块优先级,"在需求总纲中明确首批 MVP 模块优先级,指导后续 Issue CSV 拆分和开发排序。","1) 统一审批中心为 P0;2) 报销/发票/OCR、请假/补卡/外出/加班、采购/费用、合同/附件归档、企业微信入口通知均有优先级;3) 每个模块写清角色、入口、主流程、数据归档、权限边界、测试方式。","人工步骤:逐项检查 MVP 模块表与模块章节,确认每个模块都有优先级和验收口径。",manual,DONE,DONE,DONE,docs/requirements-specification.md,"[""A2""]","模块优先级用于下一轮执行合同拆分,不代表本轮开发新功能。" +A4,明确暂不做范围,"明确飞书、企业微信原生审批、复杂 BPM 设计器、重型项目管理和付费低代码依赖均不进入首批 MVP。","1) 文档明确飞书方案废弃;2) 企业微信原生审批不作为正式审批入口;3) 不依赖付费低代码或付费第三方应用;4) 复杂 BPM、重型项目管理、CRM、完整 ERP 不进入 MVP。","人工步骤:搜索 docs/requirements-specification.md 中的暂不做范围,确认不会误导后续开发接入飞书或企业微信审批。",manual,DONE,DONE,DONE,docs/requirements-specification.md,"[""A2""]","如后续业务方向变化,必须新建 Issue CSV 重新决策。" +A5,补齐需求验收与后续开发流程,"把软件开发项目流程、需求到 Issue CSV 的转化规则和 MVP 验收口径写入需求总纲。","1) 文档包含需求事实源 -> Issue CSV -> 方案 -> 开发 -> 测试 -> 部署验收 -> 复盘流程;2) MVP 验收覆盖 PC、移动、企业微信入口、审批、报销、请假、采购和文档检查;3) 本轮只做文档验证,不运行后端/前端构建。","命令:python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-09_15-39-29-mvp-requirements-baseline.csv && git diff --check;人工步骤:确认需求总纲可作为后续开发指挥文档。",python3|git,DONE,DONE,DONE,issues/2026-05-09_15-39-29-mvp-requirements-baseline.csv|docs/requirements-specification.md,"[""A1"",""A2"",""A3"",""A4""]","本轮不部署;完成后提交中文 commit 并引用 A1-A5。"