GetPluralityInfoOsfService.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. package com.kingdee.eas.custom.szpgm.osf.service;
  2. import com.kingdee.bos.BOSException;
  3. import com.kingdee.bos.Context;
  4. import com.kingdee.bos.bsf.service.app.IHRMsfService;
  5. import com.kingdee.eas.common.EASBizException;
  6. import com.kingdee.eas.util.app.DbUtil;
  7. import com.kingdee.jdbc.rowset.IRowSet;
  8. import org.apache.commons.lang3.StringUtils;
  9. import java.text.SimpleDateFormat;
  10. import java.util.*;
  11. /**
  12. * 员工兼职信息查询OSF服务
  13. * <p>
  14. * 实现 {@link IHRMsfService} 接口,提供员工兼职信息的分页查询功能。
  15. * 支持按员工编号(精确)、最后修改时间进行过滤筛选。
  16. * 同一员工存在多个上级时,会将上级编码合并到一个列表中返回。
  17. * </p>
  18. *
  19. * <h3>关联数据表</h3>
  20. * <ul>
  21. * <li><b>T_HR_PluralityAddBizBillEntry</b>(pae)—— 员工加入兼职单分录表,主表</li>
  22. * <li><b>T_HR_PluralityAddBizBill</b>(pa)—— 员工加入兼职单表,获取审批状态</li>
  23. * <li><b>T_HR_PluralityDelBizBillentry</b>(pde)—— 员工移除兼职单分录表,获取结束日期</li>
  24. * <li><b>T_HR_PluralityDelBizBill</b>(pd)—— 员工移除兼职单表,获取移除审批状态</li>
  25. * <li><b>t_bd_person</b>(pe)—— 员工基础信息表,取员工编号</li>
  26. * <li><b>t_org_position</b>(pos)—— 岗位表,取兼职岗位信息</li>
  27. * <li><b>t_org_admin</b>(org)—— 行政组织表</li>
  28. * <li><b>T_HR_EMPPOSTEXPERIENCEHIS</b>(phis)—— 岗位任职历史表,通过上级岗位取当前上级</li>
  29. * <li><b>t_bd_person</b>(ppe)—— 员工基础信息表(二次关联,取上级员工信息)</li>
  30. * </ul>
  31. *
  32. * <h3>入参说明</h3>
  33. * <ul>
  34. * <li><b>modifyDate</b>(String,可选)—— 最后修改日期(格式yyyy-MM-dd),筛选该日期之后修改的兼职记录(增量同步场景)</li>
  35. * <li><b>startDate</b>(String,可选)—— 生效开始日期(格式yyyy-MM-dd),筛选在该日期之后仍有效的兼职记录,即兼职未结束或结束日期>=此日期</li>
  36. * <li><b>endDate</b>(String,可选)—— 生效结束日期(格式yyyy-MM-dd),筛选兼职开始日期<=此日期的记录</li>
  37. * <li><b>personCode</b>(String,可选)—— 员工编号,精确匹配</li>
  38. * <li><b>page</b>(int/String,可选,默认1)—— 页码</li>
  39. * <li><b>size</b>(int/String,可选,默认100,上限1000)—— 每页条数</li>
  40. * </ul>
  41. *
  42. * <h3>返回结果</h3>
  43. * <pre>{@code
  44. * {
  45. * "msg": "成功",
  46. * "code": "200",
  47. * "page": 1,
  48. * "size": 100,
  49. * "data": [{
  50. * "id": "分录主键",
  51. * "personid": "员工ID",
  52. * "personCode": "员工编号",
  53. * "startDate": "兼职开始日期(yyyy-MM-dd)",
  54. * "endDate": "兼职结束日期(yyyy-MM-dd),未结束则为2199-12-31",
  55. * "orgCode": "兼职组织编码",
  56. * "posCode": "兼职岗位编码",
  57. * "leader": ["上级员工编码列表"],
  58. * "description": "兼职备注"
  59. * }]
  60. * }
  61. * }</pre>
  62. *
  63. * <h3>业务规则</h3>
  64. * <ul>
  65. * <li>只查询加入兼职单审批通过的记录(pa.fbillstate=3)</li>
  66. * <li>兼职结束日期:只有移除兼职单审批通过(pd.fbillstate=3)时才取实际结束日期,否则返回"2199-12-31"表示未结束</li>
  67. * </ul>
  68. *
  69. * @author szpgm
  70. * @since 2026/07/02
  71. */
  72. public class GetPluralityInfoOsfService implements IHRMsfService {
  73. /**
  74. * 处理员工兼职信息查询请求
  75. *
  76. * @param context EAS上下文对象,用于执行数据库查询
  77. * @param map 入参Map,包含过滤条件和分页参数
  78. * @return Map 包含查询结果、状态码和分页信息
  79. * @throws EASBizException 业务异常
  80. * @throws BOSException 平台异常
  81. */
  82. @Override
  83. public Object process(Context context, Map map) throws EASBizException, BOSException {
  84. // 返回结果容器
  85. Map<String, Object> result = new HashMap<String, Object>();
  86. List<Map<String, Object>> list = new ArrayList<>();
  87. // ==================== 入参解析 ====================
  88. // 最后修改日期(格式:yyyy-MM-dd),同时检查加入兼职表和移除兼职表(增量同步场景)
  89. String modifyDate = (String) map.get("modifyDate");
  90. // 生效开始日期(格式:yyyy-MM-dd),筛选兼职未结束或结束日期>=此日期的记录
  91. String startDateStr = (String) map.get("startDate");
  92. // 生效结束日期(格式:yyyy-MM-dd),筛选兼职开始日期<=此日期的记录
  93. String endDateStr = (String) map.get("endDate");
  94. // 员工编号(精确匹配)
  95. String code = (String) map.get("personCode");
  96. // 每页条数(默认100,上限1000)
  97. int maxSize = 1000;
  98. int size = 100;
  99. if (map.get("size") != null) {
  100. if (map.get("size") instanceof Integer) {
  101. size = (int) map.get("size");
  102. } else if (map.get("size") instanceof String) {
  103. size = Integer.parseInt((String) map.get("size"));
  104. }
  105. size = Math.min(maxSize, size);
  106. }
  107. // 页码(默认1)
  108. int page = 1;
  109. if (map.get("page") != null) {
  110. if (map.get("page") instanceof Integer) {
  111. page = (int) map.get("page");
  112. } else if (map.get("page") instanceof String) {
  113. page = Integer.parseInt((String) map.get("page"));
  114. }
  115. }
  116. // ==================== 查询数据 ====================
  117. try {
  118. // 初始化成功响应
  119. result.put("msg", "成功");
  120. result.put("code", "200");
  121. result.put("page", page);
  122. result.put("size", size);
  123. result.put("data", list);
  124. // 构建基础SQL,动态追加过滤条件
  125. StringBuilder sql = getSql();
  126. // SQL参数列表(与?占位符一一对应)
  127. List<Object> paramArr = new ArrayList<>();
  128. // 修改日期过滤(增量同步):加入兼职单或移除兼职单的最后修改时间 >= 指定日期
  129. if (StringUtils.isNotBlank(modifyDate)) {
  130. sql.append(" and (pa.flastupdatetime >= ? or pd.flastupdatetime >= ?) ");
  131. paramArr.add(modifyDate);
  132. paramArr.add(modifyDate);
  133. }
  134. // 生效开始日期过滤:筛选在该日期之后仍有兼职效力的记录
  135. // 即:移除兼职单未审批通过(兼职未结束,视为2199-12-31),或结束日期 >= startDate
  136. if (StringUtils.isNotBlank(startDateStr)) {
  137. sql.append(" and (pd.fbillstate is null or pd.fbillstate != 3 or pde.fbizdate >= ?) ");
  138. paramArr.add(startDateStr);
  139. }
  140. // 生效结束日期过滤:筛选兼职开始日期 <= endDate 的记录
  141. if (StringUtils.isNotBlank(endDateStr)) {
  142. sql.append(" and pae.fbizdate <= ? ");
  143. paramArr.add(endDateStr);
  144. }
  145. // 员工编号精确匹配
  146. if (StringUtils.isNotBlank(code)) {
  147. sql.append(" and pe.fnumber = ? ");
  148. paramArr.add(code);
  149. }
  150. // 分页:LIMIT 返回条数 OFFSET 跳过条数
  151. sql.append(" order by pae.fpersonid desc LIMIT ? OFFSET ? ");
  152. paramArr.add(size);
  153. paramArr.add(((page - 1) * size));
  154. // 执行SQL查询
  155. IRowSet rs = DbUtil.executeQuery(context, sql.toString(), paramArr.toArray());
  156. // 以员工ID为key的Map,用于合并同一员工的多条记录(一个员工可能有多个上级)
  157. Map<String, Map<String, Object>> maps = new HashMap<>();
  158. // 日期格式化器(yyyy-MM-dd)
  159. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  160. // 遍历结果集,将同一员工的多行数据合并为一条记录
  161. while (rs.next()) {
  162. String id = rs.getString("personid");
  163. Map<String, Object> param = new HashMap<>();
  164. // Map<String, Object> param = maps.get(id);
  165. // // 首次遇到该员工时,初始化记录并创建上级编码列表
  166. // if (null == param) {
  167. // param = new HashMap<>();
  168. // List<String> leaderIds = new ArrayList<>();
  169. // param.put("leader", leaderIds);
  170. // maps.put(id, param);
  171. // }
  172. // // 收集该员工的所有上级编码(多行数据的共同字段会覆盖,但值相同)
  173. // List<String> leader = (List<String>) param.get("leader");
  174. // leader.add(rs.getString("leader"));
  175. // ---- 员工基本信息 ----
  176. param.put("id", rs.getString("id"));
  177. // 员工ID
  178. param.put("personid", id);
  179. String leaderStr = rs.getString("leader");
  180. List<String> leaders = new ArrayList<>();
  181. if(StringUtils.isNotBlank(leaderStr)){
  182. leaders.addAll(Arrays.asList(leaderStr.split(",")));
  183. }
  184. param.put("leader", leaders);
  185. param.put("personCode", rs.getString("personCode"));
  186. // ---- 兼职日期(Date -> String 格式化) ----
  187. // 兼职开始日期
  188. Date startDate = rs.getDate("startDate");
  189. param.put("startDate", (null != startDate ? sdf.format(startDate) : null));
  190. // 移除兼职单审批状态(3表示审批通过)
  191. int pdbillstate = rs.getInt("pdbillstate");
  192. // 兼职结束日期:只有移除兼职单审批通过时才取实际结束日期,否则返回"2199-12-31"表示兼职未结束
  193. Date endDate = rs.getDate("endDate");
  194. param.put("endDate", (null != endDate && 3 == pdbillstate ? sdf.format(endDate) : "2199-12-31"));
  195. // ---- 组织与岗位 ----
  196. // 兼职所属组织编码
  197. param.put("orgCode", rs.getString("orgCode"));
  198. // 兼职岗位编码
  199. param.put("posCode", rs.getString("posCode"));
  200. // ---- 备注 ----
  201. // 兼职备注(来自加入兼职单分录)
  202. param.put("description", rs.getString("description"));
  203. list.add(param);
  204. }
  205. // 将合并后的员工列表写入返回结果
  206. result.put("data", list);
  207. } catch (Exception e) {
  208. // 异常时返回错误信息
  209. e.printStackTrace();
  210. result.put("msg", e.getMessage());
  211. result.put("code", "500");
  212. }
  213. return result;
  214. }
  215. /**
  216. * 构建员工兼职信息查询的基础SQL
  217. * <p>
  218. * 从员工加入兼职单分录表出发,LEFT JOIN 加入兼职单表获取审批状态,
  219. * LEFT JOIN 移除兼职单分录表和移除兼职单表获取结束日期和移除审批状态,
  220. * LEFT JOIN 员工基础信息表、岗位表、行政组织表获取基本字段,
  221. * LEFT JOIN 岗位任职历史表和员工基础信息表(二次关联)获取当前上级信息。
  222. * 只查询加入兼职单审批通过(pa.fbillstate=3)的记录。
  223. * </p>
  224. *
  225. * <h3>核心关联逻辑</h3>
  226. * <ol>
  227. * <li>pae → pa:关联加入兼职单,获取审批状态,WHERE条件限定 fbillstate=3(审批通过)</li>
  228. * <li>pae → pe:通过 fpersonid 关联员工基础信息表,获取员工编号</li>
  229. * <li>pae → pde:通过 fpersonid 关联移除兼职单分录,获取兼职结束日期</li>
  230. * <li>pde → pd:关联移除兼职单,获取移除审批状态,用于判断结束日期是否有效</li>
  231. * <li>pae → pos:通过 fpositionid 关联岗位表,获取兼职岗位编码</li>
  232. * <li>pae → org:通过 fadminorgid 关联行政组织表,获取兼职组织编码</li>
  233. * <li>pos → phis:通过 pos.fparentid(上级岗位ID)关联任职历史,取当前有效上级(fpositionenddate >= 2199-12-31)</li>
  234. * <li>phis → ppe:关联员工基础信息表(二次),获取上级员工编码</li>
  235. * </ol>
  236. *
  237. * @return 包含完整SELECT语句的StringBuilder(以 WHERE pa.fbillstate=3 结尾,便于后续追加AND条件)
  238. */
  239. private static StringBuilder getSql() {
  240. StringBuilder sql = new StringBuilder();
  241. // /*dialect*/ 标记用于EAS平台识别SQL方言
  242. sql.append(" /*dialect*/ select ");
  243. // ===== SELECT 字段列表 =====
  244. sql.append(" pae.fid id, ");
  245. sql.append(" pae.fpersonid personid, "); // 员工FID
  246. sql.append(" pe.fnumber personCode, "); // 员工编号
  247. sql.append(" pae.fbizdate startDate, "); // 兼职开始日期
  248. sql.append(" pos.fnumber posCode, "); // 兼职岗位编码
  249. sql.append(" org.fnumber orgCode, "); // 兼职所属组织编码
  250. // 使用STRING_AGG将同一上级岗位下的多位领导编码合并为逗号分隔字段(Kingbase/PostgreSQL语法)
  251. sql.append(" (SELECT STRING_AGG(ppe_inner.fnumber, ',' ORDER BY ppe_inner.fnumber) ");
  252. sql.append(" FROM T_HR_EMPPOSTEXPERIENCEHIS phis_inner ");
  253. sql.append(" LEFT JOIN t_bd_person ppe_inner ON ppe_inner.fid = phis_inner.fpersonid ");
  254. sql.append(" WHERE phis_inner.fpositionid = pos.fparentid ");
  255. sql.append(" AND phis_inner.fpositionenddate >= '2199-12-31 00:00:00') leader, ");
  256. // sql.append(" ppe.fnumber leader, "); // 上级员工编码(通过岗位任职历史关联)
  257. sql.append(" pae.fdescription description, "); // 加入兼职单备注
  258. sql.append(" pde.fdescription description2, "); // 移除兼职单备注(查询用,未返回给调用方)
  259. sql.append(" pde.fbizdate endDate, "); // 兼职结束日期(来自移除兼职单)
  260. sql.append(" pd.fbillstate pdbillstate "); // 移除兼职单审批状态(3:审批通过)
  261. // ===== FROM 子句及关联表 =====
  262. sql.append(" from ");
  263. sql.append(" T_HR_PluralityAddBizBillEntry pae "); // 员工加入兼职单分录表(主表)
  264. sql.append(" left join T_HR_PluralityAddBizBill pa on pae.fbillid = pa.fid "); // 加入兼职单表(获取审批状态)
  265. sql.append(" left join T_HR_PluralityDelBizBillentry pde on pde.FPERSONID = pae.fpersonid "); // 移除兼职单分录表(获取结束日期)
  266. sql.append(" left join T_HR_PluralityDelBizBill pd on pd.fid = pde.fbillid "); // 移除兼职单表(获取移除审批状态)
  267. sql.append(" left join t_bd_person pe on pe.fid = pae.fpersonid "); // 员工基础信息表(取员工编号)
  268. sql.append(" left join t_org_position pos on pos.fid = pae.fpositionid "); // 岗位表(取兼职岗位信息)
  269. sql.append(" left join t_org_admin org on org.fid = pae.fadminorgid "); // 行政组织表
  270. // sql.append(" left join T_HR_EMPPOSTEXPERIENCEHIS phis on phis.fpositionid = pos.fparentid "); // 岗位任职历史表(通过上级岗位ID关联)
  271. // sql.append(" and phis.fpositionenddate >= '2199-12-31 00:00:00'"); // 只取当前有效的任职记录
  272. // sql.append(" left join t_bd_person ppe on ppe.fid = phis.fpersonid "); // 上级员工(任职历史中的人员)
  273. // WHERE 条件:只查加入兼职单审批通过的记录,后续可动态追加 AND 条件
  274. sql.append(" where pa.fbillstate=3 ");
  275. return sql;
  276. }
  277. }