Tyx 1 hete
szülő
commit
903d38cb9e

+ 89 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/haos/staff/plugin/task/PositionTree.java

@@ -0,0 +1,89 @@
+package nckd.jimin.jyyy.hr.haos.staff.plugin.task;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PositionTree implements Serializable
+{
+
+    /** 节点ID */
+    private Long id;
+
+    /** 节点名称 */
+    private String label;
+
+    /** 父ID */
+    private Long parentId;
+
+    /** 子节点 */
+    private List<PositionTree> children = new ArrayList<>();
+    private Integer level;
+
+    public PositionTree()
+    {
+
+    }
+
+    public PositionTree(Long id, String label, Long parentId) {
+        this.id = id;
+        this.label = label;
+        this.parentId = parentId;
+    }
+
+    public PositionTree(PositionTree treeSelect)
+    {
+        this.id = treeSelect.getId();
+        this.label = treeSelect.getLabel();
+        this.children = treeSelect.getChildren();
+    }
+
+    public Long getId()
+    {
+        return id;
+    }
+
+    public void setId(Long id)
+    {
+        this.id = id;
+    }
+
+    public Long getParentId()
+    {
+        return parentId;
+    }
+
+    public void setParentId(Long parentId)
+    {
+        this.parentId = parentId;
+    }
+
+    public String getLabel()
+    {
+        return label;
+    }
+
+    public void setLabel(String label)
+    {
+        this.label = label;
+    }
+
+    public Integer getLevel() {
+        return level;
+    }
+
+    public void setLevel(Integer level) {
+        this.level = level;
+    }
+
+    public List<PositionTree> getChildren()
+    {
+        return children;
+    }
+
+    public void setChildren(List<PositionTree> children)
+    {
+        this.children = children;
+    }
+}
+

+ 224 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/haos/staff/plugin/task/SyncAdminOrgTask.java

