# JLN_Sys **Repository Path**: ichang1636/jln_-sys ## Basic Information - **Project Name**: JLN_Sys - **Description**: 集团模拟公开测试系统 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: stable - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-07-17 - **Last Updated**: 2026-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 钜力能集团智能管理系统 钜力能集团内部企业管理平台。本次生产版本以交互原型为唯一启用范围:提供按月配置、历史周只读的 OKR 配置、员工本人填报、逐级审批、超级管理员治理和 OKR 数据看板,并保留员工管理、 审批链配置、系统设置、审计日志及工分兑换闭环。业务口径定义“账户余额即 OKR 工分”:员工 提交后,系统冻结本轮计算结果和部门审批链;部门负责人或管理员按步骤审批,最后一步通过时 确认工分入账。每份 OKR 的每个审批轮次只允许一笔幂等入账流水,工分保留两位小数,兑换只 使用账户余额的整数部分。旧 KPI、评价、依赖旧 KPI 的工分发放、任务工分、 统一待办、入职、消息签署和旧数据看板代码仅作为回滚兼容保留,生产界面永久隐藏且 API 失败关闭,不属于本次上线能力。 > 文档随代码同步,最后更新:2026-11-03。历史变更见 [CHANGELOG.md](./CHANGELOG.md)。 ## 本次生产启用范围 - `OKR配置与考核`:配置管理、超级管理、仅统计已审批通过结果的 OKR 数据看板、我的填报。 - `工分兑换`:本人余额、当前周兑换比例、兑换申请与记录;管理员/超级管理员配置比例并审批。 - `员工管理`、`审批流程`(审批链配置、OKR 待审批与审批历史)、`系统设置`、`审计日志`。 - 数据可视化支持员工总分排名和最多 4 人对比;管理员可新增部门/岗位;管理员和部门负责人可在员工管理页创建员工账号并绑定员工档案。 - 生产环境必须显式配置 `KPI_PROTOTYPE_ONLY_MODE=true`。即使访问旧书签或直接调用旧 API,也会返回 `prototype_scope_module_disabled`,不会执行旧业务读写。 - 超级管理员的“模块显示控制”只列出上述当前模块;`OKR配置与考核` 固定开启。旧 KPI、 入职、待办、消息签署及旧看板等退休模块即使历史配置保存为 `true` 也会被服务端强制为 `false`,不能通过旧配置重新显示。生产发布不得关闭 OKR-only 边界。 - 数据库备份接口继续作为恢复能力保留;旧 KPI 归档、统计和全局配置接口不开放。 工分范围采用精确白名单:开放 `GET /api/points/my-balance`、`GET/POST /api/points/rate`、 `POST /api/points/redeem`、`GET /api/points/redemptions`,以及管理员/超级管理员对兑换记录的 通过/驳回接口。依赖旧 KPI 的 `POST /api/points/distribute`、公司级余额管理 `GET /api/points/balances` 和已撤销的任务工分能力继续关闭。兑换页只消费工分账户中已经存在的 余额,申请只接受正整数;账户可保留两位小数,但可兑换上限取可用余额的整数部分。OKR 最终 审批通过后由不可重复的账本 credit 入账。已入账 OKR 重新打开或作废前必须先写冲正流水;若 待审批与已通过兑换的占用使冲正后余额不足,操作会被拒绝。作废采用逻辑删除,保留 O/KR、 审批快照和账本证据。旧 KPI `PointsBalance` 不计入新的 OKR 工分账户。 --- ## 部署地址 | 地址 | 说明 | |------|------| | `https:///` | 生产前端与 `/api/` 公网入口(由受审查的 Nginx 配置提供 TLS) | | `http://127.0.0.1:8008/docs` | 仅本地开发使用的 FastAPI 文档 | | `https:///docs` | 生产 Nginx 入口固定返回 404;`/redoc`、`/openapi.json` 同样不公开 | --- ## 技术栈 | 层 | 技术 | |---|---| | 后端 | Python 3.12 + FastAPI + SQLAlchemy;本地 SQLite,横向扩容 PostgreSQL 17 | | 前端 | 纯 HTML/JS(`frontend/index.html` + 模块化 `js/core/` `js/pages/`) | | 认证 | PyJWT + pwdlib(Argon2id;旧 bcrypt 登录时渐进升级) | | 报表 | openpyxl (xlsx) / reportlab (pdf) | | 手册构建 | python-docx + LibreOffice(仅开发/验收环境) | | 迁移 | Alembic 分方言基线(PG 冻结基线 `e7f8a9b0c1d2`)、统一目标 revision(当前 head `c2d3e4f5a6b7`) | | 测试 | pytest(含生产安全、权限、迁移、架构、业务流程与前端回归) | --- ## 快速开始 Windows PowerShell: ```powershell # 首次运行:用明确的 Python 3.12 路径创建项目虚拟环境 # 不使用系统 PATH 猜测解释器,避免与同机其他项目互相干扰 & 'C:\明确路径\python.exe' -m venv venv .\venv\Scripts\python.exe -m pip install --require-hashes -r requirements-bootstrap.txt .\venv\Scripts\python.exe -m pip install --require-hashes -r requirements.txt -r requirements-dev.txt # 只读诊断:不会启动 Docker、停止进程或显示密钥 .\scripts\doctor.ps1 -Mode Local # 使用项目专属数据、环境、日志和进程状态启动 .\scripts\dev.ps1 -Action Start .\scripts\dev.ps1 -Action Status .\scripts\dev.ps1 -Action Stop ``` Windows 隔离启动默认只绑定 `127.0.0.1:8008/3000`,数据库、附件、签署证据、开发密钥、 日志和 PID 状态全部位于 Git 忽略的 `.kpi-runtime/`。端口被占用时脚本直接失败,不会结束 占用端口的其他系统;停止时会核对 PID、启动时间、解释器路径和命令标识。详细说明见 [本地隔离运行说明](./doc/本地隔离运行说明.md)。 Linux/macOS: ```bash python3 -m venv venv ./venv/bin/python -m pip install --require-hashes -r requirements-bootstrap.txt ./venv/bin/python -m pip install --require-hashes -r requirements.txt -r requirements-dev.txt # 终端 1 ./venv/bin/python -m uvicorn src.main:app --host 127.0.0.1 --port 8008 # 终端 2 ./venv/bin/python -m http.server 3000 --directory frontend ``` 浏览器访问 `http://127.0.0.1:3000`;本地 API 文档位于 `http://127.0.0.1:8008/docs`。不要直接双击 `frontend/index.html`,否则浏览器的来源和 API 地址推导会与正常开发环境不同。 需要验证多 worker/多实例时,使用 PostgreSQL 编排而不是放大 SQLite worker 数: ```bash docker compose -f docker-compose.multi.yml up -d --build --scale backend=2 ``` 该拓扑先运行一次迁移任务,再启动每实例两个 Uvicorn worker、独立 outbox worker 和 API 负载入口。完整边界、环境变量和多主机共享存储要求见 [多实例运行说明](./doc/architecture/multi-instance.md)。 六份正式 Word 手册由 Markdown 单一来源生成。开发环境安装 `requirements.txt`、`requirements-dev.txt` 和 LibreOffice 后,先生成到隔离目录并逐页验收,确认后才允许发布: ```powershell venv\Scripts\python.exe scripts\generate_docx.py --check venv\Scripts\python.exe scripts\generate_docx.py --output-dir .pytest_tmp\docx-staging venv\Scripts\python.exe scripts\render_docx_windows.py .pytest_tmp\docx-staging\系统整体手册.docx --output_dir .pytest_tmp\docx-render venv\Scripts\python.exe scripts\generate_docx.py --publish ``` 生产服务器运行系统不依赖 LibreOffice;它仅用于构建和视觉验收手册。 生产发布不得直接拉取 `dev` 或使用 `pkill + nohup` 启动。请按 [开发与发布流程](./doc/开发与发布流程.md) 将已验收代码合入 `stable`、创建不可变版本标签, 再按 [云服务器部署说明](./doc/云服务器部署说明.md) 使用 systemd 部署指定标签。正式切换前 还必须用 `scripts/preflight_release.py --database <数据库备份副本>` 完成只读数据门禁; 不得对运行中的正式库执行测试套件。 单服务器 PostgreSQL 多实例的每日备份、隔离恢复验证、保留策略和 systemd 调度见 [自动备份与恢复验证](./doc/architecture/backup-and-restore.md)。只有带 `RESTORE_VERIFIED` 的同批数据库与附件恢复点可用于灾难恢复。 --- ## 初始账号 | 账号 | 密码 | 角色 | 公司 | |---|---|---|---| | admin | [首次部署后随机生成] | 超级管理员 | 鹿仕达 | | 其余初始化演示账号 | [首次部署后随机生成] | 部门负责人/员工 | 鹿仕达 | > ⚠️ **安全提醒**:生产初始化会为所有账号生成一次性随机密码,并写入仅本机可读、已被 > Git 忽略的 `data/initial-passwords.txt`。管理员应通过受控渠道交付,要求首次登录立即修改, > 确认交付后删除该文件。切勿在文档或版本控制中记录真实密码。 > > 全新初始化会为每个部门建立“超级管理员单级审批”的安全兜底链,避免无链自动放行或系统 > 无法提交。正式启用业务前必须按组织授权关系替换这些兜底链并完成一次提交/驳回演练。 --- ## 本次生产可用能力与保留实现 ### 多公司隔离(核心链路已实现) - JWT 由 `src/core/security.py` 在每次请求重新校验所选公司;业务代码通过 `src/core/company_scope.py:selected_company_id()` 取得当前公司上下文 - 核心业务表和 Task、工分、互评、入职等数据均带公司范围,ID 查询必须同时校验公司归属 - KPI 导出会同时校验考核、员工和当前公司;通知记录已带必填 `company_id`,列表、已读和 动作链接均按当前公司过滤 - 前端传入的 `company_id` 不是授权事实;跨公司读取或写入属于阻断级缺陷 ### 员工批量导入 - 管理员和超级管理员可在员工页先点“下载模板”,再按“填写员工导入 → 上传预检 → 核对工号/ 警告 → 确认整批提交”完成导入;部门负责人和员工无模板下载、预检或导入权限 - `GET /api/employees/import/template` 动态生成数据为空的 XLSX,不依赖运行环境中的 `data/` 目录;文件不包含公司、员工、登录账号或密码数据 - 仓库 [`data/员工导入模板.xlsx`](./data/员工导入模板.xlsx) 是正式合同/参考制品;自动化会比较 下载版与制品的工作表、表头、状态下拉和关键说明,防止两套模板漂移 - 管理员和超级管理员可上传 `.xlsx` 或 UTF-8/UTF-8 BOM `.csv`,先调用服务端预检,再确认整批写入 - 预检和正式导入共用同一套公司、部门、岗位、日期、状态、工号和手机号校验;正式导入会再次校验 - 任一行失败时整批零写入;空工号由服务端生成并在确认前展示 - 模板说明明确禁止公式、单文件上限 5 MiB、每批最多 1000 条,以及手机号在当前公司内唯一; 模板不创建账号,也不采集账号或密码 ### OKR 考核配置 Excel 导入 - 超级管理员及同时获得 OKR 配置、发布权限的管理员或部门负责人可在配置页上传 `.xlsx` 考核表;必需表头为 `目标(O)`、`关键成果(KR)`、`KR目标值`、`单位`、`KR权重`,可选 `序号` 和 `O大类占比` - 服务端在每个工作表前 20 行查找表头,并要求整份工作簿恰好只有一个可导入配置表;页面会显示 实际选中的工作表和表内标题。支持 O 合并单元格、百分比单元格、字面量百分号格式及 General 小数权重,拒绝公式、含糊的小数逗号、无效权重与超限文件;未提供 O 大类占比时按目标数平均 分配至 100% 并显示复核警告 - 导入只生成并替换当前页面的 O/KR 草稿,不选择员工或期间,不改变总工分和超额策略,也不 直接写数据库;配置人员核对后仍须点击“保存并发布配置”,并继续接受发布权限、状态锁和 清空已有实际值二次确认等原有门禁。发布成功后页面显示明确弹窗;弹窗显示异常不会改变服务端 已成功保存的结果 ### 统一待办(代码保留,本次生产关闭) - `tasks` 表作为跨模块待办索引层 - `business_key` 幂等键保证不重复 - 已接入 KPI 审批、文件签署和入职步骤;Task 与业务事实同事务更新 - `frontend/js/pages/page-tasks.js` 提供稳定的上一页/下一页分页、筛选、刷新、角标和业务跳转; 切换筛选或页面时会取消/忽略过期请求,不用旧响应覆盖当前列表 - 自评/互评当前仍使用独立 `ReviewTask`,尚未并入统一 `tasks`;`training` 没有业务模型、 任务生产流程或待办筛选入口 ### 前端 Page Contract - `frontend/js/core/page-contract.js` + `api-client.js` - 生产导航注册 `okr`、`approval`、`redemption`、`employees`、`settings`、`audit`; `tasks` 与 `onboarding` 仅保留为受产品开关禁用的回滚兼容代码 - 导航统一走 `App.navigateTo`;兑换动作使用根事件委托且不再暴露 window 全局桥 - 注册时只强制校验 `init/onActivate/onDeactivate/render` 四个方法;8 条行为规则还依赖测试和评审 - 页面 root 与共享 Shell 边界由静态和 Node 行为测试守护 ### 入职模块(代码保留,本次生产关闭) - `src/modules/onboarding/` — 首个新架构模块 - 模板/案例/步骤 CRUD + 多角色任务编排 - 入职统计看板、材料私有上传、哈希完整性校验 - 复用电子手写确认,确认后自动完成步骤并结案 - 兼容代码中保留管理员/超级管理员入职入口,但生产菜单和 API 均永久关闭 - 普通经办人从「我的待办」进入最小处理视图,按 Task 的 `step_id` 精确定位案例;该视图不加载 管理员统计、模板、员工或文件集合,只显示并允许操作分配给本人的步骤 - 退休入职模块不属于当前产品验收;本段仅用于回滚和历史数据维护说明 ### 生产安全修复 - 驳回后的 KPI 附件仍按本次考核冻结的指标快照校验,不读取后来修改的当前配置 - `/api/admin/config` 是只读兼容端点;`PUT` 返回 `410 Gone` 且不保存,实际策略分别在 OKR 超级管理和「工分兑换」维护;退休的「评价中心」不属于当前生产配置入口 - 注册拒绝仅在确认账号无员工档案和任何业务引用时硬删除并释放用户名;已有历史的账号改为 停用并以“保留历史”状态展示,不会刷新后重新出现在待审批列表 - 升级前 inactive 且无员工绑定的来源不明账号显示“历史状态待核验”,通用启用、重置、审批、 删除和员工绑定均失败关闭;当前尚需新增专门的核验/恢复流程 - 所有角色降级、账号停用、超管转让和员工离职联动共用串行事务保护;提交时按 `role=super_admin AND is_active=true` 校验至少保留一名启用的超级管理员 - 在当前支持的 SQLite 单应用进程拓扑内,公司启停使用并发保护,不能停用最后一家活跃公司 - 本人改密与超级管理员重置密码共用账户状态锁;密码、角色、公司、启停变化会自动 递增 token version,使旧 JWT 失效;初始/重置密码只能先改密 - 审批链所有配置入口按部门串行,步骤从 1 连续且步骤/审批人不可重复;并发唯一冲突返回 `409`, 锁等待期间操作者或审批人被降级、停用、切出公司时写入失败关闭 - 工分管理锁后重新鉴权;兑换审批通过条件更新只允许一个请求成功取得处理权,凭证使用 `VCH-` 加 128-bit 不可预测令牌、碰撞重试和数据库唯一约束 - 入职案例/模板在锁内刷新;每名员工至多一个进行中案例,离职员工和归档模板不能新建,同公司 模板名冲突返回 `409`,员工步骤只分派给同公司启用的关联账号 - 仓库容器镜像以固定 UID/GID 的非 root 用户运行;Compose 验证拓扑只绑定宿主机 `127.0.0.1:3000/8008`,并启用只读根文件系统、能力删除和 `no-new-privileges`。旧 root 卷只能按[部署与局域网共享说明](./doc/部署与局域网共享说明.md)中的一次性迁移流程处理 ### 模块加载与安全依赖 - `src/module_loader.py` 会按名称排序自动发现 `src/modules/` 下不以 `_` 开头的模块并调用 `register(app, event_bus)`;模块缺少注册函数时启动失败 - 认证与角色依赖统一从 `src/core/security.py` 导入,所选公司统一从 `src/core/company_scope.py` 取得;业务模块不得从 `src/api/auth.py` 导入安全依赖 ### Alembic 迁移体系 - 当前唯一 head 为 `c5d6e7f8a9b0`;新库、旧库和内存库均只走 `upgrade head` - 冻结 baseline 可从零创建完整库,旧自研 runner 与 `create_all + stamp` 启动兜底已删除 - SQLite batch mode 兼容 - `a7b8c9d0e1f2` 在 DDL 前预检审批链重复步骤/审批人、不连续步骤及孤儿/跨租户关系;把 `approval_chains.company_id` 设为非空,并分别唯一约束“公司+部门+步骤”和“公司+部门+审批人” - 同一迁移预检重复非空 `voucher_code` 后增加唯一约束,并把 `users`、`departments`、 `positions`、`employees`、`approval_chains`、`approval_records`、`review_tasks`、 `kpi_attachments`、`onboarding_attachments` 九表改为 SQLite `AUTOINCREMENT`,防止历史 ID 复用 ### 并发正确性边界 - 账户、考核、审批链、入职和部分工分写入使用进程内串行锁;等待前结束依赖阶段旧快照, 锁后重载操作者、所选公司和目标对象。批量锁带按固定顺序获取,不在持有线程锁时执行 `await` - 考核、审批链和入职采用固定 64 条锁带;不同业务键碰到同一锁带只会降低并行度,不影响正确性 - 这些保证依赖当前 SQLite + 单 Uvicorn worker 拓扑。启用多 worker、多实例或其他数据库前, 必须改用数据库 CAS/条件更新、行锁、唯一约束或分布式锁,并重新完成并发与恢复验证 ## 已知改进与新增方向 后续产品演进仍需决定自评/互评 `ReviewTask` 与统一 `tasks` 的合并或长期并存方案;`training` 在存在真实业务模型和任务生产器之前不宣称为已上线能力。跨模块新增联动必须使用事务 Outbox, 不得恢复进程内“发出即丢”的事件作为可靠交付。 中期新增方向包括试用期 30/60/90 天目标、转正审批、培训/考试和更细的入职材料权限。PostgreSQL、 多实例与外部平台/AI 集成均需独立迁移、权限、审计和回滚设计,不能通过简单替换连接串直接启用。 --- ## 架构文档 完整文档入口、单一事实源和维护规则见 [文档索引](./doc/README.md)。 | 文档 | 说明 | |------|------| | [ADR-001](./doc/architecture/ADR-001-alembic.md) | 数据库迁移统一采用 Alembic | | [ADR-002](./doc/architecture/ADR-002-task-model.md) | Task 作为统一待办索引层 | | [ADR-003](./doc/architecture/ADR-003-transaction-boundary.md) | 核心业务与 Task 同事务更新 | | [ADR-004](./doc/architecture/ADR-004-page-contract.md) | 前端 Page Contract 规范 | | [ADR-005](./doc/architecture/ADR-005-module-template.md) | 模块轻量结构 + 演进规则 | | [迁移指南](./doc/architecture/migrations.md) | Alembic 操作规范 | | [待办开发指南](./doc/architecture/task-development-guide.md) | Task 模型使用说明 | | [前端页面指南](./doc/architecture/frontend-page-guide.md) | Page Contract 开发模板 | | [模块开发指南](./doc/architecture/module-development-guide.md) | 后端模块脚手架 | | [部署检查清单](./doc/architecture/deploy-checklist.md) | 每次推送后必做检查 | | [2026-07-30 生产候选基线](./doc/architecture/BASELINE-2026-07-30-production.md) | 历史 No-Go 证据与当时阻断项,不代表当前状态 | | [生产权限矩阵](./doc/权限矩阵.md) | 五角色与公司/部门/本人数据边界 | | [基线 v0.3.0](./doc/architecture/BASELINE-v0.3.0.md) | 历史架构基线(不可作为当前生产验收结论) | --- ## 使用手册 | 文档 | 适用角色 | |------|---------| | [系统整体手册](./doc/系统整体手册.md) | 所有人(建议先读) | | [超级管理员](./doc/使用手册-超级管理员.md) | super_admin | | [管理员](./doc/使用手册-管理员.md) | admin | | [部门负责人](./doc/使用手册-部门负责人.md) | dept_head | | [员工](./doc/使用手册-员工.md) | employee | | [开发与发布](./doc/开发与发布流程.md) | 开发者 | | [云服务器部署](./doc/云服务器部署说明.md) | 运维 | --- ## API 概览(前缀 `/api`) | 状态 | 模块 | 端点 | |---|---|---| | 启用 | auth | `POST /login` `/register` `PUT /password` `GET /me` | | 启用 | okr | `/okr/eligible-employees`、`/okr/assessments*`、`/okr/mine`、`/okr/dashboard`、`/okr/control`、`/okr/templates*`、`/okr/export` | | 启用 | employees / organization | `/employees*`、`/users*`、`/departments*`、`/positions*`、`/companies*`(均按既有角色和公司范围鉴权) | | 启用 | approval chains | `/approvals/chains*`、`/approvals/approvers`;旧 KPI 审批动作不启用 | | 启用 | audit | `GET /audit` `/audit/export` | | 启用 | points redemption | `/points/my-balance`、`GET/POST /points/rate`、`POST /points/redeem`、`GET /points/redemptions`、管理员通过/驳回 | | 运维保留 | database backup | `POST /admin/db/backup` | | 关闭 | legacy | `/assessments*`、`/kpi*`、评价、旧审批动作、旧看板、旧 KPI 工分发放、任务工分、通知、签署、待办、入职及旧管理员统计/归档/配置接口 | 关闭接口在 `KPI_PROTOTYPE_ONLY_MODE=true` 时统一返回 `403` 与错误码 `prototype_scope_module_disabled`;不是通过前端隐藏来代替后端控制。