|
@@ -0,0 +1,311 @@
|
|
|
|
|
+import com.actionsoft.bpms.dw.exec.event.ideimport.DataWindowAfterImport;
|
|
|
|
|
+import com.actionsoft.bpms.dw.exec.imp.model.ImportModel;
|
|
|
|
|
+import com.actionsoft.bpms.server.UserContext;
|
|
|
|
|
+import com.actionsoft.bpms.commons.database.RowMap;
|
|
|
|
|
+import com.actionsoft.bpms.util.DBSql;
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
|
|
+
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
|
|
+import java.math.RoundingMode;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.YearMonth;
|
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.time.format.DateTimeParseException;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+
|
|
|
|
|
+public class DataWindowImportAfterEvent extends DataWindowAfterImport {
|
|
|
|
|
+ // ====================== 全局静态常量(统一配置,方便维护) =======================
|
|
|
|
|
+ // 日期格式化常量:核心精确到天,兼容账期转换
|
|
|
|
|
+ private static final DateTimeFormatter FORMATTER_DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 当前时间(精确到天)
|
|
|
|
|
+ private static final DateTimeFormatter FORMATTER_YEAR_MONTH_DASH = DateTimeFormatter.ofPattern("yyyy-MM"); // 账期格式1
|
|
|
|
|
+ private static final DateTimeFormatter FORMATTER_YEAR_MONTH_NO_DASH = DateTimeFormatter.ofPattern("yyyyMM"); // 账期格式2
|
|
|
|
|
+ private static final DateTimeFormatter FORMATTER_YEAR_MONTH_DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 周期起止日期
|
|
|
|
|
+
|
|
|
|
|
+ // 数据库表名常量(对应你的业务表)
|
|
|
|
|
+ private static final String TABLE_INSTALLED_PRODUCT = "BO_EU_DNCRM_INSTALLED_PRODUCT"; // 已安装产品表(匹配合同ID)
|
|
|
|
|
+ private static final String TABLE_CONTRACT_PERIOD = "BO_EU_DNCTT_CONTRACT_SERVICE_PERIOD"; // 合同周期表(最终更新表)
|
|
|
|
|
+ private static final String TABLE_PROXY_PAY = "BO_EU_DNCTT_PROXY_PAY"; // 代缴费表(核心数据来源,手机号/账期/金额)
|
|
|
|
|
+
|
|
|
|
|
+ // 数据库字段名常量(与表结构严格对应)
|
|
|
|
|
+ private static final String FIELD_CONTRACT_SERVICE_ID = "CONTRACT_SERVICE_ID"; // 合同服务ID
|
|
|
|
|
+ private static final String FIELD_PERIOD_PRICE = "PERIOD_PRICE"; // 周期金额(待更新)
|
|
|
|
|
+ private static final String FIELD_REFERENCE_NUMBER = "REFERENCE_NUMBER"; // 参考编号(提取手机号后四位)
|
|
|
|
|
+ private static final String FIELD_EXTENDED_PRICE = "EXTENDED_PRICE"; // 含税总金额(数据库金额来源)
|
|
|
|
|
+ private static final String FIELD_CREATE_DATE = "CREATE_DATE"; // 创建时间(查询条件)
|
|
|
|
|
+ private static final String FIELD_PHONE = "PHONE"; // 完整手机号
|
|
|
|
|
+ private static final String FIELD_FEE_DATE = "FEE_DATE"; // 账期
|
|
|
|
|
+
|
|
|
|
|
+ // 提示信息常量(统一日志输出)
|
|
|
|
|
+ private static final String MSG_NO_CURRENT_DATA = "未查询到当天(" + LocalDate.now().format(FORMATTER_DATE) + ")的有效代缴费数据";
|
|
|
|
|
+ private static final String MSG_NO_CONTRACT_ID = "未匹配到对应的合同服务ID";
|
|
|
|
|
+ private static final String MSG_FEE_DATE_FORMAT_ERROR = "账期格式错误(需为yyyy-MM或yyyyMM)";
|
|
|
|
|
+ private static final String MSG_NO_VALID_DATA = "无有效数据可进行后续处理";
|
|
|
|
|
+ private static final String MSG_PROCESS_COMPLETE = "处理完成:更新周期表%d条数据";
|
|
|
|
|
+
|
|
|
|
|
+ // 简单日志方法(无SDK依赖,控制台输出)
|
|
|
|
|
+ private static void log(String msg) {
|
|
|
|
|
+ System.out.println("[DataWindowImportAfterEvent] " + msg);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ====================== 核心业务方法(重写平台导入事件,全数据库驱动) =======================
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void excute(UserContext userContext, String processDefId, String formDefId, List<ImportModel> importModels, String type, Map<String, Object> extendParams) {
|
|
|
|
|
+ // 1. 获取当前系统日期(精确到天),作为数据库查询核心条件
|
|
|
|
|
+ String currentDate = LocalDate.now().format(FORMATTER_DATE);
|
|
|
|
|
+ log("===== 开始处理,当前查询日期(精确到天):" + currentDate + " =====");
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 从代缴费表查询当天所有有效数据(手机号、账期、金额)
|
|
|
|
|
+ List<RowMap> proxyPayList = getProxyPayDataByCurrentDate(currentDate);
|
|
|
|
|
+ if (proxyPayList == null || proxyPayList.isEmpty()) {
|
|
|
|
|
+ log(MSG_NO_CURRENT_DATA);
|
|
|
|
|
+ log("===== 处理结束,无有效数据 =====");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ log("查询到当天代缴费记录共:" + proxyPayList.size() + " 条");
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 整理数据库返回数据(格式化、去重、过滤无效数据)
|
|
|
|
|
+ Map<String, BigDecimal> phoneAmountMap = new HashMap<>(); // 手机号后四位 → 含税金额
|
|
|
|
|
+ Map<String, String> phoneFeeDateMap = new HashMap<>(); // 手机号后四位 → 账期
|
|
|
|
|
+ Set<String> phoneLast4Set = new HashSet<>(); // 去重手机号后四位
|
|
|
|
|
+
|
|
|
|
|
+ for (RowMap row : proxyPayList) {
|
|
|
|
|
+ // 3.1 提取并校验完整手机号(截取后四位)
|
|
|
|
|
+ String fullPhone = Objects.toString(row.get(FIELD_PHONE), "").trim();
|
|
|
|
|
+ if (StringUtils.isBlank(fullPhone) || fullPhone.length() < 4) {
|
|
|
|
|
+ log("手机号无效(为空或长度不足4位),跳过当前记录:" + fullPhone);
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ String phoneLast4 = fullPhone.substring(fullPhone.length() - 4); // 截取后四位
|
|
|
|
|
+
|
|
|
|
|
+ // 3.2 提取并校验账期
|
|
|
|
|
+ String feeDate = Objects.toString(row.get(FIELD_FEE_DATE), "").trim();
|
|
|
|
|
+ if (StringUtils.isBlank(feeDate)) {
|
|
|
|
|
+ log("账期为空,跳过当前记录(手机号后四位:" + phoneLast4 + ")");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3.3 提取并格式化金额(兼容多类型,避免报错)
|
|
|
|
|
+ BigDecimal amount = formatAmountFromRow(row);
|
|
|
|
|
+ if (amount.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
|
|
+ log("金额无效(为0或转换失败),跳过当前记录(手机号后四位:" + phoneLast4 + ")");
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3.4 封装到容器(自动去重,Map重复key会覆盖,保留最新数据)
|
|
|
|
|
+ phoneLast4Set.add(phoneLast4);
|
|
|
|
|
+ phoneAmountMap.put(phoneLast4, amount);
|
|
|
|
|
+ phoneFeeDateMap.put(phoneLast4, feeDate);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 校验整理后的数据是否有效
|
|
|
|
|
+ if (phoneLast4Set.isEmpty() || phoneAmountMap.isEmpty()) {
|
|
|
|
|
+ log(MSG_NO_VALID_DATA);
|
|
|
|
|
+ log("===== 处理结束,无有效数据 =====");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ log("整理后有效数据共:" + phoneLast4Set.size() + " 条");
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 手机号后四位匹配合同服务ID(建立映射关系)
|
|
|
|
|
+ Map<String, String> phoneContractMap = matchPhoneToContractId(new ArrayList<>(phoneLast4Set));
|
|
|
|
|
+ if (phoneContractMap.isEmpty()) {
|
|
|
|
|
+ log(MSG_NO_CONTRACT_ID);
|
|
|
|
|
+ log("===== 处理结束,无匹配合同 =====");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ log("匹配到有效合同服务ID共:" + phoneContractMap.size() + " 个");
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 转换为合同ID-金额映射(提取基准账期,用于后续日期转换)
|
|
|
|
|
+ Map<String, BigDecimal> contractAmountMap = new HashMap<>();
|
|
|
|
|
+ String targetFeeDate = ""; // 基准账期(取第一个有效账期)
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<String, String> entry : phoneContractMap.entrySet()) {
|
|
|
|
|
+ String phoneLast4 = entry.getKey();
|
|
|
|
|
+ String contractId = entry.getValue();
|
|
|
|
|
+ BigDecimal amount = phoneAmountMap.getOrDefault(phoneLast4, BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
|
|
|
|
|
+
|
|
|
|
|
+ // 过滤无效金额
|
|
|
|
|
+ if (amount.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
|
|
+ contractAmountMap.put(contractId, amount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取基准账期(仅取第一个有效值)
|
|
|
|
|
+ if (StringUtils.isBlank(targetFeeDate)) {
|
|
|
|
|
+ targetFeeDate = phoneFeeDateMap.getOrDefault(phoneLast4, "");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 账期转换为当月起止日期(精准更新对应周期)
|
|
|
|
|
+ Map<String, String> periodDateMap = convertFeeDateToPeriod(targetFeeDate);
|
|
|
|
|
+ if (periodDateMap == null) {
|
|
|
|
|
+ log(MSG_FEE_DATE_FORMAT_ERROR);
|
|
|
|
|
+ log("===== 处理结束,账期转换失败 =====");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ String periodBeginDate = periodDateMap.get("begin");
|
|
|
|
|
+ String periodEndDate = periodDateMap.get("end");
|
|
|
|
|
+ log("账期转换完成,更新周期:" + periodBeginDate + " 至 " + periodEndDate);
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 批量更新合同服务周期表(核心落地步骤)
|
|
|
|
|
+ int updatedCount = batchUpdateContractPeriod(contractAmountMap, periodBeginDate, periodEndDate);
|
|
|
|
|
+
|
|
|
|
|
+ // 9. 打印最终处理结果,流程闭环
|
|
|
|
|
+ log("===== 处理完成,共更新周期表 " + updatedCount + " 条数据 =====");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ====================== 工具方法(各司其职,解耦主流程,无报错兼容) =======================
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 核心:以当前日期(精确到天)为条件,查询代缴费表当天所有有效数据
|
|
|
|
|
+ * 忽略CREATE_DATE的时分秒,避免因时间戳差异导致数据遗漏
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<RowMap> getProxyPayDataByCurrentDate(String currentDate) {
|
|
|
|
|
+ // SQL优化:使用DATE()函数提取日期部分,兼容DATETIME/DATE类型字段
|
|
|
|
|
+ String sql = "SELECT " + FIELD_PHONE + ", " + FIELD_FEE_DATE + ", " + FIELD_EXTENDED_PRICE + ", " + FIELD_CREATE_DATE +
|
|
|
|
|
+ " FROM " + TABLE_PROXY_PAY +
|
|
|
|
|
+ " WHERE DATE(" + FIELD_CREATE_DATE + ") = ?"; // 忽略时分秒,精准匹配当天所有记录
|
|
|
|
|
+
|
|
|
|
|
+ // 执行查询,返回当天所有代缴费记录(平台通用方法,无报错)
|
|
|
|
|
+ return DBSql.getMaps(sql, currentDate);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 格式化从RowMap中提取的金额(兼容BigDecimal/Number/String类型,避免转换报错)
|
|
|
|
|
+ */
|
|
|
|
|
+ private BigDecimal formatAmountFromRow(RowMap row) {
|
|
|
|
|
+ Object amountObj = row.get(FIELD_EXTENDED_PRICE);
|
|
|
|
|
+ BigDecimal amount = BigDecimal.ZERO;
|
|
|
|
|
+
|
|
|
|
|
+ if (amountObj != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (amountObj instanceof BigDecimal) {
|
|
|
|
|
+ amount = (BigDecimal) amountObj;
|
|
|
|
|
+ } else if (amountObj instanceof Number) {
|
|
|
|
|
+ // 兼容Integer/Double/Long等数值类型
|
|
|
|
|
+ amount = BigDecimal.valueOf(((Number) amountObj).doubleValue());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 兼容字符串类型金额,去除空格后转换
|
|
|
|
|
+ amount = new BigDecimal(amountObj.toString().trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ log("金额转换失败,无效值:" + amountObj.toString());
|
|
|
|
|
+ amount = BigDecimal.ZERO;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 格式化金额:保留2位小数,四舍五入(符合财务数据规范)
|
|
|
|
|
+ return amount.setScale(2, RoundingMode.HALF_UP);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 匹配手机号后四位与合同服务ID(从已安装产品表查询,批量匹配提高效率)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, String> matchPhoneToContractId(List<String> phoneLast4List) {
|
|
|
|
|
+ Map<String, String> phoneContractMap = new HashMap<>();
|
|
|
|
|
+ if (phoneLast4List == null || phoneLast4List.isEmpty()) {
|
|
|
|
|
+ return phoneContractMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 动态拼接IN条件SQL,批量查询(避免循环单条查询,提高效率)
|
|
|
|
|
+ StringBuilder sql = new StringBuilder("SELECT RIGHT(" + FIELD_REFERENCE_NUMBER + ",4) AS REF_LAST4, " +
|
|
|
|
|
+ FIELD_CONTRACT_SERVICE_ID + " FROM " + TABLE_INSTALLED_PRODUCT + " WHERE RIGHT(" +
|
|
|
|
|
+ FIELD_REFERENCE_NUMBER + ",4) IN (");
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < phoneLast4List.size(); i++) {
|
|
|
|
|
+ sql.append(i == 0 ? "?" : ",?");
|
|
|
|
|
+ }
|
|
|
|
|
+ sql.append(")");
|
|
|
|
|
+
|
|
|
|
|
+ // 执行查询,封装映射关系
|
|
|
|
|
+ List<RowMap> contractList = DBSql.getMaps(sql.toString(), phoneLast4List.toArray());
|
|
|
|
|
+ for (RowMap row : contractList) {
|
|
|
|
|
+ String refLast4 = row.getString("REF_LAST4");
|
|
|
|
|
+ refLast4 = refLast4 == null ? "" : refLast4.trim();
|
|
|
|
|
+ String contractId = row.getString(FIELD_CONTRACT_SERVICE_ID);
|
|
|
|
|
+ contractId = contractId == null ? "" : contractId.trim();
|
|
|
|
|
+
|
|
|
|
|
+ // 过滤无效数据,封装有效映射
|
|
|
|
|
+ if (StringUtils.isNoneBlank(refLast4, contractId)) {
|
|
|
|
|
+ phoneContractMap.put(refLast4, contractId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log("匹配手机号与合同ID异常:" + e.getMessage());
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return phoneContractMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 账期转换为当月起止日期(支持yyyy-MM和yyyyMM两种格式,兼容业务场景)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, String> convertFeeDateToPeriod(String feeDate) {
|
|
|
|
|
+ if (StringUtils.isBlank(feeDate)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 解析账期为YearMonth对象,兼容两种格式
|
|
|
|
|
+ YearMonth feeYearMonth = feeDate.contains("-") ?
|
|
|
|
|
+ YearMonth.parse(feeDate, FORMATTER_YEAR_MONTH_DASH) :
|
|
|
|
|
+ YearMonth.parse(feeDate, FORMATTER_YEAR_MONTH_NO_DASH);
|
|
|
|
|
+
|
|
|
|
|
+ // 构建当月起止日期(1号至月末最后一天)
|
|
|
|
|
+ Map<String, String> periodMap = new HashMap<>(2);
|
|
|
|
|
+ periodMap.put("begin", feeYearMonth.atDay(1).format(FORMATTER_YEAR_MONTH_DAY));
|
|
|
|
|
+ periodMap.put("end", feeYearMonth.atEndOfMonth().format(FORMATTER_YEAR_MONTH_DAY));
|
|
|
|
|
+
|
|
|
|
|
+ return periodMap;
|
|
|
|
|
+ } catch (DateTimeParseException e) {
|
|
|
|
|
+ log("账期格式转换异常,无效账期:" + feeDate + ",异常信息:" + e.getMessage());
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 批量更新合同服务周期表(使用CASE WHEN语法,一条SQL完成所有更新,高效防注入)
|
|
|
|
|
+ */
|
|
|
|
|
+ private int batchUpdateContractPeriod(Map<String, BigDecimal> contractAmountMap, String periodBeginDate, String periodEndDate) {
|
|
|
|
|
+ if (contractAmountMap.isEmpty() || StringUtils.isAnyBlank(periodBeginDate, periodEndDate)) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 动态拼接批量更新SQL
|
|
|
|
|
+ StringBuilder sql = new StringBuilder("UPDATE " + TABLE_CONTRACT_PERIOD + " SET " +
|
|
|
|
|
+ FIELD_PERIOD_PRICE + " = CASE " + FIELD_CONTRACT_SERVICE_ID + " ");
|
|
|
|
|
+ List<Object> batchParams = new ArrayList<>();
|
|
|
|
|
+ List<String> contractIdList = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 拼接CASE WHEN条件(合同ID → 对应金额)
|
|
|
|
|
+ for (Map.Entry<String, BigDecimal> entry : contractAmountMap.entrySet()) {
|
|
|
|
|
+ String contractId = entry.getKey();
|
|
|
|
|
+ BigDecimal amount = entry.getValue();
|
|
|
|
|
+ sql.append("WHEN ? THEN ? ");
|
|
|
|
|
+ batchParams.add(contractId);
|
|
|
|
|
+ batchParams.add(amount);
|
|
|
|
|
+ contractIdList.add(contractId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 拼接WHERE条件(限定合同ID和周期日期范围,避免误改数据)
|
|
|
|
|
+ sql.append("END WHERE ").append(FIELD_CONTRACT_SERVICE_ID).append(" IN (");
|
|
|
|
|
+ for (int i = 0; i < contractIdList.size(); i++) {
|
|
|
|
|
+ sql.append(i == 0 ? "?" : ",?");
|
|
|
|
|
+ }
|
|
|
|
|
+ sql.append(") AND PERIOD_BEGIN_DATE >= ? AND PERIOD_END_DATE <= ?");
|
|
|
|
|
+
|
|
|
|
|
+ // 追加WHERE条件参数
|
|
|
|
|
+ batchParams.addAll(contractIdList);
|
|
|
|
|
+ batchParams.add(periodBeginDate);
|
|
|
|
|
+ batchParams.add(periodEndDate);
|
|
|
|
|
+
|
|
|
|
|
+ // 执行批量更新,返回受影响行数
|
|
|
|
|
+ return DBSql.update(sql.toString(), batchParams.toArray());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log("批量更新周期表异常:" + e.getMessage());
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|