@@ -0,0 +1,224 @@
+package nckd.jimin.jyyy.hr.haos.staff.plugin.task;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.orm.util.CollectionUtils;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.util.StringUtils;
+import kd.sdk.plugin.Plugin;
+import kd.hr.hbp.business.servicehelper.HRBaseServiceHelper;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 2025-04-16 Tyx
+ * 组织变动数据同步MDM
+ * 1.组织名称变化/组织调整时,需要把该组织下所有岗位及人员也传给MDM
+ * 2.其余变化时,只需要传递组织信息,岗位人员不需要
+ */
+public class SyncAdminOrgTask extends AbstractTask implements Plugin {
+
+    private static final Log log = LogFactory.getLog(SyncAdminOrgTask.class);
+    private static String changeOperDetail_entity = "haos_changeoperdetail";
+    private static String adminOrgDetail_entity = "haos_adminorgdetail";
+    private static String orgChgRecord_entity = "homs_orgchgrecord";
+    private static String orgChgRecord_e_entity = "orgchgentry";
+    private static String startTime = "";
+    private static String endTime = "";
+
+
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        /**
+         * 获取调度作业下配置的参数:
+         * isAuto : 如果配置为是,则取日志中上一次同步时间至今发生变化的组织信息,为否则优先取以下两个手工配置的时间参数
+         * startTime : 手工配置开始时间 yyyy-MM-dd hh24:mi:ss
+         * endTime : 手工配置结束时间 yyyy-MM-dd hh24:mi:ss
+         */
+        log.info("-------- SyncAdminOrgTask 开始执行同步行政组织 --------");
+        //最后调MDM的jsonObject
+        JSONObject ob = new JSONObject();
+        initDateRange(map);
+        HRBaseServiceHelper helper = new HRBaseServiceHelper(orgChgRecord_entity);
+        String selectProperties = "adminorg,orgchgentry.changescene.number,createtime";
+        //查出发生过变化的组织
+        DynamicObjectCollection changeOperateCol = helper.queryOriginalCollection(selectProperties, this.getQFilters());
+        //查询出来为空直接返回
+        if(CollectionUtils.isEmpty(changeOperateCol)) {
+            log.info("-------- 未查询到变化组织 -------- ");
+            SyncUtil.createLog(SyncUtil.v_error, startTime, endTime, "未查询到变化组织", "未调用", SyncUtil.SyncOrg);
+        }
+        else {
+            //发生变化的组织Map
+            //按照变动场景编码分成不同的List,后续根据场景分开处理
+            //ToDo 如果查询时间内,某个组织先发生组织调整,再修改除名称外其他信息怎么处理?
+            //按照组织分组,只取时间段内最新的一条记录
+            //暂时只处理(10,20,30,40,50);
+            //ToDo(80,90)目前前台界面做不了该业务,后续考虑;
+            //ToDo 60尚不明确,需确认;
+            //1010_S	    组织新设
+            //1020_S	    上级调整
+            //1030_S	    组织更名
+            //1040_S	    组织停用
+            //1050_S	    初始化新增
+            //1060_S	    初始化禁用
+            //1080_S	    组织合并
+            //1090_S	    组织拆分
+            //CS_1110_SY01	组织修订
+//            Map<String, List<DynamicObject>> changeOperateMap = (Map)changeOperateCol.stream().collect(Collectors.groupingBy(
+//                            dyx -> dyx.getString("orgchgentry.changescene.number")
+//                    ));
+            //按组织ID汇总
+            Map<Long, DynamicObject> changeOperateMap = (Map)changeOperateCol.stream().collect(Collectors.toMap((dyx) -> {
+                return dyx.getLong("adminorg");
+            }, (dyx) -> {
+                return dyx;
+            }, (key1, key2) -> {
+                return key2;
+            }));
+            log.info("-------- 变化组织数(不去重):" + changeOperateCol.size() + " --------");
+            log.info("-------- 变化组织数(去重):" + changeOperateMap.keySet().size() + " --------");
+            log.info("-------- 变化组织Id:" + changeOperateMap.keySet() + " --------");
+
+            // ToDo 根据变动操作字段获取发生了组织调整的组织,后续需要根据这些组织同步下级岗位和人员
+            //组织历史查询,如果查询时间内发生多次变化,
+            //haos_adminorgdetail 组织历史查询 所属表t_haos_adminorg Id与t_org_org一样
+            selectProperties = "id,boid,number,name,orglongname,level,nckd_easid,parentorg.id,parentorg.name,parentorg.number,establishmentdate,bsed,enable,adminorgtype.name,disabledate,modifytime";
+            QFilter idQFilter = new QFilter("id", "in", changeOperateMap.keySet());
+            DynamicObject[] orgDyArr = new HRBaseServiceHelper(adminOrgDetail_entity).query(selectProperties, new QFilter[]{idQFilter});
+            //处理下长编码/长名称,星瀚内是4VTX1ACRU8A9!4VU/45/K1PJZ!4W5LG9NNW9EW这种格式 -- 直接取行政组织结构里面的长编码
+            Map<Long, DynamicObject> bosOrgMap = SyncUtil.initOrgInfo();
+            log.info("-------- 查询完组织详细信息数:" + orgDyArr.length + " --------");
+            //构建入参
+            buildJSON(ob, orgDyArr, changeOperateMap, bosOrgMap);
+            //获取调用接口地址
+            String url = SyncUtil.getUrl("org_url");
+            log.info("-------- 组织同步调用url : " + url + " --------");
+            //调用接口
+            JSONObject response = SyncUtil.doPostByHttpClient(url, ob);
+            //处理返回结果
+            String status = SyncUtil.dealResponseStatus(response);
+            //记录日志
+            SyncUtil.createLog(status, startTime, endTime, ob.toJSONString(), response.toJSONString(), SyncUtil.SyncOrg);
+        }
+    }
+
+    /**
+     * 构建最后调用MDM接口的参数
+     * @param ob
+     */
+    public void buildJSON(JSONObject ob, DynamicObject[] orgDyArr, Map<Long, DynamicObject> changeOperateMap, Map<Long, DynamicObject> bosOrgMap) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        JSONArray obj = new JSONArray();
+        for(DynamicObject org : orgDyArr) {
+            JSONObject data = new JSONObject(true);
+            //主体标签 默认JY
+            data.put("MAIN_BODY_ID", "JY");
+            //主键 EAS老组织用EASID 否则用星瀚ID
+            String primary_key = StringUtils.isBlank(org.getString("nckd_easid")) ? org.getPkValue().toString() : org.getString("nckd_easid");
+            data.put("ADM_PRIMARY_KEY", primary_key);
+            //长编码
+            data.put("ADM_LONG_CODE", bosOrgMap.get(org.getLong("id")).getString("structure.longnumber"));
+            //组织名称
+            data.put("MD_DESCRIPTION", org.getString("name"));
+            //上级行政组织ID 看是否有easid 有就传EASID 否则传星瀚ID
+            data.put("ADM_PARENT_NUMBER", org.getString("parentorg.id"));
+            //上级组织名称
+            data.put("ADM_PARENT_NAME", org.getString("parentorg.name"));
+            //行政组织长名称 orglongname没值 去平台那边取
+            data.put("ADM_LONG_NAME", bosOrgMap.get(org.getLong("id")).getString("structure.fullname"));
+            //业务板块 默认11-济煜医药
+            data.put("ADM_BUSINESS_SECTOR", "11");
+            //级别
+            data.put("MD_LEVEL", org.getInt("level"));
+            //行政组织类型
+            data.put("ADM_TYPE", org.getString("adminorgtype.name"));
+            //行政组织旧编码,s-HR中的组织导到星瀚中前面加了JY-,后续新增的使用星瀚的编码规则
+            String orgNumber = org.getString("number");
+            if(orgNumber.indexOf("JY-") == 0) {
+                data.put("ADM_OLD_NUMER", orgNumber.substring(3, orgNumber.length()));
+            }
+            else {
+                data.put("ADM_OLD_NUMER", orgNumber);
+            }
+            //创建时间 取行政组织的创建时间
+            Date createDate = bosOrgMap.get(org.getLong("id")).getDate("createtime");
+            data.put("ADM_CREATION_TIME", sdf.format(createDate));
+            //最后修改时间
+            data.put("ADM_LAST_UPTIME", sdf.format(org.getDate("modifytime")));
+            //行政组织是否封存 enable 0-已停用 1-已启用
+            int enable = org.getInt("enable");
+            if(enable == 0) {
+                data.put("ADM_SEALUP", "是");
+                //行政组织封存时间
+                Date disableDate = org.getDate("disabledate");
+                data.put("ADM_STORAGE_TIME", sdf.format(disableDate));
+            }
+            else {
+                data.put("ADM_SEALUP", "否");
+                data.put("ADM_STORAGE_TIME", null);
+            }
+
+            //是否成本中心实体组织 TODO
+            data.put("ADM_ENTITY_COST_CENTER", null);
+            //是否成本中心组织 TODO
+            data.put("ADM_COST_CENTER", null);
+            //成本中心是否封存 TODO
+            data.put("ADM_COST_CENTER_SEALUP", null);
+            obj.add(data);
+        }
+        SyncUtil.setJSONArraySorted(obj, "MD_LEVEL");
+        ob.put("obj", obj);
+    }
+
+
+    /**
+     * 根据调度作业上配置的参数初始化时间范围
+     * @param map
+     */
+    public void initDateRange(Map<String, Object> map) {
+        boolean isAuto = Boolean.valueOf(map.get("isAuto").toString());
+        if(!isAuto) {
+            startTime = map.get("startTime").toString();
+            endTime = map.get("endTime").toString();
+        }
+        //取最近一次日志的时间
+        else {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            // ToDo
+            //startTime = "";
+            endTime = sdf.format(new Date());
+        }
+    }
+
+    /**
+     * 构建查询变化组织的过滤条件
+     * @return
+     */
+    public QFilter[] getQFilters() {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            QFilter filter = new QFilter("createtime", ">=", sdf.parse(startTime));
+            filter.and("createtime", "<", sdf.parse(endTime));
+            //filter.and("org.otclassify", "=", 1010L);
+            //filter.and("adminorg.number", QCP.equals, "JY-20240612-0009");
+            log.info("-------- 查询组织变化过滤条件:" + filter.toString() + " --------");
+            return new QFilter[]{filter};
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}

