Commit d640c5c3 by jianan

保费到期提醒24-保单邮件逐条通知

parent bf7d94c2
...@@ -31,6 +31,7 @@ import java.util.Arrays; ...@@ -31,6 +31,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -92,8 +93,9 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService { ...@@ -92,8 +93,9 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService {
String fileName = null; String fileName = null;
String recipientsSnapshot = null; String recipientsSnapshot = null;
boolean failBatchRecorded = false; boolean failBatchRecorded = false;
List<String> failures = new ArrayList<>();
try { try {
// 1. 配置 fail-fast:任何收件配置错误都在发送前暴露,避免留下半批 FAIL // 1. 配置 fail-fast:任何收件配置错误都在发信前暴露
List<String> summaryRecipients = parseAddressList(recipientsConfig); List<String> summaryRecipients = parseAddressList(recipientsConfig);
List<String> policyToList = parseAddressList(policyToConfig); List<String> policyToList = parseAddressList(policyToConfig);
List<String> policyCcList = parseAddressList(policyCcConfig); List<String> policyCcList = parseAddressList(policyCcConfig);
...@@ -122,14 +124,13 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService { ...@@ -122,14 +124,13 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService {
renewalRemindPolicyMailService.listByBizMonth(bizMonth)); renewalRemindPolicyMailService.listByBizMonth(bizMonth));
// 5. 逐封发送:R2 先、R1 后;单封失败仅记该封 FAIL 并继续 // 5. 逐封发送:R2 先、R1 后;单封失败仅记该封 FAIL 并继续
List<String> failures = new ArrayList<>();
int[] missingTotal = {0}; int[] missingTotal = {0};
int[] attempted = {0}; int[] attempted = {0};
// R2 先发送(宽限期紧迫),R1 后发送 // R2 先发送(宽限期紧迫),R1 后发送
sendRound(r2List, RenewalMailTypeEnum.R2, bizMonth, policyTo, ccSnapshot, sendRound(r2List, RenewalMailTypeEnum.R2, bizMonth, policyTo, ccSnapshot,
succeededKeys, failures, missingTotal, attempted); succeededKeys, failures, missingTotal, attempted, data.getSkipped());
sendRound(r1List, RenewalMailTypeEnum.R1, bizMonth, policyTo, ccSnapshot, sendRound(r1List, RenewalMailTypeEnum.R1, bizMonth, policyTo, ccSnapshot,
succeededKeys, failures, missingTotal, attempted); succeededKeys, failures, missingTotal, attempted, data.getSkipped());
// 6. 存在任一 FAIL:批次 FAIL,不发 Excel 汇总 // 6. 存在任一 FAIL:批次 FAIL,不发 Excel 汇总
if (!failures.isEmpty()) { if (!failures.isEmpty()) {
...@@ -158,12 +159,12 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService { ...@@ -158,12 +159,12 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService {
log.info("续期提醒发送完成。bizMonth={}, R1={}, R2={}, 待核对项={}", log.info("续期提醒发送完成。bizMonth={}, R1={}, R2={}, 待核对项={}",
bizMonth, r1List.size(), r2List.size(), missingTotal[0]); bizMonth, r1List.size(), r2List.size(), missingTotal[0]);
} catch (BusinessException e) { } catch (BusinessException e) {
// 配置 fail-fast、逐单失败等业务异常:确保批次落 FAIL(逐单失败分支已落库则不重复写) // 配置 fail-fast、中断、逐单失败等业务异常:确保批次落 FAIL(逐单失败分支已落库则不重复写)
if (!failBatchRecorded) { if (!failBatchRecorded) {
String errorMsg = StringUtils.substring(e.getMessage(), 0, ERROR_MSG_MAX_LEN); String errorMsg = StringUtils.substring(e.getMessage(), 0, ERROR_MSG_MAX_LEN);
saveBatch(existBatch, execMonth, data, fileName, saveBatch(existBatch, execMonth, data, fileName,
RenewalRemindStatusEnum.FAIL, errorMsg, recipientsSnapshot, RenewalRemindStatusEnum.FAIL, errorMsg, recipientsSnapshot,
data == null ? 0 : data.getDunPolicies().size(), 0); data == null ? 0 : data.getDunPolicies().size(), failures.size());
} }
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
...@@ -171,18 +172,20 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService { ...@@ -171,18 +172,20 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService {
String errorMsg = StringUtils.substring(e.getMessage(), 0, ERROR_MSG_MAX_LEN); String errorMsg = StringUtils.substring(e.getMessage(), 0, ERROR_MSG_MAX_LEN);
saveBatch(existBatch, execMonth, data, fileName, saveBatch(existBatch, execMonth, data, fileName,
RenewalRemindStatusEnum.FAIL, errorMsg, recipientsSnapshot, RenewalRemindStatusEnum.FAIL, errorMsg, recipientsSnapshot,
data == null ? 0 : data.getDunPolicies().size(), 0); data == null ? 0 : data.getDunPolicies().size(), failures.size());
log.error("续期提醒跑批失败。bizMonth={}", bizMonth, e); log.error("续期提醒跑批失败。bizMonth={}", bizMonth, e);
throw new BusinessException("续期提醒跑批失败: " + e.getMessage()); throw new BusinessException("续期提醒跑批失败: " + e.getMessage());
} }
} }
/** /**
* 发送一轮逐单邮件。已 SUCCESS 的保单邮件跳过;单封异常不中断整轮。 * 发送一轮逐单邮件。已 SUCCESS 的保单邮件跳过;发送失败仅记该封 FAIL 不中断整轮;
* 模板构建失败(正常已在筛选层拦截,如空保单号)跳过该单计入异常明细,不拖垮整批。
*/ */
private void sendRound(List<RenewalDuePolicy> items, RenewalMailTypeEnum type, String bizMonth, private void sendRound(List<RenewalDuePolicy> items, RenewalMailTypeEnum type, String bizMonth,
String to, String ccSnapshot, Set<String> succeededKeys, String to, String ccSnapshot, Set<String> succeededKeys,
List<String> failures, int[] missingTotal, int[] attempted) { List<String> failures, int[] missingTotal, int[] attempted,
Map<String, String> skipped) {
if (CollectionUtils.isEmpty(items)) { if (CollectionUtils.isEmpty(items)) {
return; return;
} }
...@@ -199,10 +202,13 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService { ...@@ -199,10 +202,13 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService {
built = policyMailBuilder.build(item, type); built = policyMailBuilder.build(item, type);
missingTotal[0] += built.getMissingFieldCount(); missingTotal[0] += built.getMissingFieldCount();
} catch (Exception e) { } catch (Exception e) {
// 保单号缺失等无法标识保单的情形:记 FAIL 留痕,数据修复后重跑可补发 // 无法构建(如保单号为空,筛选层本应已拦):跳过计异常,批次可 SUCCESS,数据修复后重跑补发
log.error("续期提醒邮件构建失败,{},policyFollowId={}", type, policyFollowId, e); log.error("续期提醒邮件构建失败,跳过该单,{},policyFollowId={}", type, policyFollowId, e);
upsertMailRecord(item, type, bizMonth, to, ccSnapshot, false, e.getMessage()); if (skipped != null) {
failures.add(failureDesc(item, type, e.getMessage())); skipped.putIfAbsent(String.valueOf(policyFollowId),
"邮件构建失败(" + type.getItemValue() + "): "
+ StringUtils.substring(StringUtils.defaultString(e.getMessage()), 0, 200));
}
continue; continue;
} }
...@@ -342,11 +348,14 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService { ...@@ -342,11 +348,14 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService {
} }
private String buildSubject(RenewalReminderData data) { private String buildSubject(RenewalReminderData data) {
return String.format("【续期提醒】%d年%d-%d月到期保单清单(共%d张)", LocalDate start = data.getWindowStart();
data.getWindowStart().getYear(), LocalDate end = data.getWindowEnd();
data.getWindowStart().getMonthValue(), // 跨年发生在执行月 11 月(窗口为 12/1~次年 1/31):同月省年、跨年带两年
data.getWindowEnd().getMonthValue(), String period = start.getYear() == end.getYear()
data.getDuePolicies().size()); ? String.format("%d年%d-%d月", start.getYear(), start.getMonthValue(), end.getMonthValue())
: String.format("%d年%d月-%d年%d月",
start.getYear(), start.getMonthValue(), end.getYear(), end.getMonthValue());
return String.format("【续期提醒】%s到期保单清单(共%d张)", period, data.getDuePolicies().size());
} }
private String buildContent(RenewalReminderData data, int missingTotal) { private String buildContent(RenewalReminderData data, int missingTotal) {
...@@ -356,10 +365,13 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService { ...@@ -356,10 +365,13 @@ public class RenewalDispatchServiceImpl implements RenewalDispatchService {
if (CollectionUtils.isNotEmpty(data.getDunPolicies())) { if (CollectionUtils.isNotEmpty(data.getDunPolicies())) {
sb.append(String.format(" 本期宽限期跟进 %d 封。", data.getDunPolicies().size())); sb.append(String.format(" 本期宽限期跟进 %d 封。", data.getDunPolicies().size()));
} }
// 两个口径分开:跳过是整张保单(张),待核对是字段项(项),不可合并计数
int skippedCount = data.getSkipped() == null ? 0 : data.getSkipped().size(); int skippedCount = data.getSkipped() == null ? 0 : data.getSkipped().size();
int abnormalTotal = skippedCount + missingTotal; if (skippedCount > 0) {
if (abnormalTotal > 0) { sb.append(String.format(" 数据异常跳过 %d 张,请核对源数据(详见跑批日志)。", skippedCount));
sb.append(String.format(" 数据异常/待核对 %d 项,请核对源数据(详见跑批日志)。", abnormalTotal)); }
if (missingTotal > 0) {
sb.append(String.format(" 字段待核对 %d 项。", missingTotal));
} }
return sb.toString(); return sb.toString();
} }
......
...@@ -145,27 +145,38 @@ public final class RenewalDueDateCalculator { ...@@ -145,27 +145,38 @@ public final class RenewalDueDateCalculator {
} }
/** /**
* 解析缴费年期为整数年,兼容 "5"、"5.0"、5、5.0(BigDecimal) 等存储形态。 * 解析缴费年期为整数年,兼容 "5"、"5.0"、5、5.00(BigDecimal) 等整数存储形态;
* 非正、非整数年(如 5.9,旧逻辑静默截断为 5)一律判空,由上层计异常。
*/ */
private static Integer parseIssueNumber(Object issueNumber) { private static Integer parseIssueNumber(Object issueNumber) {
if (Objects.isNull(issueNumber)) { if (Objects.isNull(issueNumber)) {
return null; return null;
} }
if (issueNumber instanceof Number) { BigDecimal bd;
BigDecimal bd = new BigDecimal(issueNumber.toString()); if (issueNumber instanceof BigDecimal) {
return bd.signum() > 0 ? bd.intValue() : null; bd = (BigDecimal) issueNumber;
} } else if (issueNumber instanceof Number) {
bd = new BigDecimal(issueNumber.toString());
} else {
String s = StringUtils.trimToNull(issueNumber.toString()); String s = StringUtils.trimToNull(issueNumber.toString());
if (s == null) { if (s == null) {
return null; return null;
} }
try { try {
BigDecimal bd = new BigDecimal(s); bd = new BigDecimal(s);
return bd.signum() > 0 ? bd.intValue() : null;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return null; return null;
} }
} }
if (bd.signum() <= 0 || bd.stripTrailingZeros().scale() > 0) {
return null;
}
try {
return bd.intValueExact();
} catch (ArithmeticException e) {
return null;
}
}
/** /**
* 计算结果:dueDate 非空表示纳入提醒,为空时 skipReason 说明原因。 * 计算结果:dueDate 非空表示纳入提醒,为空时 skipReason 说明原因。
......
...@@ -6,7 +6,6 @@ import com.yd.csf.service.utils.SimpleDateUtils; ...@@ -6,7 +6,6 @@ import com.yd.csf.service.utils.SimpleDateUtils;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.math.BigDecimal; import java.math.BigDecimal;
...@@ -27,7 +26,6 @@ public class RenewalPolicyMailBuilder { ...@@ -27,7 +26,6 @@ public class RenewalPolicyMailBuilder {
private static final String MISSING_TC = "待核對"; private static final String MISSING_TC = "待核對";
private static final String FONT = "'Microsoft YaHei','PingFang SC',Arial,sans-serif"; private static final String FONT = "'Microsoft YaHei','PingFang SC',Arial,sans-serif";
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
private static final DecimalFormat MONEY_FMT = new DecimalFormat("#,##0.00");
/** /**
* 需求稿强调色:应缴总保费、备注整行红色 * 需求稿强调色:应缴总保费、备注整行红色
*/ */
...@@ -38,12 +36,6 @@ public class RenewalPolicyMailBuilder { ...@@ -38,12 +36,6 @@ public class RenewalPolicyMailBuilder {
private static final int LABEL_WIDTH = 6; private static final int LABEL_WIDTH = 6;
/** /**
* grace_period 缺失时括注中的默认天数,与筛选侧同源配置
*/
@Value("${renewal.reminder.default-grace-days:31}")
private Integer configuredDefaultGraceDays;
/**
* 构建一封逐单通知邮件。 * 构建一封逐单通知邮件。
* *
* @return 主题、HTML 正文、缺失字段数(非主键字段「待核对」项数,用于批次数据异常统计) * @return 主题、HTML 正文、缺失字段数(非主键字段「待核对」项数,用于批次数据异常统计)
...@@ -77,15 +69,17 @@ public class RenewalPolicyMailBuilder { ...@@ -77,15 +69,17 @@ public class RenewalPolicyMailBuilder {
String dueDateText = item.getDueDate().format(DATE_FMT); String dueDateText = item.getDueDate().format(DATE_FMT);
String graceEndText = item.getGraceEnd().format(DATE_FMT); String graceEndText = item.getGraceEnd().format(DATE_FMT);
String effectiveText = effectiveDate == null ? null : effectiveDate.format(DATE_FMT); String effectiveText = effectiveDate == null ? null : effectiveDate.format(DATE_FMT);
// 兜底天数取自筛选侧解析结果(item.graceDays),模板不重复持有配置
String graceNoteSc = item.isGraceFallback() String graceNoteSc = item.isGraceFallback()
? "(宽限天数待核对(按默认 " + resolveDefaultGraceDays() + " 天估算))" ? "(宽限天数待核对(按默认 " + item.getGraceDays() + " 天估算))"
: "(获取保费到期日起计" + item.getGraceDays() + "天)"; : "(获取保费到期日起计" + item.getGraceDays() + "天)";
String graceNoteTc = item.isGraceFallback() String graceNoteTc = item.isGraceFallback()
? "(寬限天數待核對(按預設 " + resolveDefaultGraceDays() + " 天估算))" ? "(寬限天數待核對(按預設 " + item.getGraceDays() + " 天估算))"
: "(获取保费到期日起计" + item.getGraceDays() + "天)"; : "(获取保费到期日起计" + item.getGraceDays() + "天)";
String subject = (type == RenewalMailTypeEnum.R2 ? "【宽限期跟进】" : "") // 主题进入邮件头:剥除控制字符(CR/LF 截断头注入,其余 Cntrl 破坏头编码)
+ "续期保费通知-【" + policyNo + "】"; String subject = sanitizeHeader((type == RenewalMailTypeEnum.R2 ? "【宽限期跟进】" : "")
+ "续期保费通知-【" + policyNo + "】");
String html = "<div style=\"font-family:" + FONT + ";font-size:14px;color:#000000;line-height:1.9;\">" String html = "<div style=\"font-family:" + FONT + ";font-size:14px;color:#000000;line-height:1.9;\">"
+ segment(Labels.SC, MISSING_SC, type, company, product, esc(policyNo), holder, insured, + segment(Labels.SC, MISSING_SC, type, company, product, esc(policyNo), holder, insured,
...@@ -187,15 +181,16 @@ public class RenewalPolicyMailBuilder { ...@@ -187,15 +181,16 @@ public class RenewalPolicyMailBuilder {
} }
private String formatMoney(String currency, BigDecimal total) { private String formatMoney(String currency, BigDecimal total) {
String number = MONEY_FMT.format(total); // DecimalFormat 非线程安全,局部创建
String number = new DecimalFormat("#,##0.00").format(total);
return currency == null ? number : esc(currency) + " " + number; return currency == null ? number : esc(currency) + " " + number;
} }
private int resolveDefaultGraceDays() { /**
if (configuredDefaultGraceDays == null || configuredDefaultGraceDays <= 0) { * 剥除主题中的 ASCII 控制字符(含 CR/LF/Tab),防邮件头注入与非法头。
return 31; */
} private String sanitizeHeader(String header) {
return configuredDefaultGraceDays; return header.replaceAll("\\p{Cntrl}", "");
} }
/** /**
......
...@@ -9,7 +9,7 @@ import java.util.Map; ...@@ -9,7 +9,7 @@ import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* 续期提醒筛选结果:窗口信息、R1/R2 命中保单、异常跳过明细(保单号 -> 原因) * 续期提醒筛选结果:窗口信息、R1/R2 命中保单、异常跳过明细(policyFollowId -> 原因)
*/ */
@Data @Data
@AllArgsConstructor @AllArgsConstructor
...@@ -36,7 +36,7 @@ public class RenewalReminderData { ...@@ -36,7 +36,7 @@ public class RenewalReminderData {
private List<RenewalDuePolicy> dunPolicies; private List<RenewalDuePolicy> dunPolicies;
/** /**
* 异常跳过明细:保单号 -> 跳过原因(仅统计缺字段/无法解析等脏数据) * 异常跳过明细:policyFollowId(String) -> 跳过原因(缺字段/无法解析/保单号为空等脏数据)
*/ */
private Map<String, String> skipped; private Map<String, String> skipped;
......
...@@ -11,6 +11,7 @@ import com.yd.csf.service.component.renewal.RenewalDueDateCalculator; ...@@ -11,6 +11,7 @@ import com.yd.csf.service.component.renewal.RenewalDueDateCalculator;
import com.yd.csf.service.utils.SimpleDateUtils; import com.yd.csf.service.utils.SimpleDateUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -55,8 +56,41 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ ...@@ -55,8 +56,41 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ
if (CollectionUtils.isNotEmpty(effectivePolicies)) { if (CollectionUtils.isNotEmpty(effectivePolicies)) {
for (PolicyFollow policy : effectivePolicies) { for (PolicyFollow policy : effectivePolicies) {
// 保单号是主题与留痕标识,为空无法发信:跳过计异常,批次照常,数据修复后重跑可补
if (StringUtils.isBlank(policy.getPolicyNo())) {
markSkipped(skipped, policy, "保单号为空");
continue;
}
LocalDate effectiveDate = SimpleDateUtils.toLocalDate(policy.getEffectiveDate()); LocalDate effectiveDate = SimpleDateUtils.toLocalDate(policy.getEffectiveDate());
// R2 先判定(宽限紧迫,发送顺序也在先)
boolean r2Hit = false;
RenewalDueDateCalculator.Result currentResult = RenewalDueDateCalculator.currentDueDate(
effectiveDate, policy.getPaymentFrequency(), policy.getIssueNumber(), baseDate);
if (!currentResult.isIncluded()) {
// R2 计算异常不影响 R1(入参相同,正常情况下异常原因一致)
markSkippedIfAbnormal(skipped, policy, currentResult);
} else {
LocalDate currentDueDate = currentResult.getDueDate();
// 首期未付新单(k=0,当期缴费日=生效日)不属续期宽限跟进
// 预缴保单一律不催 R2(宁可不催,不可误催);两者均不影响 R1
if (!currentDueDate.equals(effectiveDate)
&& !Integer.valueOf(1).equals(policy.getIsPrepay())) {
Grace grace = resolveGrace(policy, currentDueDate, defaultGraceDays);
if (!grace.graceEnd.isBefore(baseDate) && !grace.graceEnd.isAfter(dunWindowEnd)) {
dunPolicies.add(new RenewalDuePolicy(
policy, currentDueDate, grace.graceEnd, grace.fallback, grace.graceDays));
r2Hit = true;
}
}
}
// R2 已命中则不再发 R1:月缴且缴费日恰为 1 号时,当期宽限届满日恰等于下一缴费日,
// 同一保单会同时落入两轮,按「只催 R2」剔除 R1
if (r2Hit) {
continue;
}
// R1:下一保费到期日落入 [M+1月1号, M+2月月末] // R1:下一保费到期日落入 [M+1月1号, M+2月月末]
RenewalDueDateCalculator.Result nextResult = RenewalDueDateCalculator.calculate( RenewalDueDateCalculator.Result nextResult = RenewalDueDateCalculator.calculate(
effectiveDate, policy.getPaymentFrequency(), policy.getIssueNumber(), baseDate); effectiveDate, policy.getPaymentFrequency(), policy.getIssueNumber(), baseDate);
...@@ -70,29 +104,6 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ ...@@ -70,29 +104,6 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ
policy, nextDueDate, grace.graceEnd, grace.fallback, grace.graceDays)); policy, nextDueDate, grace.graceEnd, grace.fallback, grace.graceDays));
} }
} }
// R2:当期缴费日 ≤ 基准日,且宽限期到期日落在 [基准日, 次月月末]
RenewalDueDateCalculator.Result currentResult = RenewalDueDateCalculator.currentDueDate(
effectiveDate, policy.getPaymentFrequency(), policy.getIssueNumber(), baseDate);
if (!currentResult.isIncluded()) {
// R2 计算异常不影响 R1(入参相同,正常情况下异常原因一致)
markSkippedIfAbnormal(skipped, policy, currentResult);
continue;
}
LocalDate currentDueDate = currentResult.getDueDate();
// 首期未付新单(k=0,当期缴费日=生效日)不属续期宽限跟进,剔除
if (currentDueDate.equals(effectiveDate)) {
continue;
}
// 预缴保单一律不催 R2(宁可不催,不可误催);R1 不剔除
if (Integer.valueOf(1).equals(policy.getIsPrepay())) {
continue;
}
Grace grace = resolveGrace(policy, currentDueDate, defaultGraceDays);
if (!grace.graceEnd.isBefore(baseDate) && !grace.graceEnd.isAfter(dunWindowEnd)) {
dunPolicies.add(new RenewalDuePolicy(
policy, currentDueDate, grace.graceEnd, grace.fallback, grace.graceDays));
}
} }
} }
...@@ -114,9 +125,18 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ ...@@ -114,9 +125,18 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ
private void markSkippedIfAbnormal(Map<String, String> skipped, PolicyFollow policy, private void markSkippedIfAbnormal(Map<String, String> skipped, PolicyFollow policy,
RenewalDueDateCalculator.Result result) { RenewalDueDateCalculator.Result result) {
if (result.isAbnormal()) { if (result.isAbnormal()) {
log.warn("续期提醒跳过异常保单,policyNo={}, 原因={}", markSkipped(skipped, policy, result.getSkipReason());
policy.getPolicyNo(), result.getSkipReason()); }
skipped.putIfAbsent(policy.getPolicyNo(), result.getSkipReason()); }
/**
* skipped 以 policyFollowId 为键:保单号本身可能为空(空保单号脏数据也要可统计)。
*/
private void markSkipped(Map<String, String> skipped, PolicyFollow policy, String reason) {
String key = String.valueOf(policy.getId());
if (skipped.putIfAbsent(key, reason) == null) {
log.warn("续期提醒跳过异常保单,policyFollowId={}, policyNo={}, 原因={}",
policy.getId(), policy.getPolicyNo(), reason);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment