yuanzhi_kuang hai 4 horas
pai
achega
c023d267ef

+ 71 - 0
单点_待办skill/kdshr-dev/SKILL.md

@@ -0,0 +1,71 @@
+---
+name: kdshr-dev
+description: >-
+  Kingdee EAS/SHR (sHR) custom development discipline for BaoYPro and similar
+  EAS/sHR projects — not Cosmic/苍穹 or other Kingdee product lines.
+  Use when writing or reviewing Java integration code (weaverTodo, SSO, BMC, OpenAPI)
+  on EAS/sHR, when API fields or platform getters are unclear, or when the user
+  mentions 不要乱猜, 不确定先问, 减少多余判断, or coding standards for Kingdee sHR
+  secondary development.
+---
+
+# Kingdee SHR 二开编码纪律
+
+适用:`kdshrpmsg` / `kdshrsso` 及 **金蝶 EAS/sHR**(如 BaoYPro、YHPro)二开。与业务 skill 一起用;本 skill 管「怎么写、不确定怎么办」。
+
+**不适用**:金蝶其他产品线(如 **苍穹 Cosmic**、云星空等)——那些项目的目录与源码编码约定不同,**不要**套用下文 `src`/GBK、`websrc`/UTF-8 规则。
+
+---
+
+## 不确定就先问,禁止乱猜(强制)
+
+对 **平台 API、BMC/Info 字段、getter、表结构、对方接口必填项** 拿不准时:
+
+1. **先向用户提问**(可一次问清),写清:缺什么、可选方案、你倾向哪种。
+2. **用户确认或给出文档/源码依据后再写代码**。
+3. **禁止**:
+   - 反射遍历一串可能存在的 `getXxx` 碰运气;
+   - 编造未证实的方法名、字段名、bosType、枚举值;
+   - 用多层 `try/catch` + 空忽略把「猜」藏起来;
+   - 为兜底再堆一串多余分支「也许有用」。
+
+已确认可用的(如文档、现网日志、同类已测通代码、用户明确指定)才能写进实现。
+
+### 已确认(BaoYPro BMC → 消息中心 sendMsg,测试已通过)
+
+- 人员标识:只传 `workCode`(工号),**不传** `employeeId`。
+- 发送人:`msgInfo.getSenderId()` → `getUserById` → `UserInfo.getNumber()` / `getName()`。
+- 接收人:`msgInfo.getReceiver()` → `resolveReceiverWorkcode`(工号)。
+- 正文:`msgInfo.getBody()`;空则回退 `getTitle()`。
+- 标题:`msgInfo.getTitle()`。
+- 环境 moduleId/eventId:以 `weaverTodoConfig.properties` 为准,测试与生产不同,勿写死在代码里。
+
+### 提问示例(仍未知时)
+
+- 对方接口必填项与文档表头冲突时,以用户/现场补丁结论为准(如只要 workCode)。
+- 新客户 BMC 字段未确认前,不要反射猜 getter。
+
+---
+
+## 实现偏好
+
+- **配置优于猜测**:对方要求固定发送人、租户、模块 id 时,用 properties;不要从 Info 里瞎抠。
+- **少分支**:一个来源够用就不要写三套 fallback。
+- **失败要响**:必填缺失直接打 error 日志并返回失败,并写清缺哪项配置/字段。
+- **编码(仅 EAS/sHR 工程)**:
+  - `src/`:后端 Java 一般 **GBK**。
+  - `websrc/`:前端/门户侧 Java(含 handler、osf 等)一般 **UTF-8**。
+  - 配置如 `weaverTodoConfig.properties`、`integration-config-*.json`:多为 **UTF-8**,须与加载代码一致。
+  - 改文件前先确认目录;**苍穹等非 sHR 工程勿套用本条**。
+
+---
+
+## 与业务 skill 的关系
+
+| Skill | 管什么 |
+|-------|--------|
+| `kdshrpmsg` | 待办/通知业务 intake、接口选型、切生产清单 |
+| `kdshrsso` | SSO 模式 intake、切生产 |
+| **`kdshr-dev`(本 skill)** | 不确定先问、不乱猜 API、少写多余判断 |
+
+做消息/SSO 时:先走对应业务 intake;写具体取值逻辑时遵守本 skill。

+ 120 - 15
单点_待办skill/kdshrpmsg/SKILL.md