+ 20 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/haos/staff/plugin/task/SyncPersonTask.java

@@ -0,0 +1,20 @@
+package nckd.jimin.jyyy.hr.haos.staff.plugin.task;
+
+import kd.bos.context.RequestContext;
+import kd.bos.exception.KDException;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.sdk.plugin.Plugin;
+
+import java.util.Map;
+
+/**
+ * 2025-04-16 Tyx
+ * 人员变动数据同步MDM
+ */
+public class SyncPersonTask extends AbstractTask implements Plugin {
+
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+
+    }
+}

+ 194 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/haos/staff/plugin/task/SyncPositionTask.java

@@ -0,0 +1,194 @@
+package nckd.jimin.jyyy.hr.haos.staff.plugin.task;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.context.RequestContext;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.orm.util.CollectionUtils;
+import kd.bos.schedule.executor.AbstractTask;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.util.StringUtils;
+import kd.hr.hbp.business.servicehelper.HRBaseServiceHelper;
+import kd.sdk.plugin.Plugin;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 2025-04-16 Tyx
+ * 岗位变动数据同步MDM
+ */
+public class SyncPositionTask extends AbstractTask implements Plugin {
+    private static final Log log = LogFactory.getLog(SyncPositionTask.class);
+    private static String posChgRecord_entity = "hbpm_chgrecord";   //岗位变动明细
+    private static String posDetail_entity = "homs_positionhis";    //岗位历史查询
+    private static String startTime = "";
+    private static String endTime = "";
+    @Override
+    public void execute(RequestContext requestContext, Map<String, Object> map) throws KDException {
+        log.info("-------- SyncPositionTask 开始执行同步岗位 --------");
+        JSONObject ob = new JSONObject();
+        initDateRange(map);
+        HRBaseServiceHelper helper = new HRBaseServiceHelper(posChgRecord_entity);
+        String selectProperties = "position,evententry.changescene.number,createtime";
+        //String selectProperties = "aaa";
+        //查出发生过变化的岗位
+        DynamicObjectCollection changeOperateCol = helper.queryOriginalCollection(selectProperties, this.getQFilters());
+        //查询出来为空直接返回
+        if(CollectionUtils.isEmpty(changeOperateCol)) {
+            log.info("-------- 未查询到变化岗位 -------- ");
+            SyncUtil.createLog(SyncUtil.v_error, startTime, endTime, "未查询到变化岗位", "未调用", SyncUtil.SyncPosition);
+        }
+        else {
+            //按岗位ID汇总,查询时间段内发生变化的岗位
+            Map<Long, DynamicObject> changeOperateMap = (Map)changeOperateCol.stream().collect(Collectors.toMap((dyx) -> {
+                return dyx.getLong("position");
+            }, (dyx) -> {
+                return dyx;
+            }, (key1, key2) -> {
+                return key2;
+            }));
+            log.info("-------- 变化岗位数(不去重):" + changeOperateCol.size() + " --------");
+            log.info("-------- 变化岗位数(去重):" + changeOperateMap.keySet().size() + " --------");
+            log.info("-------- 变化岗位Id:" + changeOperateMap.keySet() + " --------");
+
+            //查询出变化岗位之后,找到对应岗位数据以及所属组织数据
+            selectProperties = "id,adminorg.id,adminorg.name,adminorg.number,adminorg.nckd_easid,parent.name,parent.id,parent.number,number,name,enable,isleader,modifytime";
+            QFilter idQFilter = new QFilter("id", "in", changeOperateMap.keySet());
+            DynamicObjectCollection posDyArr = new HRBaseServiceHelper(posDetail_entity).queryOriginalCollection(selectProperties, new QFilter[]{idQFilter});
+            log.info("-------- 查询完岗位详细信息数:" + posDyArr.size() + " --------");
+            //处理下组织长名称
+            Map<Long, DynamicObject> orgMap = (Map)posDyArr.stream().collect(Collectors.toMap((dyx) -> {
+                return dyx.getLong("adminorg.id");
+            }, (dyx) -> {
+                return dyx;
+            }, (key1, key2) -> {
+                return key2;
+            }));
+            //工具类查询组织相关信息返回Map key为组织ID
+            Map<Long, DynamicObject> orgDyxMap = SyncUtil.initOrgInfo(orgMap);
+            //获取调用接口地址
+            String url = SyncUtil.getUrl("position_url");
+            log.info("-------- 岗位同步调用url : " + url + " --------");
+            //构建入参
+            buildJSON(ob, posDyArr, changeOperateMap, orgDyxMap);
+            //调用接口
+            JSONObject response = SyncUtil.doPostByHttpClient(url, ob);
+            //处理返回结果
+            String status = SyncUtil.dealResponseStatus(response);
+            //记录日志
+            SyncUtil.createLog(status, startTime, endTime, ob.toJSONString(), response.toJSONString(), SyncUtil.SyncPosition);
+        }
+    }
+
+    /**
+     * 构建调MDM接口的入参
+     * @param ob                //最后调用的JSONObject
+     * @param posDyArr          //岗位历史查询,查询出来的岗位相关信息
+     * @param changeOperateMap  //岗位变动明细,备用,暂用不上
+     * @param orgDyxMap         //组织信息,用于处理长名称
+     */
+    public void buildJSON(JSONObject ob, DynamicObjectCollection posDyArr, Map<Long, DynamicObject> changeOperateMap, Map<Long, DynamicObject> orgDyxMap) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        //构造岗位的树形结构
+        Map<Long, Integer> levelMap = SyncUtil.initPositionTree();
+        JSONArray obj = new JSONArray();
+        for(DynamicObject pos : posDyArr) {
+            JSONObject data = new JSONObject(true);
+            //主体标签
+            data.put("MAIN_BODY_ID", "JY");
+            //级别
+            data.put("MD_LEVEL", levelMap.get(pos.getLong("id")));
+            //所属行政组织名称
+            data.put("POSI_SUB_DEP_NAME",pos.getString("adminorg.name"));
+            //所属行政组织ID TODO 是EASID还是星瀚ID?
+            String parentId = StringUtils.isBlank(pos.getString("adminorg.nckd_easid")) ? pos.getString("adminorg.id") : pos.getString("adminorg.nckd_easid");
+            data.put("POSI_SUB_DEP_CODE",parentId);
+            //所属行政组织全程 长名称
+            data.put("POSI_SUB_DEP_LNAME",orgDyxMap.get(pos.getLong("adminorg.id")).getString("structure.fullname"));
+            //汇报岗位名称
+            data.put("POSI_REPPOST_NAME",pos.getString("parent.name"));
+            //汇报岗位编码
+            data.put("POSI_REPPOST_CODE",pos.getString("parent.number"));
+            //岗位编码
+            data.put("MD_CODE",pos.getString("number"));
+            //岗位名称
+            data.put("MD_DESCRIPTION",pos.getString("name"));
+            //岗位状态 MDM:1-启用,2-禁用;星瀚:1-启用,0-禁用
+            int enable = pos.getInt("enable");
+            if(enable == 1) {
+                data.put("POSI_STATE","1");
+            }
+            else if(enable == 0){
+                data.put("POSI_STATE","2");
+            }
+            else {
+                data.put("POSI_STATE","0");
+            }
+
+            //是否负责人职位
+            int isLeader = pos.getInt("isleader");
+            if(isLeader == 1) {
+                data.put("POSI_POSIT_CHARGE","是");
+            }
+            else {
+                data.put("POSI_POSIT_CHARGE","否");
+            }
+            //最后更新时间
+            data.put("POSI_LAST_UPTIME",sdf.format(pos.getDate("modifytime")));
+            obj.add(data);
+        }
+        //入参排序
+        SyncUtil.setJSONArraySorted(obj, "MD_LEVEL");
+        ob.put("obj", obj);
+    }
+
+
+    /**
+     * 根据调度作业上配置的参数初始化时间范围
+     * @param map
+     */
+    public void initDateRange(Map<String, Object> map) {
+        boolean isAuto = Boolean.valueOf(map.get("isAuto").toString());
+        if(!isAuto) {
+            startTime = map.get("startTime").toString();
+            endTime = map.get("endTime").toString();
+        }
+        //取最近一次日志的时间
+        else {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            // ToDo
+            //startTime = "";
+            endTime = sdf.format(new Date());
+        }
+    }
+    /**
+     * 构建查询变化岗位的过滤条件
+     * @return
+     */
+    public QFilter[] getQFilters() {
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            QFilter filter = new QFilter("createtime", ">=", sdf.parse(startTime));
+            filter.and("createtime", "<", sdf.parse(endTime));
+            //filter.and("org.otclassify", "=", 1010L);
+            //filter.and("position.number", QCP.equals, "JY-20250407-01772");
+            log.info("-------- 查询岗位变化过滤条件:" + filter.toString() + " --------");
+            return new QFilter[]{filter};
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+
+}

