diff --git a/code/zcdev/zcgj-zcdev-zcdev-pr/src/main/java/zcgj/zcdev/zcdev/pr/task/EquipmentCardTaskPlugin.java b/code/zcdev/zcgj-zcdev-zcdev-pr/src/main/java/zcgj/zcdev/zcdev/pr/task/EquipmentCardTaskPlugin.java index 409a63d..d646b38 100644 --- a/code/zcdev/zcgj-zcdev-zcdev-pr/src/main/java/zcgj/zcdev/zcdev/pr/task/EquipmentCardTaskPlugin.java +++ b/code/zcdev/zcgj-zcdev-zcdev-pr/src/main/java/zcgj/zcdev/zcdev/pr/task/EquipmentCardTaskPlugin.java @@ -35,7 +35,9 @@ import java.util.stream.Collectors; /** * 实物卡片同步设备定时任务 + * 现在使用NewEquipmentCardTaskPlugin */ +@Deprecated public class EquipmentCardTaskPlugin extends AbstractTask { private static final Log log = LogFactory.getLog(EquipmentCardTaskPlugin.class); @@ -217,14 +219,7 @@ public class EquipmentCardTaskPlugin extends AbstractTask { eceq_equipment.set("zcgj_remark",remark);//备注 DynamicObjectCollection entrys = eceq_equipment.getDynamicObjectCollection("zcgj_entryentity"); QFilter qf = new QFilter("realcard.number", QCP.equals, number); -// if (entrys.size() > 0) {//若分录行数大于0 则取上月的 -// for (int i = 0; i < entrys.size(); i++) { -// DynamicObject entry = entrys.get(i); -// long billnoid = entry.getLong("zcgj_debillno"); -// qf.and(new QFilter("id", QCP.not_equals, billnoid)); -//// entrys.removeIf(record -> isLastMonth(zcgjDebillno));//若属于上个月那就删除重新塞入 -// } -// } + DynamicObjectCollection depresplitdetails = QueryServiceHelper.query("fa_depresplitdetail", "id,billno,period,splitdept,assentry.costcentrer,assentry.splitamount", new QFilter[]{qf}); log.info("前depresplitdetails:"+depresplitdetails.toArray().toString() + "长度:"+depresplitdetails.size()); Collections.sort(depresplitdetails , new Comparator(){ @@ -238,26 +233,35 @@ public class EquipmentCardTaskPlugin extends AbstractTask { } }); log.info("后depresplitdetails:"+depresplitdetails.toArray().toString() + "长度:"+depresplitdetails.size()); - entrys.clear(); + DynamicObjectCollection dynamicObjectCollection = eceq_equipment.getDynamicObjectCollection("zcgj_entryentity"); + Set existPeriodData = new HashSet<>(); + if (eceq_equipment.get("zcgj_assetnumber") != null && !dynamicObjectCollection.isEmpty()) { + existPeriodData = dynamicObjectCollection.stream().map(data -> data.getDynamicObject("zcgj_assperiod").getString("number")).collect(Collectors.toSet()); + } + //entrys.clear(); for (int x = 0 ; x smartComparator = (o1, o2) -> { -// DynamicObject p1 = o1.getDynamicObject("period"); -// DynamicObject p2 = o2.getDynamicObject("period"); -// // 空值处理 -// if (p1 == null && p2 == null) return 0; -// if (p1 == null) return 1; -// if (p2 == null) return -1; -// String c1 = p1.getString("number"); -// String c2 = p2.getString("number"); -// if (c1 == null) c1 = ""; -// if (c2 == null) c2 = ""; -// // 尝试解析为年月 -// for (String pattern : new String[]{"yyyyMM", "yyyy-MM", "yyyy/MM"}) { -// try { -// DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern); -// YearMonth ym1 = YearMonth.parse(c1, fmt); -// YearMonth ym2 = YearMonth.parse(c2, fmt); -// return ym2.compareTo(ym1); // 降序 -// } catch (Exception ignored) {} -// } -// // 纯数字比较(如 "202301") -// try { -// int num1 = Integer.parseInt(c1); -// int num2 = Integer.parseInt(c2); -// return Integer.compare(num2, num1); -// } catch (NumberFormatException e) { -// return c2.compareTo(c1); // 字符串降序 -// } -// }; -// depresplitdetails.sort(smartComparator); -// for (int i = 0; i < depresplitdetails.size(); i++) { -// DynamicObject item = depresplitdetails.get(i); -// log.info("排序前 " + i + ": " + item.getDynamicObject("period").getString("number")); -// } -//按字段 menuindex 排序 \ No newline at end of file +} \ No newline at end of file diff --git a/code/zcdev/zcgj-zcdev-zcdev-pr/src/main/java/zcgj/zcdev/zcdev/pr/task/NewEquipmentCardTaskPlugin.java b/code/zcdev/zcgj-zcdev-zcdev-pr/src/main/java/zcgj/zcdev/zcdev/pr/task/NewEquipmentCardTaskPlugin.java new file mode 100644 index 0000000..6784481 --- /dev/null +++ b/code/zcdev/zcgj-zcdev-zcdev-pr/src/main/java/zcgj/zcdev/zcdev/pr/task/NewEquipmentCardTaskPlugin.java @@ -0,0 +1,558 @@ +package zcgj.zcdev.zcdev.pr.task; + +import com.alibaba.excel.util.StringUtils; +import kd.bos.algo.DataSet; +import kd.bos.coderule.api.CodeRuleInfo; +import kd.bos.coderule.service.cache.CodeRuleCache; +import kd.bos.context.RequestContext; +import kd.bos.dataentity.entity.DynamicObject; +import kd.bos.dataentity.entity.DynamicObjectCollection; +import kd.bos.entity.operate.result.IOperateInfo; +import kd.bos.entity.operate.result.OperateErrorInfo; +import kd.bos.entity.operate.result.OperationResult; +import kd.bos.exception.KDException; +import kd.bos.logging.Log; +import kd.bos.logging.LogFactory; +import kd.bos.orm.ORM; +import kd.bos.orm.query.QCP; +import kd.bos.orm.query.QFilter; +import kd.bos.schedule.executor.AbstractTask; +import kd.bos.servicehelper.BusinessDataServiceHelper; +import kd.bos.servicehelper.QueryServiceHelper; +import kd.bos.servicehelper.coderule.CodeRuleServiceHelper; +import kd.bos.servicehelper.operation.OperationServiceHelper; +import kd.bos.servicehelper.operation.SaveServiceHelper; +import kd.bos.servicehelper.org.OrgUnitServiceHelper; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 实物卡片同步设备定时任务(已优化判空与性能) + */ +public class NewEquipmentCardTaskPlugin extends AbstractTask { + private static final Log log = LogFactory.getLog(NewEquipmentCardTaskPlugin.class); + + @Override + public void execute(RequestContext requestContext, Map map) throws KDException { + // 1. 收集 assetcat.number(避免 NPE,使用泛型) + Set strings = new HashSet<>(); + DynamicObject[] equipmenttype = BusinessDataServiceHelper.load( + "zcgj_equipmenttype", + "number,zcgj_assetcat", + new QFilter[]{new QFilter("zcgj_assetcat.number", QCP.is_notnull, "")} + ); + if (equipmenttype != null) { + for (DynamicObject dynamicObject : equipmenttype) { + if (dynamicObject == null) continue; + DynamicObject zcgjAssetcat = dynamicObject.getDynamicObject("zcgj_assetcat"); + String number = safeGetString(zcgjAssetcat, "number"); + if (!isEmpty(number)) { + strings.add(number); + } + } + } + + // 2. 获取组织及其下属组织 + QFilter filterOrgId = new QFilter("number", QCP.equals, "10006431"); // 中材矿山建设有限公司 (建议配置化) + DynamicObject adminOrg = BusinessDataServiceHelper.loadSingle("bos_org", "number,name,fullname", new QFilter[]{filterOrgId}); + if (adminOrg == null) { + log.error("未找到组织:10006431,任务终止"); + return; + } + long orgId = adminOrg.getLong("id"); + List orgIds = new ArrayList<>(1); + orgIds.add(orgId); + List subOrgIds = OrgUnitServiceHelper.getAllSubordinateOrgs(1L, orgIds, true); + if (subOrgIds == null) subOrgIds = Collections.emptyList(); + Set orgSer = new HashSet<>(subOrgIds); + + // 3. 查询资产卡片 + QFilter qFilter = new QFilter("assetcat.number", QCP.in, strings); + QFilter qFilter2 = new QFilter("assetunit.id", QCP.in, orgSer); + QFilter qFilter3 = new QFilter("bizstatus", QCP.not_equals, "DELETE"); + DynamicObject[] realcards = BusinessDataServiceHelper.load( + "fa_asset_card", + "zcgj_costcenter,headusedept,finentry,assetcat,number,assetname,model,realaccountdate," + + "assetunit,supplier,zcgj_platenumber,unit,zcgj_manufacturer,zcgj_prodate,storeplace,creator,createtime,modifier,modifytime,auditdate,auditor,finentry.fin_originalval,finentry.fin_preresidualval," + + "finentry.fin_depredamount,finentry.fin_preusingamount,finentry.fin_accumdepre,finentry.fin_networth,remark,assetamount", + new QFilter[]{qFilter, qFilter2, qFilter3} + ); + + int realcardsLen = (realcards == null ? 0 : realcards.length); + log.info("同步的实物数量有 " + realcardsLen); + StringBuilder totalErrorBuilder = new StringBuilder(); + + if (realcards == null || realcardsLen == 0) { + return; + } + + // 提前缓存一些重复查询的结果(按需) + // 遍历每个实物卡片进行同步 + for (DynamicObject realcard : realcards) { + if (realcard == null) continue; + + String number = safeGetString(realcard, "number"); // 资产编码 + DynamicObject assetcat = realcard.getDynamicObject("assetcat"); // 资产类别 + String assetname = safeGetString(realcard, "assetname"); // 资产名称 + log.info("同步的实物卡片为 " + number + " " + assetname); + + // 基本字段(都做了 null 安全读取) + String model = safeGetString(realcard, "model"); + Date realaccountdate = realcard.getDate("realaccountdate"); + DynamicObject assetunit = realcard.getDynamicObject("assetunit"); + DynamicObject supplier = realcard.getDynamicObject("supplier"); + String platenumber = safeGetString(realcard, "zcgj_platenumber"); + DynamicObject unit = realcard.getDynamicObject("unit"); + String manufacturer = safeGetString(realcard, "zcgj_manufacturer"); + Date prodate = realcard.getDate("zcgj_prodate"); + DynamicObject storeplace = realcard.getDynamicObject("storeplace"); + DynamicObject headusedept = realcard.getDynamicObject("headusedept"); + DynamicObject zcgj_costcenter = realcard.getDynamicObject("zcgj_costcenter"); + BigDecimal assetamount = safeGetBigDecimal(realcard, "assetamount"); + String remark = safeGetString(realcard, "remark"); + + DynamicObjectCollection finentrys = realcard.getDynamicObjectCollection("finentry");//财务卡片分录 + + // 存放地点 fulladdress(做了 loadSingle 后的 null 校验) + String fulladdress = null; + if (storeplace != null) { + Long spId = storeplace.getLong("id"); + if (spId != null) { + DynamicObject storeplaceinfo = BusinessDataServiceHelper.loadSingle("fa_storeplace", new QFilter[]{new QFilter("id", QCP.equals, spId)}); + if (storeplaceinfo != null) { + fulladdress = safeGetString(storeplaceinfo, "fulladdress"); + } + } + } + + DynamicObject creator = realcard.getDynamicObject("creator"); + Date createtime = realcard.getDate("createtime"); + DynamicObject modifier = realcard.getDynamicObject("modifier"); + Date modifytime = realcard.getDate("modifytime"); + DynamicObject auditor = realcard.getDynamicObject("auditor"); + Date auditdate = realcard.getDate("auditdate"); + + // 创建或更新 eceq_equipment_card 对象 + DynamicObject equipmentcard = ORM.create().newDynamicObject("eceq_equipment_card"); + + // 时间文本(修正了 format 的用法) + String timeText; + { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + timeText = format.format(new Date()); + } + + // 检查已有设备卡片 + DynamicObject eceq_equipment_card = null; + if (!isEmpty(number)) { + eceq_equipment_card = BusinessDataServiceHelper.loadSingle("eceq_equipment_card", new QFilter[]{new QFilter("zcgj_assetnumber", QCP.equals, number)}); + } + + if (eceq_equipment_card != null) { + equipmentcard.set("id", eceq_equipment_card.getLong("id")); + String cardString = safeGetString(eceq_equipment_card, "number"); + if (!isEmpty(cardString)) { + equipmentcard.set("number", cardString); + } else { + equipmentcard.set("number", getCodeRule(equipmentcard, "53BT+ZJB86=L")); + } + } else { + equipmentcard.set("number", getCodeRule(equipmentcard, "53BT+ZJB86=L")); + } + + // 基本属性赋值(保持原业务字段) + equipmentcard.set("billno", timeText); + equipmentcard.set("status", "C"); + equipmentcard.set("billstatus", "C"); + equipmentcard.set("enable", "1"); + equipmentcard.set("property", "OWN"); + equipmentcard.set("name", assetname); + equipmentcard.set("modelnum", model); + equipmentcard.set("indate", realaccountdate); + equipmentcard.set("org", assetunit); + equipmentcard.set("equipstatus", "FREE"); + equipmentcard.set("project", null); + equipmentcard.set("useorg", null); + equipmentcard.set("supplier", supplier); + equipmentcard.set("carnumber", platenumber); + equipmentcard.set("enginenumber", null); + equipmentcard.set("framenumber", null); + equipmentcard.set("unit", unit); + equipmentcard.set("power", null); + equipmentcard.set("manufacturer", manufacturer); + equipmentcard.set("proddate", prodate); + equipmentcard.set("storageplace", fulladdress); + equipmentcard.set("creator", creator); + equipmentcard.set("createtime", createtime); + equipmentcard.set("modifier", modifier); + equipmentcard.set("modifytime", modifytime); + equipmentcard.set("auditor", auditor); + equipmentcard.set("auditdate", auditdate); + equipmentcard.set("zcgj_assetnumber", number); + equipmentcard.set("zcgj_headusedepts", headusedept); + equipmentcard.set("zcgj_costcenters", zcgj_costcenter); + equipmentcard.set("zcgj_assetcat", assetcat); + equipmentcard.set("zcgj_assetamount", assetamount); + equipmentcard.set("zcgj_remark", remark); + + // 处理财务卡片分录(改进:一次加载 fin_card 并取 depredamount 最大的记录) + BigDecimal finOriginalval = BigDecimal.ZERO; + BigDecimal fin_preusingamount = BigDecimal.ZERO; + BigDecimal fin_depredamount = BigDecimal.ZERO; + BigDecimal fin_preresidualval = BigDecimal.ZERO; + BigDecimal fin_accumdepre = BigDecimal.ZERO; + BigDecimal fin_networth = BigDecimal.ZERO; + + if (finentrys != null && !finentrys.isEmpty() && !isEmpty(number)) { + DynamicObject[] fincards = BusinessDataServiceHelper.load("fa_card_fin", + "id,originalval,preusingamount,depredamount,preresidualval,accumdepre,networth", + new QFilter[]{new QFilter("number", QCP.equals, number)}); + if (fincards != null && fincards.length > 0) { + DynamicObject best = null; + BigDecimal maxDep = new BigDecimal(-1); + for (DynamicObject fincard : fincards) { + if (fincard == null) continue; + BigDecimal dep = safeGetBigDecimal(fincard, "depredamount"); + if (dep.compareTo(maxDep) > 0) { + maxDep = dep; + best = fincard; + } + } + if (best != null) { + finOriginalval = safeGetBigDecimal(best, "originalval"); + fin_preusingamount = safeGetBigDecimal(best, "preusingamount"); + fin_depredamount = safeGetBigDecimal(best, "depredamount"); + fin_preresidualval = safeGetBigDecimal(best, "preresidualval"); + fin_accumdepre = safeGetBigDecimal(best, "accumdepre"); + fin_networth = safeGetBigDecimal(best, "networth"); + + equipmentcard.set("zcgj_networth", fin_networth); + equipmentcard.set("zcgj_accumdepre", fin_accumdepre); + equipmentcard.set("zcgj_depredamount", fin_depredamount); + equipmentcard.set("zcgj_preresidualval", fin_preresidualval); + equipmentcard.set("zcgj_preusingamount", fin_preusingamount); + equipmentcard.set("unitprice", finOriginalval); + } + } + } + + // 保存或更新 equipmentcard + if (eceq_equipment_card != null) { + // 更新 + SaveServiceHelper.update(equipmentcard); + } else { + OperationResult result = OperationServiceHelper.executeOperate("save", "eceq_equipment_card", new DynamicObject[]{equipmentcard}, null); + String operationResultErrorInfo = getOperationResultErrorInfos(result); + if (!isEmpty(operationResultErrorInfo)) { + totalErrorBuilder.append(number).append(": ").append(operationResultErrorInfo).append(System.lineSeparator()); + log.error(number + assetname + " 报错信息为:" + operationResultErrorInfo); + } + } + + // 同步到 eceq_equipinfo(存在则更新) + DynamicObject eceq_equipment = null; + if (!isEmpty(number)) { + eceq_equipment = BusinessDataServiceHelper.loadSingle("eceq_equipinfo", new QFilter[]{new QFilter("zcgj_assetnumber", QCP.equals, number)}); + } + if (eceq_equipment != null) { + // 更新字段(与原逻辑保持一致) + eceq_equipment.set("zcgj_assetnumber", number); + eceq_equipment.set("zcgj_headusedepts", headusedept); + eceq_equipment.set("zcgj_costcenters", zcgj_costcenter); + eceq_equipment.set("zcgj_networth", fin_networth); + eceq_equipment.set("zcgj_accumdepre", fin_accumdepre); + eceq_equipment.set("zcgj_depredamount", fin_depredamount); + eceq_equipment.set("zcgj_preresidualval", fin_preresidualval); + eceq_equipment.set("zcgj_preusingamount", fin_preusingamount); + eceq_equipment.set("zcgj_unitprice", finOriginalval); + eceq_equipment.set("org", assetunit); + // eceq_equipment.set("equipstatus","FREE"); // 保持原注释:更新设备时不更新设备状态 + eceq_equipment.set("project", null); + eceq_equipment.set("zcgj_assetcat", assetcat); + eceq_equipment.set("zcgj_assetamount", assetamount); + eceq_equipment.set("zcgj_remark", remark); + + // 处理折旧分摊明细(优化:减少重复 loadSingle) + DynamicObjectCollection entrys = eceq_equipment.getDynamicObjectCollection("zcgj_entryentity"); + QFilter qf = new QFilter("realcard.number", QCP.equals, number); + /*DynamicObjectCollection depresplitdetails = QueryServiceHelper.query( + "fa_depresplitdetail", + "id,billno,period,splitdept,assentry.costcentrer,assentry.splitamount", + new QFilter[]{qf} + );*/ + + DynamicObject[] depresplitdetailsArray = BusinessDataServiceHelper.load( + "fa_depresplitdetail", + "id,billno,period,splitdept,assentry.costcentrer,assentry.splitamount", + new QFilter[]{qf} + ); + List depresplitdetails = new ArrayList<>(Arrays.asList(depresplitdetailsArray)); + + int beforeSize = depresplitdetailsArray.length; + log.info("前depresplitdetails length: " + beforeSize); + + // 将 depresplitdetails 转为 List 并按 period.number 降序排序,避免每次 compare 时重复 DB 查询 + if (depresplitdetails.size() > 1) { + List tmp = new ArrayList<>(depresplitdetails.size()); + for (DynamicObject d : depresplitdetails) { + tmp.add(d); + } + + tmp.sort((o1, o2) -> { + String p1 = safeNestedString(o1, "period", "number"); + String p2 = safeNestedString(o2, "period", "number"); + if (p1 == null && p2 == null) return 0; + if (p1 == null) return 1; + if (p2 == null) return -1; + return p2.compareTo(p1); // 降序 + }); + + // 清空并重新加入排好序的项(保持类型为 DynamicObjectCollection) + depresplitdetails.clear(); + depresplitdetails.addAll(tmp); + } + + int afterSize = depresplitdetails.size(); + log.info("后depresplitdetails length: " + afterSize); + + // 计算已存在的 period number 集合,避免重复添加 + Set existPeriodData = new HashSet<>(); + DynamicObjectCollection dynamicObjectCollection = eceq_equipment.getDynamicObjectCollection("zcgj_entryentity"); + if (eceq_equipment.get("zcgj_assetnumber") != null && dynamicObjectCollection != null && !dynamicObjectCollection.isEmpty()) { + for (DynamicObject data : dynamicObjectCollection) { + if (data == null) continue; + DynamicObject period = data.getDynamicObject("zcgj_assperiod"); + String pn = safeGetString(period, "number"); + if (!isEmpty(pn)) existPeriodData.add(pn); + } + } + + // 遍历 depresplitdetails(避免再次调用 loadSingle) + if (!depresplitdetails.isEmpty()) { + for (DynamicObject depresplitdetail : depresplitdetails) { + if (depresplitdetail == null) continue; + + // 直接使用已查询到的对象,不再 loadSingle + DynamicObject period = depresplitdetail.getDynamicObject("period"); + String periodnumber = safeGetString(period, "number"); + if (isEmpty(periodnumber)) continue; + + if (existPeriodData.isEmpty() || !existPeriodData.contains(periodnumber)) { + DynamicObject splitdept = depresplitdetail.getDynamicObject("splitdept"); + DynamicObjectCollection assentry = depresplitdetail.getDynamicObjectCollection("assentry"); + if (assentry == null || assentry.isEmpty()) continue; + DynamicObject firstAss = assentry.get(0); + DynamicObject costcentrer = (firstAss == null) ? null : firstAss.getDynamicObject("costcentrer"); + BigDecimal splitamount = (firstAss == null) ? BigDecimal.ZERO : safeGetBigDecimal(firstAss, "splitamount"); + + DynamicObject addNew = new DynamicObject(entrys.getDynamicObjectType()); + addNew.set("zcgj_debillno", depresplitdetail.getLong("id")); + addNew.set("zcgj_assperiod", period); + addNew.set("zcgj_headusedept", splitdept); + addNew.set("zcgj_entrybillno", number); + addNew.set("zcgj_entryname", assetname); + addNew.set("zcgj_costcenter", costcentrer); + addNew.set("zcgj_shareamount", splitamount); + + // useorg 可能为 DynamicObject 或 null,做安全处理 + Object useorgObj = eceq_equipment.get("useorg"); + if (useorgObj instanceof DynamicObject) { + DynamicObject useorgDo = (DynamicObject) useorgObj; + Long useorgId = useorgDo.getLong("id"); + addNew.set("zcgj_zjuseorg", useorgId); + } else if (useorgObj instanceof Long) { + addNew.set("zcgj_zjuseorg", (Long) useorgObj); + } + + entrys.add(addNew); + } + } + } + + // 保存 eceq_equipment + SaveServiceHelper.save(new DynamicObject[]{eceq_equipment}); + } + + // 每次循环可选打印或记录累计错误,但不要频繁 println,改用日志 + if (totalErrorBuilder.length() > 0) { + log.warn("当前累计报错(部分示例): " + (totalErrorBuilder.length() > 400 ? totalErrorBuilder.substring(0, 400) + "..." : totalErrorBuilder.toString())); + } + } // end for realcards + + if (totalErrorBuilder.length() > 0) { + log.error("总报错信息为:\n" + totalErrorBuilder.toString()); + } + } // end execute + + /** + * 判断给定字符串是否为空(null 或 空字符串) + */ + private static boolean isEmpty(String s) { + return s == null || s.trim().isEmpty(); + } + + /** + * 安全读取 DynamicObject 的 String 字段 + */ + private static String safeGetString(DynamicObject obj, String prop) { + if (obj == null) return null; + try { + Object v = obj.get(prop); + return v == null ? null : v.toString(); + } catch (Exception e) { + return null; + } + } + + /** + * 安全读取 DynamicObject 的 BigDecimal 字段 + */ + private static BigDecimal safeGetBigDecimal(DynamicObject obj, String prop) { + if (obj == null) return BigDecimal.ZERO; + try { + BigDecimal v = obj.getBigDecimal(prop); + return (v == null) ? BigDecimal.ZERO : v; + } catch (Exception e) { + return BigDecimal.ZERO; + } + } + + /** + * 安全获取嵌套字段(例如 period.number) + */ + private static String safeNestedString(DynamicObject obj, String nestedField, String prop) { + if (obj == null) return null; + try { + DynamicObject nested = obj.getDynamicObject(nestedField); + return safeGetString(nested, prop); + } catch (Exception e) { + return null; + } + } + + public static boolean isLastMonth(String dateStr) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + try { + LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter); + LocalDateTime now = LocalDateTime.now(); + YearMonth lastMonth = YearMonth.from(now.minusMonths(1)); + LocalDateTime firstDay = lastMonth.atDay(1).atStartOfDay(); + LocalDateTime lastDay = lastMonth.atEndOfMonth().atTime(23, 59, 59); + return !dateTime.isBefore(firstDay) && !dateTime.isAfter(lastDay); + } catch (DateTimeParseException e) { + log.error("日期格式错误: " + e.getMessage()); + return false; + } + } + + // 安全解析年月 + private YearMonth parseYearMonth(String code, DateTimeFormatter fmt) { + if (code == null || code.isEmpty()) { + return YearMonth.of(1900, 1); // 返回最小值 + } + try { + return YearMonth.parse(code, fmt); + } catch (DateTimeParseException e) { + log.warn("parseYearMonth failed for '" + code + "', returning minimal YearMonth", e); + return YearMonth.of(1900, 1); + } + } + + // 自动检测日期格式的辅助方法 + private DateTimeFormatter detectFormat(String code1, String code2) { + String[] patterns = { + "yyyyMM", "yyyy-MM", "yyyy/MM", + "yyyy年MM月", "MM-yyyy", "MM/yyyy" + }; + + for (String pattern : patterns) { + try { + DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern); + if (!isEmpty(code1)) YearMonth.parse(code1, fmt); + if (!isEmpty(code2)) YearMonth.parse(code2, fmt); + return fmt; + } catch (Exception ignored) { + } + } + + return DateTimeFormatter.ofPattern("yyyyMM"); + } + + /** + * 获取编码规则生成编码(修复空判断) + */ + public static String getCodeRule(DynamicObject data, String number) { + String archivebillno = null; + DynamicObject coderule = BusinessDataServiceHelper.loadSingle("bos_coderule", "id", new QFilter[]{new QFilter("number", QCP.equals, number)}); + if (coderule != null) { + try { + String id = coderule.getString("id"); + if (!isEmpty(id)) { + CodeRuleInfo codeRule = CodeRuleCache.reloadCodeRuleById(id); + if (codeRule != null) { + archivebillno = CodeRuleServiceHelper.getNumber(codeRule, data); + } + } + } catch (Exception e) { + log.warn("reloadCodeRuleById failed: " + e.getMessage(), e); + } + } + + if (isEmpty(archivebillno)) { + try { + DynamicObject dynamicObject = BusinessDataServiceHelper.newDynamicObject("eceq_equipment_card"); + CodeRuleInfo codeRule = CodeRuleServiceHelper.getCodeRule(dynamicObject.getDataEntityType().getName(), dynamicObject, null); + if (codeRule != null) { + archivebillno = CodeRuleServiceHelper.getNumber(codeRule, dynamicObject); + } + } catch (Exception e) { + log.warn("getCodeRule fallback failed: " + e.getMessage(), e); + } + } + + if (archivebillno == null) archivebillno = ""; + log.info("生成的设备编号为 " + archivebillno); + return archivebillno; + } + + /** + * 获取操作错误信息(更健壮的空安全处理) + */ + private String getOperationResultErrorInfos(OperationResult operationResult) { + if (operationResult == null) return StringUtils.EMPTY; + if (operationResult.isSuccess()) { + return StringUtils.EMPTY; + } + + List errorInfos = operationResult.getAllErrorOrValidateInfo(); + int successCount = (operationResult.getSuccessPkIds() == null ? 0 : operationResult.getSuccessPkIds().size()); + int errorCount = (errorInfos == null ? 0 : errorInfos.size()); + int size = errorCount + successCount; + + if (size > 1 && errorInfos != null && !errorInfos.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < Math.min(5, errorInfos.size()); i++) { + IOperateInfo info = errorInfos.get(i); + if (info != null) { + sb.append(info.getMessage()); + if (i < Math.min(5, errorInfos.size()) - 1) sb.append("; "); + } + } + return sb.toString(); + } else if (errorInfos != null && !errorInfos.isEmpty()) { + OperateErrorInfo errorInfo = (OperateErrorInfo) errorInfos.get(0); + String msg = errorInfo.getMessage() == null ? "" : errorInfo.getMessage(); + return msg; + } else { + String msg = operationResult.getMessage() == null ? "" : operationResult.getMessage(); + return msg; + } + } +}