@@ -16,6 +16,8 @@ Reusable pattern from **YHPro** (玉湖,已测通) and **BaoYPro** (报业 / 
 
 Related SSO deep-link entry: skill **kdshrsso** (BaoYPro: Option B-qy `/qy/auth/weaver/app.do`).
 
+Coding discipline (do not guess platform getters / uncertain fields): skill **kdshr-dev**.
+
 ---
 
 ## Agent intake — ask before coding (mandatory)
@@ -38,18 +40,71 @@ When implementing **待办 / 待阅** for a new project or new channel, do **not
 9. 状态机:待办/已办/删除/办结/待阅 与对方字段映射(Weaver `isremark` 等)——无文档则先按 weaver-baoy-plan 草案请用户改。
 10. CallLog / 失败补推是否要做;WF 延迟(如 `wf.delayMs`)经验值。
 11. 与 SSO 是否同期:深链入口必须已存在或同期交付(否则只推链接无法点开登录)。
+12. **配置文件是否齐**:对照下方「配置文件清单」——`set-server-env`、`integration-config`、`weaverTodoConfig`、`WSConfig.xml`、login/logout JS、DDL/jar;缺哪项就问「本项目要不要配」。
 
 ### Output before coding
 
-给出 **目标确认表**:渠道、配置文件名、SPI 策略、深链模板、人员字段、状态映射来源。用户确认后再改代码。用户说「按报业/按 BaoYPro」时可复述默认假设请一次确认。
+给出 **目标确认表**:渠道、配置文件名(含清单勾选)、SPI 策略、深链模板、人员字段、状态映射来源。用户确认后再改代码。用户说「按报业/按 BaoYPro」时可复述默认假设请一次确认。
+
+---
+
+## 配置文件清单(必配 / 防漏)— 新项目与切环境通用
+
+做 **待办/待阅**(尤其 Weaver + SSO)时,Agent **必须**对照下表逐项核对;其他项目若漏配,要**主动列出并问用户「是否需要配置」**,不得默认跳过。
+
+与 SSO 重叠项同时对照 skill **kdshrsso** Go-live。总清单见项目 `docs/integration/go-live-prod.html`。
+
+### A. 服务端配置(按环境区分 test/prod)
+
+| # | 文件(相对 `EAS_HOME`) | 用途 | 关键须改的关键项 | 新项目是否要 |
+|---|------------------------|------|------------------|--------------|
+| 1 | `server/bin/set-server-env.sh`(或 Windows 等价) | 选读哪份 integration JSON | `ENV=test\|prod` → `INTEGRATION_CONFIG_PATH` | **要**(有 SSO/qy 时) |
+| 2 | `server/properties/integration-config-{env}.json` | SSO OAuth、SHR、泛微 ecology | `shrServerName`、`dataCenter`、`allowedPrefixes`、`appId`/`appSecret`、`callbackUrl`、`weaver.ecologyUrl` | **要**(有 SSO 时) |
+| 3 | `server/properties/{客户}/weaverTodoConfig.properties` | 待办 OpenAPI + 待阅 sendMsg | `openapi.host`、凭证、`syscode`、`shrServerName`、`sso.appId`、`notify.moduleId`/`eventId`/`tenantKey` | **要**(有 Weaver 待办/待阅时) |
+| 4 | `server/profiles/server1/config/portalConfig/WSConfig.xml` | 平台消息 SPI 注册 | `<path>`=Dao 全类名;`<dataCenter>`=账套(与 json **一致**) | **要**(走 MessageWebServiceDao 时) |
+| 5 | `server/properties/{客户}/weaverTodo-ddl.sql` | CallLog / assignReadRecord | 生产库是否已建表 | 首次上线要执行 |
+
+报业示例目录名 `baoye/`;其他客户换自己的 properties 子目录,**不要**把多渠道揉进一个文件。
+
+### B. 前端 / 门户静态脚本(有 SSO 联邦登录/退出时)
+
+| # | 文件 | 用途 | 关键项 |
+|---|------|------|--------|
+| 6 | `server/deploy/eas.ear/sso_web.war/common/js/login_withForm.js` | 门户进 SHR SSO | `ENV_CONFIG.prod.*`;`getCurrentEnv()` 识别生产 Host |
+| 7 | `server/deploy/easweb.ear/shr_web.war/scripts/gui/logoutHR.js` | 退出转泛微 | `currentEnv` / 生产 ecologyUrl(或按 host) |
+
+### C. 部署物(非 properties,但常漏)
+
+| # | 项 | 说明 |
+|---|------|------|
+| 8 | 二开 jar(如 weaverTodo / qy 相关) | 放入 `lib`/`sp` 等,仅改配置不部署代码无效 |
+| 9 | `qy.war` context=`/qy` | SSO 入口与 callback 依赖 |
+| 10 | 泛微后台 | callback 注册、退出白名单、异构 syscode/IP、消息中心 module/event 规则 |
+
+### Agent 防漏话术(强制)
+
+- **新项目 intake**:在确认表里列出上表 A+B,标「本项目需要 / 不需要 / 待确认」。用户未表态的,**先问再写代码**。
+- **切生产 / 覆盖包**:逐文件核对是否仍为测试值(常见漏项:`WSConfig.xml` 的 `dataCenter`、`notify.moduleId/eventId`、`ENV=prod`、`logoutHR currentEnv`)。
+- **只做待办不做 SSO**:仍要问 WSConfig + weaverTodoConfig;可标 login/logout/integration-config 为「本期不做」。
+- **只做 SSO 不做待办**:转 kdshrsso;仍提醒若后续接待办需补 3、4、5。
+
+### 报业(BaoYPro)正式环境快照(参考,勿照抄到别的客户)
+
+| 项 | 值 |
+|----|-----|
+| SHR | `https://hr.szpgm.com/shr` |
+| 泛微 OpenAPI | `http://172.16.37.23:20600` |
+| dataCenter | `SNG`(json **与** WSConfig 一致) |
+| notify | tenant=`t6srq5l4vh` module=`100011` event=`241221` |
+| 覆盖包 | `D:\workcode\baoye\patch\正式单点部署`(测试包:`...\test单点部署`) |
 
 ---
 
 ## Go-live / 切生产 (with kdshrsso)
 
-When user says **上线 / 切生产**, coordinate with **kdshrsso** Go-live. For Weaver todo channel, update server file:
+When user says **上线 / 切生产**, coordinate with **kdshrsso** Go-live, and walk the **配置文件清单** above (do not only edit `weaverTodoConfig`).
 
-`{EAS_HOME}/server/properties/baoye/weaverTodoConfig.properties`
+Key production fields for Weaver todo/notify (`weaverTodoConfig.properties`):
 
 | Key | Set to production |
 |-----|-------------------|
@@ -59,12 +114,14 @@ When user says **上线 / 切生产**, coordinate with **kdshrsso** Go-live. For
 | `weaver.shrServerName` | 生产 SHR(redirect) |
 | `weaver.sso.appId` | 与 `integration-config-prod.json` 中 appId 一致 |
 | `weaver.sso.entryPath` | 通常仍 `/qy/auth/weaver/app.do` |
+| `weaver.notify.tenantKey` | 按客户(报业 `t6srq5l4vh`) |
+| `weaver.notify.moduleId` / `eventId` | **按环境**(报业生产 `100011` / `241221`;测试 `139141` / `287602`) |
 
-Also: 泛微异构系统/IP 白名单;验收待办点击走生产 SSO。  
-Full list: project `docs/integration/go-live-prod.html`.
+Also must set: `ENV=prod`、`integration-config-prod.json`、`WSConfig.xml` `<dataCenter>`、login/logout JS。  
+Full checklist: project `docs/integration/go-live-prod.html`。  
+报业生产 SHR:`https://hr.szpgm.com/shr`。
 
 ---
-
 ## When to use
 
 | Scenario | Fit |
@@ -87,7 +144,9 @@ Need portal sync for SHR workflow/messages?
   ├─ Downstream?
   │     ├─ YH-style ESB PORTAL-* → TodoMessageServer + receiveOAConfig
-  │     └─ Weaver E10 receiveRequestInfo → weaverTodo + weaverTodoConfig
+  │     └─ Weaver E10:
+  │           ├─ 待办 → receiveRequestInfo(weaverTodo + weaverTodoConfig)
+  │           └─ BMC 通知/待阅 → 消息中心 sendMsg(勿再用 isremark=8)
   ├─ Deep link back to SHR?
   │     ├─ Weaver: relative /qy/auth/weaver/app.do?appId&redirect=approveAbs (kdshrsso B-qy)
@@ -110,6 +169,47 @@ Need portal sync for SHR workflow/messages?
 
 Reuse YHPro SPI/hooks idea; replace HTTP payload and status fields. Detail: [weaver-baoy-plan.md](weaver-baoy-plan.md)
 
+### BaoYPro:BMC 通知 / 待阅 → 消息中心 sendMsg(**测试已通过**)
+
+不再走统一待办 `receiveRequestInfo` / `isremark=8`。实现类:`WeaverToBeReadMessageServer` + `WeaverSendMsgClient`。
+
+| 项 | 约定 |
+|----|------|
+| 接口 | `/papi/openapi/api/mc/msg/sendMsg`(`weaver.openapi.sendMsgPath`) |
+| 人员 | **只要 `workCode`,不传 `employeeId`** |
+| 发送人 | `BMCMessageInfo.getSenderId()` → `getUserById` → `UserInfo.getNumber()` / `getName()` |
+| 接收人 | `getReceiver()` → `resolveReceiverWorkcode` |
+| title | `getTitle()` |
+| text | **`getBody()`**;空则回退 title |
+| todo | `false`;已读不回写泛微 |
+| entity.pcUrl | 有 BMC url 才传(SSO 相对入口) |
+| channels | 默认 `[1]`(IM) |
+| 配置 | `weaver.notify.tenantKey` / `moduleId` / `eventId` / `channels` |
+| 环境固定参数 | 测试:tenant=`t6srq5l4vh` module=`139141` event=`287602`;生产:tenant=`t6srq5l4vh` module=`100011` event=`241221`(切环境只改 properties,勿混用) |
+| 配置热加载 | `WeaverTodoConfig` 按文件 mtime 重载,**改 notify 参数一般不用重启** |
+| 日志前缀 | 待办 `[WEAVER-TODO]`;待阅 `[WEAVER-TOREAD]`(含 `WeaverSendMsgClient`) |
+| CallLog | 表 `OAMsgCallLog`;`OperType=通知发送`;`RequestUrl`=host+path(**不含 token**);`ResponseResult`=对方完整 JSON |
+| OpenAPI token | **JVM 级缓存**(跨多次 new Dao);authorize 遇 Sentinel/`-10` 有限次退避重试 |
+
+SPI:`updateMessages(UNREADED)` → sendMsg;`addMessage` 且 `type` 含「通知」→ **跳过**(避免再推 isremark=0)。
+
+### BaoYPro:新增待办与 `wf.delayMs` 执行顺序(**勿颠倒**)
+
+旧错法:先 `sleep(wf.delayMs)` 再查 AssignmentId —— sleep 期间若任务撤回,AssignRead 已空,会误报 assignId 为空。
+
+正确顺序(`WeaverTodoMessageServer.addMessage`):
+
+1. **短轮询**拿 AssignmentId(`waitAssignIdByMsgId`,预算约 `wf.delayMs`,步进 500ms)
+2. assignId / procInstId 为空则直接放弃并打 CallLog
+3. 解析人、标题、拼 `pcurl`
+4. **`saveAssignRecord`** 先落本地关联
+5. 再 **`delayForWf()`**(整段 `wf.delayMs`),让「待办转已办」等并发先落定
+6. 最后 `receiveRequestInfo` 推 isremark=0
+
+已办/删除路径可先 `delayForWf()` 再查库(与新增不同)。配置项:`weaver.wf.delayMs`(报业常用 8000)。
+
+Coding discipline:skill **kdshr-dev**。
+
 ---
 
 ## Architecture (reference layout)
@@ -126,26 +226,31 @@ platform workflow / BMC
 ### BaoYPro (Weaver)
 
 ```
-platform workflow / BMC
+platform workflow
   → WeaverMessageWebServiceDao
   → WeaverTodoMessageServer
   → WeaverReceiveTodoClient (OpenAPI token + receiveRequestInfo)
   → pcurl from WeaverTodoSupport (entryPath + appId + redirect)
+
+platform BMC 通知
+  → WeaverMessageWebServiceDao.updateMessages(UNREADED)
+  → WeaverToBeReadMessageServer
+  → WeaverSendMsgClient (同 OpenAPI token + sendMsg)
 ```
 
 Config: `{EAS_HOME}/server/properties/baoye/weaverTodoConfig.properties`  
-Key: `weaver.sso.entryPath` (default `/qy/auth/weaver/app.do`) + `weaver.sso.appId`.
+Key: `weaver.sso.entryPath` (default `/qy/auth/weaver/app.do`) + `weaver.sso.appId`;通知另见 `weaver.notify.*`。
 
 ---
 
 ## Platform SPI map (MessageWebServiceDao)
 
-| Method | YH PORTAL | Weaver isremark |
-|--------|-----------|-----------------|
-| `addMessage` | 002 | 0 |
-| `addCompletedMessage` | 006 then 003 | 2; flow end → 4 |
-| `removeMessage` | 007 | -1 |
-| `updateMessages` | 011 / 012 | 8 (+ viewtype) |
+| Method | YH PORTAL | Weaver 待办 isremark | BaoYPro BMC 通知 |
+|--------|-----------|----------------------|------------------|
+| `addMessage` | 002 | 0(type 含「通知」则跳过) | — |
+| `addCompletedMessage` | 006 then 003 | 2; flow end → 4 | — |
+| `removeMessage` | 007 | -1 | — |
+| `updateMessages` | 011 / 012 | (历史方案 isremark=8,BaoY **不用**) | **sendMsg**(未读);已读不回写 |
 
 ---
 

+ 18 - 10
单点_待办skill/kdshrpmsg/weaver-baoy-plan.md

@@ -104,36 +104,44 @@ weaver.wf.delayMs=3000
 ## SPI (BaoYPro)
 
 Package: `com.kingdee.eas.custom.weaverTodo`  
-Facade: `WeaverMessageWebServiceDao` → `WeaverTodoMessageServer` / ToBeRead path as implemented.
+Facade: `WeaverMessageWebServiceDao` → `WeaverTodoMessageServer` / `WeaverToBeReadMessageServer`。
 
 ```
-addMessage            → isremark=0
-addCompletedMessage   → isremark=2; flow end → 4
-removeMessage         → isremark=-1
-updateMessages        → isremark=8 (+ viewtype)
+addMessage            → isremark=0(type 含「通知」则跳过)
+                      顺序:短轮询 AssignmentId → saveAssignRecord → delayForWf → push
+                      (禁止:先长 sleep 再查 assignId)
+addCompletedMessage   → 可先 delayForWf;isremark=2; flow end → 4
+removeMessage         → 可先 delayForWf;isremark=-1
+updateMessages(未读)  → 消息中心 sendMsg(BMC 通知;不是 isremark=8)
+updateMessages(已读)  → 跳过(不回写泛微)
 ```
 
-CallLog: empty `requestparams` often means exception path (e.g. authorize HTTP 502) — check server logs, not only CallLog.
+CallLog(`OAMsgCallLog`):`RequestUrl` / 完整 `ResponseResult`;空 `RequestParams` 多为异常未发出请求——先看 server 日志(authorize Sentinel 等)。
+
+OpenAPI token:JVM 级缓存 + authorize 对 Sentinel/`-10` 有限重试。
+`weaver.wf.delayMs`:新增待办用于「短轮询预算 + 落库后再 sleep」;不是「一上来先睡再查 id」。
 
 ## Checklist BaoYPro
 
 ```
-- [x] OpenAPI token + receiveRequestInfo
+- [x] OpenAPI token + receiveRequestInfo(待办)
+- [x] BMC 通知 sendMsg(测试已通过;勿再用 isremark=8)
 - [x] WeaverMessageWebServiceDao + relative pcurl via weaver entryPath
 - [x] flowid=procInstId; receivets; sanitize title
 - [x] weaver.sso.entryPath configurable
+- [x] notify 测试/生产 moduleId+eventId 分环境
 - [ ] 泛微后台:异构系统 / IP 白名单 / 流程类型开关
 - [ ] SPI 产品注册 + deploy jar/properties to EAS_HOME
-- [ ] E2E: 0→2→4; 待阅; delete -1
+- [ ] E2E: 待办 0→2→4; delete -1
 - [ ] Compensation via CallLog (optional)
-- [ ] 切生产:见项目 docs/integration/go-live-prod.html + skill Go-live
+- [ ] 切生产:见项目 docs/integration/go-live-prod.html + skill Go-live(含 notify 生产 module/event)
 ```
 
 ## Diff vs YHPro PORTAL-*
 
 | Item | YHPro | BaoY Weaver E10 |
 |------|-------|-----------------|
-| APIs | Many PORTAL URLs | One receiveRequestInfo (+ oauth) |
+| APIs | Many PORTAL URLs | 待办 receiveRequestInfo;通知 sendMsg |
 | Auth | HTTP Basic | OAuth2 → access_token |
 | Links | Absolute HTTPS OK | **Relative path only** |
 | SSO wrap | `/shr/api/oAToSHR?redirect=` | `/qy/auth/weaver/app.do?appId&redirect=` |

+ 9 - 3
单点_待办skill/kdshrpmsg/yhpro-reference.md

@@ -21,19 +21,25 @@ src/com/kingdee/eas/custom/synctask/
 
 Config: `{EAS_HOME}/server/properties/yuhu/receiveOAConfig.properties`
 
-## Todo flow
+## Full flow + failure triage (read this)
+
+**[yhpro-todo-toread-flow.md](yhpro-todo-toread-flow.md)** — 待办/待阅支线、日志关键字、空参与竞态排查。
+
+## Todo flow (short)
 
 addMessage: sleep 8s -> assignId -> addFlow(op=1) -> assignReadRecord
 addCompletedMessage: sleep 8s -> if task terminal updateAgency(op=4) -> if flow terminal updateFlow(op=2)
 removeMessage: sleep 9s -> deleteAgency(op=3)
 
+**BaoYPro / Weaver 注意**:新增待办顺序与上面不同——先短轮询 AssignmentId → `saveAssignRecord` → 再 `delayForWf` → push。详见主 skill「新增待办与 wf.delayMs 执行顺序」。不要把 YH 的「先长 sleep 再查 id」原样搬到 BaoY。
+
 ## Status map
 
 COMPLETED->complete; REJECTED/ROLLBACKED->refuse; CANCELED/TERMINATED->cancel; ABORTED->revoke; else approving
 
-## ToBeRead
+## ToBeRead (short)
 
-bosType 41C9B39B; UNREADED->011; READED->012; BMCMessageControllerBeanEx hooks create
+bosType 41C9B39B; 抄送(bizType 0/80)补链; 无URL→钉钉飞书; UNREADED->011; READED->012; BMCMessageControllerBeanEx hooks create
 
 ## Deep links
 

+ 280 - 0
单点_待办skill/kdshrpmsg/yhpro-todo-toread-flow.md

@@ -0,0 +1,280 @@
+# YHPro ���� / ��������ҵ���ֲ�
+
+�����YH��SHR �� �Ż� ESB��PORTAL-*�����͡�ʧ��ʱ�ȶ��ձ��ġ���־�ؼ��� �� ֧�� �� ԭ�򡹣��ٲ� `OAMsgCallLog`��
+
+��ش��룺
+
+| ��ɫ | �� |
+|------|-----|
+| SPI ��� | `OAMessageWebServiceDao` |
+| ���Ĵ��� | `BMCMessageControllerBeanEx` |
+| �����߼� | `TodoMessageServer` |
+| �����߼� | `ToBeReadMessageServer` |
+| ���� | `MessageCommonUtil` / `MessageConfig` |
+| ���� | `{EAS_HOME}/server/properties/yuhu/receiveOAConfig.properties` |
+
+�ӿ��ֶμ�ͬĿ¼ `portal-api-map.md`��
+
+---
+
+## 1. ������������������
+
+```
+ƽ̨��Ϣ����
+    ��
+    ���������� A �� ���������졿MessageWebServiceDao��ƽ̨ SPI��
+    ��      OAMessageWebServiceDao
+    ��         ���� addMessage          �� ��������     PORTAL-002
+    ��         ���� addCompletedMessage �� �Ѱ�/���    PORTAL-006 / 003
+    ��         ���� removeMessage       �� ɾ������     PORTAL-007
+    ��
+    ���������� B �� BMC ֪ͨ���ġ�BMCMessage ��⹳��
+           BMCMessageControllerBeanEx._addnew / _addnewBatchData
+              �� OAMessageWebServiceDao.updateMessages
+                 �� ToBeReadMessageServer
+                    ���� �� URL �� ������/���飨�����Ż����ģ�
+                    ���� UNREADED �� PORTAL-011 ��������
+                    ���� READED   �� PORTAL-012 ����ת����
+```
+
+- **����**��������������Assign���ߣ��� `assignId` / `procInstId` / `billId`��
+- **����**���� BMC ֪ͨ��Ϣ�ߣ���Ϣ bosType ������ `41C9B39B`��
+- ��������**��ͬʱ����**�������ύ�������������� + ���ʹ��ļ���ͬ�룩�����ʹ���ƴ���������� `assignReadRecord`���ñ���**���������ɹ���**д�� �� ���ھ�̬������ 5 �ڣ���
+
+---
+
+## 2. ���� A������ �� ʲô��Ϣ����������
+
+| ƽ̨��ʱ���� | Dao ���� | ҵ���� | �Ż��ӿ� |
+|--------------|----------|----------|----------|
+| �����´������� | `addMessage(WSMessage)` | ��������+���� | **PORTAL-002** `addFlow` op=1 |
+| �������/����/���ϵ� | `addCompletedMessage(WfrAssignMessage)` | �ȿ���ת�Ѱ죬�ٿ��ܸ������� | **006** �� **003** |
+| ����ɾ��/�ջ� | `removeMessage(assignId, ctx)` | ɾ�Ż����� | **PORTAL-007** op=3 |
+
+### 2.1 �������� `addMessage`��PORTAL-002��
+
+**��־������** `�����ƶ���ʼ` �� `*********�����������Ϳ�ʼ��msgID=...��**********` �� `����id...` �� `��������ؼ�����...`
+
+**���裺**
+
+1. sleep **8 ��**���ȹ�����״̬�ȶ���
+2. `getAssignIdByMsgId(msgID)` �� �� AssignRead������ `assignId=null`���� warn���� NPE��
+3. ȡ `billId`��`procInstId`������ WSMessage��
+4. ����־��`��������ؼ�����` / ���п��� `����������ڿղ���`
+5. ��� `addFlow`��`instanceId=procInstId`��`taskList[].taskId=assignId`
+6. ���Ż������۳ɰ�����д�� `assignReadRecord`��`addMsgId`��
+7. д `OAMsgCallLog`�������Ƿ� `returnStatus=S`
+
+**�ղ��Ի������**��������Ĭ�� URL�������Ż�����������ֶξ��ա�
+
+### 2.2 ���/״̬���� `addCompletedMessage`��006 + 003��
+
+**��־������** `*********����״̬���¿�ʼ��assignId=...��**********`
+
+**˫״̬��**
+
+| ��Դ | ���� | ��; |
+|------|------|------|
+| ����ʵ�� `ProcInstInfo.state` | `wfStateStr` | �Ƿ�� **PORTAL-003** �������� |
+| ���� `WfrAssignMessage.state` �� `WfAssignmentState` | `taskStatus` | �Ƿ��ȵ� **PORTAL-006** ����ת�Ѱ� |
+
+**״̬ӳ�䣨���룩��**
+
+| SHR | �� flowStatus / taskStatus |
+|-----|------------------------------|
+| COMPLETED | complete |
+| REJECTED / ROLLBACKED | refuse |
+| CANCELED / TERMINATED | cancel |
+| ABORTED | revoke |
+| ���� | approving |
+
+**�ս�״̬��** `complete` / `cancel` / `refuse` / `revoke`
+
+**֧�ߣ�**
+
+```
+procInstId �գ� �� ֱ��ʧ�ܣ��򡸴���ת�Ѱ�ʧ�ܣ�procInstIdΪ�ա���
+    ��
+    ���� sleep 8s �� ������״̬ �� wfStateStr
+    ���� ��������״̬ �� taskStatus������ʧ���� return��
+    ��
+    ���� taskStatus ���ս᣿
+    ��     �� �� PORTAL-006 updateAgency������ת�Ѱ죩
+    ��           ʧ�������� return false�����ٵ� 003��
+    ��
+    ���� wfStateStr ���ս᣿
+          �� �� PORTAL-003 updateFlow�����̰��/���ϵȣ�
+          �� �� ���� 003���������ܷ��� false��isOk Ĭ�� false��
+```
+
+ע�⣺�����սᵫ����δ�ս�ʱ��ֻ�� 006�������ս���� 003��
+
+### 2.3 ɾ������ `removeMessage`��PORTAL-007��
+
+**��־������** `*********ɾ���������Ϳ�ʼ��assignId=...��**********`
+
+1. sleep **9 ��**
+2. �� `assignId` ���� `msgId`��`procInstId`���� `assignReadRecord`��
+3. �� `deleteAgency`��`taskIdList=assignId`
+
+`assignId` �� `context` �� �� ֱ�� warn ���أ������Ż���
+
+---
+
+## 3. ���� B������ �� ʲô��Ϣ������֧��
+
+### 3.1 ˭����
+
+| ��� | ʱ�� | ����״̬ |
+|------|------|----------|
+| `BMCMessageControllerBeanEx._addnew` / `_addnewBatchData` | BMC ֪ͨ**�½�** | �̶� `MsgStatus.UNREADED` |
+| ��ƽ̨����·������ `updateMessages`�� | �Ѷ��� | `READED` �� |
+
+**��־������** `���������������` / `���������������` / `����֪ͨ��ʼ` / `*********֪ͨ����Ϣ������ʼ*********`
+
+### 3.2 ��բ��
+
+```
+bosType == 41C9B39B��
+  �� �� ����֪ͨ����Ϣ������������
+  �� �� �������������
+```
+
+### 3.3 ������֧�ߣ��� URL / �� URL��
+
+����Ĭ�� `pcUrl`��`mobileUrl`���� `/qy/auth/V2/sso`����
+
+#### ֧�� B1�����Ͳ�������ѡ��
+
+����ͬʱ���㣺
+
+- `bizType` ֵ = **0 �� 80**
+- `category` ��
+- `bizCustomizedFlag` ��
+
+**��־��** `����������Ϣ,��URL,����URL` + `֪ͨ����ϢbosType��WFATINST|WFPCINST`
+
+| source ���� | ��Ϊ |
+|-------------|------|
+| **WFATINST** | �ʵ�� �� `procInstId` �� �� `assignReadRecord` �� �� assignID/billId ƴ�������� |
+| **WFPCINST** | ����ʵ�� �� �� Assign ���� �� �� assign/bizObj ƴ�� |
+| ���� / �鲻�� | ����Ĭ�ϴ��� URL�����ж� |
+
+��־��`����WFATINSTƴ������` / `����WFPCINSTƴ������`���� `assignID=null` �ȣ���
+
+#### ֧�� B2�������� URL
+
+`msgInfo.url` �Կգ�
+
+- ״̬���� **READED** �� ֱ�� false�����ƣ�
+- ���� �� **ֻ�ƶ��� + ����**������ PORTAL-011  
+  ��־��`��URL�����Ͷ���`
+
+#### ֧�� B3���� URL �� �Ż�����
+
+| msgStatus | ��Ϊ | �ӿ� |
+|-----------|------|------|
+| **UNREADED** | �������ģ��� `bizCustomizedFlag=PERFWEB` ��������ƶ��˼�Ч���� | **PORTAL-011** |
+| **READED** | ����ת���� | **PORTAL-012** |
+| ���� | ����֧�ֵ���Ϣ״̬�� | �� |
+
+��Ч PERFWEB �ӷ�֧��
+
+- ���⺬����Ч���Һ������ȷ�ϡ������ߡ��� ���ȷ��ҳ
+- ������Ч �� ��Ч go ҳ
+
+---
+
+## 4. ��Ա�������������������ã�
+
+| �� | ���� |
+|----|------|
+| �Ż���Ա | `cfidmusercode`��`getPersonCode`�����鲻�� **�״�**����Ա��ϢΪ�ա� |
+| PC ������ assignId | `/shr/api/oAToSHR?redirect=...transferApprove.jsp?AssignmentId&billID` |
+| PC �� assignId | Ĭ�Ϲ�������ͼ `shr.workflow.view`����־��`getPcUrlCore ��assignId����Ĭ��URL`�� |
+| �ƶ��� assignId | MBOS ���� URL �� `/qy/auth/V2/sso` |
+| �ƶ��� assignId | MBOS �����б� �� SSO����־��`getAppUrlCoreV2 ��assignId`�� |
+
+---
+
+## 5. ���뾺̬
+
+### assignReadRecord
+
+| �ֶ� | ���� |
+|------|------|
+| MsgId | ������Ϣ ID |
+| assignID | ���� ID���� taskId�� |
+| billId | ���� ID |
+| procInstId | ����ʵ�� ID |
+| workflowname | ������ |
+
+- **д�룺** �������� `addMessage` ĩβ `addMsgId`
+- **��ȡ��** ���ʹ��� WFATINST��ɾ��/���ʱ����
+
+**��̬��** ���ʹ��������ڴ���д������־����� `assignID=null` / `getDataByProcInstId �޼�¼`��������Ĭ�ϣ������Կ����Ƴɹ������ȥ���ǵ���ҳ��
+
+### OAMsgCallLog
+
+��ÿ���Ż����õ�����/��Ӧ/״̬��ʧ���Ȳ�˱���
+
+---
+
+## 6. ʧ���Ų��ٲ��
+
+����־�ؼ��ֶ�λ֧�ߣ��ٶ�ԭ��
+
+| ��־�ؼ��� | ����֧�� | ����ԭ�� |
+|------------|----------|----------|
+| `�����ƶ���ʼ` + `������������ʧ��` | A �� 002 | �����������쳣�����Ż� returnStatus��S |
+| `����������ڿղ���` / `procInstId=null` | A �� 002 | WSMessage δ������ʵ����AssignRead �� assignId |
+| `��msgId��ȡassignIdΪ��` | A �� 002 ȡ���� | AssignRead δ������sleep ��������ǹ�������Ϣ |
+| `��Ա��ϢΪ��` / getPersonCode | A/B ��� | �û�δ�� `cfidmusercode` |
+| `����״̬���¿�ʼ` | A �� 006/003 | �� |
+| `����ת�Ѱ�ʧ�ܣ�procInstIdΪ��` | A �� ��� | WfrAssignMessage ������ ID |
+| `����״̬�޷�����` | A �� ��� | state ö�ٶԲ��� |
+| `����ת�Ѱ�ʧ��`��006�� | A �� 006 | �Ż����� / ��Ӧ�� |
+| `ɾ���������Ϊ��` | A �� 007 | assignId �� ctx �� |
+| `������������` / `����֪ͨ��ʼ` | B | �� |
+| `��֪ͨ����Ϣ������` | B բ�� | bosType �� 41C9B39B |
+| `����������Ϣ` + `WFATINST` | B1 ���� | �� |
+| `���͡�ƴ������` �� `=null` | B1 | ��̬���� assignReadRecord |
+| `��URL�����Ͷ���` | B2 | ֻ�� IM���Ż��޴��� |
+| `֪ͨ����Ϣ����ʧ��` | B ���� | ����ջ���ֶ�Ϊҵ��/���ö��� assign toString |
+| `getPcUrlCore ��assignId` | ���� | Ԥ����Ĭ��ҳ����һ��ʧ�� |
+| OAMsgCallLog Status�ٳɹ� | �Ż��� | �� request/response �� portal-api-map |
+
+### �Ƽ��Ų�˳��
+
+1. ��־������ `��������` ���� `֪ͨ����Ϣ` / `��������` �� ������ A �� B  
+2. �� `paramDesc` �����־��`xxx=null` / `xxx=""` �� �����ֶ�  
+3. �� `OAMsgCallLog` �Ƿ��ѵ��Ż���������  
+4. ���ʹ������Ӳ��� �� ��ͬ���Ƿ��д��졢`assignReadRecord` �Ƿ��и� `procInstId`  
+5. ��Ա���� �� �������/������ IDM ����  
+
+---
+
+## 7. ��ʱ������ֵ��
+
+| ���� | sleep |
+|------|-------|
+| �������� | 8s |
+| ���/���̸��� | 8s |
+| ɾ������ | 9s |
+
+Ŀ�ģ��� SHR ������������ȡ AssignRead / ����״̬�������׳��� `assignId`/`procInstId` �ա�
+
+---
+
+## 8. һ�仰����
+
+| ����ȷ�ϡ� | ����־ / �� |
+|-----------|-------------|
+| �Dz����´������Ż��� | `�����������Ϳ�ʼ` + PORTAL-002 |
+| �Dz��ǰ�᣿ | `����״̬����` + �Ƿ��� 006 �� 003 |
+| �Dz���ɾ���죿 | `ɾ���������Ϳ�ʼ` |
+| �Dz����´��ģ� | `�����������` + `֪ͨ����Ϣ` + bosType 41C9B39B |
+| �Dz��dz��Ͳ����� | `����������Ϣ` + WFATINST/WFPCINST |
+| �Dz���ֻ�ƶ���û�Ż��� | `��URL�����Ͷ���` |
+| �ĸ������գ� | `��������ؼ�����` / `paramDesc` / `=null` |
+| �Ż����׻���ɶ�� | `OAMsgCallLog` |

+ 10 - 7
单点_待办skill/kdshrsso/SKILL.md

@@ -14,7 +14,7 @@ description: >-
 
 # Kingdee SSO — Implementation Patterns
 
-Pick **one primary pattern** per SHR SSO integration. Do not mix servlet Ltpa login with filter `_const_cas_assertion_` on the same entry without explicit design.
+Related message push: skill **kdshrpmsg**. Coding discipline (ask when unsure, no API guessing): skill **kdshr-dev**.
 
 ---
 
@@ -47,20 +47,23 @@ Reply with a short **目标确认表**(模式、入口 URL、callback、映射
 
 ## Go-live / 切生产 (mandatory when user says 上线)
 
-For **BaoYPro-style B-qy**, do not silently rewrite files. First ask for production SHR URL, ecology URL, appId/secret, dataCenter. Then list and edit:
+For **BaoYPro-style B-qy**, do not silently rewrite files. First ask for production SHR URL, ecology URL, appId/secret, dataCenter. Then list and edit.
+
+BaoY 生产 SHR(已确认):`https://hr.szpgm.com/shr`。覆盖包:`D:\workcode\baoye\patch\正式单点部署`(测试包:`...\test单点部署`)。
 
 | # | File / config | Change |
 |---|---------------|--------|
 | 1 | `set-server-env.sh` (or equiv.) | `ENV=prod` → `INTEGRATION_CONFIG_PATH=.../integration-config-prod.json`;重启 |
 | 2 | `integration-config-prod.json` | `shrServerName`、`dataCenter`、`allowedPrefixes`、`appId`/`appSecret`、`callbackUrl`、`weaver.ecologyUrl` |
-| 3 | `login_withForm.js` | `ENV_CONFIG.prod.*` + `getCurrentEnv()` 识别生产 Host |
-| 4 | `logoutHR.js` | `prod.shrBaseUrl` / `weaverAppId`;`currentEnv='prod'`(或按 host) |
-| 5 | 泛微后台 | 生产 callback 注册;**退出 service 白名单** = 生产 SHR 根地址 |
+| 3 | `login_withForm.js` | `ENV_CONFIG.prod.*` + `getCurrentEnv()` 识别生产 Host(如 `hr.szpgm.com`) |
+| 4 | `logoutHR.js` | `prod.ecologyUrl`(正式泛微)+ `currentEnv='prod'` |
+| 5 | 泛微后台 | 生产 callback 注册;**退出 service 白名单** = 生产 SHR |
+| 6 | (待办同期)见 **kdshrpmsg** | `weaverTodoConfig`、`WSConfig.xml` dataCenter、notify module/event、DDL/jar |
 
-Also remind **kdshrpmsg** go-live for `weaverTodoConfig.properties`.  
+Also remind **kdshrpmsg**「配置文件清单」——新项目漏配要主动提问。  
 Full checklist: project `docs/integration/go-live-prod.html`.
 
-验收:生产门户登录、退出后再进 SHR、地址栏无测试 IP。
+验收:生产门户登录、退出后再进 SHR、地址栏无测试 IP;待阅见 kdshrpmsg
 
 ---
 

+ 10 - 7
单点_待办skill/金蝶SHR-Skills使用说明.md

@@ -1,11 +1,12 @@
-# 金蝶 EAS/sHR 对接 Skills 使用说明
+                                      # 金蝶 EAS/sHR 对接 Skills 使用说明
 
-本压缩包包含两个 Agent Skill(`SKILL.md` 标准目录),可用于 **Cursor** 与 **Claude Code**,指导 AI 在金蝶 EAS/sHR 上做 **单点登录(SSO)** 与 **待办/待阅推送**。
+本压缩包包含 Agent Skill(`SKILL.md` 标准目录),可用于 **Cursor** 与 **Claude Code**,指导 AI 在金蝶 EAS/sHR 上做 **单点登录(SSO)**、**待办/待阅推送** 与 **二开编码纪律**。
 
 | 目录 | Skill 名 | 用途 |
 |------|----------|------|
 | `kdshrsso/` | **kdshrsso** | 单点登录:泛微 E10 / 钉钉 / IDM / Azure 等对接 sHR |
-| `kdshrpmsg/` | **kdshrpmsg** | 待办、待阅推送:泛微统一待办 / 门户 PORTAL-* 等 |
+| `kdshrpmsg/` | **kdshrpmsg** | 待办、待阅推送:泛微统一待办 / 消息中心 sendMsg / 门户 PORTAL-* 等 |
+| `kdshr-dev/` | **kdshr-dev** | 二开纪律:不确定先问、禁止乱猜平台 getter / 多余兜底 |
 
 两者常一起用:待办深链要能登录,入口须与 SSO 一致。  
 **同一套文件夹内容**可分别拷到 Cursor 或 Claude Code 的 skills 目录(路径不同,见下)。
@@ -41,12 +42,14 @@ skills/                    ← 上面表里对应的 .cursor 或 .claude 下的
 ├── kdshrsso/
 │   ├── SKILL.md           ← 必需
 │   └── *.md               ← 参考文档,随目录一起拷贝
-└── kdshrpmsg/
-    ├── SKILL.md
-    └── *.md
+├── kdshrpmsg/
+│   ├── SKILL.md
+│   └── *.md
+└── kdshr-dev/
+    └── SKILL.md
 ```
 
-同时用 Cursor 和 Claude Code 时:把 `kdshrsso`、`kdshrpmsg` **各拷一份**到 `.cursor/skills/` 与 `.claude/skills/`,或项目内两套路径各放一份。
+同时用 Cursor 和 Claude Code 时:把上述 skill 目录 **各拷一份**到 `.cursor/skills/` 与 `.claude/skills/`,或项目内两套路径各放一份。
 
 ### 1.4 注意