+ 282 - 0
code/jyyy/nckd-jimin-jyyy-hr/src/main/java/nckd/jimin/jyyy/hr/haos/staff/plugin/task/SyncUtil.java

@@ -0,0 +1,282 @@
+package nckd.jimin.jyyy.hr.haos.staff.plugin.task;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import kd.bos.dataentity.entity.DynamicObject;
+import kd.bos.dataentity.entity.DynamicObjectCollection;
+import kd.bos.exception.KDException;
+import kd.bos.logging.Log;
+import kd.bos.logging.LogFactory;
+import kd.bos.orm.query.QCP;
+import kd.bos.orm.query.QFilter;
+import kd.bos.servicehelper.BusinessDataServiceHelper;
+import kd.bos.servicehelper.QueryServiceHelper;
+import kd.bos.servicehelper.operation.SaveServiceHelper;
+import kd.bos.util.HttpClientUtils;
+import kd.bos.workflow.exception.WFErrorCode;
+import kd.hr.hbp.business.servicehelper.HRBaseServiceHelper;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class SyncUtil {
+    private static Log logger = LogFactory.getLog(SyncUtil.class);
+    private static String KEY_ENTITY_LOG = "nckd_syncmdmlog";
+    public static String v_success = "A";
+    public static String v_partsuccess = "B";
+    public static String v_error = "C";
+    public static String SyncOrg = "组织同步";
+    public static String SyncPerson = "人员同步";
+    public static String SyncPosition = "岗位同步";
+
+
+    /**
+     * 获取调用MDM的地址,取nckd_commonparams中编码=MDM的参数
+     * @param type
+     * @return
+     */
+    public static String getUrl(String type) {
+        QFilter filter = new QFilter("number", QCP.equals, "MDM");
+        filter.and("nckd_entryentity.nckd_key",QCP.equals, type);
+        DynamicObject bill = QueryServiceHelper.queryOne("nckd_commonparams", "nckd_entryentity.nckd_value", new QFilter[]{filter});
+        logger.info("-------- 获取MDM url:" + bill.getString("nckd_entryentity.nckd_value") + " -------- ");
+        return bill.getString("nckd_entryentity.nckd_value");
+    }
+
+    /**
+     * 初始化组织Id和长编码的关系
+     *
+     * @return
+     */
+    public static Map<Long, DynamicObject> initOrgInfo() {
+        String selectFields = "id,structure.vieworg.id,structure.longnumber,structure.fullname,createtime";
+        QFilter filter = new QFilter("structure.view.id",QCP.equals,1L);
+        DynamicObjectCollection orgs = QueryServiceHelper.query("bos_org", selectFields, new QFilter[]{filter});
+
+        Map<Long, DynamicObject> orgMap = (Map)orgs.stream().collect(Collectors.toMap((org) -> {
+            return org.getLong("id");
+        }, (org) -> {
+            return org;
+        }, (key1, key2) -> {
+            return key2;
+        }));
+
+        return orgMap;
+    }
+
+    public static Map<Long, DynamicObject> initOrgInfo(Map orgMap) {
+        String selectFields = "id,structure.vieworg.id,structure.longnumber,structure.fullname,createtime";
+        QFilter filter = new QFilter("structure.view.id",QCP.equals,1L);
+        filter.and("id",QCP.in,orgMap.keySet());
+        DynamicObjectCollection orgs = QueryServiceHelper.query("bos_org", selectFields, new QFilter[]{filter});
+
+        Map<Long, DynamicObject> orgDyxMap = (Map)orgs.stream().collect(Collectors.toMap((org) -> {
+            return org.getLong("id");
+        }, (org) -> {
+            return org;
+        }, (key1, key2) -> {
+            return key2;
+        }));
+
+        return orgDyxMap;
+    }
+
+    /**
+     * 岗位树形结构,为了解决level字段问题
+     *
+     * @return
+     */
+    public static Map<Long, Integer> initPositionTree() {
+        String selectProperties = "id,parent.id,name,iscurrentversion";
+        QFilter filter = new QFilter("iscurrentversion", QCP.equals, "1");
+        DynamicObjectCollection posDyArr = new HRBaseServiceHelper("homs_positionhis").queryOriginalCollection(selectProperties, new QFilter[]{filter});
+        List<PositionTree> list = new ArrayList<PositionTree>();
+        for (DynamicObject pos : posDyArr) {
+            PositionTree pTree = new PositionTree(pos.getLong("id"), pos.getString("name"), pos.getLong("parent.id"));
+            list.add(pTree);
+        }
+        //构造树形结构,带level
+        List<PositionTree> tree = buildTree(list);
+        //处理实现结构,返回一个id-level的Map
+        Map<Long, Integer> levelMap = convertToIdLevelMap(tree);
+        return levelMap;
+    }
+
+    public static Map<Long, Integer> convertToIdLevelMap(List<PositionTree> roots) {
+        Map<Long, Integer> idLevelMap = new HashMap<>();
+        for(PositionTree root : roots) {
+            traverseTree(root, idLevelMap);
+        }
+        return idLevelMap;
+    }
+
+    private static void traverseTree(PositionTree node, Map<Long, Integer> map) {
+        map.put(node.getId(), node.getLevel());
+        for (PositionTree child : node.getChildren()) {
+            traverseTree(child, map); // 递归处理子节点‌:ml-citation{ref="1,3" data="citationList"}
+        }
+    }
+
+    // 递归打印树
+    private static void printTree(PositionTree node, int indent) {
+        String spaces = String.join("", Collections.nCopies(indent, "  "));
+        System.out.println(spaces + "└─ " + node.getLabel());
+        for (PositionTree child : node.getChildren()) {
+            printTree(child, indent + 1);
+        }
+    }
+
+    /**
+     * 构建完整树形结构
+     * @param nodes 所有平铺节点
+     * @return 根节点列表(支持多根)
+     */
+    public static List<PositionTree> buildTree(List<PositionTree> nodes) {
+        // 1. 创建快速查找的哈希表
+        Map<Long, PositionTree> nodeMap = nodes.stream()
+                .collect(Collectors.toMap(PositionTree::getId, node -> node));
+
+        // 2. 构建父子关系
+        List<PositionTree> roots = new ArrayList<>();
+        for (PositionTree node : nodes) {
+            Long parentId = node.getParentId();
+            if (parentId == null || parentId == 0L) {
+                roots.add(node); // 根节点
+            } else {
+                PositionTree parent = nodeMap.get(parentId);
+                if (parent != null) {
+                    parent.getChildren().add(node);
+                } else {
+                    // 处理孤儿节点(可选)
+                    roots.add(node); // 将无父节点的视为根节点
+                }
+            }
+        }
+
+        // 3. 计算层级(可选扩展)
+        calculateLevels(roots, 0);
+
+        return roots;
+    }
+
+    /** 递归计算层级深度 */
+    private static void calculateLevels(List<PositionTree> nodes, int level) {
+        for (PositionTree node : nodes) {
+            node.setLevel(level);
+            calculateLevels(node.getChildren(), level + 1);
+        }
+    }
+
+    /**
+     * JSONArray排序,按照级次排序,避免下级数据新增的时候上级还没有
+     * @param arr
+     * @param fieldName
+     */
+    public static void setJSONArraySorted(JSONArray arr, String fieldName) {
+        arr.sort((a, b) -> {
+            JSONObject objA = (JSONObject) a;
+            JSONObject objB = (JSONObject) b;
+            return objA.getIntValue(fieldName) - objB.getIntValue(fieldName);
+        });
+    }
+
+    /**
+     *
+     * @param url
+     * @param bodyData
+     * @return
+     */
+    public static JSONObject doPostByHttpClient(String url, JSONObject bodyData) {
+        try {
+            Map<String, String> headers = new HashMap();
+            headers.put("Content-Type", "application/json");
+            headers.put("Accept", "*/*");
+            logger.info(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+            System.out.println(String.format("url[%s],data[%s]", url, bodyData.toJSONString()));
+
+            String responseEntify = HttpClientUtils.postjson(url, headers, bodyData.toJSONString());
+
+            logger.info(responseEntify);
+            JSONObject result = (JSONObject)JSONObject.parse(responseEntify);
+            return result;
+        } catch (IOException var5) {
+            throw new KDException(var5, WFErrorCode.httpRequestException(), new Object[]{var5.getMessage()});
+        }
+    }
+
+    /**
+     * 处理MDM返回结果 A-成功 B-部分成功 C-失败
+     * @param response
+     * @return
+     */
+    public static String dealResponseStatus (JSONObject response) {
+        String status = "";
+        int successCount = 0;
+        int errorCount = 0;
+        JSONArray responseData = response.getJSONArray("responseData");
+        //根据明细数据status分组
+        Map<String, JSONArray> responseDataMap = responseData.stream().map(obj -> (JSONObject) obj)
+                .collect(Collectors.groupingBy(
+                        json -> json.getString("status"),
+                        () -> new HashMap<>(),
+                        Collectors.mapping(JSONObject::toJSON, Collectors.toCollection(JSONArray::new))
+                ));
+        JSONArray sucArr = responseDataMap.get("S");
+        JSONArray errArr = responseDataMap.get("E");
+        if(sucArr != null)
+            successCount = sucArr.size();
+        if(errArr != null)
+            errorCount = errArr.size();
+
+        if(errorCount > 0 && successCount > 0) {
+            status = "B";
+        }
+        else if(successCount == 0  && errorCount > 0) {
+            status = "C";
+        }
+        else if(successCount > 0 && errorCount == 0) {
+            status = "A";
+        }
+
+        return status;
+    }
+
+    /**
+     * 创建同步日志
+     * @param status 同步状态 A-成功 B-部分成功 C-失败
+     * @param startTime 变动开始时间
+     * @param endTime   变动结束时间
+     * @param request   请求报文
+     * @param response  返回报文
+     * @param syncType  同步类型
+     */
+    public static void createLog (String status, String startTime, String endTime, String request, String response, String syncType) {
+        DynamicObject dynamicObject = BusinessDataServiceHelper.newDynamicObject(KEY_ENTITY_LOG);
+        String uuid = UUID.randomUUID().toString().replace("-", "");
+        dynamicObject.set("enable", "1");
+        dynamicObject.set("status", "C");
+        dynamicObject.set("number", uuid.substring(0,29));
+        dynamicObject.set("nckd_starttime", startTime);
+        dynamicObject.set("nckd_endtime", endTime);
+        dynamicObject.set("nckd_status", status);
+        dynamicObject.set("nckd_synctype", syncType);
+        if (request.length() < 200) {
+            dynamicObject.set("nckd_request", request);
+        } else {
+            dynamicObject.set("nckd_request", request.substring(0, 200) + "...");
+        }
+        dynamicObject.set("nckd_request_tag", request);
+
+        if (response.length() < 200) {
+            dynamicObject.set("nckd_response", response);
+        } else {
+            dynamicObject.set("nckd_response", response.substring(0, 200) + "...");
+        }
+        dynamicObject.set("nckd_response_tag", response);
+        SaveServiceHelper.save(new DynamicObject[]{dynamicObject});
+        logger.info("-------- 保存日志 --------");
+    }
+
+
+}