WCFB 复用工作台企微打卡同步密钥
这个提交包含在:
@@ -104,7 +104,7 @@ Final responses should include:
|
||||
- R6 scope defaults to reimbursement, leave, procurement, approval, Enterprise WeChat entry/notification, finance payment/export.
|
||||
- Do not expand to budget, ERP, AI, full travel, or complex BPM during R6; record those as follow-up Issues.
|
||||
- Enterprise WeChat personal app message is the formal approval notification channel after R5; mock and group bots are not acceptance evidence for R6 business notification.
|
||||
- After HRWD/HCLT, ERP is not a future OA integration target, Enterprise WeChat native approval is not the formal workflow, and OA-driven Enterprise WeChat directory create/update/disable is live verified. For later HR/WeCom directory issues, first check configuration, permission, department, and data-specific causes before treating the capability as unfinished.
|
||||
- After HRWD/HCLT, ERP is not a future OA integration target, Enterprise WeChat native approval is not the formal workflow, and OA-driven Enterprise WeChat directory create/update/disable is live verified. For later HR/WeCom directory issues, first check configuration, permission, department, and data-specific causes before treating the capability as unfinished. After WCFB, Enterprise WeChat check-in sync uses an authorized custom app Secret: `WECOM_CHECKIN_SECRET` is an explicit override for a dedicated check-in app, and when it is empty OA can reuse the authorized workbench app `WECOM_AGENT_SECRET`.
|
||||
- Approval-chain correctness can be proven by PC or automation, while Enterprise WeChat validates entry, binding,免登录, notification, deeplink, and mobile layout.
|
||||
- If real-device mobile validation is paused after automation passes, record the gate as `PAUSED`; do not close RS6/UI6/R6 mobile blockers until representative real-device confirmation is recorded.
|
||||
- Role-specific pages should use one shared OA/mobile framework and tailor summaries, entries, actions, and sensitive fields by role. Do not create unrelated UI systems for employee, approver, finance, admin, or CEO views.
|
||||
|
||||
@@ -67,6 +67,7 @@ WECOM_OAUTH_ENABLED=false
|
||||
WECOM_REDIRECT_URI=https://oa.example.com/api/auth/wecom/callback
|
||||
WECOM_ALLOWED_RETURN_HOSTS=oa.example.com
|
||||
WECOM_CHECKIN_SYNC_ENABLED=false
|
||||
# 企业微信打卡同步 Secret:显式配置优先;留空时复用已授权调用打卡 API 的 WECOM_AGENT_SECRET
|
||||
WECOM_CHECKIN_SECRET=
|
||||
WECOM_CONTACT_SYNC_ENABLED=false
|
||||
WECOM_CONTACT_SECRET=
|
||||
|
||||
+3
-1
@@ -34,11 +34,12 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开
|
||||
- 2026-05-17 SYSTEM-COMPLETE-GATE 已完成系统开发完成门禁:公网 `/ready`、public bundle、live smoke `3 passed`、PDF live OCR `1 passed`,只读 flag 核验显示 `payment_batch_v1=force_on`、`erp_sync=force_off`;公网移动/企业微信 UA 矩阵 `14 passed`;结论 `GO_TO_USE`。当前首批正式能力为员工报销、PDF OCR、审批、请假、采购、通知、移动/企业微信入口、财务台账、付款批次、银行回单附件和代账交接。
|
||||
- 2026-05-17 LAUNCH-OPS-V1 已固定首周正式使用运营口径:`wangshunli` 为上线拍板、管理员复核和回滚决策人,`wangzhiling` 为出纳/付款执行和代账资料准备人;首周问题登记、每日巡检、暂停条件、代账交接节奏和 UX8/可选功能冻结口径已回写现有手册。后续新增功能只按真实反馈和业务频次另建 Issue CSV。
|
||||
- 2026-05-17 REQ-COMPLETE-AUDIT 已完成需求完成度复核和考勤边界校正:首批正式能力保持 `GO_TO_USE` 结论不变;企业微信打卡作为原始采集和风控底座,OA 不重造定位、人脸、Wi-Fi、排班、设备和防作弊。
|
||||
- 2026-05-17 ATTV1 已完成考勤统一入口实现并部署到公网:历史 OA 自建打卡 `attendance_v1` 关闭;OA 新增企微打卡摘要同步、补卡/外出/加班修正审批和月度考勤解释;所有审批继续统一在 OA,企业微信只负责打卡原始采集和风控。公网复检显示旧打卡 API 返回 409、统一考勤月度汇总和修正列表可读、移动/企业微信矩阵 14 passed;企微 live 同步依赖 `WECOM_CHECKIN_SECRET` 和企微打卡 API 权限,缺配置时记 `SKIP`。
|
||||
- 2026-05-17 ATTV1 已完成考勤统一入口实现并部署到公网:历史 OA 自建打卡 `attendance_v1` 关闭;OA 新增企微打卡摘要同步、补卡/外出/加班修正审批和月度考勤解释;所有审批继续统一在 OA,企业微信只负责打卡原始采集和风控。公网复检显示旧打卡 API 返回 409、统一考勤月度汇总和修正列表可读、移动/企业微信矩阵 14 passed;企微 live 同步依赖已授权调用打卡 API 的自建应用 Secret 和企微权限,`WECOM_CHECKIN_SECRET` 可显式覆盖,未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`,缺配置时记 `SKIP`。
|
||||
- 2026-05-17 LAUNCH-AUTO-OPS-V1 已完成正式启用后自动化收口脚本和证据刷新:本地 Prisma/node check/focused 单测、前端 typecheck/build、公网 readiness、PDF live OCR、统一考勤 live 检查、公网移动矩阵和企业微信 UA/渲染矩阵均通过;显式企微打卡同步检查返回 `SKIP wecom_checkin_sync_disabled`;但只读 flag 检查发现当前公网 `payment_batch_v1=force_off`,与 FIN-PROD-ENABLE 记录的正式常开口径不一致,结论为 `PAUSE_FOR_FIX`。runner 已跳过付款批次写入,未切换 flag、未部署、不接真实支付、不接 ERP。
|
||||
- 2026-05-17 AUTO-REMAINING-CLOSEOUT 已完成剩余无需人工操作收口:受控恢复公网 `payment_batch_v1=force_on`,保持 `erp_sync=force_off`;复跑 `run-launch-auto-ops.sh` 通过,公网测试报销 `BX-20260517-0027` 完成审批、付款批次创建/复核、回单上传/下载、付款执行、AP 导出和审计核验;公网移动矩阵 6 passed、企业微信渲染矩阵 8 passed。显式企微打卡同步仍为 `SKIP wecom_checkin_sync_disabled`,属于外部后台配置缺口,不阻断 OA 统一考勤入口和正式运营门禁;结论 `GO_TO_OPERATE`。
|
||||
- 2026-05-17 HR-WECOM-DIRECTORY-V1 已完成并部署公网:产品口径已调整为 ERP 未来不接入 OA;人事生命周期和考勤同属 HR 大域但分模块实现。OA 继续作为员工、权限、审批和审计主事实源;企业微信作为通讯录、入口、通知和打卡采集基础设施。HRWD 新增企业微信通讯录同步 flag、配置、同步记录、入职创建/资料更新/离职禁用钩子和员工管理重试入口。本地后端/前端/Playwright 通过,部署后公网 readiness、PDF live OCR、HRWD 端点 SKIP 检查和公网移动/企微矩阵通过;部署过程中发现 `seed-config` 会覆盖正式 feature flag,已修正为只创建缺失开关并复部署验证,最终 `payment_batch_v1=force_on`、`erp_sync=force_off`。
|
||||
- 2026-05-17 HRWD-CONTACT-LIVE-TEST 已完成公网企业微信通讯录真实写入闭环:使用 `HRWDTEST-20260517214548` 测试员工,验证 OA 创建员工后由 onboarding 触发企微 create、资料更新触发企微 update、离职 checklist 完成后禁用 OA 账号并触发企微 disable;三次企微同步均为 `wecom_contact_synced` 且 `errcode=0`。普通员工查看/重试他人同步状态被拒绝,审计日志包含 `WECOM_DIRECTORY_CREATE/UPDATE/DISABLE`;测试成员按约定保留离职/禁用状态,不删除。
|
||||
- 2026-05-17 WCFB 已完成企微打卡同步 Secret 兼容修正的本地验证:用户确认「工作台」就是 OA 企业微信入口应用,且该应用已在企业微信打卡 API「可调用接口的应用」中授权。OA 后端调整为 `WECOM_CHECKIN_SECRET` 显式优先,未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`,避免重复维护同一个 Secret;本地语法检查、focused 单测、文档和项目 skill 校验通过。该修正尚未部署到公网,部署后需复检企微打卡同步。
|
||||
|
||||
## 关键证据索引
|
||||
|
||||
@@ -120,6 +121,7 @@ DCL7 已删除本文件中 R70-R91 的长流水历史,避免旧环境、旧开
|
||||
| LAUNCH-AUTO-OPS-V1 正式启用后自动化收口 | 暂停,结论 `PAUSE_FOR_FIX` | `run-launch-auto-ops.sh` 报告 `artifacts/qa-flow/launch-auto-ops-20260517-175548/`;本地门禁、live readiness、PDF live OCR、考勤统一入口、公网移动矩阵 6 passed、企业微信渲染矩阵 8 passed;只读 flag 检查失败:`payment_batch_v1=force_off`,期望为正式常开 `force_on`,因此付款批次写入回归被安全跳过。 |
|
||||
| AUTO-REMAINING-CLOSEOUT 剩余自动化收口 | 通过,结论 `GO_TO_OPERATE` | 新增 QA-only flag 恢复脚本并受控恢复 `payment_batch_v1=force_on`;`run-launch-auto-ops.sh` 报告 `artifacts/qa-flow/launch-auto-ops-20260517-193317/`,本地门禁、live readiness、PDF live OCR、正式 flag、统一考勤、付款批次常开闭环、公网移动矩阵 6 passed、企业微信渲染矩阵 8 passed;测试报销 `BX-20260517-0027` paid,票据 archived,AP 台账 paid;企微打卡同步缺后台配置按 `SKIP wecom_checkin_sync_disabled` 记录。 |
|
||||
| HRWD-CONTACT-LIVE-TEST 企微通讯录公网闭环 | 通过,结论 `PASS` | 新增 QA-only runner `scripts/qa/run-hrwd-contact-live-test.mjs`;公网创建 `HRWDTEST-20260517214548` 测试员工,onboarding 触发企微 create、资料变更触发 update、offboarding checklist 触发 disable,三次同步均 `errcode=0`;OA 账号最终 archived/inactive,离职后登录 403 `AUTH_003`,普通员工同步状态权限负例和审计动作均通过;报告 `artifacts/qa-flow/hrwd-contact-live-test-20260517T134548Z/summary.json`。 |
|
||||
| WCFB 企微打卡 Secret 复用 | 本地通过,待部署复检 | Issue `issues/2026-05-17_22-57-39-wecom-checkin-agent-secret-fallback.csv`;`WECOM_CHECKIN_SECRET` 显式优先,未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`;本地 `node --check`、`attendance.test.js`、Issue CSV 校验、文档审计和 skill 校验通过。 |
|
||||
|
||||
## R7EP / OPS8 / QAF8 / UX8 / P2T 预检推进
|
||||
|
||||
|
||||
+2
-1
@@ -60,6 +60,7 @@
|
||||
| `AUTO-REMAINING-CLOSEOUT` | `issues/2026-05-17_19-30-44-auto-remaining-closeout.csv` | `EVIDENCE` | 剩余无需人工操作事项已收口,结论 `GO_TO_OPERATE`:公网 `payment_batch_v1` 已恢复 `force_on`,`erp_sync=force_off`;正式自动巡检报告 `artifacts/qa-flow/launch-auto-ops-20260517-193317/` 为 PASS,付款批次常开闭环、移动矩阵和企业微信渲染矩阵通过;企微打卡同步仍因外部配置 `SKIP wecom_checkin_sync_disabled` |
|
||||
| `HR-WECOM-DIRECTORY-V1` | `issues/2026-05-17_20-00-40-hr-wecom-directory-v1.csv` | `EVIDENCE` | 人事生命周期与企业微信通讯录同步已部署并收口:ERP 未来不接入 OA;HR 大域拆分为人事生命周期和考勤两个模块;OA 驱动企微通讯录创建/更新/禁用,失败不回滚 OA 人事动作;公网端点检查显示缺通讯录配置时重试记录 `SKIP wecom_contact_sync_disabled`,不阻断 OA 人事闭环;seed-config 已改为不覆盖已有正式 feature flag,复部署后 `payment_batch_v1=force_on`、`erp_sync=force_off` |
|
||||
| `HRWD-CONTACT-LIVE-TEST` | `issues/2026-05-17_21-41-43-hrwd-contact-live-test.csv` | `EVIDENCE` | 公网企微通讯录真实写入闭环已收口:`HRWDTEST-20260517214548` 测试员工完成 create/update/disable,企微 `errcode=0`,OA 员工最终 archived/inactive,登录负例、普通员工权限负例和审计动作均通过;报告 `artifacts/qa-flow/hrwd-contact-live-test-20260517T134548Z/summary.json` |
|
||||
| `WCFB` | `issues/2026-05-17_22-57-39-wecom-checkin-agent-secret-fallback.csv` | `LOCAL_VERIFIED` | 企微打卡同步 Secret 兼容修正:`WECOM_CHECKIN_SECRET` 显式优先,未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`;本地语法和 focused 单测通过,待部署后公网复检 |
|
||||
|
||||
旧 Issue CSV 保留为历史审计证据:不删除、不批量回写、不移动到 `docs/archive/` 或其他归档目录。新连续合同如替代旧合同,只在新合同 `Notes` 或文档索引中说明 supersedes。
|
||||
|
||||
@@ -173,4 +174,4 @@
|
||||
|
||||
- `ME6-3` 代表性企业微信真机门禁已关闭。
|
||||
- MIF7-1..7 与 MIF7T 已完成本地整改、公网部署、公网匿名复检和带凭据 live smoke/OCR 复检。
|
||||
- R7E 真实扩大试运行阶段验收、P2 排序、R7E 收口、QAOPS8 上线硬化阶段收口、QAOPS9 上线硬化连续合同、PRELAUNCH 正式启用前公网预回归、CASHIER-VOUCHER-V1 财务闭环与银行回单附件、LAUNCH-DEPLOY-FINANCE 部署复检、FIN-PROD-ENABLE 正式常开、ACCOUNTING-HANDOFF-V1 代账交接 SOP、SYSTEM-COMPLETE-GATE 系统完成门禁、LAUNCH-OPS-V1 首周运营保障、REQ-COMPLETE-AUDIT 需求完成度复核、ATTV1 考勤统一入口、AUTO-REMAINING-CLOSEOUT 剩余自动化收口和 HRWD-CONTACT-LIVE-TEST 公网企微通讯录真实写入闭环均已完成,当前 P0/P1 阻断为 0;P2D 已完成决策分流,P2-ORG 只读巡检已完成且无需写修复合同。LAUNCH-AUTO-OPS-V1 发现的公网 `payment_batch_v1=force_off` 漂移已由 AUTOREM 恢复为 `force_on`,正式自动巡检结论为 `GO_TO_OPERATE`;`erp_sync=force_off`,ERP 未来不接入 OA,正式支付接口、图片 OCR 和银行流水导入继续不进入当前路线。ATTV1 已部署:历史 OA 自建打卡 `attendance_v1` 关闭,OA 补卡/外出/加班审批和月度考勤解释已纳入统一考勤主线;企微打卡 live 同步能力依赖 `WECOM_CHECKIN_SECRET` 和企微 API 权限,缺配置时记录 `SKIP`。HRWD 已完成 OA 驱动企业微信通讯录同步,并通过 `HRWDTEST-20260517214548` 真实公网 create/update/disable 验证;测试成员按审计要求保留离职/禁用状态。
|
||||
- R7E 真实扩大试运行阶段验收、P2 排序、R7E 收口、QAOPS8 上线硬化阶段收口、QAOPS9 上线硬化连续合同、PRELAUNCH 正式启用前公网预回归、CASHIER-VOUCHER-V1 财务闭环与银行回单附件、LAUNCH-DEPLOY-FINANCE 部署复检、FIN-PROD-ENABLE 正式常开、ACCOUNTING-HANDOFF-V1 代账交接 SOP、SYSTEM-COMPLETE-GATE 系统完成门禁、LAUNCH-OPS-V1 首周运营保障、REQ-COMPLETE-AUDIT 需求完成度复核、ATTV1 考勤统一入口、AUTO-REMAINING-CLOSEOUT 剩余自动化收口和 HRWD-CONTACT-LIVE-TEST 公网企微通讯录真实写入闭环均已完成,当前 P0/P1 阻断为 0;P2D 已完成决策分流,P2-ORG 只读巡检已完成且无需写修复合同。LAUNCH-AUTO-OPS-V1 发现的公网 `payment_batch_v1=force_off` 漂移已由 AUTOREM 恢复为 `force_on`,正式自动巡检结论为 `GO_TO_OPERATE`;`erp_sync=force_off`,ERP 未来不接入 OA,正式支付接口、图片 OCR 和银行流水导入继续不进入当前路线。ATTV1 已部署:历史 OA 自建打卡 `attendance_v1` 关闭,OA 补卡/外出/加班审批和月度考勤解释已纳入统一考勤主线;企微打卡 live 同步能力使用已授权调用打卡 API 的自建应用 Secret,`WECOM_CHECKIN_SECRET` 显式优先,未配置时可复用已授权工作台应用的 `WECOM_AGENT_SECRET`,缺配置时记录 `SKIP`。HRWD 已完成 OA 驱动企业微信通讯录同步,并通过 `HRWDTEST-20260517214548` 真实公网 create/update/disable 验证;测试成员按审计要求保留离职/禁用状态。
|
||||
|
||||
+3
-1
@@ -20,7 +20,7 @@
|
||||
|
||||
## 最近事实源链
|
||||
|
||||
R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q -> P2AUTO -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE -> PRELAUNCH -> CASHIER-VOUCHER-V1 -> LAUNCH-DEPLOY-FINANCE -> FIN-PROD-ENABLE -> ACCOUNTING-HANDOFF-V1 -> SYSTEM-COMPLETE-GATE -> LAUNCH-OPS-V1 -> REQ-COMPLETE-AUDIT -> ATTV1 -> LAUNCH-AUTO-OPS-V1 -> AUTO-REMAINING-CLOSEOUT -> HR-WECOM-DIRECTORY-V1 -> HRWD-CONTACT-LIVE-TEST。
|
||||
R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> MIF7/MIF7T(公网部署与带凭据公网复检已收口) -> DG8 -> DG9 -> R7E -> FBA -> QAOPS9/P2D -> P2-ORG -> P2-FIN-DECISION -> P2-FIN-V1 -> P2-OCR-SAMPLE -> P2Q -> P2AUTO -> P2AF -> P2-FIN-LIVE-DECISION -> P2-FIN-LIVE -> PRELAUNCH -> CASHIER-VOUCHER-V1 -> LAUNCH-DEPLOY-FINANCE -> FIN-PROD-ENABLE -> ACCOUNTING-HANDOFF-V1 -> SYSTEM-COMPLETE-GATE -> LAUNCH-OPS-V1 -> REQ-COMPLETE-AUDIT -> ATTV1 -> LAUNCH-AUTO-OPS-V1 -> AUTO-REMAINING-CLOSEOUT -> HR-WECOM-DIRECTORY-V1 -> HRWD-CONTACT-LIVE-TEST -> WCFB。
|
||||
|
||||
- R6、PC6、PA6、FL6、AP6、RI6、LP6 已完成并部署。
|
||||
- ME6 自动化、公网视觉矩阵和 `ME6-3` 代表性企业微信真机门禁已完成。
|
||||
@@ -54,6 +54,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M
|
||||
- AUTO-REMAINING-CLOSEOUT 是 LAUNCH-AUTO-OPS-V1 后的剩余自动化收口合同:已受控恢复公网 `payment_batch_v1=force_on` 并保持 `erp_sync=force_off`,复跑正式启用后自动巡检通过;`LAUNCHAUTO` marker 测试报销 `BX-20260517-0027` 完成付款批次、回单、AP 导出和审计闭环;公网移动矩阵 6 passed,企业微信渲染矩阵 8 passed。企微打卡 live 同步仍因外部配置记录 `SKIP wecom_checkin_sync_disabled`,不影响 OA 统一考勤入口完成态。
|
||||
- HR-WECOM-DIRECTORY-V1 是 HRWD 当前合同:ERP 从冻结待办改为未来不接入 OA;人事生命周期和考勤同属 HR 大域但分模块实现;OA 为员工、权限、审批和审计主事实源,企业微信作为通讯录、入口、通知和打卡采集基础设施。新增 `hr_wecom_directory_sync_v1`、企业微信通讯录同步记录、入职创建/调岗更新/离职禁用和管理员重试;缺通讯录 Secret 或 API 权限时 OA 人事闭环照常完成,同步记录为可解释 SKIP/失败。
|
||||
- HRWD-CONTACT-LIVE-TEST 已完成公网企微通讯录真实写入闭环:使用 `HRWDTEST-20260517214548` 测试员工验证 OA 创建员工后由 onboarding 触发企微成员创建、资料更新触发企微 update、离职 checklist 完成后禁用 OA 账号并触发企微 disable;同步均返回 `wecom_contact_synced`、`errcode=0`,普通员工同步记录权限负例和审计动作核验通过。测试成员按约定保留离职/禁用状态,不删除。
|
||||
- WCFB 是企微打卡同步 Secret 兼容修正合同:用户确认工作台应用已作为企微入口应用配置且已授权调用打卡 API;OA 后端调整为 `WECOM_CHECKIN_SECRET` 显式优先,未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`,避免重复维护同一个 Secret。本地语法和考勤 focused 单测通过;公网需部署后复检企微打卡同步。
|
||||
|
||||
## 当前活跃治理流
|
||||
|
||||
@@ -94,6 +95,7 @@ R6 -> PC6 -> PA6 -> FL6 -> AP6 -> RI6 -> LP6 -> ME6/QG6 -> DG7/MRF7 -> MIA7 -> M
|
||||
| `AUTO-REMAINING-CLOSEOUT` | `issues/2026-05-17_19-30-44-auto-remaining-closeout.csv` | 已收口:剩余自动化运营门禁通过,结论 `GO_TO_OPERATE` | 恢复正式 `payment_batch_v1=force_on`、复跑 `run-launch-auto-ops.sh`、付款批次常开闭环、移动/企微矩阵、企微同步 SKIP 证据;报告 `artifacts/qa-flow/launch-auto-ops-20260517-193317/` | 不部署、不改业务代码、不接真实支付、不接 ERP、不开放图片 OCR、不导入银行流水;企微打卡同步外部配置仍需后台配置 |
|
||||
| `HR-WECOM-DIRECTORY-V1` | `issues/2026-05-17_20-00-40-hr-wecom-directory-v1.csv` | 已收口:人事生命周期与企业微信通讯录同步闭环已部署公网 | ERP 永不接入 OA;入职/调岗/资料变更/离职驱动企微通讯录创建、更新、禁用;员工管理和详情展示同步状态与重试;部署后 live smoke、HRWD 端点 SKIP 检查、公网移动/企微矩阵通过;seed 已改为不覆盖正式 flag | 不使用企业微信原生审批、不接真实支付、不开放图片 OCR、不导入银行流水;企微通讯录真实写入依赖后台 Secret、默认部门和 API 权限 |
|
||||
| `HRWD-CONTACT-LIVE-TEST` | `issues/2026-05-17_21-41-43-hrwd-contact-live-test.csv` | 已收口:公网企微通讯录真实写入闭环通过 | `HRWDTEST-20260517214548` 测试员工完成 OA 创建、onboarding 企微 create、资料 update、offboarding 企微 disable、OA 禁用、登录负例、权限负例和审计核验;报告 `artifacts/qa-flow/hrwd-contact-live-test-20260517T134548Z/summary.json` | 不删除企业微信测试成员、不重置数据库、不改业务 API/schema/UI、不部署;测试成员最终保留离职/禁用状态 |
|
||||
| `WCFB` | `issues/2026-05-17_22-57-39-wecom-checkin-agent-secret-fallback.csv` | 本地通过:企微打卡同步 Secret 兼容修正 | `WECOM_CHECKIN_SECRET` 显式优先;未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`,避免重复维护同一个 Secret;本地语法、focused 单测和文档/skill 校验通过 | 不新增业务 API/schema/UI;不改变企微权限边界;部署后需公网复检打卡同步 |
|
||||
|
||||
旧 Issue CSV 不删除、不批量回写、不移动到归档目录。若新治理流替代旧合同,只在新 CSV 的 `Notes` 或本索引说明 supersedes,旧文件保留历史审计语义。
|
||||
|
||||
|
||||
+2
-2
@@ -240,7 +240,7 @@ REQAUD 复核后,ATTV1 已按“企业微信打卡、OA 审批解释”口径
|
||||
| 请假影响团队状态/日历 | `DONE` | 已批准请假可进入团队状态和日历展示。 |
|
||||
| 企业微信入口、免登/绑定、通知 deeplink 回 OA | `DONE` | 企业微信只作为入口、身份和提醒,审批动作仍回 OA。 |
|
||||
| 企业微信打卡运营使用 | `DONE` | 员工继续按公司要求在企业微信打卡;这是企业微信侧能力,不代表 OA 已同步打卡 API。 |
|
||||
| 企业微信打卡 API 同步到 OA | `PARTIAL` | 已新增 `attendance_unified_v1` 同步批次和打卡摘要表;有企微打卡 Secret/API 权限时可同步,缺配置时按 `SKIP` 记录,不伪造通过。 |
|
||||
| 企业微信打卡 API 同步到 OA | `PARTIAL` | 已新增 `attendance_unified_v1` 同步批次和打卡摘要表;有已授权调用打卡 API 的自建应用 Secret/API 权限时可同步,缺配置时按 `SKIP` 记录,不伪造通过。`WECOM_CHECKIN_SECRET` 显式优先,未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`。 |
|
||||
| 员工补卡申请审批 | `DONE` | 已新增 OA 考勤修正申请,补卡只解释企微缺卡,不向企业微信写回。 |
|
||||
| 外出申请审批 | `DONE` | 已新增外出修正申请和 OA 审批,用于月度考勤解释;不重造企微定位风控。 |
|
||||
| 加班申请审批 | `DONE` | 已新增加班修正申请和 OA 审批,记录加班事实和补偿口径。 |
|
||||
@@ -404,7 +404,7 @@ REQAUD 复核后,ATTV1 已按“企业微信打卡、OA 审批解释”口径
|
||||
|
||||
后续事项必须另立 Issue CSV,不直接进入当前 MVP:
|
||||
|
||||
- 企业微信打卡 live 同步配置和权限稳定化。
|
||||
- 企业微信打卡 live 同步验证和真实考勤数据口径稳定化。
|
||||
- 补卡、外出、加班的真实使用反馈体验优化。
|
||||
- 合同台账、合同编号、合同审批和离线档案号的细化。
|
||||
- 预算控制和付款/AP 运营真实使用反馈优化;ERP 不再规划出站凭证或实时集成。
|
||||
|
||||
+4
-3
@@ -309,7 +309,7 @@ ORDER BY e.employee_no;
|
||||
- 普通员工主导航不出现预算、AP、AI、完整差旅等当前首批不开放的备用模块;ERP 入口保持关闭。
|
||||
|
||||
## 11. 4 周观测操作法
|
||||
1. 每周一进入 `Portal`、`审批待办` 和 `考勤` 查看近 7 天登录覆盖率、审批积压、报销/OCR 异常、请假/考勤修正审批和企业微信入口错误;企微打卡同步缺配置时记录为 `SKIP`。
|
||||
1. 每周一进入 `Portal`、`审批待办` 和 `考勤` 查看近 7 天登录覆盖率、审批积压、报销/OCR 异常、请假/考勤修正审批和企业微信入口错误;企微打卡同步缺已授权应用 Secret 或 API 权限时记录为 `SKIP`。
|
||||
2. 每周五记录本周结论、关键阻塞、是否需要调整审批规则、报销规则、企业微信入口或导航。
|
||||
3. 若某周出现员工未登录、报销长期停留、发票重复处理不清、审批积压或企业微信绑定异常,优先修正规则和入口,不额外维护表格作为正式记录。
|
||||
4. 连续 4 周都达到预期后,再扩大到 P1 采购/费用、合同归档和项目轻管理试点。
|
||||
@@ -336,7 +336,8 @@ ORDER BY e.employee_no;
|
||||
- `WECOM_REDIRECT_URI=https://oa.hyinda.com/api/auth/wecom/callback`
|
||||
- `WECOM_ALLOWED_RETURN_HOSTS=oa.hyinda.com`
|
||||
5. 先让管理员用账号密码登录,在 `个人设置` 完成一次绑定,再测试企业微信工作台直达;已绑定用户从手机企业微信进入时应直接看到移动工作台。
|
||||
6. 不配置企业微信原生审批模板作为正式流程;审批提醒使用企业微信消息,审批动作回到 OA 页面完成。
|
||||
6. 若启用 OA 同步企业微信打卡数据,在企业微信打卡 API「可调用接口的应用」中授权 `海音达工作台`。OA 默认可复用 `WECOM_AGENT_SECRET`;只有单独创建考勤同步应用时才额外配置 `WECOM_CHECKIN_SECRET`。
|
||||
7. 不配置企业微信原生审批模板作为正式流程;审批提醒使用企业微信消息,审批动作回到 OA 页面完成。
|
||||
|
||||
### 员工会看到的典型状态
|
||||
- 未绑定:登录页会提示“当前企业微信账号尚未绑定”,回退到账号密码登录。
|
||||
@@ -386,7 +387,7 @@ ORDER BY e.employee_no;
|
||||
- `APP_ORIGIN=https://oa.example.com`
|
||||
- `COOKIE_SECURE=true`
|
||||
- `PUBLIC_SECURITY_HARDENING_ENABLED=true`
|
||||
3. 若启用企业微信入口,补齐:`WECOM_*` 与 `WECOM_ALLOWED_RETURN_HOSTS`。若启用 OA 驱动企业微信通讯录同步,还需补齐 `WECOM_CONTACT_SYNC_ENABLED=true`、`WECOM_CONTACT_SECRET`、`WECOM_CONTACT_DEFAULT_DEPARTMENT_ID`,并确认该 Secret 具备通讯录写权限。
|
||||
3. 若启用企业微信入口,补齐:`WECOM_*` 与 `WECOM_ALLOWED_RETURN_HOSTS`。若启用 OA 驱动企业微信通讯录同步,还需补齐 `WECOM_CONTACT_SYNC_ENABLED=true`、`WECOM_CONTACT_SECRET`、`WECOM_CONTACT_DEFAULT_DEPARTMENT_ID`,并确认该 Secret 具备通讯录写权限。若启用企微打卡同步,确认 `海音达工作台` 已被授权调用打卡 API;`WECOM_CHECKIN_SECRET` 留空时复用 `WECOM_AGENT_SECRET`,单独考勤同步应用才需要填写独立 Secret。
|
||||
4. 将 `OPENAPI_DOCS_ENABLED` 保持为 `false`,不要在公网生产暴露文档入口。
|
||||
5. 定期检查审计日志中的登录失败、Refresh 来源不匹配与涉密资料查看记录。
|
||||
6. 准备独立 smoke 账号,不使用老板、管理员或真实员工日常账号跑自动化;smoke 账号的角色和直属上级应按正式审批链配置。
|
||||
|
||||
+2
-2
@@ -164,7 +164,7 @@ deeplink 必须校验目标地址白名单,避免跳转到非 OA 域名或未
|
||||
|
||||
- 企业微信打卡用于原始采集,保留其定位、人脸识别、Wi-Fi、排班和设备能力。
|
||||
- OA 首批不重造打卡定位、人脸识别、Wi-Fi、排班、设备绑定、防作弊和异常判断引擎。
|
||||
- ATTV1 新增企微打卡摘要同步、同步批次和 raw hash 留痕;缺 Secret 或 API 权限时同步批次记录 `SKIP`。
|
||||
- ATTV1 新增企微打卡摘要同步、同步批次和 raw hash 留痕;缺已授权自建应用 Secret 或 API 权限时同步批次记录 `SKIP`。若工作台应用已在企业微信打卡 API「可调用接口的应用」中授权,OA 可复用现有 `WECOM_AGENT_SECRET`;`WECOM_CHECKIN_SECRET` 仅作为单独考勤同步应用的显式覆盖。
|
||||
- OA 把企业微信打卡摘要与 OA 请假、补卡、外出、加班审批结果合并,形成正式考勤解释。
|
||||
- 企业微信打卡异常不直接等于最终考勤异常;最终解释必须结合 OA 审批记录。
|
||||
|
||||
@@ -173,7 +173,7 @@ REQAUD 复核后,ATTV1 已完成统一入口实现,企业微信打卡边界
|
||||
| 项目 | 状态 | 当前口径 |
|
||||
| --- | --- | --- |
|
||||
| 企业微信打卡运营使用 | `DONE` | 员工继续按公司要求使用企业微信打卡,定位、人脸、Wi-Fi、设备和防作弊由企业微信侧承担。 |
|
||||
| OA 同步企业微信打卡 API | `PARTIAL` | 同步表、批次、摘要和脱敏错误码已实现;公网真实同步取决于 `WECOM_CHECKIN_SECRET` 和企微打卡 API 权限。 |
|
||||
| OA 同步企业微信打卡 API | `PARTIAL` | 同步表、批次、摘要和脱敏错误码已实现;公网真实同步取决于已授权调用打卡 API 的自建应用 Secret 和企微打卡 API 权限。默认显式 `WECOM_CHECKIN_SECRET` 优先;未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`。 |
|
||||
| OA 历史 `attendance_v1` 极简打卡 | `FROZEN_CLOSED` | 该模块已从正式入口关闭;旧表保留,旧写接口返回 409,不作为企微打卡能力。 |
|
||||
| 正式考勤解释 | `DONE` | OA 已支持请假、补卡、外出、加班审批和月度解释;不向企业微信原生审批或补卡接口写回。 |
|
||||
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ REQAUD 复核后,ATTV1 已完成统一入口实现,当前运行时完成度
|
||||
| 请假申请、审批、撤回、编辑、移动查看 | `DONE` | 已接入统一审批中心和 PC/移动页面。 |
|
||||
| 请假对团队状态/日历的影响 | `DONE` | 已批准请假可作为团队状态和日历依据。 |
|
||||
| 企业微信打卡作为原始采集 | `DONE` | 由企业微信侧承载定位、人脸、Wi-Fi、设备和防作弊能力;OA 不重造。 |
|
||||
| 企业微信打卡 API 同步 | `PARTIAL` | 已新增同步批次和企微打卡摘要表;有 `WECOM_CHECKIN_SECRET` 和企微权限时同步,缺配置时记录 `SKIP`。 |
|
||||
| 企业微信打卡 API 同步 | `PARTIAL` | 已新增同步批次和企微打卡摘要表;有已授权调用打卡 API 的自建应用 Secret 和企微权限时同步,缺配置时记录 `SKIP`。`WECOM_CHECKIN_SECRET` 可作为单独考勤同步应用的显式覆盖;未配置时复用已授权工作台应用的 `WECOM_AGENT_SECRET`。 |
|
||||
| 员工补卡申请审批 | `DONE` | 员工可在 OA 提交补卡修正,进入统一审批;通过后用于月度考勤解释,不写回企业微信。 |
|
||||
| 外出申请审批 | `DONE` | 员工可在 OA 提交外出修正,记录地点摘要和事由,审批后用于月度解释。 |
|
||||
| 加班申请审批 | `DONE` | 员工可在 OA 提交加班修正,记录时间、原因和补偿口径,审批后用于月度解释。 |
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
ID,Title,Description,Acceptance,Test_Method,Tools,Dev_Status,Review1_Status,Regression_Status,Files,Dependencies,Notes
|
||||
WCFB-0,合同建档,记录用户确认工作台应用 Secret 已作为企业微信入口环境变量配置且工作台已被授权调用打卡 API,Issue CSV 通过校验且范围限定为企微打卡 Secret 复用兼容,python3 /home/shunli/.codex/skills/plan/scripts/validate_issues_csv.py issues/2026-05-17_22-57-39-wecom-checkin-agent-secret-fallback.csv,plan,DONE,DONE,DONE,issues/2026-05-17_22-57-39-wecom-checkin-agent-secret-fallback.csv,[],不新增业务功能不改数据库不部署
|
||||
WCFB-1,后端 Secret 复用,企微打卡同步在 WECOM_CHECKIN_SECRET 为空时复用已配置的 WECOM_AGENT_SECRET 作为已授权工作台应用 Secret,check-live-attendance-unified 不再因仅缺 WECOM_CHECKIN_SECRET 直接 SKIP 且仍在无任何 Secret 时返回缺配置,cd server && node --check src/config.js src/services/integrations/wecomCheckin.js && npm test -- tests/unit/routes/attendance.test.js,node,DONE,DONE,DONE,server/src/config.js|server/src/services/integrations/wecomCheckin.js|server/tests/unit/routes/attendance.test.js,["WCFB-0"],回滚路径为恢复强制 WECOM_CHECKIN_SECRET 的检查
|
||||
WCFB-2,文档口径同步,更新企业微信和考勤事实源说明显式打卡 Secret 优先且已授权工作台应用可复用 WECOM_AGENT_SECRET,文档和项目 skill 不再误导为必须寻找内置打卡应用 Secret,node scripts/qa/audit-docs.mjs --check-registry && python3 /home/shunli/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/hyb-oa-delivery && rg -n 'WECOM_CHECKIN_SECRET|WECOM_AGENT_SECRET|工作台应用' docs .agents/skills/hyb-oa-delivery/SKILL.md,rg|node|skill-creator,DONE,DONE,DONE,docs/需求-企业微信基础设施规约.md|docs/需求-请假考勤修正规约.md|docs/trialrun/人工试运行报告.md|docs/海音达OA-MVP需求规格说明书.md|docs/管理员手册.md|docs/文档索引.md|docs/文档事实源状态矩阵.md|deploy/trialrun/.env.example|.agents/skills/hyb-oa-delivery/SKILL.md,["WCFB-1"],不新增综合规划文档
|
||||
WCFB-3,收口验证,完成静态检查单测和 diff 检查并记录是否需要服务器重启或公网复检,本地验证通过且给出公网下一步命令,git diff --check && git status --short,git,DONE,DONE,DONE,N/A,"[""WCFB-1"",""WCFB-2""]",公网只需重启 server 后跑企微打卡同步检查
|
||||
|
无法渲染此文件,因为它包含了意外字符,其位于第 3 行和第 550 列。
|
@@ -40,6 +40,13 @@ function sanitizeCheckinApiResponse(payload) {
|
||||
return Object.keys(result).length ? result : null;
|
||||
}
|
||||
|
||||
function resolveCheckinSecret(config) {
|
||||
return (
|
||||
toSafeString(config?.wecomCheckinSecret, 500) ||
|
||||
toSafeString(config?.wecomAgentSecret, 500)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeCheckinRecord(raw, { employeeByExternalUserId, timeZone }) {
|
||||
const externalUserId = toSafeString(raw?.userid ?? raw?.UserId, 128);
|
||||
const employee = employeeByExternalUserId.get(externalUserId);
|
||||
@@ -82,13 +89,14 @@ async function fetchWecomCheckinData({
|
||||
response: null,
|
||||
};
|
||||
}
|
||||
if (!toSafeString(config?.wecomCheckinSecret, 500)) {
|
||||
const checkinSecret = resolveCheckinSecret(config);
|
||||
if (!checkinSecret) {
|
||||
return {
|
||||
ok: false,
|
||||
skipped: true,
|
||||
retryable: false,
|
||||
code: "wecom_checkin_secret_missing",
|
||||
message: "Missing WECOM_CHECKIN_SECRET",
|
||||
message: "Missing WECOM_CHECKIN_SECRET or WECOM_AGENT_SECRET",
|
||||
records: [],
|
||||
response: null,
|
||||
};
|
||||
@@ -108,7 +116,7 @@ async function fetchWecomCheckinData({
|
||||
const tokenResult = await getWecomAppAccessToken({
|
||||
config: {
|
||||
...config,
|
||||
wecomAgentSecret: config.wecomCheckinSecret,
|
||||
wecomAgentSecret: checkinSecret,
|
||||
},
|
||||
timeoutMs: config.wecomRequestTimeoutMs ?? 5000,
|
||||
fetchImpl,
|
||||
@@ -177,5 +185,6 @@ module.exports = {
|
||||
fetchWecomCheckinData,
|
||||
formatDateInTimezone,
|
||||
normalizeCheckinRecord,
|
||||
resolveCheckinSecret,
|
||||
sanitizeCheckinApiResponse,
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ const { AppError } = require("../../../src/utils/AppError");
|
||||
const EMPLOYEE_ID = "11111111-1111-4111-8111-111111111111";
|
||||
const MANAGER_ID = "22222222-2222-4222-8222-222222222222";
|
||||
const DEPARTMENT_ID = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
|
||||
const originalFetch = global.fetch;
|
||||
|
||||
function buildEmployee(overrides = {}) {
|
||||
return {
|
||||
@@ -36,6 +37,14 @@ function buildFlag(flagKey, overrideMode = "force_on") {
|
||||
};
|
||||
}
|
||||
|
||||
function buildJsonResponse(payload, overrides = {}) {
|
||||
return {
|
||||
ok: overrides.ok ?? true,
|
||||
status: overrides.status ?? 200,
|
||||
json: async () => payload,
|
||||
};
|
||||
}
|
||||
|
||||
function buildApp(options = {}) {
|
||||
const app = Fastify();
|
||||
app.setErrorHandler((error, request, reply) => {
|
||||
@@ -91,6 +100,7 @@ function buildApp(options = {}) {
|
||||
wecomCheckinSyncEnabled: false,
|
||||
wecomCheckinSecret: "",
|
||||
leaveHolidayCalendarEnabled: false,
|
||||
...options.config,
|
||||
});
|
||||
app.decorate("prisma", {
|
||||
featureFlag: { findUnique: featureFlagFindUnique },
|
||||
@@ -179,6 +189,7 @@ describe("attendance unified routes", () => {
|
||||
let currentApp = null;
|
||||
|
||||
afterEach(async () => {
|
||||
global.fetch = originalFetch;
|
||||
if (currentApp) {
|
||||
await currentApp.close();
|
||||
currentApp = null;
|
||||
@@ -320,4 +331,115 @@ describe("attendance unified routes", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("records WeCom sync as skipped when no authorized app secret is configured", async () => {
|
||||
const { app, prisma } = buildApp({
|
||||
config: {
|
||||
wecomCheckinSyncEnabled: true,
|
||||
wecomCorpId: "corp-id",
|
||||
wecomAgentId: "1000001",
|
||||
wecomCheckinSecret: "",
|
||||
wecomAgentSecret: "",
|
||||
},
|
||||
});
|
||||
currentApp = app;
|
||||
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/attendance/wecom/sync",
|
||||
payload: {
|
||||
startDate: "2026-05-11",
|
||||
endDate: "2026-05-12",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data).toMatchObject({
|
||||
skipped: true,
|
||||
code: "wecom_checkin_secret_missing",
|
||||
employeeCount: 1,
|
||||
boundUserCount: 1,
|
||||
recordCount: 0,
|
||||
});
|
||||
expect(prisma.wecomCheckinSyncRun.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
status: "skipped",
|
||||
error_code: "wecom_checkin_secret_missing",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses the authorized workbench app secret fallback for WeCom checkin sync", async () => {
|
||||
global.fetch = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(buildJsonResponse({ errcode: 0, access_token: "token-from-workbench" }))
|
||||
.mockResolvedValueOnce(
|
||||
buildJsonResponse({
|
||||
errcode: 0,
|
||||
checkindata: [
|
||||
{
|
||||
userid: "wecom-user-1",
|
||||
checkin_time: 1778461200,
|
||||
checkin_type: "上班打卡",
|
||||
exception_type: "",
|
||||
source_type: "app",
|
||||
result: "正常",
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const { app, prisma } = buildApp({
|
||||
config: {
|
||||
wecomCheckinSyncEnabled: true,
|
||||
wecomCorpId: "corp-id",
|
||||
wecomAgentId: "1000001",
|
||||
wecomCheckinSecret: "",
|
||||
wecomAgentSecret: "workbench-secret",
|
||||
},
|
||||
});
|
||||
currentApp = app;
|
||||
|
||||
const response = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/attendance/wecom/sync",
|
||||
payload: {
|
||||
startDate: "2026-05-11",
|
||||
endDate: "2026-05-12",
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data).toMatchObject({
|
||||
skipped: false,
|
||||
code: "wecom_checkin_fetched",
|
||||
employeeCount: 1,
|
||||
boundUserCount: 1,
|
||||
recordCount: 1,
|
||||
});
|
||||
expect(String(global.fetch.mock.calls[0][0])).toContain("corpsecret=workbench-secret");
|
||||
expect(String(global.fetch.mock.calls[1][0])).toContain("/cgi-bin/checkin/getcheckindata");
|
||||
expect(prisma.wecomCheckinRecord.createMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: [
|
||||
expect.objectContaining({
|
||||
employee_id: EMPLOYEE_ID,
|
||||
external_user_id: "wecom-user-1",
|
||||
checkin_type: "上班打卡",
|
||||
result: "正常",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(prisma.wecomCheckinSyncRun.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
status: "success",
|
||||
record_count: 1,
|
||||
error_code: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户