package shkd.repc.task; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import kd.bos.context.RequestContext; import kd.bos.dataentity.OperateOption; import kd.bos.dataentity.entity.DynamicObject; import kd.bos.dataentity.entity.DynamicObjectCollection; import kd.bos.entity.operate.result.OperationResult; import kd.bos.exception.KDException; import kd.bos.id.ID; 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.schedule.api.MessageHandler; import kd.bos.schedule.executor.AbstractTask; import kd.bos.servicehelper.BusinessDataServiceHelper; import kd.bos.servicehelper.QueryServiceHelper; import kd.bos.servicehelper.operation.OperationServiceHelper; import kd.bos.servicehelper.operation.SaveServiceHelper; import kd.sdk.plugin.Plugin; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import shkd.utils.DobeDWUtils; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 后台任务插件 * 数仓成本科目同步 * 同步规则:以编码为准,系统有则判断更新,无则新增 */ public class DobeDWaccountTask extends AbstractTask implements Plugin { private static final Log logger = LogFactory.getLog(DobeDWaccountTask.class); private static final String entityName = "recos_stdcostaccount";//标准成本科目实体 供应链库 表名 t_recos_scostaccount private static final String acctViewEntity = "bd_accountview";//会计科目实体 财务库 表名 T_BD_Account private static final String dw_menthod = "mdm_costsubject"; // private static final String accEntity = "costaccountentry";//科目分录实体 供应链库 表名 t_recos_costaccount // private static final String projectEntity = "repmd_projectbill";//项目实体 表名 t_repmd_projectbill @Override public void execute(RequestContext requestContext, Map map) throws KDException { //调用数仓查询接口 OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder().url(DobeDWUtils.dwUrl + dw_menthod) .post(DobeDWUtils.createRequestBody("account", 1)) .header("Content-Type", "application/json") .header("Authorization", DobeDWUtils.appCode) .build(); String resultData; try { Response response = client.newCall(request).execute(); resultData = response.body().string(); logger.info("成本科目接口返回结果:\n{}", resultData); } catch (IOException e) { logger.info(String.format("成本科目接口异常:%s", e.getMessage())); throw new RuntimeException("获取数仓成本科目数据失败", e); } JSONObject json_body = JSON.parseObject(resultData); //定时任务获取的参数和参数值是配置调度程序和作业的时候设置,根据参数值保存从数仓获取到的完整json if (map != null && "yes".equals(map.get("savedwjson"))) { DobeDWUtils.saveLog("savedwjson1", "成本科目接口", resultData, null, true, "定时任务"); } //接口返回的数据进行了分页 // int totalNum = json_body.getIntValue("totalNum");//分页-SQL查询总数据量 //根据id查找标准成本科目的表头信息 long bzkmid; String accountId = requestContext.getAccountId();//获取账套ID if ("2231954870896690176".equals(accountId)) { bzkmid = 2234107107932663808L;//测试id } else { bzkmid = 2037039962179791872L;//正式id } logger.info("accountId " + accountId); DynamicObject billinfo = BusinessDataServiceHelper.loadSingle(entityName, new QFilter[]{new QFilter("id", "=", bzkmid)}); if (billinfo == null) { //根据标准科目id未找到对应记录 DobeDWUtils.saveLog(bzkmid + "", "数仓成本科目同步", "", "根据标准科目ID未找到对应记录:" + bzkmid, false, "定时任务"); return; } JSONArray detailsJson = json_body.getJSONArray("data"); if (detailsJson==null || detailsJson.isEmpty()){ logger.info("解析data为空,不继续处理!"); return; } DynamicObjectCollection doDetails = billinfo.getDynamicObjectCollection("costaccountentry");//标准科目分录数据 if (doDetails!=null && doDetails.size()!=0){ //成本科目最少需要一级数据一条,例:CB this.dataComparison(detailsJson,doDetails,billinfo); } //调用保存操作 OperationResult save = OperationServiceHelper.executeOperate("save", entityName, new DynamicObject[]{billinfo}, OperateOption.create()); if (!save.isSuccess()) { logger.error(save.getMessage()); } } private void dataComparison(JSONArray detailsJson, DynamicObjectCollection doDetails,DynamicObject billinfo) { // 用于存储需要新增的数据(接口中有但系统中没有的) List toAddList = new ArrayList<>(); int updateCount = 0; // 记录更新条数 int addCount = 0; // 记录新增条数 for (int i = 0; i < detailsJson.size(); i++) { JSONObject jsonObject = detailsJson.getJSONObject(i); String status = jsonObject.getString("costsubject_status"); if ("启用".equals(status)) { String code = jsonObject.getString("costsubject_code"); if (code != null) { boolean found = false; for (int j = 0; j < doDetails.size(); j++) { DynamicObject dynamicObject = doDetails.get(j); String longNumber = dynamicObject.getString("caentry_longnumber"); if (longNumber != null) { // 去除系统中的"."后与接口编码比较 String cleanedLongNumber = longNumber.replace(".", ""); if (code.equals(cleanedLongNumber)) { found = true; // 检查名称或其他字段是否有变化 String systemName = dynamicObject.getString("caentry_name"); // 假设系统中名称字段为caentry_name String jsonName = jsonObject.getString("costsubject_name"); // 如果名称不同或需要检查其他字段变化,则直接更新 if (!jsonName.equals(systemName)) { //更新科目名称 dynamicObject.set("caentry_name",jsonName); updateCount++; System.out.println("[MyLog]更新科目: " + jsonName + ", 编码: " + code); logger.info("更新科目名称: {}, 编码: {}", jsonName, code); } //会计科目 String accSubjectCode = jsonObject.getString("accsubject_code"); if (accSubjectCode!=null){ DynamicObject sysAccount = dynamicObject.getDynamicObject("caentry_account"); if (sysAccount==null){ //直接匹配会计科目赋值id Long accountId=this.getSysAccountId(accSubjectCode); if (accountId!=null){ dynamicObject.set("caentry_account",accountId); logger.info("成功关联会计科目,科目编码: {}, 会计科目ID: {}", code, accountId); System.out.println("[MyLog]关联会计科目: " + code + " -> " + accountId); } }else { //编码匹配是否改变 String number = sysAccount.getString("number"); if (!accSubjectCode.equals(number)){ //匹配会计科目赋值id Long accountId=this.getSysAccountId(accSubjectCode); if (accountId!=null){ dynamicObject.set("caentry_account",accountId); logger.info("更新会计科目关联成功,科目编码: {}, 新会计科目ID: {}", code, accountId); System.out.println("[MyLog]更新会计科目关联: " + code + " -> " + accountId); } } } } break; // 找到匹配项后跳出循环 } } } // 如果没有找到匹配项,则加入新增列表 if (!found) { toAddList.add(jsonObject); addCount++; System.out.println("[MyLog]准备新增科目: " + jsonObject.getString("costsubject_name") + ", 编码: " + code); logger.info("准备新增科目: {}, 编码: {}", jsonObject.getString("costsubject_name"), code); } } } } // 打印统计信息 System.out.println("[MyLog]数据比对结果 - 更新条数: " + updateCount + ", 新增条数: " + addCount); logger.info("数据比对结果 - 更新条数: {}, 新增条数: {}", updateCount, addCount); //更新 if (toAddList.size()!=0){ this.addCostSubject(toAddList,doDetails,billinfo); } } /** * 获取会计科目id * @param accSubjectCode * @return */ private Long getSysAccountId(String accSubjectCode) { Long pkValue = null; QFilter number = new QFilter("number", QCP.equals, accSubjectCode); DynamicObject dynamicObject = BusinessDataServiceHelper.loadSingle("bd_accountview", number.toArray()); if (dynamicObject!=null){ pkValue= (Long) dynamicObject.getPkValue(); } return pkValue; } /** * 新增标准成本科目数据 * @param toAddList 需要新增的数据 * @param doDetails 单据体 */ private void addCostSubject(List toAddList, DynamicObjectCollection doDetails,DynamicObject billinfo) { System.out.println("[MyLog]开始新增科目,数量: " + toAddList.size()); logger.info("开始新增科目,数量: {}", toAddList.size()); // 用于缓存已创建的父级科目 Map parentCache = new HashMap<>(); for (JSONObject jsonObject : toAddList) { try { // 1. 准备基础数据 //父级编码CB001 String pCode = jsonObject.getString("costsubject_pcode"); //系统需要的编码CB.001 String formattedCode = formatParentCode(pCode); //科目编码CB001002 String code = jsonObject.getString("costsubject_code"); //系统短编码002 String shortNumber = code.substring(pCode.length()); // 2. 处理父级科目 DynamicObject parentEntry = findOrCreateParent( jsonObject, pCode, formattedCode, doDetails, parentCache,billinfo); // 设置父级科目行号(如果父级是新创建的) if (!parentCache.containsKey(formattedCode)) { parentEntry.set("seq", doDetails.size()-1); // 使用当前总条数作为行号 parentCache.put(formattedCode, parentEntry); System.out.println("[MyLog]新增父级科目: " + parentEntry.getString("caentry_name") + ", 编码: " + parentEntry.getString("caentry_longnumber")); logger.info("新增父级科目: {}, 编码: {}", parentEntry.getString("caentry_name"), parentEntry.getString("caentry_longnumber")); } // 3. 创建当前科目 DynamicObject dynamicObject = doDetails.addNew(); setupDynamicObject(dynamicObject, jsonObject, parentEntry, formattedCode, shortNumber); // 4. 设置当前科目行号 dynamicObject.set("seq", doDetails.size()-1); // 使用当前总条数-1作为行号 System.out.println("[MyLog]新增科目: " + dynamicObject.getString("caentry_name") + ", 编码: " + dynamicObject.getString("caentry_longnumber")); logger.info("新增科目: {}, 编码: {}", dynamicObject.getString("caentry_name"), dynamicObject.getString("caentry_longnumber")); } catch (Exception e) { System.err.println("[MyLog]新增科目失败: " + jsonObject.getString("costsubject_name") + ", 错误: " + e.getMessage()); logger.error("新增科目失败: {}, 错误: {}", jsonObject.getString("costsubject_name"), e.getMessage(), e); } } System.out.println("[MyLog]科目新增完成,共新增: " + toAddList.size() + "条记录"); logger.info("科目新增完成,共新增: {}条记录", toAddList.size()); } /** * 查找或创建父级科目 */ private DynamicObject findOrCreateParent(JSONObject jsonObject, String pCode, String formattedCode, DynamicObjectCollection doDetails, Map cache,DynamicObject billinfo) { // 1. 先检查缓存 if (cache.containsKey(formattedCode)) { return cache.get(formattedCode); } // 2. 在现有数据中查找 for (DynamicObject entry : doDetails) { if (formattedCode.equals(entry.getString("caentry_longnumber"))) { return entry; } } // 3. 查找顶级父级科目(CB) DynamicObject topParent = findTopParent(doDetails); if (topParent == null) { throw new RuntimeException("未找到顶级父级科目(CB)"); } // 4. 创建新的父级科目 DynamicObject parentEntry = doDetails.addNew(); String parentShortCode = pCode.substring(2); // 设置父级科目属性 parentEntry.set("pid", topParent.getPkValue()); // 父级 ID——CB parentEntry.set("caentry_number", parentShortCode); parentEntry.set("caentry_longnumber", formattedCode); parentEntry.set("caentry_name", jsonObject.getString("costsubject_pname")); parentEntry.set("caentry_enable", true); parentEntry.set("caentry_org", 100000L); parentEntry.set("caentry_createorg", 100000L); parentEntry.set("caentry_useorg", 100000L); parentEntry.set("caentry_isleaf", false); parentEntry.set("caentry_level", 2); parentEntry.set("caentry_standlibflag", true); parentEntry.set("caentry_isimportaccount", false); parentEntry.set("caentry_isstdaccount", true); // 立即保存父级科目以生成pkValue OperationResult saveResult = OperationServiceHelper.executeOperate("save", entityName, new DynamicObject[]{billinfo}, OperateOption.create()); if (!saveResult.isSuccess()) { throw new RuntimeException("保存父级科目失败: " + saveResult.getMessage()); } return parentEntry; } /** * 设置科目属性 */ private void setupDynamicObject(DynamicObject dynamicObject, JSONObject jsonObject, DynamicObject parentEntry, String formattedCode, String shortNumber) { // 1. 设置基础属性 dynamicObject.set("pid", parentEntry.getPkValue()); dynamicObject.set("caentry_fullname", "开发成本_" + parentEntry.getString("caentry_name") + "_" + jsonObject.getString("costsubject_name")); dynamicObject.set("caentry_number", shortNumber); dynamicObject.set("caentry_longnumber", formattedCode + "." + shortNumber); dynamicObject.set("caentry_name", jsonObject.getString("costsubject_name")); // 2. 设置固定属性 dynamicObject.set("caentry_ciaccountflag", "1"); dynamicObject.set("caentry_measureway", 1026043947790783488L); dynamicObject.set("caentry_apportionway", 1026043947790783488L); dynamicObject.set("caentry_enable", true); dynamicObject.set("caentry_org", 100000L); dynamicObject.set("caentry_createorg", 100000L); dynamicObject.set("caentry_useorg", 100000L); dynamicObject.set("caentry_isleaf", true); dynamicObject.set("caentry_level", 3); dynamicObject.set("caentry_srcid", dynamicObject.getPkValue()); dynamicObject.set("caentry_standlibflag", true); dynamicObject.set("caentry_isimportaccount", false); dynamicObject.set("caentry_isstdaccount", true); // 3. 会计科目 String accSubjectCode = jsonObject.getString("accsubject_code"); if (jsonObject.getString("accsubject_code") != null) { Long accountId=this.getSysAccountId(accSubjectCode); if (accountId!=null){ dynamicObject.set("caentry_account",accountId); } } } /** * 查找顶级父级科目(CB) */ private DynamicObject findTopParent(DynamicObjectCollection doDetails) { for (DynamicObject entry : doDetails) { if ("CB".equals(entry.getString("caentry_longnumber"))) { return entry; } } return null; } /** * 格式化父级编码(CB0010 → CB.0010;CB001 → CB.001) */ private String formatParentCode(String pCode) { String prefix = pCode.substring(0, 2); // 获取前两位字母(CB) String numberPart = pCode.substring(2); // 获取数字部分(001) return prefix + "." + numberPart;//CB.001 } }