diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build.gradle b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build.gradle new file mode 100644 index 0000000..0022b35 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'java' +} + +group 'com.actionsoft' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + //必须绝对路径,bin/patch目录下的jar + def patchlib = fileTree(dir: 'D:\\Actionsoft\\aws7\\bin\\patch', includes: ['**\\*.jar']) + //必须绝对路径,bin/lib目录 + def binLib = fileTree(dir: 'D:\\Actionsoft\\aws7\\bin\\lib', includes: ['**\\*.jar']) + //必须绝对路径,bin/jdbc目录。排除inceptor-driver-8.16.0.jar(因为里面包含了和common-io jar冲突的类导致编译报错 + def jdbc = fileTree(dir: 'D:\\Actionsoft\\aws7\\bin\\jdbc', includes: ['**\\*.jar']).exclude('inceptor-driver-8.16.0.jar') + + def lib2 = fileTree(dir: 'D:\\Actionsoft\\aws7\\apps\\install\\com.awspaas.user.apps.bnbm.datalinkup\\lib', includes: ['**\\*.jar']) + implementation lib2 + + implementation patchlib + implementation binLib + implementation jdbc + + //当前应用的lib, 如果应用引用了三方jar包,建意提前下载手动拷贝到应用lib(%AWS_HOME%/apps/install/APPID/lib)目录下 + def lib = fileTree(dir: project.projectDir.toString() + '\\lib', includes: ['**\\*.jar']) + implementation lib +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/controller/DataLinkUpController.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/controller/DataLinkUpController.class new file mode 100644 index 0000000..8909112 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/controller/DataLinkUpController.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/entity/DateRange.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/entity/DateRange.class new file mode 100644 index 0000000..0e98db4 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/entity/DateRange.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/entity/Location.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/entity/Location.class new file mode 100644 index 0000000..26c63ff Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/entity/Location.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/job/PurchaseDataLinkUpJob.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/job/PurchaseDataLinkUpJob.class new file mode 100644 index 0000000..27e7512 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/job/PurchaseDataLinkUpJob.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/job/SaleDataLinkUpJob.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/job/SaleDataLinkUpJob.class new file mode 100644 index 0000000..274d916 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/job/SaleDataLinkUpJob.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/DataSummaryService.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/DataSummaryService.class new file mode 100644 index 0000000..1d38a62 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/DataSummaryService.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/DataSyncService.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/DataSyncService.class new file mode 100644 index 0000000..cf1fa83 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/DataSyncService.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSummaryServiceImpl.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSummaryServiceImpl.class new file mode 100644 index 0000000..813b4c4 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSummaryServiceImpl.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSyncServiceImpl.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSyncServiceImpl.class new file mode 100644 index 0000000..b0a6df4 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSyncServiceImpl.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleCountDimensionImpl.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleCountDimensionImpl.class new file mode 100644 index 0000000..94977bf Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleCountDimensionImpl.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSummaryServiceImpl.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSummaryServiceImpl.class new file mode 100644 index 0000000..1ca4718 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSummaryServiceImpl.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSyncServiceImpl.class b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSyncServiceImpl.class new file mode 100644 index 0000000..f4454b4 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/classes/java/main/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSyncServiceImpl.class differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/libs/com.awspaas.user.apps.bnbm.datalinkup-1.0-SNAPSHOT.jar b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/libs/com.awspaas.user.apps.bnbm.datalinkup-1.0-SNAPSHOT.jar new file mode 100644 index 0000000..a060547 Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/libs/com.awspaas.user.apps.bnbm.datalinkup-1.0-SNAPSHOT.jar differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/tmp/compileJava/previous-compilation-data.bin b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000..13aa23f Binary files /dev/null and b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/tmp/jar/MANIFEST.MF b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/tmp/jar/MANIFEST.MF new file mode 100644 index 0000000..59499bc --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/build/tmp/jar/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/controller/DataLinkUpController.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/controller/DataLinkUpController.java new file mode 100644 index 0000000..427bc78 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/controller/DataLinkUpController.java @@ -0,0 +1,384 @@ +package com.awspaas.user.apps.bnbm.datalinkup.controller; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.bpmn.engine.model.run.delegate.ProcessInstance; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.commons.mvc.view.ResponseObject; +import com.actionsoft.bpms.server.UserContext; +import com.actionsoft.bpms.server.bind.annotation.Controller; +import com.actionsoft.bpms.server.bind.annotation.Mapping; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.sdk.local.SDK; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService; +import com.awspaas.user.apps.bnbm.datalinkup.service.impl.*; +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * @ClassName: DataLinkUpController + * @Description: + * @author: 李春洋 + * @date: 2025/8/8 10:38 + * @Blog: https:// + */ +@Controller +public class DataLinkUpController { + private static final Logger LOGGER = LoggerFactory.getLogger(DataLinkUpController.class); + + /** + * 配置字段自动配置 + * @param sid + * @param toTable + * @param bindid + * @param byTable + * @return + */ + @Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_createTableFiled") + public ResponseObject createTableFiled(String sid, String toTable, String bindid, String byTable) { + ResponseObject ro = ResponseObject.newOkResponse(); + LOGGER.info("请求参数:sid:{},toTable:{},bindid:{},byTable{}", sid, toTable, bindid,byTable); + +// List maps = DBSql.getMaps("SHOW COLUMNS FROM " + toTable + " FROM aws7"); + List maps = DBSql.getMaps("SELECT COLUMN_NAME ,COLUMN_COMMENT \n" + + "FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'aws7' \n" + + "AND TABLE_NAME = '"+byTable+"'"); + + if (maps == null) { + ro.warn("表中没有字段"); + return ro; + } + ArrayList bos = new ArrayList<>(); + int i = 1; + for (RowMap map : maps) { + if (map.getString("COLUMN_NAME").equals("ID") || map.getString("COLUMN_NAME").equals("ORGID") + || map.getString("COLUMN_NAME").equals("BINDID") + || map.getString("COLUMN_NAME").equals("CREATEDATE") || map.getString("COLUMN_NAME").equals("CREATEUSER") + || map.getString("COLUMN_NAME").equals("UPDATEDATE") + || map.getString("COLUMN_NAME").equals("UPDATEUSER") || map.getString("COLUMN_NAME").equals("PROCESSDEFID") + || map.getString("COLUMN_NAME").equals("ISEND")) { + continue; + } else { + BO bo = new BO(); + bo.set("XH", i++); + bo.set("TBB", toTable); + bo.set("TBBZD", map.getString("COLUMN_NAME")); + bo.set("LDB", byTable); + bo.set("LDBZD", map.getString("COLUMN_NAME")); + bo.set("LDBZDM", map.getString("COLUMN_COMMENT")); + bos.add(bo); + } + } + ProcessInstance instanceById = SDK.getProcessAPI().getInstanceById(bindid); + SDK.getBOAPI().create("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB", bos, instanceById, UserContext.fromSessionId(sid)); + ro.ok("创建子表数据完成"); + return ro; + } + + /** + * 手动同步数据按钮(使用DataSyncService实现) + * + * @param dataStr 同步数据表配置(JSON数组) + * @param sid 用户会话ID + * @param formattedDate 同步时间(此实现中不使用该参数,由服务内部处理时间范围) + * @return 响应结果 + */ + @Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_manualSynchronization") + public ResponseObject manualSynchronization(String dataStr, String sid, String formattedDate) { + long methodStartTime = System.currentTimeMillis(); + LOGGER.info("【开始】数据同步操作,开始时间:{}", new Date(methodStartTime)); + + ResponseObject ro = ResponseObject.newOkResponse(); + try { + // 1. 解析JSON数据 + JSONArray configArray = new JSONArray(dataStr); + List mainConfigs = new ArrayList<>(); + // 3. 创建数据同步服务实例 + DataSyncService syncService = null; + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -1); // 昨天 + Date endDate = cal.getTime(); + + // 解析formattedDate为日期对象并计算时间范围 + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate date = LocalDate.parse(formattedDate, dateFormatter); + LocalDateTime startDateTime = date.atStartOfDay(); // 当天00:00:00 + Date startDate = Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant()); + + // 2. 转换为BO对象列表 + for (int i = 0; i < configArray.length(); i++) { + JSONObject config = configArray.getJSONObject(i); + String ssyw = config.getString("SSYW"); + if ("销售".equals(ssyw)) { + syncService = new SaleDataSyncServiceImpl(); + LOGGER.info("销售的接口"); + }else { + syncService = new PurchaseDataSyncServiceImpl(); + LOGGER.info("采购的接口"); + } + String timeField = config.getString("SJZD"); + String ccId = config.getString("CC_ID"); + String targetTable = config.getString("LDB"); + String partitionField = config.getString("FQBZD"); + String tableName = config.getString("TBB"); + String bkgs = config.getString("SSBK"); + + List fieldMappings = SDK.getBOAPI() + .query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB") + .addQuery("BINDID =", config.optString("_BINDID")) + .list(); + + // 删除目标表数据(根据时间字段是否为空决定删除范围) + if (timeField == null || timeField.isEmpty()) { + // 全量删除 + syncService.deleteAllTargetData(targetTable); + } else { + // 获取目标表时间字段名 + String targetTimeField = syncService.getTargetTimeField(fieldMappings, timeField); + if (targetTimeField == null) { + LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField); + return ro.warn("无法找到源时间字段["+timeField+"]对应的目标表字段,跳过同步"); + } + // 按时间范围删除 + syncService.deleteTargetData(targetTable, targetTimeField, startDate, endDate); + } + + syncService.querySourceData(ccId, tableName, timeField, startDate, endDate, partitionField, + fieldMappings, targetTable); + } + + LOGGER.info("开始使用DataSyncService处理数据同步 ({}条配置)", mainConfigs.size()); + + ro.put("success", true); + ro.put("message", "数据同步操作已完成"); + LOGGER.info("数据同步成功完成"); + } catch (Exception e) { + LOGGER.error("手动同步异常", e); + ro.put("success", false); + ro.put("message", "数据同步失败: " + e.getMessage()); + } + + long methodEndTime = System.currentTimeMillis(); + LOGGER.info("【完成】数据同步操作,总耗时:{}ms", methodEndTime - methodStartTime); + return ro; + } + + /** + * 执行数据汇总计算(使用DataSummaryService实现) + * @return 响应结果 + */ + @Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_calculateSummary") + public ResponseObject calculateSummary(String dataStr, String sid, String formattedDate) { + long methodStartTime = System.currentTimeMillis(); + LOGGER.info("【开始】数据计算汇总操作,开始时间:{}", new Date(methodStartTime)); + ResponseObject ro = ResponseObject.newOkResponse(); + JSONArray configArray = new JSONArray(dataStr); + + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -1); // 昨天 + Date endDate = cal.getTime(); + + // 解析formattedDate为日期对象并计算时间范围 + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate date = LocalDate.parse(formattedDate, dateFormatter); + LocalDateTime startDateTime = date.atStartOfDay(); // 当天00:00:00 + Date startDate = Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant()); + DataSummaryService summaryService = null; + SaleCountDimensionImpl saleCountDimension = null; + try { + LOGGER.info("开始执行销售数据多维度汇总计算"); + + DateRange dateRange = new DateRange(); + dateRange.setStartDate(startDate); + dateRange.setEndDate(endDate); + LOGGER.info("汇总计算开始时间为:{},结束时间为:{}",startDate,endDate); + // 2. 执行汇总计算 + for (int i = 0; i < configArray.length(); i++) { + JSONObject config = configArray.getJSONObject(i); + String timeField = config.getString("SJZD"); + String ccId = config.getString("CC_ID"); + String targetTable = config.getString("LDB"); + String partitionField = config.getString("FQBZD"); + String tableName = config.getString("TBB"); + String bkgs = config.getString("SSBK"); + String ssyw = config.getString("SSYW"); + + // 1. 创建数据汇总服务实例 + if ("销售".equals(ssyw)) { + summaryService = new SaleDataSummaryServiceImpl(); + saleCountDimension = new SaleCountDimensionImpl(); + LOGGER.info("销售销售的接口"); + }else { + summaryService = new PurchaseDataSummaryServiceImpl(); + LOGGER.info("采购销售的接口"); + } + + List bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS"); + if (bkgsMaps!=null) { + for (RowMap map : bkgsMaps) { + BO bo = new BO(); + bo.set("BKGS", map.getString("BKGS")); + summaryService.calculateSummary(dateRange, bo); + if (saleCountDimension!=null){ + //计算销售的维度 + LOGGER.info("======== 开始执行销售数据汇总计算 ========"); + saleCountDimension.calculateSummary(dateRange, bo); + LOGGER.info("======== 销售数据汇总计算完成 ========"); + } + } + } + } + + ro.put("success", true); + ro.put("message", "数据汇总计算完成"); + LOGGER.info("销售数据多维度汇总计算完成"); + } catch (Exception e) { + String errorMsg = "数据汇总计算失败: " + e.getMessage(); + LOGGER.error(errorMsg, e); + ro.put("success", false); + ro.put("message", errorMsg); + } + long methodEndTime = System.currentTimeMillis(); + LOGGER.info("【完成】数据计算汇总操作,总耗时:{}ms", methodEndTime - methodStartTime); + return ro; + } + + /** + * 各板块数据汇总 + * @param dataStr 同步数据表配置(JSON数组) + * @param sid 用户会话ID + * @param formattedDate 同步时间(此实现中不使用该参数,由服务内部处理时间范围) + * @return 响应结果 + */ + @Mapping("com.awspaas.user.apps.bnbm.datalinkup.controller.DataLinkUpController_summaryData") + public ResponseObject summaryData(String dataStr, String sid, String formattedDate){ + long methodStartTime = System.currentTimeMillis(); + LOGGER.info("【开始】数据汇总操作,开始时间:{}", new Date(methodStartTime)); + + ResponseObject ro = ResponseObject.newOkResponse(); + + DataSyncService dataSyncService = null; + + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -1); // 昨天 + Date endDate = cal.getTime(); + + // 解析formattedDate为日期对象并计算时间范围 + DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDate date = LocalDate.parse(formattedDate, dateFormatter); + LocalDateTime startDateTime = date.atStartOfDay(); // 当天00:00:00 + Date startDate = Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant()); + LOGGER.info("时间范围:{}-{}",startDate,endDate); + + JSONArray configArray = new JSONArray(dataStr); + // 2. 转换为BO对象列表 + for (int i = 0; i < configArray.length(); i++) { + long configStartTime = System.currentTimeMillis(); + + JSONObject mainConfig = configArray.getJSONObject(i); + String targetTable = mainConfig.getString("LDB");//落地表 + String timeField = mainConfig.getString("SJZD");//时间字段 + String bindId = mainConfig.getString("_BINDID");//bindid + String plate = mainConfig.getString("SSBK");// 所属板块 + String tablename = mainConfig.getString("TABLENAME");//表名 + String ssyw = mainConfig.getString("SSYW"); + String hzb = "";//汇总表 + + LOGGER.info("【开始处理】配置项[{}],板块:{},BindID:{},开始时间:{}", + i+1, plate, bindId, new Date(configStartTime)); + + if ("销售".equals(ssyw)) { + dataSyncService = new SaleDataSyncServiceImpl(); + if ("销售表".equals(tablename)){ + hzb = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ"; + }else { + hzb = "BO_EU_BNBM_DATALINKUP_XS_YSL"; + } + LOGGER.info("销售销售的接口"); + }else { + dataSyncService = new PurchaseDataSyncServiceImpl(); + if ("采购单".equals(tablename)){ + hzb = "BO_EU_DWD_ORDER_CGDD_HZ"; + } else if ("应付单".equals(tablename)){ + hzb = "BO_EU_DWD_ORDER_YFD_HZ"; + } else if ("库存".equals(tablename)) { + hzb = "BO_EU_DWD_ORDER_KC_HZ"; + } else { + hzb = "BO_EU_DWD_ORDER_RKD_HZ"; + } + LOGGER.info("采购销售的接口"); + } + + LOGGER.info("即将同步的数据:{}",plate); + try { + // 查询子表字段映射配置 + long queryStartTime = System.currentTimeMillis(); + List fieldMappings = SDK.getBOAPI() + .query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB") + .addQuery("BINDID =", bindId) + .list(); + //获取板块公司 + String bkgs = DBSql.getString("SELECT BKGS FROM " + targetTable, "BKGS"); + + LOGGER.info("字段映射配置查询完成,耗时:{}ms", System.currentTimeMillis() - queryStartTime); + + // 删除目标表数据(根据时间字段是否为空决定删除范围) + long deleteStartTime = System.currentTimeMillis(); + if (timeField == null || timeField.isEmpty()) { + // 全量删除 + String deleteSql = "DELETE FROM "+hzb+" WHERE BKGS = '"+bkgs+"'"; + int deletedCount = DBSql.update(deleteSql); + LOGGER.info("已删除目标表["+hzb+"]中{}条数据(时间范围: {} - {}),耗时:{}ms", + deletedCount, startDate, endDate, System.currentTimeMillis() - deleteStartTime); + + // 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ + // 全量分页迁移数据到汇总表 + long summarizeStartTime = System.currentTimeMillis(); + dataSyncService.summarizeScopeData(targetTable, null, null, null, hzb); + LOGGER.info("全量数据汇总完成,耗时:{}ms", System.currentTimeMillis() - summarizeStartTime); + } else { + // 获取目标表时间字段名 + String targetTimeField = dataSyncService.getTargetTimeField(fieldMappings, timeField); + if (targetTimeField == null) { + LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField); + } + // 按时间范围删除 + String deleteSql = "DELETE FROM " + hzb + + " WHERE BKGS = '"+bkgs+"' AND " + targetTimeField + " BETWEEN ? AND ?"; + int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate}); + LOGGER.info("已删除目标表["+hzb+"]中{}条数据(时间范围: {} - {})", + deletedCount, startDate, endDate); + + // 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ + // 按时间范围分页迁移数据到汇总表 + long summarizeStartTime = System.currentTimeMillis(); + dataSyncService.summarizeScopeData(targetTable, startDate, endDate, targetTimeField, hzb); + LOGGER.info("范围数据汇总完成,耗时:{}ms", System.currentTimeMillis() - summarizeStartTime); + } + LOGGER.info("【完成处理】配置项[{}],板块:{},总耗时:{}ms", + i+1, plate, System.currentTimeMillis() - configStartTime); + } catch (Exception e) { + LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}", + plate, mainConfig.getString("_BINDID"), e.getMessage(), e); + return ro.err("处理配置失败 [板块='"+plate+"', BindID='"+mainConfig.getString("BINDID")+"']: '"+e.getMessage()+"'"); + } + } + long methodEndTime = System.currentTimeMillis(); + LOGGER.info("【完成】数据汇总操作,总耗时:{}ms", methodEndTime - methodStartTime); + return ro.ok("汇总数据完成"); + } + + +} diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/entity/DateRange.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/entity/DateRange.java new file mode 100644 index 0000000..f1e6ba4 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/entity/DateRange.java @@ -0,0 +1,47 @@ +package com.awspaas.user.apps.bnbm.datalinkup.entity; + +import java.util.Date; + +/** + * @ClassName: DateRange + * @Description: + * @author: 李春洋 + * @date: 2025/8/12 13:42 + * @Blog: https:// + */ +public class DateRange { + private Date startDate; + private Date endDate; + + public DateRange() { + } + + public DateRange(Date startDate, Date endDate) { + this.startDate = startDate; + this.endDate = endDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + @Override + public String toString() { + return "DateRange{" + + "startDate=" + startDate + + ", endDate=" + endDate + + '}'; + } +} diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/entity/Location.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/entity/Location.java new file mode 100644 index 0000000..e67ad13 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/entity/Location.java @@ -0,0 +1,17 @@ +package com.awspaas.user.apps.bnbm.datalinkup.entity; + +/** + * 经纬度 + */ +public class Location { + private final String longitude; + private final String latitude; + + public Location(String longitude, String latitude) { + this.longitude = longitude; + this.latitude = latitude; + } + + public String getLongitude() { return longitude; } + public String getLatitude() { return latitude; } +} diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/job/PurchaseDataLinkUpJob.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/job/PurchaseDataLinkUpJob.java new file mode 100644 index 0000000..f2f8393 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/job/PurchaseDataLinkUpJob.java @@ -0,0 +1,73 @@ +package com.awspaas.user.apps.bnbm.datalinkup.job; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.schedule.IJob; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.sdk.local.SDK; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.service.impl.PurchaseDataSummaryServiceImpl; +import com.awspaas.user.apps.bnbm.datalinkup.service.impl.PurchaseDataSyncServiceImpl; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @ClassName: PurchaseDataLinkUpJob + * @Description: 采购数据同步定时任务 + */ +public class PurchaseDataLinkUpJob implements IJob { + private static final Logger LOGGER = LoggerFactory.getLogger(PurchaseDataLinkUpJob.class); + + @Override + public void execute(JobExecutionContext job) throws JobExecutionException { + //时间范围数值 + ArrayList list = new ArrayList<>(); + PurchaseDataSyncServiceImpl syncService = new PurchaseDataSyncServiceImpl(); + PurchaseDataSummaryServiceImpl purchaseDataSummaryService = new PurchaseDataSummaryServiceImpl(); + LOGGER.info("======== 开始执行销售数据同步任务 ========"); + // 查询销售业务的主配置 + List mainConfigs = SDK.getBOAPI().query("BO_EU_BNBM_DATALINKUP_SJGTPZ") + .addQuery("SSYW =", "采购") + .list(); + // 使用服务层处理同步逻辑 + ArrayList dateRanges = syncService.syncDataByConfigs(mainConfigs); + list.addAll(dateRanges); + LOGGER.info("======== 完成销售数据同步任务 ========"); + + // 汇总各板块数据 + LOGGER.info("======== 开始执行销售汇总各板块数据 ========"); + syncService.sumBkTable(mainConfigs); + LOGGER.info("======== 销售汇总各板块数据执行完成 ========"); + + //计算汇总维度 + Set collect = list.stream().filter(o -> o.getStartDate() != null || o.getEndDate() != null) + .collect(Collectors.toSet()); + DateRange dateRange = new DateRange(); + for (DateRange range : collect) { + if (range!=null || range.getStartDate()==null || range.getEndDate()==null) { + dateRange.setStartDate(range.getStartDate()); + dateRange.setEndDate(range.getEndDate()); + } + } + LOGGER.info("采购数据汇总计算开始时间为:{},结束时间为:{}",dateRange.getStartDate(),dateRange.getEndDate()); + for (BO mainConfig : mainConfigs) { + String targetTable = mainConfig.getString("LDB"); + List bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS"); + if (bkgsMaps!=null) { + for (RowMap map : bkgsMaps) { + BO bo = new BO(); + bo.set("BKGS", map.getString("BKGS")); + purchaseDataSummaryService.calculateSummary(dateRange, bo); + } + } + } + LOGGER.info("======== 销售数据同步任务执行完成 ========"); + } +} diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/job/SaleDataLinkUpJob.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/job/SaleDataLinkUpJob.java new file mode 100644 index 0000000..7880740 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/job/SaleDataLinkUpJob.java @@ -0,0 +1,108 @@ +package com.awspaas.user.apps.bnbm.datalinkup.job; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.schedule.IJob; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.sdk.local.SDK; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService; +import com.awspaas.user.apps.bnbm.datalinkup.service.impl.SaleCountDimensionImpl; +import com.awspaas.user.apps.bnbm.datalinkup.service.impl.SaleDataSummaryServiceImpl; +import com.awspaas.user.apps.bnbm.datalinkup.service.impl.SaleDataSyncServiceImpl; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @ClassName: SaleDataLinkUpJob + * @Description: 销售数据同步定时任务 + */ +public class SaleDataLinkUpJob implements IJob { + private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataLinkUpJob.class); + + /** + * 定时任务执行入口 + * @param context 任务执行上下文 + * @throws JobExecutionException 任务执行异常 + */ + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + //时间范围数值 + ArrayList list = new ArrayList<>(); + SaleDataSyncServiceImpl syncService = new SaleDataSyncServiceImpl(); + SaleDataSummaryServiceImpl summaryService = new SaleDataSummaryServiceImpl(); + SaleCountDimensionImpl saleCountDimension = new SaleCountDimensionImpl(); + try { + LOGGER.info("======== 开始执行销售数据同步任务 ========"); + // 查询销售业务的主配置 + List mainConfigs = SDK.getBOAPI().query("BO_EU_BNBM_DATALINKUP_SJGTPZ") + .addQuery("SSYW =", "销售") + .list(); + // 使用服务层处理同步逻辑 + ArrayList dateRanges = syncService.syncDataByConfigs(mainConfigs); + list.addAll(dateRanges); + LOGGER.info("======== 完成销售数据同步任务 ========"); + + // 汇总各板块数据 + LOGGER.info("======== 开始执行销售汇总各板块数据 ========"); + syncService.sumBkTable(mainConfigs); + LOGGER.info("======== 销售汇总各板块数据执行完成 ========"); + + // 数据同步完成后执行汇总计算 + LOGGER.info("======== 开始执行一体化-销售数据汇总计算 ========"); + //获取汇总计算时间 + Set collect = list.stream().filter(o -> o.getStartDate() != null || o.getEndDate() != null) + .collect(Collectors.toSet()); + DateRange dateRange = new DateRange(); + for (DateRange range : collect) { + if (range!=null || range.getStartDate()==null || range.getEndDate()==null) { + dateRange.setStartDate(range.getStartDate()); + dateRange.setEndDate(range.getEndDate()); + } + } + LOGGER.info("汇总计算开始时间为:{},结束时间为:{}",dateRange.getStartDate(),dateRange.getEndDate()); + for (BO mainConfig : mainConfigs) { + String targetTable = mainConfig.getString("LDB"); + List bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS"); + if (bkgsMaps!=null) { + for (RowMap map : bkgsMaps) { + BO bo = new BO(); + bo.set("BKGS", map.getString("BKGS")); + summaryService.calculateSummary(dateRange, bo); + } + } + } + LOGGER.info("======== 一体化-销售数据汇总计算完成 ========"); + + LOGGER.info("======== 开始执行销售数据汇总计算 ========"); + for (BO mainConfig : mainConfigs) { + String targetTable = mainConfig.getString("LDB"); + List bkgsMaps = DBSql.getMaps("SELECT BKGS FROM " + targetTable + " GROUP BY BKGS"); + if (bkgsMaps!=null) { + for (RowMap map : bkgsMaps) { + BO bo = new BO(); + bo.set("BKGS", map.getString("BKGS")); + saleCountDimension.calculateSummary(dateRange, bo); + } + } + } + LOGGER.info("======== 销售数据汇总计算完成 ========"); + + LOGGER.info("======== 销售数据同步任务执行完成 ========"); + + } catch (Exception e) { + String errorMsg = "销售数据同步任务执行失败: " + e.getMessage(); + LOGGER.error(errorMsg, e); + throw new JobExecutionException(errorMsg, e); + } + } + +} \ No newline at end of file diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/DataSummaryService.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/DataSummaryService.java new file mode 100644 index 0000000..e563d35 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/DataSummaryService.java @@ -0,0 +1,15 @@ +package com.awspaas.user.apps.bnbm.datalinkup.service; + +import com.actionsoft.bpms.bo.engine.BO; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; + +/** + * @ClassName: DataSummaryService + * @Description: 数据汇总服务接口,定义数据汇总的核心操作 + */ +public interface DataSummaryService { + /** + * 执行数据汇总计算 + */ + void calculateSummary(DateRange dateRange, BO mainConfig); +} \ No newline at end of file diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/DataSyncService.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/DataSyncService.java new file mode 100644 index 0000000..64597f4 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/DataSyncService.java @@ -0,0 +1,103 @@ +package com.awspaas.user.apps.bnbm.datalinkup.service; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.RowMap; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @ClassName: DataSyncService + * @Description: 数据同步服务接口,定义数据同步的核心操作 + */ +public interface DataSyncService { + /** + * 根据主配置列表执行数据同步 + * @param configs 主配置列表(BO_EU_BNBM_DATALINKUP_SJGTPZ表记录) + */ + ArrayList syncDataByConfigs(List configs); + + /** + * 处理单个主配置的数据同步 + * @param mainConfig 主配置对象(BO_EU_BNBM_DATALINKUP_SJGTPZ表单条记录) + */ + DateRange processMainConfig(BO mainConfig); + + /** + * 获取目标表时间字段名 + * @param fieldMappings 字段映射配置列表 + * @param timeField 源表时间字段名 + * @return 目标表时间字段名,未找到返回null + */ + String getTargetTimeField(List fieldMappings, String timeField); + + /** + * 删除目标表中指定时间范围的数据 + * @param targetTable 目标表名 + * @param targetTimeField 目标表时间字段名 + * @param startDate 开始时间 + * @param endDate 结束时间 + * @throws RuntimeException 删除失败时抛出 + */ + void deleteTargetData(String targetTable, String targetTimeField, Date startDate, Date endDate); + + void deleteAllTargetData(String targetTable); + + /** + * 高斯数据库专用查询方法(支持分区和分页) + * @param tableName + * @param timeField + * @param startDate + * @param endDate + * @param partitionField + * @param fieldMappings + * @param targetTable + */ +// void queryGaussDataWithCondition(String tableName, String timeField, Date startDate, Date endDate, String partitionField, List fieldMappings, String targetTable); + + /** + * 跨库查询源表数据 + * @param ccId 跨库连接ID + * @param tableName 源表名 + * @param timeField 源表时间字段名 + * @param startDate 开始时间 + * @param endDate 结束时间 + * @return 查询结果数据集 + * @throws RuntimeException 查询失败或参数无效时抛出 + */ + void querySourceData(String ccId, String tableName, String timeField, Date startDate, Date endDate, String partitionField, List fieldMappings, String targetTable); + + /** + * 各板块数据汇总 + * @param configs + */ + void sumBkTable(List configs); + + /** + * 将范围内数据汇总 + * @param targetTable + * @param startDate + * @param endDate + * @param targetTimeField + */ + void summarizeScopeData(String targetTable, Date startDate, Date endDate, String targetTimeField, String hzb); + + /** + * 处理并插入数据到目标表 + * @param sourceData 源数据列表 + * @param mappings 字段映射配置 + * @param targetTable 目标表名 + */ + int processAndInsertData(List sourceData, List mappings, String targetTable); + + /** + * 字段映射转换 + * @param source 源数据记录 + * @param mappings 字段映射配置 + * @return 转换后的BO对象 + */ + BO convertFields(RowMap source, List mappings); +} \ No newline at end of file diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSummaryServiceImpl.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSummaryServiceImpl.java new file mode 100644 index 0000000..e0d3b9a --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSummaryServiceImpl.java @@ -0,0 +1,798 @@ +package com.awspaas.user.apps.bnbm.datalinkup.service.impl; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.server.UserContext; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.sdk.local.SDK; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 采购汇总计算实现类 + */ +public class PurchaseDataSummaryServiceImpl implements DataSummaryService { + private static final Logger LOGGER = LoggerFactory.getLogger(PurchaseDataSummaryServiceImpl.class); + + // 维度表名常量 + /** + * 采购_年月采购明细 + */ + private static final String PROCUREMENT_DETAILS_YEAR_MONTH = "BO_EU_CG_NYCGMX"; + /** + * 采购_年月日采购明细 + */ + private static final String PROCUREMENT_DETAILS_YEAR_MONTH_DAY = "BO_EU_CG_NYRCGMX"; + /** + * 采购_基地_年月采购明细 + */ + private static final String JD_PROCUREMENT_DETAILS_YEAR_MONTH = "BO_EU_CG_JD_NYCGMX"; + /** + * + */ + private static final String BO_EU_CG_NYRKMX = "BO_EU_CG_NYRKMX"; + + // 原始数据表名 + /** + * 采购_采购单_汇总 + */ + private static final String PROCUREMENT_PURCHASE_ORDER_SUMMARY = "BO_EU_DWD_ORDER_CGDD_HZ"; + /** + * + */ + private static final String BO_EU_DWD_ORDER_RKD_HZ = "BO_EU_DWD_ORDER_RKD_HZ"; + + // 缓存物料分类列表 + private List distinctMaterialList = null; + private long lastMaterialListUpdateTime = 0; + private static final long MATERIAL_LIST_CACHE_TIME = 30 * 60 * 1000; // 30分钟缓存 + + @Override + public void calculateSummary(DateRange dateRange, BO mainConfig) { + try { + // 从主配置获取BKGS值 + String bkgs = mainConfig.getString("BKGS"); + if (bkgs == null || bkgs.isEmpty()) { + LOGGER.error("主配置中BKGS为空,无法进行汇总计算"); + return; + } + + // 获取物料分类列表(带缓存) + List distinctList = getDistinctMaterialList(); + + if (dateRange == null || dateRange.getStartDate() == null || dateRange.getEndDate() == null) { + LOGGER.info("未提供有效时间范围,按当前日期计算"); + calculateForCurrentDate(bkgs, distinctList); + } else { + LOGGER.info("开始执行采购数据多维度汇总计算(时间范围: {} 至 {})", + dateRange.getStartDate(), dateRange.getEndDate()); + + // 计算月度维度数据(按月遍历) + calculateMonthlyData(dateRange, bkgs, distinctList); + + // 计算日度维度数据(按天遍历) + calculateDailyData(dateRange, bkgs, distinctList); + + LOGGER.info("采购数据多维度汇总计算完成"); + } + } catch (Exception e) { + String errorMsg = "采购数据汇总计算失败: " + e.getMessage(); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 获取物料分类列表(带缓存) + */ + private List getDistinctMaterialList() { + long currentTime = System.currentTimeMillis(); + if (distinctMaterialList != null && (currentTime - lastMaterialListUpdateTime) < MATERIAL_LIST_CACHE_TIME) { + return distinctMaterialList; + } + + try { + List maps = DBSql.getMaps("SELECT WLMC FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + " GROUP BY WLMC"); + Set resultSet = new HashSet<>(); + for (RowMap row : maps) { + String wlmc = row.getString("WLMC"); + if (StringUtils.isBlank(wlmc)) continue; + + if (wlmc.contains("|")) { + String[] parts = wlmc.split("\\|"); + if (parts.length > 0 && StringUtils.isNotBlank(parts[0])) { + resultSet.add(parts[0].trim()); + } + } else { + resultSet.add(wlmc.trim()); + } + } + + distinctMaterialList = new ArrayList<>(resultSet); + lastMaterialListUpdateTime = currentTime; + LOGGER.info("物料分类列表已更新,共{}种物料", distinctMaterialList.size()); + + return distinctMaterialList; + } catch (Exception e) { + LOGGER.error("获取物料分类列表失败: {}", e.getMessage(), e); + return Collections.emptyList(); + } + } + + /** + * 按当前日期计算(无时间范围时使用) + */ + private void calculateForCurrentDate(String bkgs, List distinctList) { + Calendar cal = Calendar.getInstance(); + int year = cal.get(Calendar.YEAR); + int month = cal.get(Calendar.MONTH) + 1; + int day = cal.get(Calendar.DAY_OF_MONTH); + + // 当前年月 + String yearMonth = String.format("%04d-%02d", year, month); + + // 上月 + cal.add(Calendar.MONTH, -1); + String yearLastMonth = String.format("%04d-%02d", cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1); + + // 上年同期 + cal.add(Calendar.YEAR, 1); // 回到当前年 + cal.add(Calendar.YEAR, -1); // 再减一年 + String lastYearMonth = String.format("%04d-%02d", cal.get(Calendar.YEAR), month); + + Date currentDate = new Date(System.currentTimeMillis()); + + // 1. 计算并保存各板块物料采购总额、总量、单价(按月存储) + monthlyMaterialSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList); + + // 2. 根据年月汇总板块、基地、年月、当期、上期、同期数据 + monthlyBaseSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList); + + // 3. 计算并保存各板块物料采购总额、总量、单价(按日存储) + dailyMaterialSummaryBySegment(year, month, day, currentDate, bkgs, distinctList); + + // 4. 根据日期、入库单号、物料名称分页查询 日期、入库单号、物料编码、物料名称、规格型号、 + // (汇总)入库数量、单位、入库单价、(汇总)入库金额、供应商、订单编号、库存数 + dailyWarehousingSummary(year, month, day, currentDate, bkgs, distinctList); + + } + + /** + * 计算月度维度数据 + */ + private void calculateMonthlyData(DateRange dateRange, String bkgs, List distinctList) { + Calendar startCal = Calendar.getInstance(); + startCal.setTime(dateRange.getStartDate()); + startCal.set(Calendar.DAY_OF_MONTH, 1); // 设置为月份的第一天 + + Calendar endCal = Calendar.getInstance(); + endCal.setTime(dateRange.getEndDate()); + endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH)); // 设置为月份的最后一天 + + Calendar monthCal = (Calendar) startCal.clone(); + + while (!monthCal.after(endCal)) { + int year = monthCal.get(Calendar.YEAR); + int month = monthCal.get(Calendar.MONTH) + 1; + + // 当前年月 + String yearMonth = String.format("%04d-%02d", year, month); + + // 上月 + Calendar lastMonthCal = (Calendar) monthCal.clone(); + lastMonthCal.add(Calendar.MONTH, -1); + String yearLastMonth = String.format("%04d-%02d", lastMonthCal.get(Calendar.YEAR), lastMonthCal.get(Calendar.MONTH) + 1); + + // 上年同期 + Calendar lastYearMonthCal = (Calendar) monthCal.clone(); + lastYearMonthCal.add(Calendar.YEAR, -1); + String lastYearMonth = String.format("%04d-%02d", lastYearMonthCal.get(Calendar.YEAR), lastYearMonthCal.get(Calendar.MONTH) + 1); + + LOGGER.info("计算月度汇总数据: {}-{};上月:{};上年同期:{}", year, month, yearLastMonth, lastYearMonth); + + // 1. 计算并保存各板块物料采购总额、总量、单价(按月存储) + monthlyMaterialSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList); + + // 2. 根据年月汇总板块、基地、年月、当期、上期、同期数据 + monthlyBaseSummaryBySegment(year, month, yearMonth, yearLastMonth, lastYearMonth, bkgs, distinctList); + + // 移动到下个月 + monthCal.add(Calendar.MONTH, 1); + } + } + + /** + * 计算日度维度数据 + */ + private void calculateDailyData(DateRange dateRange, String bkgs, List distinctList) { + Calendar dayCal = Calendar.getInstance(); + dayCal.setTime(dateRange.getStartDate()); + + Calendar endCal = Calendar.getInstance(); + endCal.setTime(dateRange.getEndDate()); + + while (!dayCal.after(endCal)) { + int year = dayCal.get(Calendar.YEAR); + int month = dayCal.get(Calendar.MONTH) + 1; + int day = dayCal.get(Calendar.DAY_OF_MONTH); + Date currentDate = dayCal.getTime(); + + LOGGER.info("计算日度汇总数据: {}-{}-{}", year, month, day); + + // 计算并保存各板块物料采购总额、总量、单价(按日存储) + dailyMaterialSummaryBySegment(year, month, day, currentDate, bkgs, distinctList); + + // 根据日期、入库单号、物料名称分页查询 日期、入库单号、物料编码、物料名称、规格型号、 + //(汇总)入库数量、单位、入库单价、(汇总)入库金额、供应商、订单编号、库存数 + dailyWarehousingSummary(year, month, day, currentDate, bkgs, distinctList); + + // 下一天 + dayCal.add(Calendar.DATE, 1); + } + } + + /** + * 根据年月汇总 板块、基地、年月、当期(采购总额、采购总量、采购平均价)、上期(采购总额、采购总量、采购平均价)、同期(采购总额、采购总量、采购平均价) + * @param year + * @param month + * @param yearMonth + * @param yearLastMonth + * @param lastYearMonth + * @param bkgs + */ + private void monthlyBaseSummaryBySegment(int year, int month, String yearMonth, String yearLastMonth, + String lastYearMonth, String bkgs, List distinctList) { + try { + LOGGER.info("开始计算{}年{}月物料采购各基地月度汇总数据,板块公司:{}", year, month, bkgs); + + // 1. 删除该月份已存在的汇总数据(避免重复) + String deleteSql = "DELETE FROM " + JD_PROCUREMENT_DETAILS_YEAR_MONTH + + " WHERE BKGS = ? AND YEARMONTH = ?"; + DBSql.update(deleteSql, new Object[]{bkgs, yearMonth}); + + // 2. 一次性查询当前月、上月和去年同期数据(包含基地信息) + // 当前月数据 + String currentMonthSql = "SELECT WLMC, XQGC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " + + "FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + + " WHERE BKGS = ? AND CGRQ LIKE ? " + + "GROUP BY WLMC, XQGC"; + List currentMonthData = DBSql.getMaps(currentMonthSql, bkgs, yearMonth + "%"); + + // 上月数据 + List lastMonthData = Collections.emptyList(); + if (StringUtils.isNotBlank(yearLastMonth)) { + String lastMonthSql = "SELECT WLMC, XQGC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " + + "FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + + " WHERE BKGS = ? AND CGRQ LIKE ? " + + "GROUP BY WLMC, XQGC"; + lastMonthData = DBSql.getMaps(lastMonthSql, bkgs, yearLastMonth + "%"); + } + + // 去年同期数据 + List lastYearMonthData = Collections.emptyList(); + if (StringUtils.isNotBlank(lastYearMonth)) { + String lastYearMonthSql = "SELECT WLMC, XQGC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " + + "FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + + " WHERE BKGS = ? AND CGRQ LIKE ? " + + "GROUP BY WLMC, XQGC"; + lastYearMonthData = DBSql.getMaps(lastYearMonthSql, bkgs, lastYearMonth + "%"); + } + + // 3. 创建物料名称和基地组合键映射的数据Map + // 当前月数据映射 + Map currentMonthMap = currentMonthData.stream() + .collect(Collectors.toMap( + row -> { + String wlmc = row.getString("WLMC"); + String base = row.getString("XQGC"); + String processedWlmc = processMaterialName(wlmc); + return processedWlmc + "|" + base; + }, + row -> row, + (existing, replacement) -> { + double existingAmount = existing.getDouble("totalAmount"); + double replacementAmount = replacement.getDouble("totalAmount"); + existing.put("totalAmount", existingAmount + replacementAmount); + + double existingQuantity = existing.getDouble("totalQuantity"); + double replacementQuantity = replacement.getDouble("totalQuantity"); + existing.put("totalQuantity", existingQuantity + replacementQuantity); + + return existing; + } + )); + + // 上月数据映射 + Map lastMonthMap = lastMonthData.stream() + .collect(Collectors.toMap( + row -> { + String wlmc = row.getString("WLMC"); + String base = row.getString("XQGC"); + String processedWlmc = processMaterialName(wlmc); + return processedWlmc + "|" + base; + }, + row -> row, + (existing, replacement) -> { + double existingAmount = existing.getDouble("totalAmount"); + double replacementAmount = replacement.getDouble("totalAmount"); + existing.put("totalAmount", existingAmount + replacementAmount); + + double existingQuantity = existing.getDouble("totalQuantity"); + double replacementQuantity = replacement.getDouble("totalQuantity"); + existing.put("totalQuantity", existingQuantity + replacementQuantity); + + return existing; + } + )); + + // 去年同期数据映射 + Map lastYearMonthMap = lastYearMonthData.stream() + .collect(Collectors.toMap( + row -> { + String wlmc = row.getString("WLMC"); + String base = row.getString("XQGC"); + String processedWlmc = processMaterialName(wlmc); + return processedWlmc + "|" + base; + }, + row -> row, + (existing, replacement) -> { + double existingAmount = existing.getDouble("totalAmount"); + double replacementAmount = replacement.getDouble("totalAmount"); + existing.put("totalAmount", existingAmount + replacementAmount); + + double existingQuantity = existing.getDouble("totalQuantity"); + double replacementQuantity = replacement.getDouble("totalQuantity"); + existing.put("totalQuantity", existingQuantity + replacementQuantity); + + return existing; + } + )); + + // 4. 获取所有基地 + Set bases = new HashSet<>(); + for (RowMap row : currentMonthData) { + bases.add(row.getString("XQGC")); + } + for (RowMap row : lastMonthData) { + bases.add(row.getString("XQGC")); + } + for (RowMap row : lastYearMonthData) { + bases.add(row.getString("XQGC")); + } + + // 5. 批量插入 + List bos = new ArrayList<>(); + for (String wlmc : distinctList) { + for (String base : bases) { + String key = wlmc + "|" + base; + RowMap currentMonthRow = currentMonthMap.get(key); + if (currentMonthRow == null || currentMonthRow.getDouble("totalAmount") == 0.0) continue; + + BO summaryBO = new BO(); + summaryBO.set("BKGS", bkgs); + summaryBO.set("JD", base); + List bnbmCgPzwlfl = SDK.getBOAPI().query("BO_EU_BNBM_CG_PZWLFL"). + addQuery("BKMC = ", bkgs).addQuery("WLMC LIKE '%" + wlmc + "%'", null).list(); + if (bnbmCgPzwlfl!=null){ + String wlfl = bnbmCgPzwlfl.stream().map(o -> o.getString("WLFL")).collect(Collectors.joining("|")); + summaryBO.set("WLMC", wlfl); + }else { + summaryBO.set("WLMC", wlmc); + } + summaryBO.set("YEARMONTH", yearMonth); + summaryBO.set("CGZE", currentMonthRow.getDouble("totalAmount")); + summaryBO.set("CGZL", currentMonthRow.getDouble("totalQuantity")); + double avgPrice = currentMonthRow.getDouble("totalQuantity") != 0 ? + currentMonthRow.getDouble("totalAmount") / currentMonthRow.getDouble("totalQuantity") : 0; + summaryBO.set("PJDJ", avgPrice); + + // 添加上月数据 + RowMap lastMonthRow = lastMonthMap.get(key); + if (lastMonthRow != null) { + summaryBO.set("SQCGZE", lastMonthRow.getDouble("totalAmount")); + summaryBO.set("SQCGZL", lastMonthRow.getDouble("totalQuantity")); + double lastMonthAvgPrice = lastMonthRow.getDouble("totalQuantity") != 0 ? + lastMonthRow.getDouble("totalAmount") / lastMonthRow.getDouble("totalQuantity") : 0; + summaryBO.set("SQPJDJ", lastMonthAvgPrice); + } + + // 添加去年同期数据 + RowMap lastYearMonthRow = lastYearMonthMap.get(key); + if (lastYearMonthRow != null) { + summaryBO.set("TQCGZE", lastYearMonthRow.getDouble("totalAmount")); + summaryBO.set("TQCGZL", lastYearMonthRow.getDouble("totalQuantity")); + double lastYearMonthAvgPrice = lastYearMonthRow.getDouble("totalQuantity") != 0 ? + lastYearMonthRow.getDouble("totalAmount") / lastYearMonthRow.getDouble("totalQuantity") : 0; + summaryBO.set("TQPJDJ", lastYearMonthAvgPrice); + } + + bos.add(summaryBO); + } + } + + if (!bos.isEmpty()) { + // 批量插入数据 + int batchSize = 1000; + for (int i = 0; i < bos.size(); i += batchSize) { + int end = Math.min(i + batchSize, bos.size()); + List batch = bos.subList(i, end); + SDK.getBOAPI().createDataBO(JD_PROCUREMENT_DETAILS_YEAR_MONTH, batch, UserContext.fromUID("admin")); + } + } + + LOGGER.info("成功保存{}条月度基地汇总数据,板块:{}", bos.size(), bkgs); + + } catch (Exception e) { + String errorMsg = String.format("月度基地汇总计算失败(年月=%s,板块=%s): %s", yearMonth, bkgs, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 计算并保存各板块物料采购总额、总量、单价(按月存储) + * 逻辑:按物料名称(WLMC)分组,计算月度采购总额(SUM(JSHJ))、采购总量(SUM(YSSL))和平均单价(采购总额/采购总量) + * @param year 年份 + * @param month 月份 + * @param yearMonth 年月字符串(格式:yyyy-MM) + * @param bkgs 板块公司 + */ + private void monthlyMaterialSummaryBySegment(int year, int month, String yearMonth, + String yearLastMonth, String lastYearMonth, String bkgs, List distinctList) { + try { + LOGGER.info("开始计算{}年{}月物料采购月度汇总数据,板块公司:{}", year, month, bkgs); + + // 1. 删除该月份已存在的汇总数据(避免重复) + String deleteSql = "DELETE FROM " + PROCUREMENT_DETAILS_YEAR_MONTH + + " WHERE BKGS = ? AND YEARMONTH = ?"; + DBSql.update(deleteSql, new Object[]{bkgs, yearMonth}); + + // 2. 一次性查询当前月、上月和去年同期数据 + // 当前月数据 + String currentMonthSql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " + + "FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + + " WHERE BKGS = ? AND CGRQ LIKE ? " + + "GROUP BY WLMC"; + List currentMonthData = DBSql.getMaps(currentMonthSql, bkgs, yearMonth + "%"); + + // 上月数据 + List lastMonthData = Collections.emptyList(); + if (StringUtils.isNotBlank(yearLastMonth)) { + String lastMonthSql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " + + "FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + + " WHERE BKGS = ? AND CGRQ LIKE ? " + + "GROUP BY WLMC"; + lastMonthData = DBSql.getMaps(lastMonthSql, bkgs, yearLastMonth + "%"); + } + + // 去年同期数据 + List lastYearMonthData = Collections.emptyList(); + if (StringUtils.isNotBlank(lastYearMonth)) { + String lastYearMonthSql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " + + "FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + + " WHERE BKGS = ? AND CGRQ LIKE ? " + + "GROUP BY WLMC"; + lastYearMonthData = DBSql.getMaps(lastYearMonthSql, bkgs, lastYearMonth + "%"); + } + + // 3. 创建物料名称映射的数据Map + // 当前月数据映射 + Map currentMonthMap = currentMonthData.stream() + .collect(Collectors.toMap( + row -> processMaterialName(row.getString("WLMC")), + row -> row, + (existing, replacement) -> { + double existingAmount = existing.getDouble("totalAmount"); + double replacementAmount = replacement.getDouble("totalAmount"); + existing.put("totalAmount", existingAmount + replacementAmount); + + double existingQuantity = existing.getDouble("totalQuantity"); + double replacementQuantity = replacement.getDouble("totalQuantity"); + existing.put("totalQuantity", existingQuantity + replacementQuantity); + + return existing; + } + )); + + // 上月数据映射 + Map lastMonthMap = lastMonthData.stream() + .collect(Collectors.toMap( + row -> processMaterialName(row.getString("WLMC")), + row -> row, + (existing, replacement) -> { + double existingAmount = existing.getDouble("totalAmount"); + double replacementAmount = replacement.getDouble("totalAmount"); + existing.put("totalAmount", existingAmount + replacementAmount); + + double existingQuantity = existing.getDouble("totalQuantity"); + double replacementQuantity = replacement.getDouble("totalQuantity"); + existing.put("totalQuantity", existingQuantity + replacementQuantity); + + return existing; + } + )); + + // 去年同期数据映射 + Map lastYearMonthMap = lastYearMonthData.stream() + .collect(Collectors.toMap( + row -> processMaterialName(row.getString("WLMC")), + row -> row, + (existing, replacement) -> { + double existingAmount = existing.getDouble("totalAmount"); + double replacementAmount = replacement.getDouble("totalAmount"); + existing.put("totalAmount", existingAmount + replacementAmount); + + double existingQuantity = existing.getDouble("totalQuantity"); + double replacementQuantity = replacement.getDouble("totalQuantity"); + existing.put("totalQuantity", existingQuantity + replacementQuantity); + + return existing; + } + )); + + // 4. 批量插入 + List bos = new ArrayList<>(); + for (String wlmc : distinctList) { + RowMap currentMonthRow = currentMonthMap.get(wlmc); + if (currentMonthRow == null || currentMonthRow.getDouble("totalAmount") == 0.0) continue; + + BO summaryBO = new BO(); + summaryBO.set("YEARMONTH", yearMonth); + List bnbmCgPzwlfl = SDK.getBOAPI().query("BO_EU_BNBM_CG_PZWLFL"). + addQuery("BKMC = ", bkgs).addQuery("WLMC LIKE '%" + wlmc + "%'", null).list(); +// if (bnbmCgPzwlfl!=null){ +// String wlfl = bnbmCgPzwlfl.stream().map(o -> o.getString("WLFL")).collect(Collectors.joining("|")); +// summaryBO.set("WLMC", wlfl); +// }else { + summaryBO.set("WLMC", wlmc); +// } + summaryBO.set("CGZE", currentMonthRow.getDouble("totalAmount")); + summaryBO.set("CGZL", currentMonthRow.getDouble("totalQuantity")); + double avgPrice = currentMonthRow.getDouble("totalQuantity") != 0 ? + currentMonthRow.getDouble("totalAmount") / currentMonthRow.getDouble("totalQuantity") : 0; + summaryBO.set("PJDJ", avgPrice); + + // 添加上月数据 + RowMap lastMonthRow = lastMonthMap.get(wlmc); + if (lastMonthRow != null) { + summaryBO.set("SQCGZE", lastMonthRow.getDouble("totalAmount")); + summaryBO.set("SQCGZL", lastMonthRow.getDouble("totalQuantity")); + } + + // 添加去年同期数据 + RowMap lastYearMonthRow = lastYearMonthMap.get(wlmc); + if (lastYearMonthRow != null) { + summaryBO.set("TQCGZE", lastYearMonthRow.getDouble("totalAmount")); + summaryBO.set("TQCGZL", lastYearMonthRow.getDouble("totalQuantity")); + } + + summaryBO.set("BKGS", bkgs); + bos.add(summaryBO); + } + + if (!bos.isEmpty()) { + // 批量插入数据 + int batchSize = 1000; + for (int i = 0; i < bos.size(); i += batchSize) { + int end = Math.min(i + batchSize, bos.size()); + List batch = bos.subList(i, end); + SDK.getBOAPI().createDataBO(PROCUREMENT_DETAILS_YEAR_MONTH, batch, UserContext.fromUID("admin")); + } + } + + LOGGER.info("成功保存{}条月度汇总数据,板块:{}", bos.size(), bkgs); + + } catch (Exception e) { + String errorMsg = String.format("月度汇总计算失败(年月=%s,板块=%s): %s", yearMonth, bkgs, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 计算并保存各板块物料采购总额、总量、单价(按日存储) + * 逻辑:按物料名称(WLMC)分组,计算当日采购总额(SUM(JSHJ))、采购总量(SUM(YSSL))和平均单价(采购总额/采购总量) + * @param year 年份 + * @param month 月份 + * @param day 日期 + * @param currentDate 当前日期对象 + * @param bkgs 板块公司 + */ + private void dailyMaterialSummaryBySegment(int year, int month, int day, Date currentDate, + String bkgs, List distinctList) { + try { + // 格式化日期字符串 (yyyy-MM-dd) + String dateStr = String.format("%04d-%02d-%02d", year, month, day); + LOGGER.info("开始计算{}物料采购日度汇总数据,板块公司:{}", dateStr, bkgs); + + // 1. 删除该日期已存在的汇总数据 + String deleteSql = "DELETE FROM " + PROCUREMENT_DETAILS_YEAR_MONTH_DAY + + " WHERE BKGS = ? AND RQ = ?"; + DBSql.update(deleteSql, new Object[]{bkgs,dateStr}); + + // 2. 查询当天数据 + String querySql = "SELECT WLMC, SUM(JSHJ) AS totalAmount, SUM(YSSL) AS totalQuantity " + + "FROM " + PROCUREMENT_PURCHASE_ORDER_SUMMARY + + " WHERE BKGS = ? AND CGRQ = ? " + + "GROUP BY WLMC"; + List dailyData = DBSql.getMaps(querySql, bkgs, dateStr); + + // 3. 创建物料数据映射 + Map dailyMap = dailyData.stream() + .collect(Collectors.toMap( + row -> processMaterialName(row.getString("WLMC")), + row -> row, + (existing, replacement) -> { + double existingAmount = existing.getDouble("totalAmount"); + double replacementAmount = replacement.getDouble("totalAmount"); + existing.put("totalAmount", existingAmount + replacementAmount); + + double existingQuantity = existing.getDouble("totalQuantity"); + double replacementQuantity = replacement.getDouble("totalQuantity"); + existing.put("totalQuantity", existingQuantity + replacementQuantity); + + return existing; + } + )); + + // 4. 批量插入 + List bos = new ArrayList<>(); + for (String wlmc : distinctList) { + RowMap row = dailyMap.get(wlmc); + if (row == null || row.getDouble("totalAmount") == 0.0) continue; + + BO summaryBO = new BO(); + summaryBO.set("RQ", dateStr); + List bnbmCgPzwlfl = SDK.getBOAPI().query("BO_EU_BNBM_CG_PZWLFL"). + addQuery("BKMC = ", bkgs).addQuery("WLMC LIKE '%" + wlmc + "%'", null).list(); +// if (bnbmCgPzwlfl!=null){ +// String wlfl = bnbmCgPzwlfl.stream().map(o -> o.getString("WLFL")).collect(Collectors.joining("|")); +// summaryBO.set("WLMC", wlfl); +// }else { + summaryBO.set("WLMC", wlmc); +// } + summaryBO.set("CGZE", row.getDouble("totalAmount")); + summaryBO.set("CGZL", row.getDouble("totalQuantity")); + double avgPrice = row.getDouble("totalQuantity") != 0 ? + row.getDouble("totalAmount") / row.getDouble("totalQuantity") : 0; + summaryBO.set("PJDJ", avgPrice); + summaryBO.set("BKGS", bkgs); + bos.add(summaryBO); + } + + if (!bos.isEmpty()) { + // 批量插入数据 + int batchSize = 1000; + for (int i = 0; i < bos.size(); i += batchSize) { + int end = Math.min(i + batchSize, bos.size()); + List batch = bos.subList(i, end); + SDK.getBOAPI().createDataBO(PROCUREMENT_DETAILS_YEAR_MONTH_DAY, batch, UserContext.fromUID("admin")); + } + } + + LOGGER.info("成功保存{}条日度汇总数据,板块:{}", bos.size(), bkgs); + } catch (Exception e) { + String errorMsg = String.format("日度汇总计算失败(日期=%s,板块=%s): %s", currentDate, bkgs, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * // 根据日期、入库单号、物料名称分页查询 日期、入库单号、物料编码、物料名称、规格型号、 + * (汇总)入库数量、单位、入库单价、(汇总)入库金额、供应商、订单编号、库存数 + * @param year 年 + * @param month 月 + * @param day 日 + * @param currentDate 开始时间 + * @param bkgs 板块公司 + * @param distinctList 物料名称数组 + */ + private void dailyWarehousingSummary(int year, int month, int day, Date currentDate, + String bkgs, List distinctList) { + String dateStr = String.format("%04d-%02d-%02d", year, month, day); + String yearMonth = String.format("%04d%02d", year, month); // 年月格式YYYYMM + LOGGER.info("开始计算{}入库明细日度汇总数据,板块公司:{}", dateStr, bkgs); + + try { + // 1. 删除该日期已存在的汇总数据(避免重复) + String deleteSql = "DELETE FROM BO_EU_CG_NYRKMX WHERE BKGS = ? AND DATE = ?"; + DBSql.update(deleteSql, new Object[]{bkgs, dateStr}); + LOGGER.info("已清理{}的旧入库明细数据", dateStr); + + // 2. 计算总记录数用于分页 + String countSql = "SELECT COUNT(1) AS total FROM BO_EU_DWD_ORDER_RKD_HZ " + + "WHERE BKGS = ? AND DATE(DJRQ) = ?"; + RowMap countResult = DBSql.getMap(countSql, bkgs, dateStr); + int totalCount = countResult.getInt("total"); + LOGGER.info("共查询到{}条入库记录需要处理", totalCount); + + if (totalCount == 0) { + LOGGER.info("无入库数据需要处理,跳过"); + return; + } + + // 3. 分页处理数据 + int pageSize = 1000; + int totalPages = (int) Math.ceil((double) totalCount / pageSize); + LOGGER.info("开始分页处理入库数据,共{}页", totalPages); + + for (int page = 0; page < totalPages; page++) { + int offset = page * pageSize; + LOGGER.debug("正在处理第{}页入库数据,偏移量:{}", page + 1, offset); + + // 分页查询入库数据 +// String querySql = "SELECT DJRQ, DJH, WLBM, WLMC, GGXH, SLGC, RKSL, JLDW, " + +// "HSDJHYF, JEHYF, GYSNAME, CGDDH " + +// "FROM BO_EU_DWD_ORDER_RKD_HZ " + +// "WHERE BKGS = ? AND DATE(DJRQ) = ? " + +// "ORDER BY DJH LIMIT ? OFFSET ?"; + + String querySql = "SELECT DJRQ, DJH, WLMC, WLBM, GGXH, SLGC, SUM(RKSL) AS RKSL, " + + "JLDW, HSDJHYF, SUM(JEHYF) AS JEHYF, GYSNAME, CGDDH " + + "FROM BO_EU_DWD_ORDER_RKD_HZ WHERE BKGS = ? AND DATE(DJRQ) = ? " + + "GROUP BY DJRQ,DJH,WLMC "+ + "ORDER BY DJH LIMIT ? OFFSET ?"; + + List pageData = DBSql.getMaps(querySql, bkgs, dateStr, pageSize, offset); + LOGGER.debug("第{}页查询到{}条记录", page + 1, pageData.size()); + + // 转换并批量插入数据 + List bos = new ArrayList<>(); + for (RowMap row : pageData) { + BO detailBO = new BO(); + detailBO.set("DATE", row.getDate("DJRQ")); // 入库日期 + detailBO.set("RKDH", row.getString("DJH")); // 入库单号 + detailBO.set("WLBM", row.getString("WLBM")); // 物料编码 + detailBO.set("WLMC", row.getString("WLMC")); // 物料名称 + detailBO.set("GGXH", row.getString("GGXH")); // 规格型号 + detailBO.set("GC", row.getString("SLGC")); // 收料工厂 + detailBO.set("RKSL", row.getDouble("RKSL")); // 入库数量 + detailBO.set("DW", row.getString("JLDW")); // 单位 + detailBO.set("RKDJ", row.getDouble("HSDJHYF")); // 入库单价(含税单价含运费) + detailBO.set("RKJE", row.getDouble("JEHYF")); // 入库金额(金额含运费) + detailBO.set("GYS", row.getString("GYSNAME")); // 供应商 + detailBO.set("DDBH", row.getString("CGDDH")); // 订单编号 + detailBO.set("KCS", 0.00); // 库存数(默认为0,需后续计算) + detailBO.set("BKGS", bkgs); // 板块公司 + detailBO.set("YEARMONTH", yearMonth); // 年月 + bos.add(detailBO); + } + + // 批量插入当前页数据 + if (!bos.isEmpty()) { + SDK.getBOAPI().createDataBO(BO_EU_CG_NYRKMX, bos, UserContext.fromUID("admin")); + LOGGER.debug("入库明细日度汇总数据,板块公司:{}, 第{}页数据插入成功,共{}条,数量总共{}条", + bkgs, page + 1, bos.size(),totalCount); + } + } + + LOGGER.info("{}入库明细日度汇总完成,共处理{}条数据", dateStr, totalCount); + } catch (Exception e) { + String errorMsg = String.format("日度入库明细汇总失败(日期=%s,板块=%s): %s", + dateStr, bkgs, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 处理物料名称(提取竖线前的部分) + */ + private String processMaterialName(String wlmc) { + if (StringUtils.isBlank(wlmc)) { + return ""; + } + if (wlmc.contains("|")) { + String[] parts = wlmc.split("\\|"); + return parts[0].trim(); + } + return wlmc.trim(); + } +} diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSyncServiceImpl.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSyncServiceImpl.java new file mode 100644 index 0000000..0e313e0 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/PurchaseDataSyncServiceImpl.java @@ -0,0 +1,692 @@ +package com.awspaas.user.apps.bnbm.datalinkup.service.impl; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.DBUtils; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.server.UserContext; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.bpms.util.UtilDate; +import com.actionsoft.sdk.local.SDK; +import com.actionsoft.sdk.local.api.cc.RDSAPI; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @ClassName: PurchaseDataSyncServiceImpl + * @Description: + * @author: 李春洋 + * @date: 2025/8/15 15:46 + * @Blog: https:// + */ +public class PurchaseDataSyncServiceImpl implements DataSyncService { + private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataSyncServiceImpl.class); + /** + * 时间范围常量:同步最近30天数据(不包括当天) + */ + private static final int DAYS_BACK = Integer.parseInt(SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "days_back")); + + /** + * 增加分页大小常量 + */ + private static final int PAGE_SIZE = 1000; // 每页查询1000条记录 + + private static final String ORACLE_DATE_FORMAT = "YYYY-MM-DD HH24:MI:SS"; + @Override + public ArrayList syncDataByConfigs(List configs) { + ArrayList list = new ArrayList<>(); + if (configs.isEmpty()) { + LOGGER.info("未找到有效的同步配置"); + return list; + } + + // 按所属板块(SSBK)分组配置 + Map> configsByPlate = configs.stream() + .collect(Collectors.groupingBy(bo -> bo.getString("SSBK"))); + // 遍历处理每个板块的配置 + for (Map.Entry> entry : configsByPlate.entrySet()) { + String plate = entry.getKey(); + List plateConfigs = entry.getValue(); + LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size()); + + // 处理当前板块的每条配置 + boolean connectionFailed = false; + String errorMsg = ""; + for (BO mainConfig : plateConfigs) { + try { + DateRange dateRange = processMainConfig(mainConfig); + list.add(dateRange); + } catch (Exception e) { + LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}", + plate, mainConfig.getString("BINDID"), e.getMessage(), e); + } + } + } + return list; + } + + @Override + public DateRange processMainConfig(BO mainConfig) { + String bindId = mainConfig.getString("BINDID"); + String tableName = mainConfig.getString("TBB"); + String timeField = mainConfig.getString("SJZD"); + String targetTable = mainConfig.getString("LDB"); + String ccId = mainConfig.getString("CC_ID"); + String partitionField = mainConfig.getString("FQBZD"); + String bkgs = mainConfig.getString("BKGS"); + DateRange dateRange = new DateRange(); + + LOGGER.info("处理配置:BindID={}, 源表={}, 目标表={}, CC_ID={}, 时间字段={}, 分区字段配置={}", + bindId, tableName, targetTable, ccId,timeField,partitionField); + + // 查询子表字段映射配置 + List fieldMappings = SDK.getBOAPI() + .query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB") + .addQuery("BINDID =", bindId) + .list(); + + if (fieldMappings.isEmpty()) { + LOGGER.warn("未找到BindID={}的字段映射配置", bindId); + return dateRange; + } + + // 根据时间字段是否为空设置日期范围 + Date startDate = null; + Date endDate = null; + + // 删除目标表数据(根据时间字段是否为空决定删除范围) + if (timeField == null || timeField.isEmpty()) { + // 全量删除 + deleteAllTargetData(targetTable); + } else { + // 计算时间范围(当前日期-30天 ~ 昨天) + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -1); // 昨天 + endDate = cal.getTime(); + cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前(含) + startDate = cal.getTime(); + + // 获取目标表时间字段名 + String targetTimeField = getTargetTimeField(fieldMappings, timeField); + if (targetTimeField == null) { + LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField); + return dateRange; + } + // 按时间范围删除 + deleteTargetData(targetTable, targetTimeField, startDate, endDate); + } + querySourceData(ccId, tableName, timeField, startDate, endDate, partitionField, + fieldMappings, targetTable); +// } + dateRange.setStartDate(startDate); + dateRange.setEndDate(endDate); + return dateRange; + } + + /** + * 获取目标表时间字段名 + * @param mappings 字段映射配置列表 + * @param sourceTimeField 源表时间字段名 + * @return 目标表时间字段名,未找到返回null + */ + @Override + public String getTargetTimeField(List mappings, String sourceTimeField) { + for (BO mapping : mappings) { + if (sourceTimeField.equals(mapping.getString("TBBZD"))) { + return mapping.getString("LDBZD"); + } + } + return null; + } + + /** + * 删除目标表中指定时间范围的数据 + * @param targetTable 目标表名 + * @param targetTimeField 目标表时间字段名 + * @param startDate 开始时间 + * @param endDate 结束时间 + * @throws RuntimeException 删除失败时抛出 + */ + @Override + public void deleteTargetData(String targetTable, String targetTimeField, + Date startDate, Date endDate) { + try { + String deleteSql = "DELETE FROM " + targetTable + + " WHERE " + targetTimeField + " BETWEEN ? AND ?"; + int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate}); + LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})", + targetTable, deletedCount, startDate, endDate); + } catch (Exception e) { + throw new RuntimeException("删除目标表数据失败: " + e.getMessage(), e); + } + } + + @Override + public void deleteAllTargetData(String targetTable) { + try { + String deleteSql = "DELETE FROM " + targetTable ; + int deletedCount = DBSql.update(deleteSql); + LOGGER.info("已全量删除目标表[{}]中{}条数据", targetTable, deletedCount); + } catch (Exception e) { + throw new RuntimeException("全量删除目标表数据失败: " + e.getMessage(), e); + } + } + + /** + * 跨库查询源表数据 + * @param ccId 跨库连接ID + * @param tableName 源表名 + * @param timeField 源表时间字段名 + * @param startDated 开始时间 + * @param endDated 结束时间 + * @return 查询结果数据集 + * @throws RuntimeException 查询失败或参数无效时抛出 + */ + @Override + public void querySourceData(String ccId, String tableName, + String timeField, Date startDated, Date endDated,String partitionField, + List fieldMappings, String targetTable) { + int totalRows = 0; // 总查询行数 + int totalSuccess = 0; // 总成功插入行数 + int pageNo = 1; + boolean hasMore; + RDSAPI rdsapi = null; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String startDate = simpleDateFormat.format(startDated); + String endDate = simpleDateFormat.format(endDated); + try { + rdsapi = SDK.getCCAPI().getRDSAPI(ccId); + DBUtils.SUPPLY supply = rdsapi.getSupply(); + String DBname = supply.getName(); + LOGGER.info("数据库为:{}",DBname); + if ("ORACLE".equalsIgnoreCase(DBname)){ + // 构建查询条件 + StringBuilder conditionBuilder = new StringBuilder(); + List params = new ArrayList<>(); // 存储查询参数 + + // 分区字段和时间字段组合查询条件 + if (partitionField != null && !partitionField.isEmpty()) { + // 1. 查询最大分区值 + String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName; + List maxPartitionResult = rdsapi.getMaps(maxPartitionSql); + + if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) { + LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField); + return; + } + + String maxPartition = maxPartitionResult.get(0).getString("max_partition"); + LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition); + + // 添加分区条件 + conditionBuilder.append(partitionField) + .append(" = '") + .append(maxPartition) + .append("'"); + + // 如果时间字段存在,添加时间范围条件 + if (timeField != null && !timeField.isEmpty()) { + conditionBuilder.append(" AND TO_DATE(") + .append(timeField) + .append(", '") + .append(ORACLE_DATE_FORMAT) + .append("') BETWEEN TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("') AND TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("')"); + params.add(startDate); + params.add(endDate); + } + } else if (timeField != null && !timeField.isEmpty()) { + // 没有分区字段,但时间字段存在,使用时间范围条件 + // 仅时间范围条件(使用占位符) + conditionBuilder.append("TO_DATE(") + .append(timeField) + .append(", '") + .append(ORACLE_DATE_FORMAT) + .append("') BETWEEN TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("') AND TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("')"); + params.add(startDate); + params.add(endDate); + } else { + // 既没有分区字段也没有时间字段,查询全表 + LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!"); + conditionBuilder.append("1=1"); + } + + // 分页查询数据 + do { + // 使用Oracle分页语法 (12c+) + String querySql = "SELECT * FROM ( " + + "SELECT t.*, ROWNUM rn FROM " + tableName + " t " + + "WHERE " + conditionBuilder.toString() + " AND ROWNUM <= " + (pageNo * PAGE_SIZE) + + ") WHERE rn > " + ((pageNo - 1) * PAGE_SIZE); + + LOGGER.debug("执行Oracle查询: {}", querySql); + + List pageData; + // 根据条件类型执行查询 + if (partitionField != null && !partitionField.isEmpty() && + timeField != null && !timeField.isEmpty()) { + // 分区+时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else if (timeField != null && !timeField.isEmpty()) { + // 仅时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else { + // 无时间范围查询(仅分区或全表) + pageData = rdsapi.getMaps(querySql); + } + + if (pageData != null && !pageData.isEmpty()) { + // 直接处理当前页数据 + int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable); + totalRows += pageData.size(); + totalSuccess += successCount; + hasMore = pageData.size() == PAGE_SIZE; + pageNo++; + } else { + hasMore = false; + } + } while (hasMore); + }else { + // 构建查询条件 + StringBuilder conditionBuilder = new StringBuilder(); + // 修改点:分区字段和时间字段组合查询条件 + if (partitionField != null && !partitionField.isEmpty()) { + // 1. 查询最大分区值 + String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName; + List maxPartitionResult = rdsapi.getMaps(maxPartitionSql); + + if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) { + LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField); + return; + } + + String maxPartition = maxPartitionResult.get(0).getString("max_partition"); + LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition); + + // 添加分区条件 + conditionBuilder.append(partitionField) + .append(" = '") + .append(maxPartition) + .append("'"); + + // 如果时间字段存在,添加时间范围条件 + if (timeField != null && !timeField.isEmpty()) { + conditionBuilder.append(" AND ") + .append(timeField) + .append(" BETWEEN ? AND ?"); + } + } else if (timeField != null && !timeField.isEmpty()) { + // 没有分区字段,但时间字段存在,使用时间范围条件 + conditionBuilder.append(timeField) + .append(" BETWEEN ? AND ?"); + } else { + // 既没有分区字段也没有时间字段,查询全表(实际应避免这种情况) + LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!"); + conditionBuilder.append("1=1"); + } + // 分页查询数据 + do { + String querySql = "SELECT * FROM " + tableName + + " WHERE " + conditionBuilder.toString() + + " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE; + + LOGGER.debug("执行查询: {}", querySql); + + List pageData; + // 根据条件类型执行查询 + if (partitionField != null && !partitionField.isEmpty() && + timeField != null && !timeField.isEmpty()) { + // 分区+时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else if (timeField != null && !timeField.isEmpty()) { + // 仅时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else { + // 无时间范围查询(仅分区或全表) + pageData = rdsapi.getMaps(querySql); + } + + if (pageData != null && !pageData.isEmpty()) { + // 直接处理当前页数据 + int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable); + totalRows += pageData.size(); + totalSuccess += successCount; + hasMore = pageData.size() == PAGE_SIZE; + pageNo++; + } else { + hasMore = false; + } + } while (hasMore); + } + + LOGGER.info("从表[{}]共查询到{}条数据,成功同步{}条数据", + tableName, totalRows, totalSuccess); + } catch (Exception e) { + throw new RuntimeException("查询源表[" + tableName + "]数据失败: " + e.getMessage(), e); + }finally { + + } + } + + /** + * 处理并插入数据到目标表 + * @param sourceData 源数据列表 + * @param mappings 字段映射配置 + * @param targetTable 目标表名 + */ + public int processAndInsertData(List sourceData, + List mappings, String targetTable) { + if (sourceData.isEmpty()) { + LOGGER.info("没有需要同步的数据"); + return 0; + } + + List batchList = new ArrayList<>(); + int successCount = 0; + int totalCount = sourceData.size(); + int processedCount = 0; // 已处理记录数 + + for (int i = 0; i < totalCount; i++) { + RowMap record = sourceData.get(i); + processedCount++; // 增加已处理计数 + try { + // 字段映射转换 + BO targetData = convertFields(record, mappings); + batchList.add(targetData); + + // 批量插入条件:达到批处理大小或最后一条 + if (batchList.size() >= PAGE_SIZE || i == totalCount - 1) { + // 使用管理员权限批量插入 + SDK.getBOAPI().createDataBO(targetTable, batchList, UserContext.fromUID("admin")); + successCount += batchList.size(); + batchList.clear(); // 清空批次 + } + } catch (Exception e) { + LOGGER.error("数据处理失败: {}", e.getMessage(), e); + } + } + // 增加详细日志输出:共处理多少条,成功同步多少条 + LOGGER.info("本次处理{}条数据,成功同步{}条数据到表[{}]", + processedCount, successCount, targetTable); + return successCount; + } + + /** + * 字段映射转换 + * @param source 源数据记录 + * @param mappings 字段映射配置 + * @return 转换后的BO对象 + */ + public BO convertFields(RowMap source, List mappings) { + BO target = new BO(); + + for (BO mapping : mappings) { + String sourceField = mapping.getString("TBBZD"); + String targetField = mapping.getString("LDBZD"); + +// if (!source.containsKey(sourceField)) { +// LOGGER.debug("源字段[{}]不存在于查询结果中", sourceField); +// continue; +// } + + String operationExpr = mapping.getString("TBBZDJSLJ"); + if (StringUtils.isNotBlank(operationExpr)) { + // 解析运算表达式 (格式: [运算符][数字]) + char operator = operationExpr.charAt(0); + String numberPart = operationExpr.substring(1); + + try { + // 获取源值并转换为BigDecimal + String sourceValue = source.getString(sourceField); + if (StringUtils.isBlank(sourceValue)) { + target.set(targetField, null); + continue; + } + + if ("GG".equals(sourceField)){ + String string = source.getString(sourceField); + if (StringUtils.isNotBlank(string) && string.contains("×")){ + String[] split = string.split("×"); + String s = split[split.length - 1]; + target.set(targetField, s); + continue; + } + } + + BigDecimal sourceNum = new BigDecimal(sourceValue); + BigDecimal operand = new BigDecimal(numberPart); + BigDecimal result; + + // 执行相应运算 + switch (operator) { + case '*': + result = sourceNum.multiply(operand); + break; + case '/': + if (BigDecimal.ZERO.compareTo(operand) == 0) { + LOGGER.error("除零错误: 源字段[{}] 除数为0", sourceField); + result = sourceNum; // 避免除零异常 + } else { + // 除法保留10位小数并四舍五入 + result = sourceNum.divide(operand, 10, RoundingMode.HALF_UP); + } + break; + case '+': + result = sourceNum.add(operand); + break; + case '-': + result = sourceNum.subtract(operand); + break; + default: + LOGGER.error("未知运算符: {} 字段[{}]", operator, sourceField); + result = sourceNum; + } + target.set(targetField, result); + } catch (NumberFormatException e) { + LOGGER.error("数值转换失败: 源字段[{}]={}, 操作数={}", + sourceField, source.getString(sourceField), numberPart, e); + target.set(targetField, source.getString(sourceField)); + } + } else { + if ("GG".equals(sourceField)){ + String string = source.getString(sourceField); + if (StringUtils.isNotBlank(string) && string.contains("×")){ + String[] split = string.split("×"); + String s = split[split.length - 1]; + target.set(targetField, s); + }else { + // 无运算表达式时直接复制原始值 + target.set(targetField, StringUtils.isNotBlank(string)?string:""); + } + }else { + // 无运算表达式时直接复制原始值 + target.set(targetField, source.getString(sourceField)); + } + } + String ldzdmrz = mapping.getString("LDZDMRZ"); + if (StringUtils.isNotBlank(ldzdmrz)){ + target.set(mapping.getString("LDBZD"),ldzdmrz); + } + } + return target; + } + + /** + * 销售各板块数据汇总表 + * @param configs + */ + @Override + public void sumBkTable(List configs) { + Map> configsByPlate = configs.stream() + .collect(Collectors.groupingBy(bo -> bo.getString("SSBK"))); + + // 遍历处理每个板块的配置 + for (Map.Entry> entry : configsByPlate.entrySet()) { + String plate = entry.getKey(); + List plateConfigs = entry.getValue(); + LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size()); + + // 处理当前板块的每条配置 + for (BO mainConfig : plateConfigs) { + String targetTable = mainConfig.getString("LDB");//落地表 + String timeField = mainConfig.getString("SJZD");//时间字段 + String bindId = mainConfig.getString("BINDID");//bindid + String tablename = mainConfig.getString("TABLENAME");//同步表名 + String hzb = ""; + try { + if ("采购单".equals(tablename)){ + hzb = "BO_EU_DWD_ORDER_CGDD_HZ"; + }else if ("应付单".equals(tablename)){ + hzb = "BO_EU_DWD_ORDER_YFD_HZ"; + } else if ("库存".equals(tablename)) { + hzb = "BO_EU_DWD_ORDER_KC_HZ"; + } else { + hzb = "BO_EU_DWD_ORDER_RKD_HZ"; + } + // 查询子表字段映射配置 + List fieldMappings = SDK.getBOAPI() + .query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB") + .addQuery("BINDID =", bindId) + .list(); + //获取板块公司 + String bkgs = DBSql.getString("SELECT BKGS FROM " + targetTable, "BKGS"); + + // 根据时间字段是否为空设置日期范围 + Date startDate = null; + Date endDate = null; + + // 删除目标表数据(根据时间字段是否为空决定删除范围) + if (timeField == null || timeField.isEmpty()) { + // 全量删除 + String deleteSql = "DELETE FROM "+hzb+" WHERE BKGS = '"+bkgs+"'"; + int deletedCount = DBSql.update(deleteSql); + LOGGER.info("已删除目标表[{}}]中{}条数据(时间范围: {} - {})", + hzb,deletedCount, startDate, endDate); + + // 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ + // 全量分页迁移数据到汇总表 + summarizeScopeData(targetTable, null, null, null, hzb); + } else { + // 计算时间范围(当前日期-30天 ~ 昨天) + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -1); // 昨天 + endDate = cal.getTime(); + cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前(含) + startDate = cal.getTime(); + + // 获取目标表时间字段名 + String targetTimeField = getTargetTimeField(fieldMappings, timeField); + if (targetTimeField == null) { + LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField); + } + // 按时间范围删除 + String deleteSql = "DELETE FROM " + hzb + + " WHERE BKGS = '"+bkgs+"' AND " + targetTimeField + " BETWEEN ? AND ?"; + int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate}); + LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})", + hzb,deletedCount, startDate, endDate); + + // 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ + // 按时间范围分页迁移数据到汇总表 + summarizeScopeData(targetTable, startDate, endDate, targetTimeField, hzb); + } + } catch (Exception e) { + LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}", + plate, mainConfig.getString("BINDID"), e.getMessage(), e); + } + } + } + } + + /** + * 汇总各板块销售数据汇总 + * @param targetTable + * @param startDated + * @param endDated + * @param targetTimeField + */ + @Override + public void summarizeScopeData(String targetTable, Date startDated, Date endDated, String targetTimeField, String hzb) { + int pageNo = 1; + boolean hasMore; + String pageSql = ""; + List pageData = null; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String startDate = ""; + String endDate = ""; + try { + do { + if (startDated == null || endDated == null) { + pageSql = "SELECT * FROM " + targetTable + + " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE; + pageData = DBSql.getMaps(pageSql); + } else { + startDate = simpleDateFormat.format(startDated); + endDate = simpleDateFormat.format(endDated); + pageSql = "SELECT * FROM " + targetTable + + " WHERE " + targetTimeField + " BETWEEN '" + startDate + "' AND '" + endDate + "' " + + " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE; + LOGGER.info("执行查询的sql:{}", pageSql); + pageData = DBSql.getMaps(pageSql); + } + + if (pageData.isEmpty()) break; + + List bos = new ArrayList<>(); + for (RowMap map : pageData) { + BO bo = new BO(); + // 复制所有字段(排除系统字段) + for (String key : map.keySet()) { + if (!key.equalsIgnoreCase("ID") && + !key.equalsIgnoreCase("ORGID") && + !key.equalsIgnoreCase("CREATEDATE") && + !key.equalsIgnoreCase("CREATEUSER") && + !key.equalsIgnoreCase("UPDATEDATE") && + !key.equalsIgnoreCase("UPDATEUSER") && + !key.equalsIgnoreCase("ISEND") && + !key.equalsIgnoreCase("BINDID")) { + if (StringUtils.isNotBlank(targetTimeField)) { + String targetTimeField1 = map.getString(targetTimeField); + Date parse = UtilDate.parse(targetTimeField1); + int year = UtilDate.getYear(parse); + String monthFormat = UtilDate.monthFormat(parse); + int day = UtilDate.getDay(parse); + bo.set("YEARMONTH", year + monthFormat); + bo.set("YEAR", year); + bo.set("MONTH", monthFormat); + bo.set("DAY", day); + } + bo.set(key, map.get(key)); + } + } + bos.add(bo); + } + + SDK.getBOAPI().createDataBO(hzb, bos, UserContext.fromUID("admin")); + LOGGER.info("已迁移{}条数据到汇总表(页号: {},时间范围: {} - {})", + bos.size(), pageNo, startDate, endDate); + + hasMore = pageData.size() == PAGE_SIZE; + pageNo++; + } while (hasMore); + }catch (Exception e){ + LOGGER.error("汇总数据失败 [汇总表={}, 第几页={}]: {}", + hzb, PAGE_SIZE, e.getMessage(), e); + } + } +} diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleCountDimensionImpl.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleCountDimensionImpl.java new file mode 100644 index 0000000..5af889a --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleCountDimensionImpl.java @@ -0,0 +1,585 @@ +package com.awspaas.user.apps.bnbm.datalinkup.service.impl; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.server.UserContext; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.sdk.local.SDK; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.entity.Location; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 销售汇总维度 + */ +public class SaleCountDimensionImpl implements DataSummaryService { + private static final Logger LOGGER = LoggerFactory.getLogger(SaleCountDimensionImpl.class); + // 维度表名常量 + /** + * 销售_营业收入_年月 + */ + private static final String BO_EU_XS_YESR = "BO_EU_XS_YESR"; + /** + * 销售_销量销额_年月 + */ + private static final String BO_EU_XS_XLXE = "BO_EU_XS_XLXE"; + /** + * 销售_应收账款_年月 + */ + private static final String BO_EU_XS_YSZK = "BO_EU_XS_YSZK"; + /** + * 销售_区域两金占比_年月 + */ + private static final String BO_EU_XS_QYLJZB = "BO_EU_XS_QYLJZB"; + + // 原始数据表名 + /** + * 销售_销售类_汇总表 + */ + private static final String SALES_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ"; + /** + * 销售_应收类_汇总 + */ + private static final String RECEIVABLE_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_YSL"; + /** + * 采购_库存_汇总 + */ + private static final String BO_EU_DWD_ORDER_KC_HZ = "BO_EU_DWD_ORDER_KC_HZ"; + + // 日期格式化 + private static final SimpleDateFormat YEAR_MONTH_FORMAT = new SimpleDateFormat("yyyy-MM"); + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + + // 批量处理大小 + private static final int BATCH_SIZE = 1000; + // 批量分页大小 + private static final int PAGE_SIZE = 1000; + + @Override + public void calculateSummary(DateRange dateRange, BO mainConfig) { + try { + // 从主配置获取BKGS值 + String bkgs = mainConfig.getString("BKGS"); + if (bkgs == null || bkgs.isEmpty()) { + LOGGER.error("主配置中BKGS为空,无法进行汇总计算"); + return; + } + if (dateRange == null || dateRange.getStartDate() == null || dateRange.getEndDate() == null) { + LOGGER.info("未提供有效时间范围,按当前日期计算"); + calculateForCurrentDate(bkgs); + } else { + LOGGER.info("开始执行销售数据多维度汇总计算(时间范围: {} 至 {})", + dateRange.getStartDate(), dateRange.getEndDate()); + + // 计算月度维度数据(按月遍历) + calculateMonthlyData(dateRange, bkgs); + + LOGGER.info("销售数据多维度汇总计算完成"); + } + } catch (Exception e) { + String errorMsg = "销售数据汇总计算失败: " + e.getMessage(); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 按当前日期计算(无时间范围时使用) + */ + private void calculateForCurrentDate(String bkgs) { + Calendar cal = Calendar.getInstance(); + DateRange dateRange = new DateRange(); + + // 设置为当前月的第一天 + cal.set(Calendar.DAY_OF_MONTH, 1); + dateRange.setStartDate(cal.getTime()); + + // 设置为当前月的最后一天 + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + dateRange.setEndDate(cal.getTime()); + + calculateMonthlyData(dateRange, bkgs); + + } + + /** + * 计算月度维度数据 + */ + private void calculateMonthlyData(DateRange dateRange, String bkgs) { + Connection conn = null; + try { + conn = DBSql.open(); + conn.setAutoCommit(false); + // 获取时间范围内的所有月份 + List yearMonths = getYearMonthsBetweenDates(dateRange.getStartDate(), dateRange.getEndDate()); + + for (String yearMonth : yearMonths) { + LOGGER.info("开始处理{}月份的数据,板块公司: {}", yearMonth, bkgs); + + // 1. 处理营业收入数据 + LOGGER.info("开始营业收入数据"); + processRevenueData(conn, yearMonth, bkgs); + + // 2. 处理销量销额数据 + LOGGER.info("开始销量销额数据"); + processSalesVolumeData(conn, yearMonth, bkgs); + + // 3. 处理应收账款数据 + LOGGER.info("开始应收账款数据"); + processReceivableData(conn, yearMonth, bkgs); + + // 4. 处理区域两金占比 + LOGGER.info("开始区域两金占比"); + processRegionTwoFundsRatio(conn, yearMonth, bkgs); + } + conn.commit(); + LOGGER.info("所有月份数据处理完成"); + }catch (Exception e) { + try { + if (conn != null) conn.rollback(); + } catch (Exception rollbackEx) { + LOGGER.error("回滚事务失败", rollbackEx); + } + String errorMsg = "月度数据处理失败: " + e.getMessage(); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } finally { + DBSql.close(conn); + try { + LOGGER.info("销售数据汇总计算完成:关闭连接,conn状态为:{}",conn.getClientInfo()); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + } + + /** + * 处理营业收入数据 + */ + private void processRevenueData(Connection conn, String yearMonth, String bkgs) throws Exception { + LOGGER.info("开始处理营业收入数据,年月: {}, 板块公司: {}", yearMonth, bkgs); + int totalCount = 0; + + // 删除已存在的记录 + String deleteSql = "DELETE FROM " + BO_EU_XS_YESR + " WHERE YEARMONTH = ? AND BKGS = ?"; + try { + int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs}); + LOGGER.info("营业收入数据-已删除{}条营业收入记录", deleted); + }catch (Exception e){ + LOGGER.error("营业收入-删除数据{}数据错误,删除sql为:{},请检查数据库链接:{}",bkgs,deleteSql,e.getMessage()); + throw e; + } + + // 获取公司位置信息 + List companyList = DBSql.getMaps("SELECT GSMC,JD,WD FROM BO_EU_BNBM_DATALINKUP_GSJWD"); + Map resultMap = companyList.stream() + .filter(row -> row.get("GSMC") != null) + .collect(Collectors.toMap( + row -> row.get("GSMC").toString(), + row -> new Location( + row.get("JD") != null ? row.get("JD").toString() : null, + row.get("WD") != null ? row.get("WD").toString() : null + ), + (existing, replacement) -> existing + )); + + // 分页查询营业收入数据 + int page = 0; + boolean hasMore = true; + while (hasMore) { + int offset = page * PAGE_SIZE; + String querySql = "SELECT QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY, " + + "SUM(SSJERMB) as YYSR " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEARMONTH = ? AND BKGS = ? " + + "GROUP BY QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY " + + "LIMIT " + PAGE_SIZE + " OFFSET " + offset; + + LOGGER.debug("营业收入数据查询第{}页,SQL: {}", page + 1, querySql); + List maps = DBSql.getMaps(conn, querySql, yearMonth.replace("-", ""), bkgs); + + if (maps.isEmpty()) { + hasMore = false; + LOGGER.debug("营业收入数据第{}页无数据,停止分页查询", page + 1); + } else { + ArrayList bos = new ArrayList<>(); + for (RowMap map : maps) { + String gc = map.getString("KCZZ"); + BO bo = new BO(); + bo.set("YEARMONTH", yearMonth); + bo.set("BKGS", bkgs); + bo.set("QYGS", map.getString("QYGS")); + bo.set("GC", gc); + bo.set("LB_1", map.getString("LB_1")); + bo.set("LB_2", map.getString("LB_2")); + bo.set("LB_3", map.getString("LB_3")); + bo.set("SQ", map.getString("SQ")); + bo.set("CITY", map.getString("CS")); + bo.set("QX", map.getString("QY")); + if (resultMap.containsKey(gc)) { + Location location = resultMap.get(gc); + bo.set("JD", location.getLongitude()); + bo.set("WD", location.getLatitude()); + } + bo.set("YYSR", map.getDouble("YYSR")); + bos.add(bo); + } + + // 批量新增BO + if (!bos.isEmpty()) { + for (int i = 0; i < bos.size(); i += BATCH_SIZE) { + int end = Math.min(bos.size(), i + BATCH_SIZE); + List batchList = bos.subList(i, end); + SDK.getBOAPI().createDataBO(BO_EU_XS_YESR, batchList, UserContext.fromUID("admin")); + } + totalCount += bos.size(); + LOGGER.info("营业收入数据第{}页处理完成,本页{}条记录,累计{}条", page + 1, bos.size(), totalCount); + } + page++; + } + } + LOGGER.info("营业收入数据处理完成,共处理{}条记录", totalCount); + } + + /** + * 处理销量销额数据(分页查询) + */ + private void processSalesVolumeData(Connection conn, String yearMonth, String bkgs) throws Exception { + LOGGER.info("开始处理销量销额数据,年月: {}, 板块公司: {}", yearMonth, bkgs); + int totalCount = 0; + + // 删除已存在的记录 + String deleteSql = "DELETE FROM " + BO_EU_XS_XLXE + " WHERE YEARMONTH = ? AND BKGS = ?"; + try { + int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs}); + LOGGER.info("销量销额-已删除{}条记录", deleted); + } catch (Exception e) { + LOGGER.error("销量销额-删除{}数据错误,删除sql为:{},请检查数据库链接:{}", bkgs, deleteSql, e.getMessage()); + throw e; + } + + // 获取公司位置信息 + List companyList = DBSql.getMaps("SELECT GSMC,JD,WD FROM BO_EU_BNBM_DATALINKUP_GSJWD"); + Map resultMap = companyList.stream() + .filter(row -> row.get("GSMC") != null) + .collect(Collectors.toMap( + row -> row.get("GSMC").toString(), + row -> new Location( + row.get("JD") != null ? row.get("JD").toString() : null, + row.get("WD") != null ? row.get("WD").toString() : null + ), + (existing, replacement) -> existing + )); + + // 分页查询销量销额数据 + int page = 0; + boolean hasMore = true; + while (hasMore) { + int offset = page * PAGE_SIZE; + String querySql = "SELECT QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY, " + + "SUM(ZSSL) as XL, SUM(SSJERMB) as XE ,(SUM(XSSL))/10000 AS TSXL " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEARMONTH = ? AND BKGS = ? " + + "GROUP BY QYGS, KCZZ, LB_1, LB_2, LB_3, SQ, CS, QY " + + "LIMIT " + PAGE_SIZE + " OFFSET " + offset; + + LOGGER.debug("销量销额数据查询第{}页,SQL: {}", page + 1, querySql); + List maps = DBSql.getMaps(conn, querySql, yearMonth.replace("-", ""), bkgs); + + if (maps.isEmpty()) { + hasMore = false; + LOGGER.debug("销量销额数据第{}页无数据,停止分页查询", page + 1); + } else { + ArrayList bos = new ArrayList<>(); + for (RowMap map : maps) { + String gc = map.getString("KCZZ"); + BO bo = new BO(); + bo.set("YEARMONTH", yearMonth); + bo.set("BKGS", bkgs); + bo.set("QYGS", map.getString("QYGS")); + bo.set("GC", gc); + bo.set("LB_1", map.getString("LB_1")); + bo.set("LB_2", map.getString("LB_2")); + bo.set("LB_3", map.getString("LB_3")); + bo.set("SQ", map.getString("SQ")); + bo.set("CITY", map.getString("CS")); + bo.set("QX", map.getString("QY")); + if (resultMap.containsKey(gc)) { + Location location = resultMap.get(gc); + bo.set("JD", location.getLongitude()); + bo.set("WD", location.getLatitude()); + } + if (bkgs.equals("泰山石膏") && "石膏板".equals(map.getString("LB_1"))){ + bo.set("XL", map.getDouble("TSXL")); + }else { + bo.set("XL", map.getDouble("XL")); + } + bo.set("XE", map.getDouble("XE")); + bo.set("MC_JC", Math.random()%2==0?"面材":"基材"); + bo.set("JZ_GZ", Math.random()%2==0?"家装":"工装"); + bos.add(bo); + } + + // 批量新增BO + if (!bos.isEmpty()) { + for (int i = 0; i < bos.size(); i += BATCH_SIZE) { + int end = Math.min(bos.size(), i + BATCH_SIZE); + List batchList = bos.subList(i, end); + SDK.getBOAPI().createDataBO(BO_EU_XS_XLXE, batchList, UserContext.fromUID("admin")); + } + totalCount += bos.size(); + LOGGER.info("销量销额数据第{}页处理完成,本页{}条记录,累计{}条", page + 1, bos.size(), totalCount); + } + page++; + } + } + LOGGER.info("销量销额数据处理完成,共处理{}条记录", totalCount); + } + + /** + * 处理应收账款数据(分页查询) + */ + private void processReceivableData(Connection conn, String yearMonth, String bkgs) throws Exception { + LOGGER.info("开始处理应收账款数据,年月: {}, 板块公司: {}", yearMonth, bkgs); + int totalCount = 0; + + // 删除已存在的记录 + String deleteSql = "DELETE FROM " + BO_EU_XS_YSZK + " WHERE YEARMONTH = ? AND BKGS = ?"; + try { + int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs}); + LOGGER.info("应收账款-已删除{}条记录", deleted); + } catch (Exception e) { + LOGGER.error("应收账款-删除{}数据错误,删除sql为:{},请检查数据库链接:{}", bkgs, deleteSql, e.getMessage()); + throw e; + } + + // 获取指定年月最后一天 + Calendar cal = Calendar.getInstance(); + cal.setTime(YEAR_MONTH_FORMAT.parse(yearMonth)); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + String lastDayOfMonth = DATE_FORMAT.format(cal.getTime()); + + // 获取公司位置信息 + List companyList = DBSql.getMaps("SELECT GSMC,JD,WD FROM BO_EU_BNBM_DATALINKUP_GSJWD"); + Map resultMap = companyList.stream() + .filter(row -> row.get("GSMC") != null) + .collect(Collectors.toMap( + row -> row.get("GSMC").toString(), + row -> new Location( + row.get("JD") != null ? row.get("JD").toString() : null, + row.get("WD") != null ? row.get("WD").toString() : null + ), + (existing, replacement) -> existing + )); + + // 分页查询应收账款数据 + int page = 0; + boolean hasMore = true; + while (hasMore) { + int offset = page * PAGE_SIZE; + String querySql = "SELECT QYGS, XSZZ, SHENGQU, SHIQU, QX, QCYE, LJXS, LJHK, YSYE, " + + "ZLFX0_60, ZLFX60_1, ZLFX1_2, ZLFX2_3, ZLFX3_4, ZLFX4_5, ZLFX5 " + + "FROM " + RECEIVABLE_DETAIL_TABLE + " " + + "WHERE DATE(RQ) = ? AND BKGS = ? " + + "LIMIT " + PAGE_SIZE + " OFFSET " + offset; + + LOGGER.debug("应收账款数据查询第{}页,SQL: {}", page + 1, querySql); + List maps = DBSql.getMaps(conn, querySql, lastDayOfMonth, bkgs); + + if (maps.isEmpty()) { + hasMore = false; + LOGGER.debug("应收账款数据第{}页无数据,停止分页查询", page + 1); + } else { + ArrayList bos = new ArrayList<>(); + for (RowMap map : maps) { + String xszz = map.getString("XSZZ"); + BO bo = new BO(); + bo.set("YEARMONTH", yearMonth); + bo.set("BKGS", bkgs); + bo.set("QYGS", map.getString("QYGS")); + bo.set("GC", xszz); + bo.set("SQ", map.getString("SHENGQU")); + bo.set("CITY", map.getString("SHIQU")); + bo.set("QX", map.getString("QX")); + if (resultMap.containsKey(xszz)) { + Location location = resultMap.get(xszz); + bo.set("JD", location.getLongitude()); + bo.set("WD", location.getLatitude()); + } + bo.set("QCYE", map.getDouble("QCYE")); + bo.set("NFH", map.getDouble("LJXS")); + bo.set("NHK", map.getDouble("LJHK")); + bo.set("YSZE", map.getDouble("YSYE")); + bo.set("ZL60", map.getDouble("ZLFX0_60")); + bo.set("ZL60_1", map.getDouble("ZLFX60_1")); + bo.set("ZL1_2", map.getDouble("ZLFX1_2")); + bo.set("ZL2_3", map.getDouble("ZLFX2_3")); + bo.set("ZL3_4", map.getDouble("ZLFX3_4")); + bo.set("ZL4_5", map.getDouble("ZLFX4_5")); + bo.set("ZL5", map.getDouble("ZLFX5")); + bos.add(bo); + } + + // 批量新增BO + if (!bos.isEmpty()) { + for (int i = 0; i < bos.size(); i += BATCH_SIZE) { + int end = Math.min(bos.size(), i + BATCH_SIZE); + List batchList = bos.subList(i, end); + SDK.getBOAPI().createDataBO(BO_EU_XS_YSZK, batchList, UserContext.fromUID("admin")); + } + totalCount += bos.size(); + LOGGER.info("应收账款数据第{}页处理完成,本页{}条记录,累计{}条", page + 1, bos.size(), totalCount); + } + page++; + } + } + LOGGER.info("应收账款数据处理完成,共处理{}条记录", totalCount); + } + + /** + * 处理区域两金占比数据(分页查询) + */ + private void processRegionTwoFundsRatio(Connection conn, String yearMonth, String bkgs) throws Exception { + LOGGER.info("开始处理区域两金占比数据,年月: {}, 板块公司: {}", yearMonth, bkgs); + int totalCount = 0; + + // 删除已存在的记录 + String deleteSql = "DELETE FROM " + BO_EU_XS_QYLJZB + " WHERE YEARMONTH = ? AND BKGS = ?"; + try { + int deleted = DBSql.update(conn, deleteSql, new Object[]{yearMonth, bkgs}); + LOGGER.info("区域两金占比-已删除{}条记录", deleted); + } catch (Exception e) { + LOGGER.error("区域两金占比-删除{}数据错误,删除sql为:{},请检查数据库链接:{}", bkgs, deleteSql, e.getMessage()); + throw e; + } + + // 获取指定年月最后一天 + Calendar cal = Calendar.getInstance(); + cal.setTime(YEAR_MONTH_FORMAT.parse(yearMonth)); + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + String lastDayOfMonth = DATE_FORMAT.format(cal.getTime()); + + // 分页查询区域两金占比数据 + int page = 0; + boolean hasMore = true; + while (hasMore) { + int offset = page * PAGE_SIZE; + + // 第一个SQL:查询应收账款数据 + String receivableSql = "SELECT QYGS, XSZZ, SUM(YSYE) as YSZK " + + "FROM " + RECEIVABLE_DETAIL_TABLE + " " + + "WHERE YEAR(RQ) = YEAR(?) AND MONTH(RQ) = MONTH(?) AND BKGS = ? " + + "GROUP BY QYGS, BKGS" + + "LIMIT " + PAGE_SIZE + " OFFSET " + offset; + + LOGGER.debug("应收账款数据查询第{}页,SQL: {}", page + 1, receivableSql); + List receivableMaps = DBSql.getMaps(conn, receivableSql, + lastDayOfMonth, lastDayOfMonth, bkgs); + + if (receivableMaps.isEmpty()) { + hasMore = false; + LOGGER.debug("应收账款数据第{}页无数据,停止分页查询", page + 1); + } else { + ArrayList bos = new ArrayList<>(); + + // 收集所有销售组织用于库存查询 + List xszzList = new ArrayList<>(); + Map receivableMap = new HashMap<>(); + + for (RowMap map : receivableMaps) { + String xszz = map.getString("XSZZ"); + xszzList.add(xszz); + receivableMap.put(xszz, map); + } + + // 第二个SQL:查询库存金额数据 + if (!xszzList.isEmpty()) { + String placeholders = String.join(",", Collections.nCopies(xszzList.size(), "?")); + + String inventorySql = "SELECT STOCKORGNAME, SUM(BALANCE_AMOUNT) as KCJE " + + "FROM " + BO_EU_DWD_ORDER_KC_HZ + " " + + "WHERE STOCKORGNAME IN (" + placeholders + ") " + + "AND CATEGORY = '产成品' " + + "AND YEAR(INDATE) = YEAR(?) " + + "AND MONTH(INDATE) = MONTH(?) " + + "GROUP BY STOCKORGNAME"; + + LOGGER.debug("库存金额数据查询,SQL: {}", inventorySql); + + List inventoryMaps = DBSql.getMaps(conn, inventorySql, lastDayOfMonth,lastDayOfMonth); + Map inventoryMap = inventoryMaps.stream() + .collect(Collectors.toMap( + row -> row.getString("STOCKORGNAME"), + row -> row.getDouble("KCJE"), + (existing, replacement) -> existing + )); + + // 合并数据并创建BO对象 + for (RowMap receivable : receivableMaps) { + String xszz = receivable.getString("XSZZ"); + double yszk = receivable.getDouble("YSZK"); + double kcje = inventoryMap.getOrDefault(xszz, 0.0); + double ljzb = 0.0; + + if ((yszk + kcje) > 0) { + ljzb = (yszk / (yszk + kcje)) * 100; + } + + BO bo = new BO(); + bo.set("YEARMONTH", yearMonth); + bo.set("BKGS", bkgs); + bo.set("QYGS", receivable.getString("QYGS")); + bo.set("YSZK", yszk); + bo.set("KCJE", kcje); + bos.add(bo); + } + } + + // 批量新增BO + if (!bos.isEmpty()) { + for (int i = 0; i < bos.size(); i += BATCH_SIZE) { + int end = Math.min(bos.size(), i + BATCH_SIZE); + List batchList = bos.subList(i, end); + SDK.getBOAPI().createDataBO(BO_EU_XS_QYLJZB, batchList, UserContext.fromUID("admin")); + } + totalCount += bos.size(); + LOGGER.info("区域两金占比数据第{}页处理完成,本页{}条记录,累计{}条", + page + 1, bos.size(), totalCount); + } + page++; + } + } + LOGGER.info("区域两金占比数据处理完成,共处理{}条记录", totalCount); + } + + /** + * 获取两个日期之间的所有年月 + */ + private List getYearMonthsBetweenDates(Date startDate, Date endDate) { + List yearMonths = new ArrayList<>(); + Calendar startCal = Calendar.getInstance(); + startCal.setTime(startDate); + startCal.set(Calendar.DAY_OF_MONTH, 1); + + Calendar endCal = Calendar.getInstance(); + endCal.setTime(endDate); + endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH)); + + Calendar currentCal = (Calendar) startCal.clone(); + + while (!currentCal.after(endCal)) { + yearMonths.add(YEAR_MONTH_FORMAT.format(currentCal.getTime())); + currentCal.add(Calendar.MONTH, 1); + } + + return yearMonths; + } +} diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSummaryServiceImpl.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSummaryServiceImpl.java new file mode 100644 index 0000000..6ae54b1 --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSummaryServiceImpl.java @@ -0,0 +1,827 @@ +package com.awspaas.user.apps.bnbm.datalinkup.service.impl; + +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.server.UserContext; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.sdk.local.SDK; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSummaryService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * 一体化——销售汇总计算实现类 + */ +public class SaleDataSummaryServiceImpl implements DataSummaryService { + private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataSummaryServiceImpl.class); + + // 维度表名常量 + /** + * 一体化_销售_产品配套率月度表 + */ + private static final String MATCHING_RATE_YEAR_TABLE = "BO_EU_DATALINKUP_FACT_MATCHING_RATE_YEAR"; + /** + * 一体化_销售_产品销售月明细表 + */ + private static final String PRODUCT_MONTHLY_TABLE = "BO_EU_DATALINKUP_FACT_PRODUCT_MONTHLY"; + /** + * 一体化_销售_应收账款品牌月明细表 + */ + private static final String RECEIVABLE_BRAND_MONTHLY_TABLE = "BO_EU_DATALINKUP_FACT_RECEIVABLE_BRAND_MONTHLY"; + /** + * 一体化_销售_应收账款月度汇总表 + */ + private static final String RECEIVABLE_YEAR_TABLE = "BO_EU_DATALINKUP_FACT_RECEIVABLE_YEAR"; + /** + * 一体化_销售_营业收入月度汇总 + */ + private static final String REVENUE_YEAR_TABLE = "BO_EU_DATALINKUP_FACT_REVENUE_YEAR"; + /** + * 一体化_销售_产品单价日明细表 + */ + private static final String UNIT_PRICE_DAILY_TABLE = "BO_EU_DATALINKUP_FACT_UNIT_PRICE_DAILY"; + /** + * 一体化_销售_净利润月度汇总 + */ + private static final String NET_PROFIT_MONTHLY_TABLE = "BO_EU_DATALINKUP_JLR_MONTH"; + + // 原始数据表名 + /** + * 销售_销售类_汇总表 + */ + private static final String SALES_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ"; + /** + * 销售_应收类_汇总 + */ + private static final String RECEIVABLE_DETAIL_TABLE = "BO_EU_BNBM_DATALINKUP_XS_YSL"; + + @Override + public void calculateSummary(DateRange dateRange, BO mainConfig) { + try { + // 从主配置获取BKGS值 + String bkgs = mainConfig.getString("BKGS"); + if (bkgs == null || bkgs.isEmpty()) { + LOGGER.error("主配置中BKGS为空,无法进行汇总计算"); + return; + } + if (dateRange == null || dateRange.getStartDate() == null || dateRange.getEndDate() == null) { + LOGGER.info("未提供有效时间范围,按当前日期计算"); + calculateForCurrentDate(bkgs); + } else { + LOGGER.info("开始执行销售数据多维度汇总计算(时间范围: {} 至 {})", + dateRange.getStartDate(), dateRange.getEndDate()); + + // 计算月度维度数据(按月遍历) + calculateMonthlyData(dateRange, bkgs); + + // 计算日度维度数据(按天遍历) + calculateDailyData(dateRange, bkgs); + + LOGGER.info("销售数据多维度汇总计算完成"); + } + } catch (Exception e) { + String errorMsg = "销售数据汇总计算失败: " + e.getMessage(); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 按当前日期计算(无时间范围时使用) + */ + private void calculateForCurrentDate(String bkgs) { + Calendar cal = Calendar.getInstance(); + int year = cal.get(Calendar.YEAR); + int month = cal.get(Calendar.MONTH) + 1; + int day = cal.get(Calendar.DAY_OF_MONTH); + String yearMonth = String.format("%04d-%02d", year, month); + Date currentDate = new Date(System.currentTimeMillis()); + + // 1. 计算并保存产品配套率年度数据(按月存储) + calculateAndSaveMatchingRate(year, month, yearMonth, bkgs); + + // 2. 计算并保存产品销售月明细数据 + calculateAndSaveProductMonthly(year, month, yearMonth, bkgs); + + // 3. 计算并保存应收账款品牌月明细 + calculateAndSaveReceivableBrandMonthly(year, month, yearMonth, bkgs); + + // 4. 计算并保存应收账款年度汇总(按月存储) + calculateAndSaveReceivableYear(year, month, yearMonth, bkgs); + + // 5. 计算并保存营业收入年度汇总(按月存储) + calculateAndSaveRevenueYear(year, month, yearMonth, bkgs); + + // 6. 计算并保存产品单价日明细 + calculateAndSaveUnitPriceDaily(year, month, day, currentDate, bkgs); + } + + /** + * 计算月度维度数据 + */ + private void calculateMonthlyData(DateRange dateRange, String bkgs) { + Calendar startCal = Calendar.getInstance(); + startCal.setTime(dateRange.getStartDate()); + startCal.set(Calendar.DAY_OF_MONTH, 1); // 设置为月份的第一天 + + Calendar endCal = Calendar.getInstance(); + endCal.setTime(dateRange.getEndDate()); + endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH)); // 设置为月份的最后一天 + + Calendar monthCal = (Calendar) startCal.clone(); + + while (!monthCal.after(endCal)) { + int year = monthCal.get(Calendar.YEAR); + int month = monthCal.get(Calendar.MONTH) + 1; + String yearMonth = String.format("%04d-%02d", year, month); + + LOGGER.info("计算月度汇总数据: {}-{}", year, month); + + // 1. 产品配套率年度数据 + calculateAndSaveMatchingRate(year, month, yearMonth, bkgs); + + // 2. 产品销售月明细数据 + calculateAndSaveProductMonthly(year, month, yearMonth, bkgs); + + // 3. 应收账款品牌月明细 + calculateAndSaveReceivableBrandMonthly(year, month, yearMonth, bkgs); + + // 4. 应收账款年度汇总 + calculateAndSaveReceivableYear(year, month, yearMonth, bkgs); + + // 5. 营业收入年度汇总 + calculateAndSaveRevenueYear(year, month, yearMonth, bkgs); + + // 移动到下个月 + monthCal.add(Calendar.MONTH, 1); + } + } + + /** + * 计算日度维度数据 + */ + private void calculateDailyData(DateRange dateRange, String bkgs) { + Calendar dayCal = Calendar.getInstance(); + dayCal.setTime(dateRange.getStartDate()); + + Calendar endCal = Calendar.getInstance(); + endCal.setTime(dateRange.getEndDate()); + + while (!dayCal.after(endCal)) { + int year = dayCal.get(Calendar.YEAR); + int month = dayCal.get(Calendar.MONTH) + 1; + int day = dayCal.get(Calendar.DAY_OF_MONTH); + Date currentDate = dayCal.getTime(); + + LOGGER.info("计算日度汇总数据: {}-{}-{}", year, month, day); + + // 6. 产品单价日明细 + calculateAndSaveUnitPriceDaily(year, month, day, currentDate, bkgs); + + // 下一天 + dayCal.add(Calendar.DATE, 1); + } + } + + // ==================== 各维度计算方法 ==================== + + /** + * 计算并保存产品配套率年度数据(按月存储) + * 板骨配套率 = 石膏板销售数量 / 轻钢龙骨销售数量 + */ + private void calculateAndSaveMatchingRate(int year, int month, String yearMonth, String bkgs) { + try { + LOGGER.info("开始计算{}年{}月产品配套率数据", year, month); + + // 获取石膏板年度销售量(万平方米) - 从当年1月到当前月 + BigDecimal gypsumSales = getSalesToMonth(year, month, "石膏板", bkgs); + // 获取轻钢龙骨年度销售量(吨) - 从当年1月到当前月 + BigDecimal keelSales = getSalesToMonth(year, month, "轻钢龙骨", bkgs); + + // 计算板骨配套率(保留4位小数) + BigDecimal ratio = BigDecimal.ZERO; + if (keelSales.compareTo(BigDecimal.ZERO) != 0) { + // 使用精确除法并设置舍入模式 + ratio = gypsumSales.divide(keelSales, 4, RoundingMode.HALF_UP); + } + + // 创建BO对象并填充数据 + BO matchingRateBO = createBaseBO(bkgs); + matchingRateBO.set("YEARMONTH", yearMonth); + matchingRateBO.set("GYPSUM_SALES_VOLUME", gypsumSales.setScale(2, RoundingMode.HALF_UP)); + matchingRateBO.set("STEEL_SALES_VOLUME", keelSales.setScale(2, RoundingMode.HALF_UP)); + matchingRateBO.set("MATCHING_RATE", ratio.setScale(4, RoundingMode.HALF_UP)); + + // 保存数据 + saveSummaryData(matchingRateBO, MATCHING_RATE_YEAR_TABLE); + LOGGER.info("产品配套率月度累计数据保存成功"); + } catch (Exception e) { + String errorMsg = String.format("产品配套率月度累计数据计算失败: %s", e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 计算并保存产品销售月明细数据 + * 包含石膏板和轻钢龙骨的销量、营收及占比计算 + */ + private void calculateAndSaveProductMonthly(int year, int month, String yearMonth, String bkgs) { + try { + LOGGER.info("开始计算{}年{}月产品销售明细数据", year, month); + List maps = DBSql.getMaps("SELECT LB_1 FROM " + SALES_DETAIL_TABLE + " WHERE BKGS = '" + bkgs + "'" + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询 + " GROUP BY LB_1"); + for (RowMap map : maps) { + String lb_1 = map.getString("LB_1"); + processProductData(lb_1, year, month, yearMonth, bkgs); + } + // 处理石膏板数据 +// processProductData("石膏板", year, month, yearMonth, bkgs); + // 处理轻钢龙骨数据 +// processProductData("轻钢龙骨", year, month, yearMonth, bkgs); + LOGGER.info("产品销售月明细数据保存成功"); + } catch (Exception e) { + String errorMsg = String.format("产品销售月明细数据计算失败: %s", e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 处理指定产品的月明细数据 + * 计算各品牌的销量/营收总量、占比 + */ + private void processProductData(String productType, int year, int month, String yearMonth, String bkgs) { + try { + // 获取销量数据 + Map salesMap = getMonthlyData(year, month, productType, "sales", bkgs); + BigDecimal totalSales = calculateTotal(salesMap); + + // 获取营收数据 + Map revenueMap = getMonthlyData(year, month, productType, "revenue", bkgs); + BigDecimal totalRevenue = calculateTotal(revenueMap); + + // 为每个品牌创建记录 + for (String brand : salesMap.keySet()) { + BO productBO = createBaseBO(bkgs); + productBO.set("YEARMONTH", yearMonth); + if ("北新嘉宝莉".equals(bkgs)) { + if ("其他".equals(productType)){ + productBO.set("PRODUCT_TYPE", "其他"); + }else { + productBO.set("PRODUCT_TYPE", "涂料"); + } + } else if ("石膏板".equals(productType) || "轻钢龙骨".equals(productType) ) { + productBO.set("PRODUCT_TYPE", productType); + } else { + productBO.set("PRODUCT_TYPE", "其他"); + } + productBO.set("BRAND", brand); + + // 设置销量及占比 + BigDecimal brandSales = salesMap.get(brand); + productBO.set("SALES_VOLUME", brandSales.setScale(2, RoundingMode.HALF_UP)); + + // 计算销量占比(百分比,保留2位小数) + if (totalSales.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal salesRatio = brandSales.divide(totalSales, 4, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + productBO.set("SALES_VOLUME_RATIO", salesRatio.setScale(2, RoundingMode.HALF_UP)); + } else { + productBO.set("SALES_VOLUME_RATIO", BigDecimal.ZERO); + } + + // 设置营收及占比 + BigDecimal brandRevenue = revenueMap.get(brand); + productBO.set("REVENUE_AMOUNT", brandRevenue.setScale(2, RoundingMode.HALF_UP)); + + // 计算营收占比(百分比,保留2位小数) + if (totalRevenue.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal revenueRatio = brandRevenue.divide(totalRevenue, 4, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + productBO.set("REVENUE_RATIO", revenueRatio.setScale(2, RoundingMode.HALF_UP)); + } else { + productBO.set("REVENUE_RATIO", BigDecimal.ZERO); + } + + saveSummaryData(productBO, PRODUCT_MONTHLY_TABLE); + } + } catch (Exception e) { + String errorMsg = String.format("处理产品[%s]月明细数据失败: %s", productType, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 计算并保存应收账款品牌月明细 + * 区分板骨(龙牌/泰山/梦牌)和涂料(北新涂料/嘉宝莉) + */ + private void calculateAndSaveReceivableBrandMonthly(int year, int month, String yearMonth, String bkgs) { + try { + LOGGER.info("开始计算{}年{}月应收账款品牌明细数据", year, month); + + // 处理应收账款 + processReceivableData(getBoneReceivable(year, month, bkgs), yearMonth, bkgs); + // 处理涂料应收账款 +// processReceivableData(getPaintReceivable(year, month, bkgs), yearMonth, bkgs); + + LOGGER.info("应收账款品牌月明细数据保存成功"); + } catch (Exception e) { + String errorMsg = String.format("应收账款品牌月明细数据计算失败: %s", e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 处理指定产品线的应收账款数据 + */ + private void processReceivableData(Map dataMap, String yearMonth, String bkgs) { + try { + for (Map.Entry entry : dataMap.entrySet()) { + BO receivableBO = createBaseBO(bkgs); + receivableBO.set("YEARMONTH", yearMonth); +// receivableBO.set("PRODUCT_LINE", productLine); + receivableBO.set("BRAND", entry.getKey()); + receivableBO.set("RECEIVABLE_AMOUNT", entry.getValue().setScale(2, RoundingMode.HALF_UP)); + saveSummaryData(receivableBO, RECEIVABLE_BRAND_MONTHLY_TABLE); + } + } catch (Exception e) { + String errorMsg = String.format("处理板块[%s]应收账款失败: %s", bkgs, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 计算并保存应收账款年度汇总(按月存储) + * 应收账款 = 期初余额 + 累计销售 - 累计回款 + */ + private void calculateAndSaveReceivableYear(int year, int month, String yearMonth, String bkgs) { + try { + LOGGER.info("开始计算{}年{}月应收账款累计数据", year, month); + int lastYear = year - 1; + int lastYearMonth = month; // 去年同月 + + // 获取板骨应收账款数据 - 当年1月到当前月 + BigDecimal boneTotal = getReceivableToMonth(year, month, bkgs); + // 获取去年同期的板骨应收账款数据 - 去年1月到去年同月 + BigDecimal boneLastYearTotal = getReceivableToMonth(lastYear, lastYearMonth, bkgs); + BigDecimal boneYoy = calculateYoy(boneTotal, boneLastYearTotal); + + // 获取涂料应收账款数据 - 当年1月到当前月 + BigDecimal paintTotal = getReceivableToMonth(year, month, bkgs); + // 获取去年同期的涂料应收账款数据 - 去年1月到去年同月 + BigDecimal paintLastYearTotal = getReceivableToMonth(lastYear, lastYearMonth, bkgs); + BigDecimal paintYoy = calculateYoy(paintTotal, paintLastYearTotal); + + // 保存板骨数据 + saveReceivableYearData(yearMonth, boneTotal, boneYoy, bkgs); + // 保存涂料数据 + saveReceivableYearData(yearMonth, paintTotal, paintYoy, bkgs); + + LOGGER.info("应收账款月度累计数据保存成功"); + } catch (Exception e) { + String errorMsg = String.format("应收账款月度累计数据计算失败: %s", e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 保存应收账款月度累计数据 + */ + private void saveReceivableYearData(String yearMonth, BigDecimal total, BigDecimal yoy, String bkgs) { + BO receivableBO = createBaseBO(bkgs); + receivableBO.set("YEARMONTH", yearMonth); +// receivableBO.set("RECEIVABLE_TYPE", type); + receivableBO.set("ENDING_BALANCE", total.setScale(2, RoundingMode.HALF_UP)); // 期末余额 + receivableBO.set("YOY_RATE", yoy.setScale(2, RoundingMode.HALF_UP)); + saveSummaryData(receivableBO, RECEIVABLE_YEAR_TABLE); + } + + /** + * 计算并保存营业收入年度汇总(按月存储) + * 营业收入总量 = 石膏板 + 轻钢龙骨 + 涂料 + * 同比 = (当年营收 - 去年营收) / 去年营收 * 100% + */ + private void calculateAndSaveRevenueYear(int year, int month, String yearMonth, String bkgs) { + try { + LOGGER.info("开始计算{}年{}月营业收入累计数据", year, month); + int lastYear = year - 1; + int lastYearMonth = month; // 去年同月 + + // 处理石膏板数据 - 当年1月到当前月 + saveRevenueYearData(yearMonth, "石膏板", + getRevenueToMonth(year, month, "石膏板", bkgs), + getRevenueToMonth(lastYear, lastYearMonth, "石膏板", bkgs), + bkgs); + + // 处理轻钢龙骨数据 - 当年1月到当前月 + saveRevenueYearData(yearMonth, "轻钢龙骨", + getRevenueToMonth(year, month, "轻钢龙骨", bkgs), + getRevenueToMonth(lastYear, lastYearMonth, "轻钢龙骨", bkgs), + bkgs); + + // 处理涂料数据 - 当年1月到当前月 + saveRevenueYearData(yearMonth, "涂料", + getRevenueToMonth(year, month, "涂料", bkgs), + getRevenueToMonth(lastYear, lastYearMonth, "涂料", bkgs), + bkgs); + + LOGGER.info("营业收入月度累计数据保存成功"); + } catch (Exception e) { + String errorMsg = String.format("营业收入月度累计数据计算失败: %s", e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 保存营业收入月度累计数据 + */ + private void saveRevenueYearData(String yearMonth, String category, + BigDecimal currentRevenue, BigDecimal lastRevenue, String bkgs) { + BigDecimal yoy = calculateYoy(currentRevenue, lastRevenue); + + BO revenueBO = createBaseBO(bkgs); + revenueBO.set("YEARMONTH", yearMonth); + if ("北新嘉宝莉".equals(bkgs)){ + if ("其他".equals(category)){ + revenueBO.set("PRODUCT_CATEGORY", category); + }else { + revenueBO.set("PRODUCT_CATEGORY", "涂料"); + } + } else if ("石膏板".equals(category) || "轻钢龙骨".equals(category)) { + revenueBO.set("PRODUCT_CATEGORY", category); + }else { + revenueBO.set("PRODUCT_CATEGORY", "其他"); + } + + revenueBO.set("REVENUE_AMOUNT", currentRevenue.setScale(2, RoundingMode.HALF_UP)); + revenueBO.set("YOY_RATE", yoy.setScale(2, RoundingMode.HALF_UP)); + saveSummaryData(revenueBO, REVENUE_YEAR_TABLE); + } + + /** + * 计算并保存产品单价日明细 + * 单价 = 营业收入 / 销售数量 + */ + private void calculateAndSaveUnitPriceDaily(int year, int month, int day, Date date, String bkgs) { + try { + LOGGER.info("开始计算{}-{}-{}产品单价日明细数据", year, month, day); + + List maps = DBSql.getMaps("SELECT LB_1 FROM " + SALES_DETAIL_TABLE + " WHERE BKGS = '" + bkgs + "'" + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询 + " GROUP BY LB_1"); + for (RowMap map : maps) { + String lb_1 = map.getString("LB_1"); + saveUnitPriceData(date, lb_1, year, month, day, bkgs); + } + + // 处理石膏板数据 +// saveUnitPriceData(date, "石膏板", year, month, day, bkgs); + // 处理轻钢龙骨数据 +// saveUnitPriceData(date, "轻钢龙骨", year, month, day, bkgs); + + LOGGER.info("产品单价日明细数据保存成功"); + } catch (Exception e) { + String errorMsg = String.format("产品单价日明细数据计算失败: %s", e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + /** + * 保存产品单价数据(支持日/月计算) + */ + private void saveUnitPriceData(Date date, String productType, int year, int month, int day, String bkgs) { + try { + // 获取销量和营收 + Object[] params = {year, month, productType, bkgs}; + String sql = ""; + if (bkgs.contains("龙牌")) { + sql = "SELECT SUM(ZSSL)*10000 AS sales, SUM(SSJERMB) AS revenue " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEAR(DZRQ) = '" + year + "' AND MONTH(DZRQ) = '" + month + "' AND LB_1 = '" + productType + "' AND BKGS = '" + bkgs + "'" + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询 + } else if (bkgs.contains("泰山")) { + sql = "SELECT SUM(XSSL) AS sales, SUM(SSJERMB) AS revenue " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEAR(DZRQ) = '" + year + "' AND MONTH(DZRQ) = '" + month + "' AND LB_1 = '" + productType + "' AND BKGS = '" + bkgs + "'" + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询 + }else { + sql = "SELECT SUM(ZSSL) AS sales, SUM(SSJERMB) AS revenue " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEAR(DZRQ) = '" + year + "' AND MONTH(DZRQ) = '" + month + "' AND LB_1 = '" + productType + "' AND BKGS = '" + bkgs + "'" + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询 + } + + if (day > 0) { + sql += " AND DAY(DZRQ) = '"+day+"'"; + params = new Object[]{year, month, day, productType,bkgs}; + } + + RowMap result = DBSql.getMap(sql); + System.out.println("result = " + result); + if (result != null && result.get("sales") != null && result.get("revenue") != null) { + BigDecimal sales = BigDecimal.valueOf(result.getDouble("sales")); + BigDecimal revenue = BigDecimal.valueOf(result.getDouble("revenue")); + BigDecimal unitPrice = BigDecimal.ZERO; + + // 计算单价(避免除零错误) + if (sales.compareTo(BigDecimal.ZERO) > 0) { + // 石膏板单位转换:万元/万平方米 → 元/平方米 + if ("石膏板".equals(productType)) { + unitPrice = revenue.divide(sales, 4, RoundingMode.HALF_UP); + } + // 轻钢龙骨单位:元/吨 +// else + if ("轻钢龙骨".equals(productType)) { + unitPrice = revenue.divide(sales, 2, RoundingMode.HALF_UP); + } + } + + BO priceBO = createBaseBO(bkgs); + priceBO.set("DATE", new Timestamp(date.getTime())); + if ("北新嘉宝莉".equals(bkgs)) { + priceBO.set("PRODUCT_TYPE", "涂料"); + }else { + priceBO.set("PRODUCT_TYPE", productType); + } + priceBO.set("SALES_VOLUME", sales.setScale(2, RoundingMode.HALF_UP)); + priceBO.set("REVENUE_AMOUNT", revenue.setScale(2, RoundingMode.HALF_UP)); + priceBO.set("UNIT_PRICE", unitPrice.setScale(2, RoundingMode.HALF_UP)); + saveSummaryData(priceBO, UNIT_PRICE_DAILY_TABLE); + } else { + LOGGER.warn("未找到{}-{}-{}的{}销售数据", year, month, day, productType); + } + } catch (Exception e) { + String errorMsg = String.format("计算产品[%s]单价失败: %s", productType, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } + + // ==================== 工具方法 ==================== + + /** + * 创建基础BO对象(填充公共字段) + */ + private BO createBaseBO(String bkgs) { + BO bo = new BO(); +// bo.set("ORGID", ""); +// bo.set("BINDID", ""); +// bo.set("CREATEDATE", new Timestamp(System.currentTimeMillis())); +// bo.set("CREATEUSER", "admin"); +// bo.set("PROCESSDEFID", ""); +// bo.set("ISEND", 0); + bo.set("BKGS", bkgs); // 新增BKGS字段 + return bo; + } + + /** + * 计算同比变化率 + * 公式:(当期 - 同期) / 同期 * 100% + */ + private BigDecimal calculateYoy(BigDecimal current, BigDecimal last) { + if (last == null || last.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO; + } + return current.subtract(last) + .divide(last, 4, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(100)); + } + + /** + * 计算总值 + */ + private BigDecimal calculateTotal(Map dataMap) { + return dataMap.values().stream() + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + // ==================== 数据访问方法 ==================== + + /** + * 获取产品从1月到指定月份的累计销售量 + */ + private BigDecimal getSalesToMonth(int year, int month, String productType, String bkgs) { + String sql = "SELECT SUM(ZSSL) AS sales " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEAR(DZRQ) = '"+year+"' AND MONTH(DZRQ) BETWEEN 1 AND '"+month+"' AND LB_1 = '"+productType+"' AND BKGS = '"+bkgs+"'" + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询 + double value = DBSql.getDouble(sql, "sales"); + return BigDecimal.valueOf(value); + } + + /** + * 获取产品从1月到指定月份的累计营收 + */ + private BigDecimal getRevenueToMonth(int year, int month, String category, String bkgs) { + String sql = "SELECT SUM(SSJERMB) AS revenue " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEAR(DZRQ) = '"+year+"' AND MONTH(DZRQ) BETWEEN 1 AND '"+month+"' AND LB_1 LIKE '%"+category+"%' AND BKGS = '"+bkgs+"'"+ + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%')"; // 模糊查询 + double value = DBSql.getDouble(sql, "revenue"); + return BigDecimal.valueOf(value); + } + + /** + * 获取应收账款从1月到指定月份的累计值 + * 公式:期初余额 + 累计销售 - 累计回款 + */ + private BigDecimal getReceivableToMonth(int year, int month, String bkgs) { + StringBuilder sql = new StringBuilder("SELECT (SUM(QCYE) + SUM(LJXS) - SUM(LJXS)) AS receivable ") + .append("FROM " + RECEIVABLE_DETAIL_TABLE + " ") + .append("WHERE YEAR(RQ) = '"+year+"' AND MONTH(RQ) BETWEEN 1 AND '"+month+"' ") + .append("AND BKGS = '"+bkgs+"' "); + + // 构建品牌列表参数占位符 +// for (int i = 0; i < brands.size(); i++) { +// sql.append(brands.get(i)); +// if (i < brands.size() - 1) sql.append(","); +// } +// sql.append(")"); + + double value = DBSql.getDouble(sql.toString(), "receivable"); + return BigDecimal.valueOf(value); + } + + /** + * 获取月维度产品数据(销量/营收) + */ + private Map getMonthlyData(int year, int month, String productType, String dataType, String bkgs) { + String field = ""; + String sql = ""; + if (bkgs.contains("泰山")){ + field = "sales".equals(dataType) ? "SUM(XSSL)/10000" : "SUM(SSJERMB)"; + sql = "SELECT LB_2 AS brand, " + field + " AS value " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEAR(DZRQ) = ? AND MONTH(DZRQ) = ? AND LB_1 = ? AND BKGS = ? " + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询 + "GROUP BY LB_2"; + }else { + field = "sales".equals(dataType) ? "SUM(ZSSL)" : "SUM(SSJERMB)"; + sql = "SELECT LB_2 AS brand, " + field + " AS value " + + "FROM " + SALES_DETAIL_TABLE + " " + + "WHERE YEAR(DZRQ) = ? AND MONTH(DZRQ) = ? AND LB_1 = ? AND BKGS = ? " + + "AND (SQ LIKE '%新疆%' OR SQ LIKE '%海南%' OR SQ LIKE '%西藏%') " + // 模糊查询 + "GROUP BY LB_2"; + } + Map result = new HashMap<>(); + List rows = DBSql.getMaps(sql, year, month, productType, bkgs); + for (RowMap row : rows) { + if (row.get("value") != null) { + result.put(row.getString("brand"), BigDecimal.valueOf(row.getDouble("value"))); + } + } + return result; + } + + /** + * 获取应收账款数据(分品牌) + */ + private Map getReceivableData(int year, int month, String bkgs) { + StringBuilder sql = new StringBuilder("SELECT SUM(YSYE) AS receivable ") + .append("FROM " + RECEIVABLE_DETAIL_TABLE + " ") + .append("WHERE YEAR(RQ) = '"+year+"' AND MONTH(RQ) = '"+month+"' AND BKGS = '"+bkgs+"' "); + +// for (int i = 0; i < brands.size(); i++) { +// sql.append(brands.get(i)); +// if (i < brands.size() - 1) sql.append(","); +// } + + // 构建参数数组 +// Object[] params = new Object[brands.size() + 3]; +// params[0] = year; +// params[1] = month; +// params[2] = bkgs; +// for (int i = 0; i < brands.size(); i++) { +// params[i + 3] = brands.get(i); +// } + + Map result = new HashMap<>(); + List rows = DBSql.getMaps(sql.toString()); + for (RowMap row : rows) { + if (row.get("receivable") != null) { + result.put(bkgs, BigDecimal.valueOf(row.getDouble("receivable"))); + } + } + return result; + } + + private Map getBoneReceivable(int year, int month, String bkgs) { + return getReceivableData(year, month, bkgs); + } + + private Map getPaintReceivable(int year, int month, String bkgs) { + return getReceivableData(year, month, bkgs); + } + + // ==================== 数据保存方法 ==================== + + /** + * 保存汇总数据到指定表 + */ + private void saveSummaryData(BO summary, String tableName) { + try { + // 根据表名构建唯一性查询条件 + String whereClause = buildUniqueCondition(tableName, summary); + if (whereClause == null) { + SDK.getBOAPI().createDataBO(tableName, summary, UserContext.fromUID("admin")); + LOGGER.debug("{} 表创建新数据: {}", tableName, summary); + return; + } + + // 查询已存在记录 + String sql = "SELECT * FROM " + tableName + " WHERE " + whereClause; + LOGGER.info("查询已存在sql:{}",sql); + RowMap existing = DBSql.getMap(sql); + if (existing!=null){ + if (StringUtils.isNotBlank(existing.getString("ID"))) { + // 更新现有记录 + String id = existing.getString("ID"); + summary.set("ID", id); + summary.set("ORGID", existing.getString("ORGID")); + summary.set("BINDID", existing.getString("BINDID")); + summary.set("CREATEDATE", existing.getString("CREATEDATE")); + summary.set("CREATEUSER", existing.getString("CREATEUSER")); + summary.set("UPDATEDATE",new Timestamp(System.currentTimeMillis())); + summary.set("PROCESSDEFID", existing.getString("PROCESSDEFID")); + summary.set("ISEND", existing.getString("ISEND")); + SDK.getBOAPI().update(tableName, summary); + LOGGER.debug("{} 表数据更新成功: ID={}", tableName, id); + } else { + // 创建新记录 + SDK.getBOAPI().createDataBO(tableName, summary, UserContext.fromUID("admin")); + LOGGER.debug("{} 表创建新数据: {}", tableName, summary); + } + } + } catch (Exception e) { + String errorMsg = String.format("%s 表数据保存失败: %s", tableName, e.getMessage()); + LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } +// try { +// SDK.getBOAPI().createDataBO(tableName, summary, UserContext.fromUID("admin")); +// LOGGER.debug("{} 表数据保存成功: {}", tableName, summary); +// } catch (Exception e) { +// String errorMsg = String.format("%s 表数据保存失败: %s", tableName, e.getMessage()); +// LOGGER.error(errorMsg, e); +// throw new RuntimeException(errorMsg, e); +// } + } + + /** + * 构建表唯一性条件(基于业务键) + */ + private String buildUniqueCondition(String tableName, BO bo) { + String bkgs = bo.getString("BKGS"); + switch (tableName) { + case MATCHING_RATE_YEAR_TABLE: + return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'"; + + case PRODUCT_MONTHLY_TABLE: + return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'" + + " AND PRODUCT_TYPE = '" + bo.getString("PRODUCT_TYPE") + "'" + + " AND BRAND = '" + bo.getString("BRAND") + "'"; + + case RECEIVABLE_BRAND_MONTHLY_TABLE: + return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'" + +// " AND PRODUCT_LINE = '" + bo.getString("PRODUCT_LINE") + "'" + + " AND BRAND = '" + bo.getString("BRAND") + "'"; + + case RECEIVABLE_YEAR_TABLE: + return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'"; +// + +// " AND RECEIVABLE_TYPE = '" + bo.getString("RECEIVABLE_TYPE") + "'"; + + case REVENUE_YEAR_TABLE: + return "BKGS = '" + bkgs + "' AND YEARMONTH = '" + bo.getString("YEARMONTH") + "'" + + " AND PRODUCT_CATEGORY = '" + bo.getString("PRODUCT_CATEGORY") + "'"; + + case UNIT_PRICE_DAILY_TABLE: + // 日期字段特殊处理(精确到天) + Date date = bo.get("DATE", Date.class); + LOGGER.info("日期字段特殊处理(精确到天):{}",date); + String dateValue = new SimpleDateFormat("yyyy-MM-dd").format(date); + return "BKGS = '" + bkgs + "' AND DATE = '" + dateValue + "'" + + " AND PRODUCT_TYPE = '" + bo.getString("PRODUCT_TYPE") + "'"; + + default: + LOGGER.warn("表 {} 未配置唯一性条件,直接创建新记录", tableName); + return null; + } + } +} \ No newline at end of file diff --git a/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSyncServiceImpl.java b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSyncServiceImpl.java new file mode 100644 index 0000000..826600e --- /dev/null +++ b/com.awspaas.user.apps.bnbm.datalinkup/com.awspaas.user.apps.bnbm.datalinkup/src/main/java/com/awspaas/user/apps/bnbm/datalinkup/service/impl/SaleDataSyncServiceImpl.java @@ -0,0 +1,855 @@ +package com.awspaas.user.apps.bnbm.datalinkup.service.impl; + +import cn.hutool.core.date.DateTime; +import com.actionsoft.bpms.bo.engine.BO; +import com.actionsoft.bpms.commons.database.DBUtils; +import com.actionsoft.bpms.commons.database.RowMap; +import com.actionsoft.bpms.server.UserContext; +import com.actionsoft.bpms.util.DBSql; +import com.actionsoft.bpms.util.UtilDate; +import com.actionsoft.sdk.local.SDK; +import com.actionsoft.sdk.local.api.cc.RDSAPI; +import com.awspaas.user.apps.bnbm.datalinkup.entity.DateRange; +import com.awspaas.user.apps.bnbm.datalinkup.service.DataSyncService; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.*; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Date; +import java.util.stream.Collectors; + +/** + * @ClassName: SaleDataSyncServiceImpl + * @Description: 销售数据同步服务实现类,处理数据同步的核心逻辑 + */ +public class SaleDataSyncServiceImpl implements DataSyncService { + private static final Logger LOGGER = LoggerFactory.getLogger(SaleDataSyncServiceImpl.class); + // 时间范围常量:同步最近30天数据(不包括当天) + private static final int DAYS_BACK = Integer.parseInt(SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "days_back")); + + // 高斯数据库配置常量(生产环境中应改为从配置文件读取) +// private static final String GAUSSIAN_JDBC_URL = SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "jbl_data_url"); +// private static final String GAUSSIAN_USERNAME = SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "jbl_data_act"); +// private static final String GAUSSIAN_PASSWORD = SDK.getAppAPI().getProperty("com.awspaas.user.apps.bnbm.datalinkup", "jbl_data_pw"); + // 增加分页大小常量 + private static final int PAGE_SIZE = 1000; // 每页查询1000条记录 + + private static final String ORACLE_DATE_FORMAT = "YYYY-MM-DD HH24:MI:SS"; + + /** + * 根据主配置列表执行数据同步 + * @param configs 主配置列表(按所属板块分组处理) + */ + @Override + public ArrayList syncDataByConfigs(List configs) { + ArrayList list = new ArrayList<>(); + if (configs.isEmpty()) { + LOGGER.info("未找到有效的同步配置"); + return list; + } + + // 按所属板块(SSBK)分组配置 + Map> configsByPlate = configs.stream() + .collect(Collectors.groupingBy(bo -> bo.getString("SSBK"))); + // 遍历处理每个板块的配置 + for (Map.Entry> entry : configsByPlate.entrySet()) { + String plate = entry.getKey(); + List plateConfigs = entry.getValue(); + LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size()); + + // 处理当前板块的每条配置 + boolean connectionFailed = false; + String errorMsg = ""; + for (BO mainConfig : plateConfigs) { + try { +// RDSAPI ccId = SDK.getCCAPI().getRDSAPI(mainConfig.getString("CC_ID")); +// Connection open = ccId.open(); +// open.close(); + DateRange dateRange = processMainConfig(mainConfig); + list.add(dateRange); + } catch (Exception e) { + LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}", + plate, mainConfig.getString("BINDID"), e.getMessage(), e); + } + } + } + return list; + } + + /** + * 处理单个主配置的数据同步流程 + * @param mainConfig 主配置对象 + * 步骤: + * 1. 获取字段映射配置 + * 2. 计算时间范围 + * 3. 删除目标表旧数据 + * 4. 查询源表数据 + * 5. 转换并插入目标表 + */ + @Override + public DateRange processMainConfig(BO mainConfig) { + String bindId = mainConfig.getString("BINDID"); + String tableName = mainConfig.getString("TBB"); + String timeField = mainConfig.getString("SJZD"); + String targetTable = mainConfig.getString("LDB"); + String ccId = mainConfig.getString("CC_ID"); + String partitionField = mainConfig.getString("FQBZD"); + String bkgs = mainConfig.getString("BKGS"); + DateRange dateRange = new DateRange(); + + LOGGER.info("处理配置:BindID={}, 源表={}, 目标表={}, CC_ID={}, 时间字段={}, 分区字段配置={}", + bindId, tableName, targetTable, ccId,timeField,partitionField); + + // 查询子表字段映射配置 + List fieldMappings = SDK.getBOAPI() + .query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB") + .addQuery("BINDID =", bindId) + .list(); + + if (fieldMappings.isEmpty()) { + LOGGER.warn("未找到BindID={}的字段映射配置", bindId); + return dateRange; + } + + // 根据时间字段是否为空设置日期范围 + Date startDate = null; + Date endDate = null; + + // 删除目标表数据(根据时间字段是否为空决定删除范围) + if (timeField == null || timeField.isEmpty()) { + // 全量删除 + deleteAllTargetData(targetTable); + } else { + // 计算时间范围(当前日期-30天 ~ 昨天) + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -1); // 昨天 + endDate = cal.getTime(); + cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前(含) + startDate = cal.getTime(); + + // 获取目标表时间字段名 + String targetTimeField = getTargetTimeField(fieldMappings, timeField); + if (targetTimeField == null) { + LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField); + return dateRange; + } + // 按时间范围删除 + deleteTargetData(targetTable, targetTimeField, startDate, endDate); + } + + // 新增:判断是否为高斯数据库 +// if ("gaosi".equalsIgnoreCase(ccId)) { +// LOGGER.info("检测到高斯数据库特殊配置,使用JDBC连接池查询"); +// queryGaussDataWithCondition(tableName, timeField, startDate, endDate, partitionField, +// fieldMappings, targetTable); +// } else { + // 查询源表数据(跨库查询) + querySourceData(ccId, tableName, timeField, startDate, endDate, partitionField, + fieldMappings, targetTable); +// } + dateRange.setStartDate(startDate); + dateRange.setEndDate(endDate); + return dateRange; + } + + /** + * 跨库查询源表数据 + * @param ccId 跨库连接ID + * @param tableName 源表名 + * @param timeField 源表时间字段名 + * @param startDated 开始时间 + * @param endDated 结束时间 + * @return 查询结果数据集 + * @throws RuntimeException 查询失败或参数无效时抛出 + */ + @Override + public void querySourceData(String ccId, String tableName, + String timeField, Date startDated, Date endDated,String partitionField, + List fieldMappings, String targetTable) { + int totalRows = 0; // 总查询行数 + int totalSuccess = 0; // 总成功插入行数 + int pageNo = 1; + boolean hasMore; + RDSAPI rdsapi = null; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String startDate = simpleDateFormat.format(startDated); + String endDate = simpleDateFormat.format(endDated); + try { + rdsapi = SDK.getCCAPI().getRDSAPI(ccId); + DBUtils.SUPPLY supply = rdsapi.getSupply(); + String DBname = supply.getName(); + LOGGER.info("数据库为:{}",DBname); + if ("ORACLE".equalsIgnoreCase(DBname)){ + // 构建查询条件 + StringBuilder conditionBuilder = new StringBuilder(); + List params = new ArrayList<>(); // 存储查询参数 + + // 分区字段和时间字段组合查询条件 + if (partitionField != null && !partitionField.isEmpty()) { + // 1. 查询最大分区值 + String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName; + List maxPartitionResult = rdsapi.getMaps(maxPartitionSql); + + if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) { + LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField); + return; + } + + String maxPartition = maxPartitionResult.get(0).getString("max_partition"); + LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition); + + // 添加分区条件 + conditionBuilder.append(partitionField) + .append(" = '") + .append(maxPartition) + .append("'"); + + // 如果时间字段存在,添加时间范围条件 + if (timeField != null && !timeField.isEmpty()) { + conditionBuilder.append(" AND TO_DATE(") + .append(timeField) + .append(", '") + .append(ORACLE_DATE_FORMAT) + .append("') BETWEEN TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("') AND TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("')"); + params.add(startDate); + params.add(endDate); + } + } else if (timeField != null && !timeField.isEmpty()) { + // 没有分区字段,但时间字段存在,使用时间范围条件 + // 仅时间范围条件(使用占位符) + conditionBuilder.append("TO_DATE(") + .append(timeField) + .append(", '") + .append(ORACLE_DATE_FORMAT) + .append("') BETWEEN TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("') AND TO_DATE(?, '") + .append(ORACLE_DATE_FORMAT) + .append("')"); + params.add(startDate); + params.add(endDate); + } else { + // 既没有分区字段也没有时间字段,查询全表 + LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!"); + conditionBuilder.append("1=1"); + } + + // 分页查询数据 + do { + // 使用Oracle分页语法 (12c+) + String querySql = "SELECT * FROM ( " + + "SELECT t.*, ROWNUM rn FROM " + tableName + " t " + + "WHERE " + conditionBuilder.toString() + " AND ROWNUM <= " + (pageNo * PAGE_SIZE) + + ") WHERE rn > " + ((pageNo - 1) * PAGE_SIZE); + + LOGGER.debug("执行Oracle查询: {}", querySql); + + List pageData; + // 根据条件类型执行查询 + if (partitionField != null && !partitionField.isEmpty() && + timeField != null && !timeField.isEmpty()) { + // 分区+时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else if (timeField != null && !timeField.isEmpty()) { + // 仅时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else { + // 无时间范围查询(仅分区或全表) + pageData = rdsapi.getMaps(querySql); + } + + if (pageData != null && !pageData.isEmpty()) { + // 直接处理当前页数据 + int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable); + totalRows += pageData.size(); + totalSuccess += successCount; + hasMore = pageData.size() == PAGE_SIZE; + pageNo++; + } else { + hasMore = false; + } + } while (hasMore); + }else { + // 构建查询条件 + StringBuilder conditionBuilder = new StringBuilder(); + // 修改点:分区字段和时间字段组合查询条件 + if (partitionField != null && !partitionField.isEmpty()) { + // 1. 查询最大分区值 + String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName; + List maxPartitionResult = rdsapi.getMaps(maxPartitionSql); + + if (maxPartitionResult.isEmpty() || maxPartitionResult.get(0).get("max_partition") == null) { + LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField); + return; + } + + String maxPartition = maxPartitionResult.get(0).getString("max_partition"); + LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition); + + // 添加分区条件 + conditionBuilder.append(partitionField) + .append(" = '") + .append(maxPartition) + .append("'"); + + // 如果时间字段存在,添加时间范围条件 + if (timeField != null && !timeField.isEmpty()) { + conditionBuilder.append(" AND ") + .append(timeField) + .append(" BETWEEN ? AND ?"); + } + } else if (timeField != null && !timeField.isEmpty()) { + // 没有分区字段,但时间字段存在,使用时间范围条件 + conditionBuilder.append(timeField) + .append(" BETWEEN ? AND ?"); + } else { + // 既没有分区字段也没有时间字段,查询全表(实际应避免这种情况) + LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!"); + conditionBuilder.append("1=1"); + } + // 分页查询数据 + do { + String querySql = "SELECT * FROM " + tableName + + " WHERE " + conditionBuilder.toString() + + " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE; + + LOGGER.debug("执行查询: {}", querySql); + + List pageData; + // 根据条件类型执行查询 + if (partitionField != null && !partitionField.isEmpty() && + timeField != null && !timeField.isEmpty()) { + // 分区+时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else if (timeField != null && !timeField.isEmpty()) { + // 仅时间范围查询 + pageData = rdsapi.getMaps(querySql, startDate, endDate); + } else { + // 无时间范围查询(仅分区或全表) + pageData = rdsapi.getMaps(querySql); + } + + if (pageData != null && !pageData.isEmpty()) { + // 直接处理当前页数据 + int successCount = this.processAndInsertData(pageData, fieldMappings, targetTable); + totalRows += pageData.size(); + totalSuccess += successCount; + hasMore = pageData.size() == PAGE_SIZE; + pageNo++; + } else { + hasMore = false; + } + } while (hasMore); + } + + LOGGER.info("从表[{}]共查询到{}条数据,成功同步{}条数据", + tableName, totalRows, totalSuccess); + } catch (Exception e) { + throw new RuntimeException("查询源表[" + tableName + "]数据失败: " + e.getMessage(), e); + }finally { + + } + } + + /** + * 获取目标表时间字段名 + * @param mappings 字段映射配置列表 + * @param sourceTimeField 源表时间字段名 + * @return 目标表时间字段名,未找到返回null + */ + @Override + public String getTargetTimeField(List mappings, String sourceTimeField) { + for (BO mapping : mappings) { + if (sourceTimeField.equals(mapping.getString("TBBZD"))) { + return mapping.getString("LDBZD"); + } + } + return null; + } + + + /** + * 删除目标表中指定时间范围的数据 + * @param targetTable 目标表名 + * @param targetTimeField 目标表时间字段名 + * @param startDate 开始时间 + * @param endDate 结束时间 + * @throws RuntimeException 删除失败时抛出 + */ + @Override + public void deleteTargetData(String targetTable, String targetTimeField, + Date startDate, Date endDate) { + try { + String deleteSql = "DELETE FROM " + targetTable + + " WHERE " + targetTimeField + " BETWEEN ? AND ?"; + int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate}); + LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})", + targetTable, deletedCount, startDate, endDate); + } catch (Exception e) { + throw new RuntimeException("删除目标表数据失败: " + e.getMessage(), e); + } + } + + /** + * 删除目标表所有数据 + */ + @Override + public void deleteAllTargetData(String targetTable) { + try { + String deleteSql = "DELETE FROM " + targetTable ; + int deletedCount = DBSql.update(deleteSql); + LOGGER.info("已全量删除目标表[{}]中{}条数据", targetTable, deletedCount); + } catch (Exception e) { + throw new RuntimeException("全量删除目标表数据失败: " + e.getMessage(), e); + } + } + + /** + * 处理并插入数据到目标表 + * @param sourceData 源数据列表 + * @param mappings 字段映射配置 + * @param targetTable 目标表名 + */ + public int processAndInsertData(List sourceData, + List mappings, String targetTable) { + if (sourceData.isEmpty()) { + LOGGER.info("没有需要同步的数据"); + return 0; + } + + List batchList = new ArrayList<>(); + int successCount = 0; + int totalCount = sourceData.size(); + int processedCount = 0; // 已处理记录数 + + for (int i = 0; i < totalCount; i++) { + RowMap record = sourceData.get(i); + processedCount++; // 增加已处理计数 + try { + // 字段映射转换 + BO targetData = convertFields(record, mappings); + batchList.add(targetData); + + // 批量插入条件:达到批处理大小或最后一条 + if (batchList.size() >= PAGE_SIZE || i == totalCount - 1) { + // 使用管理员权限批量插入 + SDK.getBOAPI().createDataBO(targetTable, batchList, UserContext.fromUID("admin")); + successCount += batchList.size(); + batchList.clear(); // 清空批次 + } + } catch (Exception e) { + LOGGER.error("数据处理失败: {}", e.getMessage(), e); + } + } + // 增加详细日志输出:共处理多少条,成功同步多少条 + LOGGER.info("本次处理{}条数据,成功同步{}条数据到表[{}]", + processedCount, successCount, targetTable); + return successCount; + } + + /** + * 字段映射转换 + * @param source 源数据记录 + * @param mappings 字段映射配置 + * @return 转换后的BO对象 + */ + public BO convertFields(RowMap source, List mappings) { + BO target = new BO(); + + for (BO mapping : mappings) { + String sourceField = mapping.getString("TBBZD"); + String targetField = mapping.getString("LDBZD"); + +// if (!source.containsKey(sourceField)) { +// LOGGER.debug("源字段[{}]不存在于查询结果中", sourceField); +// continue; +// } + + String operationExpr = mapping.getString("TBBZDJSLJ"); + if (StringUtils.isNotBlank(operationExpr)) { + // 解析运算表达式 (格式: [运算符][数字]) + char operator = operationExpr.charAt(0); + String numberPart = operationExpr.substring(1); + + try { + // 获取源值并转换为BigDecimal + String sourceValue = source.getString(sourceField); + if (StringUtils.isBlank(sourceValue)) { + target.set(targetField, null); + continue; + } + + if ("GG".equals(sourceField)){ + String string = source.getString(sourceField); + if (StringUtils.isNotBlank(string) && string.contains("×")){ + String[] split = string.split("×"); + String s = split[split.length - 1]; + target.set(targetField, s); + continue; + } + } + + BigDecimal sourceNum = new BigDecimal(sourceValue); + BigDecimal operand = new BigDecimal(numberPart); + BigDecimal result; + // 执行相应运算 + switch (operator) { + case '*': + result = sourceNum.multiply(operand); + break; + case '/': + if (BigDecimal.ZERO.compareTo(operand) == 0) { + LOGGER.error("除零错误: 源字段[{}] 除数为0", sourceField); + result = sourceNum; // 避免除零异常 + } else { + // 除法保留10位小数并四舍五入 + result = sourceNum.divide(operand, 10, RoundingMode.HALF_UP); + } + break; + case '+': + result = sourceNum.add(operand); + break; + case '-': + result = sourceNum.subtract(operand); + break; + default: + LOGGER.error("未知运算符: {} 字段[{}]", operator, sourceField); + result = sourceNum; + } + target.set(targetField, result); + } catch (NumberFormatException e) { + LOGGER.error("数值转换失败: 源字段[{}]={}, 操作数={}", + sourceField, source.getString(sourceField), numberPart, e); + target.set(targetField, source.getString(sourceField)); + } + } else { + if ("GG".equals(sourceField)){ + String string = source.getString(sourceField); + if (StringUtils.isNotBlank(string) && string.contains("×")){ + String[] split = string.split("×"); + String s = split[split.length - 1]; + target.set(targetField, s); + }else { + // 无运算表达式时直接复制原始值 + target.set(targetField, StringUtils.isNotBlank(string)?string:""); + } + }else { + // 无运算表达式时直接复制原始值 + target.set(targetField, source.getString(sourceField)); + } + } + String ldzdmrz = mapping.getString("LDZDMRZ"); + if (StringUtils.isNotBlank(ldzdmrz)){ + target.set(mapping.getString("LDBZD"),ldzdmrz); + } + } + return target; + } + + /** + * 销售各板块数据汇总表 + * @param configs + */ + @Override + public void sumBkTable(List configs) { + Map> configsByPlate = configs.stream() + .collect(Collectors.groupingBy(bo -> bo.getString("SSBK"))); + + // 遍历处理每个板块的配置 + for (Map.Entry> entry : configsByPlate.entrySet()) { + String plate = entry.getKey(); + List plateConfigs = entry.getValue(); + LOGGER.info("处理板块【{}】的{}条配置", plate, plateConfigs.size()); + + // 处理当前板块的每条配置 + for (BO mainConfig : plateConfigs) { + String targetTable = mainConfig.getString("LDB");//落地表 + String timeField = mainConfig.getString("SJZD");//时间字段 + String bindId = mainConfig.getString("BINDID");//bindid + String tablename = mainConfig.getString("TABLENAME");//同步表名 + String hzb = ""; + try { + if ("销售表".equals(tablename)){ + hzb = "BO_EU_BNBM_DATALINKUP_XS_XSL_HZ"; + }else { + hzb = "BO_EU_BNBM_DATALINKUP_XS_YSL"; + } + // 查询子表字段映射配置 + List fieldMappings = SDK.getBOAPI() + .query("BO_EU_BNBM_DATALINKUP_SJGTPZ_SUB") + .addQuery("BINDID =", bindId) + .list(); + //获取板块公司 + String bkgs = DBSql.getString("SELECT BKGS FROM " + targetTable, "BKGS"); + + // 根据时间字段是否为空设置日期范围 + Date startDate = null; + Date endDate = null; + + // 删除目标表数据(根据时间字段是否为空决定删除范围) + if (timeField == null || timeField.isEmpty()) { + // 全量删除 + String deleteSql = "DELETE FROM "+hzb+" WHERE BKGS = '"+bkgs+"'"; + int deletedCount = DBSql.update(deleteSql); + LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})", + hzb,deletedCount, startDate, endDate); + + // 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ + // 全量分页迁移数据到汇总表 + summarizeScopeData(targetTable, null, null, null, hzb); + } else { + // 计算时间范围(当前日期-30天 ~ 昨天) + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DATE, -1); // 昨天 + endDate = cal.getTime(); + cal.add(Calendar.DATE, -DAYS_BACK + 1); // 30天前(含) + startDate = cal.getTime(); + + // 获取目标表时间字段名 + String targetTimeField = getTargetTimeField(fieldMappings, timeField); + if (targetTimeField == null) { + LOGGER.error("无法找到源时间字段[{}]对应的目标表字段,跳过同步", timeField); + } + // 按时间范围删除 + String deleteSql = "DELETE FROM " + hzb + + " WHERE BKGS = '"+bkgs+"' AND " + targetTimeField + " BETWEEN ? AND ?"; + int deletedCount = DBSql.update(deleteSql, new Object[]{startDate, endDate}); + LOGGER.info("已删除目标表[{}]中{}条数据(时间范围: {} - {})", + hzb,deletedCount, startDate, endDate); + + // 根据时间范围增加数据分页查询数据存储到BO_EU_BNBM_DATALINKUP_XS_XSL_HZ + // 按时间范围分页迁移数据到汇总表 + summarizeScopeData(targetTable, startDate, endDate, targetTimeField, hzb); + } + } catch (Exception e) { + LOGGER.error("处理配置失败 [板块={}, BindID={}]: {}", + plate, mainConfig.getString("BINDID"), e.getMessage(), e); + } + } + } + } + + /** + * 汇总各板块销售数据汇总 + * @param targetTable + * @param startDated + * @param endDated + * @param targetTimeField + */ + @Override + public void summarizeScopeData(String targetTable, Date startDated, Date endDated, String targetTimeField, String hzb) { + int pageNo = 1; + boolean hasMore; + String pageSql = ""; + List pageData = null; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String startDate = ""; + String endDate = ""; + try { + do { + if (startDated == null || endDated == null) { + pageSql = "SELECT * FROM " + targetTable + + " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE; + pageData = DBSql.getMaps(pageSql); + } else { + startDate = simpleDateFormat.format(startDated); + endDate = simpleDateFormat.format(endDated); + pageSql = "SELECT * FROM " + targetTable + + " WHERE " + targetTimeField + " BETWEEN '" + startDate + "' AND '" + endDate + "' " + + " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE; + LOGGER.info("执行查询的sql:{}", pageSql); + pageData = DBSql.getMaps(pageSql); + } + + if (pageData.isEmpty()) break; + + List bos = new ArrayList<>(); + for (RowMap map : pageData) { + BO bo = new BO(); + // 复制所有字段(排除系统字段) + for (String key : map.keySet()) { + if (!key.equalsIgnoreCase("ID") && + !key.equalsIgnoreCase("ORGID") && + !key.equalsIgnoreCase("CREATEDATE") && + !key.equalsIgnoreCase("CREATEUSER") && + !key.equalsIgnoreCase("UPDATEDATE") && + !key.equalsIgnoreCase("UPDATEUSER") && + !key.equalsIgnoreCase("ISEND") && + !key.equalsIgnoreCase("BINDID")) { + if (StringUtils.isNotBlank(targetTimeField)) { + String targetTimeField1 = map.getString(targetTimeField); + Date parse = UtilDate.parse(targetTimeField1); + int year = UtilDate.getYear(parse); + String monthFormat = UtilDate.monthFormat(parse); + int day = UtilDate.getDay(parse); + bo.set("YEARMONTH", year + monthFormat); + bo.set("YEAR", year); + bo.set("MONTH", monthFormat); + bo.set("DAY", day); + } + bo.set(key, map.get(key)); + } + } + bos.add(bo); + } + + SDK.getBOAPI().createDataBO(hzb, bos, UserContext.fromUID("admin")); + LOGGER.info("已迁移{}条数据到汇总表(页号: {},时间范围: {} - {})", + bos.size(), pageNo, startDate, endDate); + + hasMore = pageData.size() == PAGE_SIZE; + pageNo++; + } while (hasMore); + }catch (Exception e){ + LOGGER.error("汇总数据失败 [汇总表={}, 第几页={}]: {}", + hzb, PAGE_SIZE, e.getMessage(), e); + } + } + + // 高斯数据库连接池(简化版) +// public static class GaussDataSource { +// private static final String URL = GAUSSIAN_JDBC_URL; +// private static final String USER = GAUSSIAN_USERNAME; +// private static final String PASSWORD = GAUSSIAN_PASSWORD; +// +// public static Connection getConnection() throws SQLException { +// String driver = "com.huawei.gaussdb.jdbc.Driver"; +// try { +// //加载数据库驱动。 +// Class.forName(driver).newInstance(); +// } catch (Exception e) { +// e.printStackTrace(); +// return null; +// } +// Connection connection = DriverManager.getConnection(URL, USER, PASSWORD); +// boolean autoCommit = connection.getAutoCommit(); +// LOGGER.info("autoCommit:{}",autoCommit); +// return connection; +// } +// } + + // /** +// * 高斯数据库专用查询方法(支持分区和分页) +// */ +// @Override +// public void queryGaussDataWithCondition(String tableName, String timeField, +// Date startDate, Date endDate, String partitionField, +// List fieldMappings, String targetTable) { +// int totalRows = 0; // 总查询行数 +// int totalSuccess = 0; // 总成功插入行数 +// int pageNo = 1; +// boolean hasMore; +// Connection conn = null; +// +// try { +// conn = GaussDataSource.getConnection(); +// LOGGER.info("成功连接高斯数据库"); +// +// // 构建查询条件 +// StringBuilder conditionBuilder = new StringBuilder(); +// +// // 修改点:分区字段和时间字段组合查询条件 +// if (partitionField != null && !partitionField.isEmpty()) { +// // 1. 查询最大分区值 +// String maxPartitionSql = "SELECT MAX(" + partitionField + ") AS max_partition FROM " + tableName; +// try (Statement stmt = conn.createStatement(); +// ResultSet rs = stmt.executeQuery(maxPartitionSql)) { +// +// if (rs.next()) { +// String maxPartition = rs.getString("max_partition"); +// // 添加分区条件 +// conditionBuilder.append(partitionField) +// .append(" = '") +// .append(maxPartition) +// .append("'"); +// LOGGER.info("表[{}]的最大分区为: {}", tableName, maxPartition); +// } else { +// LOGGER.warn("表[{}]没有找到分区字段[{}]的数据", tableName, partitionField); +// return; +// } +// } +// } +// +// // 添加时间范围条件(无论是否有分区字段,只要时间字段存在) +// if (timeField != null && !timeField.isEmpty()) { +// if (conditionBuilder.length() > 0) { +// conditionBuilder.append(" AND "); +// } +// conditionBuilder.append(timeField) +// .append(" BETWEEN '") +// .append(new Timestamp(startDate.getTime())) +// .append("' AND '") +// .append(new Timestamp(endDate.getTime())) +// .append("'"); +// } else if (conditionBuilder.length() == 0) { +// // 既没有分区字段也没有时间字段,查询全表(实际应避免) +// LOGGER.warn("警告:未配置分区字段和时间字段,将查询全表数据!"); +// conditionBuilder.append("1=1"); +// } +// +// // 分页查询数据 +// do { +// String querySql = "SELECT * FROM " + tableName; +// // 如果有条件则添加WHERE子句 +// if (conditionBuilder.length() > 0) { +// querySql += " WHERE " + conditionBuilder.toString(); +// } +// querySql += " LIMIT " + PAGE_SIZE + " OFFSET " + (pageNo - 1) * PAGE_SIZE; +// +// LOGGER.debug("执行高斯查询: {}", querySql); +// +// try (Statement stmt = conn.createStatement(); +// ResultSet rs = stmt.executeQuery(querySql)) { +// +// List pageData = new ArrayList<>(); +// ResultSetMetaData metaData = rs.getMetaData(); +// int columnCount = metaData.getColumnCount(); +// +// while (rs.next()) { +// BO row = new BO(); +// for (int i = 1; i <= columnCount; i++) { +// String colName = metaData.getColumnName(i); +// Object value = rs.getObject(i); +// row.set(colName, value); +// } +// RowMap map = new RowMap(row.asMap()); +// pageData.add(map); +// } +// +// if (!pageData.isEmpty()) { +// // 直接处理当前页数据 +// int successCount = processAndInsertData(pageData, fieldMappings, targetTable); +// totalRows += pageData.size(); +// totalSuccess += successCount; +// hasMore = pageData.size() == PAGE_SIZE; +// pageNo++; +// } else { +// hasMore = false; +// } +// } +// } while (hasMore); +// +// LOGGER.info("从高斯表[{}]共查询到{}条数据,成功同步{}条数据", +// tableName, totalRows, totalSuccess); +// } catch (SQLException e) { +// throw new RuntimeException("高斯数据库查询失败: " + e.getMessage(), e); +// } finally { +// if (conn != null) { +// try { +// conn.close(); +// } catch (SQLException e) { +// LOGGER.error("关闭高斯数据库连接失败", e); +// } +// } +// } +// } +} \ No newline at end of file