Commit 3cab64d8 by jianan

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

parent 7fbe44f9
......@@ -18,4 +18,14 @@ public interface RenewalMailService {
*/
void sendWithAttachment(List<String> recipients, String subject, String content,
String fileName, byte[] attachment);
/**
* 发送无附件 HTML 邮件(逐单文字通知),To 单地址 + CC 列表,发件人带显示名。
*
* @param to 唯一主送人
* @param ccList 抄送列表(可为空)
* @param subject 邮件主题
* @param htmlContent HTML 正文
*/
void sendHtml(String to, List<String> ccList, String subject, String htmlContent);
}
......@@ -2,6 +2,7 @@ package com.yd.csf.api.service.impl;
import com.yd.csf.api.service.RenewalMailService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
......@@ -10,8 +11,8 @@ import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.List;
......@@ -57,6 +58,29 @@ public class RenewalMailServiceImpl implements RenewalMailService {
}
}
@Override
public void sendHtml(String to, List<String> ccList, String subject, String htmlContent) {
try {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(
message, false, StandardCharsets.UTF_8.name());
helper.setFrom(new InternetAddress(resolveFrom(), "续期保费通知", StandardCharsets.UTF_8.name()));
helper.setTo(to);
if (CollectionUtils.isNotEmpty(ccList)) {
helper.setCc(ccList.toArray(new String[0]));
}
helper.setSubject(subject);
helper.setText(htmlContent, true);
javaMailSender.send(message);
log.info("[续期提醒-邮件] HTML发送成功,To={}, CC={}, 主题={}", to, ccList, subject);
} catch (Exception e) {
log.error("[续期提醒-邮件] HTML发送失败,To={}, 主题={}", to, subject, e);
throw new RuntimeException("续期提醒HTML邮件发送失败: " + e.getMessage(), e);
}
}
private String resolveFrom() {
String from = StringUtils.defaultIfBlank(mailFrom, mailUsername);
if (StringUtils.isBlank(from)) {
......
package com.yd.csf.service.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yd.csf.service.model.RenewalRemindPolicyMail;
/**
* 针对表【renewal_remind_policy_mail(保单续期提醒逐单邮件发送记录表)】的数据库操作 Mapper
*/
public interface RenewalRemindPolicyMailMapper extends BaseMapper<RenewalRemindPolicyMail> {
}
......@@ -7,7 +7,8 @@ import lombok.Data;
import java.time.LocalDate;
/**
* 命中续期提醒窗口的保单及其保费到期日
* 命中续期提醒的保单及其缴费日信息。
* R1 轮次 {@code dueDate} 为下一保费到期日;R2 轮次为当期缴费日。
*/
@Data
@AllArgsConstructor
......@@ -15,5 +16,23 @@ public class RenewalDuePolicy {
private PolicyFollow policyFollow;
/**
* 保费到期日(R1=下一缴费日,R2=当期缴费日)
*/
private LocalDate dueDate;
/**
* 宽限期到期日(dueDate + 宽限天数 - 1,含头尾)
*/
private LocalDate graceEnd;
/**
* true=保单 grace_period 为空/非正,按配置默认宽限天数估算,邮件需标注「待核对」
*/
private boolean graceFallback;
/**
* 实际使用的宽限天数(兜底时为默认值)
*/
private int graceDays;
}
......@@ -6,22 +6,46 @@ import lombok.Data;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
/**
* 续期提醒筛选结果:窗口信息、命中保单、异常跳过明细(保单号 -> 原因)
* 续期提醒筛选结果:窗口信息、R1/R2 命中保单、异常跳过明细(保单号 -> 原因)
*/
@Data
@AllArgsConstructor
public class RenewalReminderData {
/**
* R1 窗口起(M+1月1号)
*/
private LocalDate windowStart;
/**
* R1 窗口止(M+2月月末)
*/
private LocalDate windowEnd;
/**
* R1 到期通知名单(下一保费到期日落入两月窗口)
*/
private List<RenewalDuePolicy> duePolicies;
/**
* R2 宽限期跟进名单(当期已到期、宽限期在基准日至次月月末内届满)
*/
private List<RenewalDuePolicy> dunPolicies;
/**
* 异常跳过明细:保单号 -> 跳过原因(仅统计缺字段/无法解析等脏数据)
*/
private Map<String, String> skipped;
/**
* 宽限天数缺失/非正、按默认值估算的保单数(R1+R2)
*/
public long getGraceFallbackCount() {
return Stream.concat(duePolicies.stream(), dunPolicies.stream())
.filter(RenewalDuePolicy::isGraceFallback)
.count();
}
}
package com.yd.csf.service.enums;
/**
* 续期提醒逐单邮件轮次类型
*/
public enum RenewalMailTypeEnum {
R1("到期通知", "R1"),
R2("宽限期跟进", "R2"),
;
//字典项标签(名称)
private String itemLabel;
//字典项值
private String itemValue;
RenewalMailTypeEnum(String itemLabel, String itemValue) {
this.itemLabel = itemLabel;
this.itemValue = itemValue;
}
public String getItemLabel() {
return itemLabel;
}
public String getItemValue() {
return itemValue;
}
}
......@@ -47,11 +47,21 @@ public class RenewalRemindBatch implements Serializable {
private LocalDate windowEnd;
/**
* 清单保单条数
* R1到期通知保单条数
*/
private Integer policyCount;
/**
* R2宽限期跟进发送封数
*/
private Integer dunCount;
/**
* 逐单邮件发送失败数
*/
private Integer mailFailCount;
/**
* 因数据异常跳过的保单条数
*/
private Integer skippedCount;
......
package com.yd.csf.service.model;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 保单续期提醒逐单邮件发送记录表
*
* @TableName renewal_remind_policy_mail
*/
@TableName(value = "renewal_remind_policy_mail")
@Data
public class RenewalRemindPolicyMail implements Serializable {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 邮件业务唯一ID(RM+随机串)
*/
private String mailBizId;
/**
* 执行月(yyyy-MM)
*/
private String bizMonth;
/**
* policy_follow 主键ID
*/
private Long policyFollowId;
/**
* 保单号(冗余留痕)
*/
private String policyNo;
/**
* 轮次类型:R1-到期通知 R2-宽限期跟进
*/
private String mailType;
/**
* 保费到期日(R2为当期缴费日)
*/
private LocalDate dueDate;
/**
* 宽限期到期日
*/
private LocalDate graceEnd;
/**
* 主送人邮箱快照
*/
private String toRecipients;
/**
* 抄送人邮箱快照(逗号分隔,无抄送为空)
*/
private String ccRecipients;
/**
* 发送状态:SUCCESS-成功 FAIL-失败
*/
private String status;
/**
* 失败原因摘要;重发成功后允许置空
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private String errorMsg;
/**
* 发送时间;FAIL 时允许置空
*/
@TableField(updateStrategy = FieldStrategy.IGNORED)
private LocalDateTime sendTime;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
package com.yd.csf.service.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yd.csf.service.model.RenewalRemindPolicyMail;
import java.util.List;
/**
* 保单续期提醒逐单邮件发送记录 服务类
*/
public interface IRenewalRemindPolicyMailService extends IService<RenewalRemindPolicyMail> {
/**
* 查询某执行月全部逐单邮件留痕(R1+R2),用于重跑时构建已成功集合
*
* @param bizMonth 执行月 yyyy-MM
*/
List<RenewalRemindPolicyMail> listByBizMonth(String bizMonth);
/**
* 按唯一键 (biz_month, policy_follow_id, mail_type) upsert:
* 无记录插入,有记录更新同一行(FAIL→SUCCESS 复用原行)。
*/
void saveOrUpdateByUk(RenewalRemindPolicyMail record);
}
......@@ -6,16 +6,17 @@ import java.time.LocalDate;
import java.time.YearMonth;
/**
* 续期保单筛选:查询生效保单、计算保费到期日、过滤提醒窗口
* 续期保单筛选:查询生效保单,同一次构建产出两轮名单。
* R1 到期通知窗口 = [执行月+1 月 1 号, 执行月+2 月月末];
* R2 宽限期跟进窗口 = [基准日(执行月 1 号), 执行月次月月末]。
*/
public interface RenewalReminderQueryService {
/**
* 构建指定执行月的续期提醒数据。
* 窗口 = [执行月+1 月 1 号, 执行月+2 月月末]。
* 构建指定执行月的续期提醒数据(R1 + R2)。
*
* @param execMonth 执行月
* @param today 当前日期(参数化便于测试
* @param baseDate 基准日(定时跑与补发均固定传执行月 1 号,保证结果可复现
*/
RenewalReminderData build(YearMonth execMonth, LocalDate today);
RenewalReminderData build(YearMonth execMonth, LocalDate baseDate);
}
package com.yd.csf.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.csf.service.dao.RenewalRemindPolicyMailMapper;
import com.yd.csf.service.model.RenewalRemindPolicyMail;
import com.yd.csf.service.service.IRenewalRemindPolicyMailService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
/**
* 保单续期提醒逐单邮件发送记录 服务实现类
*/
@Service
public class RenewalRemindPolicyMailServiceImpl
extends ServiceImpl<RenewalRemindPolicyMailMapper, RenewalRemindPolicyMail>
implements IRenewalRemindPolicyMailService {
private static final String MAIL_BIZ_ID_PREFIX = "RM";
@Override
public List<RenewalRemindPolicyMail> listByBizMonth(String bizMonth) {
return this.baseMapper.selectList(new LambdaQueryWrapper<RenewalRemindPolicyMail>()
.eq(RenewalRemindPolicyMail::getBizMonth, bizMonth));
}
@Override
public void saveOrUpdateByUk(RenewalRemindPolicyMail record) {
RenewalRemindPolicyMail exist = this.baseMapper.selectOne(
new LambdaQueryWrapper<RenewalRemindPolicyMail>()
.eq(RenewalRemindPolicyMail::getBizMonth, record.getBizMonth())
.eq(RenewalRemindPolicyMail::getPolicyFollowId, record.getPolicyFollowId())
.eq(RenewalRemindPolicyMail::getMailType, record.getMailType())
.last(" limit 1 "));
LocalDateTime now = LocalDateTime.now();
if (exist == null) {
record.setMailBizId(RandomStringGenerator.generateBizId16(MAIL_BIZ_ID_PREFIX));
record.setCreateTime(now);
record.setUpdateTime(now);
this.baseMapper.insert(record);
} else {
record.setId(exist.getId());
record.setMailBizId(exist.getMailBizId());
record.setCreateTime(exist.getCreateTime());
record.setUpdateTime(now);
this.baseMapper.updateById(record);
}
}
}
......@@ -11,6 +11,7 @@ import com.yd.csf.service.utils.RenewalDueDateCalculator;
import com.yd.csf.service.utils.SimpleDateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
......@@ -23,59 +24,123 @@ import java.util.List;
import java.util.Map;
/**
* 续期保单筛选实现
* 续期保单筛选实现:同一次 build 产出 R1 到期通知与 R2 宽限期跟进两轮名单。
*/
@Service
@Slf4j
public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryService {
/**
* 保单 grace_period 为空/非正时的兜底宽限天数
*/
@Value("${renewal.reminder.default-grace-days:31}")
private Integer configuredDefaultGraceDays;
@Resource
private PolicyFollowMapper policyFollowMapper;
@Override
public RenewalReminderData build(YearMonth execMonth, LocalDate today) {
public RenewalReminderData build(YearMonth execMonth, LocalDate baseDate) {
LocalDate windowStart = execMonth.plusMonths(1).atDay(1);
LocalDate windowEnd = execMonth.plusMonths(2).atEndOfMonth();
// R2 宽限期窗口:[基准日(执行月1号,含), 执行月次月月末(含)]
LocalDate dunWindowEnd = execMonth.plusMonths(1).atEndOfMonth();
int defaultGraceDays = resolveDefaultGraceDays();
List<PolicyFollow> effectivePolicies = queryEffectivePolicies();
List<RenewalDuePolicy> duePolicies = new ArrayList<>();
List<RenewalDuePolicy> dunPolicies = new ArrayList<>();
Map<String, String> skipped = new LinkedHashMap<>();
if (CollectionUtils.isNotEmpty(effectivePolicies)) {
for (PolicyFollow policy : effectivePolicies) {
RenewalDueDateCalculator.Result result = RenewalDueDateCalculator.calculate(
SimpleDateUtils.toLocalDate(policy.getEffectiveDate()),
policy.getPaymentFrequency(),
policy.getIssueNumber(),
today);
if (!result.isIncluded()) {
if (result.isAbnormal()) {
log.warn("续期提醒跳过异常保单,policyNo={}, 原因={}",
policy.getPolicyNo(), result.getSkipReason());
skipped.put(policy.getPolicyNo(), result.getSkipReason());
LocalDate effectiveDate = SimpleDateUtils.toLocalDate(policy.getEffectiveDate());
// R1:下一保费到期日落入 [M+1月1号, M+2月月末]
RenewalDueDateCalculator.Result nextResult = RenewalDueDateCalculator.calculate(
effectiveDate, policy.getPaymentFrequency(), policy.getIssueNumber(), baseDate);
if (!nextResult.isIncluded()) {
markSkippedIfAbnormal(skipped, policy, nextResult);
} else {
LocalDate nextDueDate = nextResult.getDueDate();
if (!nextDueDate.isBefore(windowStart) && !nextDueDate.isAfter(windowEnd)) {
Grace grace = resolveGrace(policy, nextDueDate, defaultGraceDays);
duePolicies.add(new RenewalDuePolicy(
policy, nextDueDate, grace.graceEnd, grace.fallback, grace.graceDays));
}
continue;
}
LocalDate dueDate = result.getDueDate();
if (!dueDate.isBefore(windowStart) && !dueDate.isAfter(windowEnd)) {
duePolicies.add(new RenewalDuePolicy(policy, dueDate));
// 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));
}
}
}
// 清单按保费到期日升序,同日按保单号
duePolicies.sort(Comparator
.comparing(RenewalDuePolicy::getDueDate)
.thenComparing(p -> p.getPolicyFollow().getPolicyNo()));
// R1 按保费到期日升序、R2 按宽限期到期日升序,同日按保单号
Comparator<RenewalDuePolicy> policyNoOrder = Comparator
.comparing(p -> p.getPolicyFollow().getPolicyNo(), Comparator.nullsLast(String::compareTo));
duePolicies.sort(Comparator.comparing(RenewalDuePolicy::getDueDate).thenComparing(policyNoOrder));
dunPolicies.sort(Comparator.comparing(RenewalDuePolicy::getGraceEnd).thenComparing(policyNoOrder));
log.info("续期提醒筛选完成,窗口=[{} ~ {}],生效保单={},命中={},异常跳过={}",
long graceFallbackCount = duePolicies.stream().filter(RenewalDuePolicy::isGraceFallback).count()
+ dunPolicies.stream().filter(RenewalDuePolicy::isGraceFallback).count();
log.info("续期提醒筛选完成,R1窗口=[{} ~ {}],生效保单={},R1命中={},R2命中={},宽限兜底={},异常跳过={}",
windowStart, windowEnd,
effectivePolicies == null ? 0 : effectivePolicies.size(),
duePolicies.size(), skipped.size());
return new RenewalReminderData(windowStart, windowEnd, duePolicies, skipped);
duePolicies.size(), dunPolicies.size(), graceFallbackCount, skipped.size());
return new RenewalReminderData(windowStart, windowEnd, duePolicies, dunPolicies, skipped);
}
private void markSkippedIfAbnormal(Map<String, String> skipped, PolicyFollow policy,
RenewalDueDateCalculator.Result result) {
if (result.isAbnormal()) {
log.warn("续期提醒跳过异常保单,policyNo={}, 原因={}",
policy.getPolicyNo(), result.getSkipReason());
skipped.putIfAbsent(policy.getPolicyNo(), result.getSkipReason());
}
}
/**
* 解析宽限期到期日;grace_period 为空/非正时按默认天数估算并标记兜底。
*/
private Grace resolveGrace(PolicyFollow policy, LocalDate dueDate, int defaultGraceDays) {
Integer gracePeriod = policy.getGracePeriod();
boolean fallback = gracePeriod == null || gracePeriod <= 0;
int graceDays = fallback ? defaultGraceDays : gracePeriod;
LocalDate graceEnd = RenewalDueDateCalculator.graceEndDate(dueDate, graceDays);
if (fallback) {
log.info("保单宽限期为空或非正,按默认 {} 天估算,policyNo={}, gracePeriod={}",
defaultGraceDays, policy.getPolicyNo(), gracePeriod);
}
return new Grace(graceEnd, fallback, graceDays);
}
private int resolveDefaultGraceDays() {
if (configuredDefaultGraceDays == null || configuredDefaultGraceDays <= 0) {
log.warn("配置 renewal.reminder.default-grace-days={} 非法,回退 31", configuredDefaultGraceDays);
return 31;
}
return configuredDefaultGraceDays;
}
/**
......@@ -101,6 +166,7 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ
PolicyFollow::getPaymentFrequency,
PolicyFollow::getIsPrepay,
PolicyFollow::getPrepaidTerm,
PolicyFollow::getGracePeriod,
PolicyFollow::getProductCate,
PolicyFollow::getInsuranceCompany,
PolicyFollow::getReconciliationCompany,
......@@ -112,4 +178,19 @@ public class RenewalReminderQueryServiceImpl implements RenewalReminderQueryServ
.eq(PolicyFollow::getStatus, PolicyFollowStatusEnum.EFFECTIVE.getItemValue())
.eq(PolicyFollow::getIsDeleted, 0));
}
/**
* 宽限期解析中间结果
*/
private static class Grace {
private final LocalDate graceEnd;
private final boolean fallback;
private final int graceDays;
Grace(LocalDate graceEnd, boolean fallback, int graceDays) {
this.graceEnd = graceEnd;
this.fallback = fallback;
this.graceDays = graceDays;
}
}
}
......@@ -72,6 +72,79 @@ public final class RenewalDueDateCalculator {
}
/**
* 计算单张保单的当期缴费日:缴费日序列(k=0 首期 = 生效日)中不晚于基准日的最大者。
*
* <p>与 {@link #calculate} 严格互补:当期缴费日 ≤ 基准日 &lt; 下一缴费日。
* 基准日早于首期(生效日)时返回空;缴费期满后的日期不参与序列,保单缴完时
* 返回最后一个有效缴费日(由上层配合宽限期窗口判断是否命中 R2)。</p>
*
* @param effectiveDate 生效日
* @param frequency 缴费频率(PaymentFrequencyEnum 的 itemValue)
* @param issueNumber 缴费年期(年)
* @param baseDate 基准日(执行月 1 号)
* @return 计算结果;无当期缴费日时 dueDate 为空并带跳过原因
*/
public static Result currentDueDate(LocalDate effectiveDate, String frequency,
Object issueNumber, LocalDate baseDate) {
String freq = StringUtils.trimToEmpty(frequency);
if (PaymentFrequencyEnum.FULL_PAYMENT.getItemValue().equals(freq)) {
return Result.skip("整付保单无续期保费", false);
}
int stepMonths;
if (PaymentFrequencyEnum.YEAR.getItemValue().equals(freq)) {
stepMonths = 12;
} else if (PaymentFrequencyEnum.SEASON.getItemValue().equals(freq)) {
stepMonths = 3;
} else if (PaymentFrequencyEnum.MONTH.getItemValue().equals(freq)) {
stepMonths = 1;
} else if (StringUtils.isBlank(freq)) {
return Result.skip("缴费频率为空", true);
} else {
return Result.skip("无法识别的缴费频率: " + freq, true);
}
if (Objects.isNull(effectiveDate)) {
return Result.skip("生效日为空", true);
}
if (Objects.isNull(baseDate)) {
return Result.skip("基准日为空", true);
}
Integer years = parseIssueNumber(issueNumber);
if (years == null) {
return Result.skip("缴费年期为空或无法解析: " + issueNumber, true);
}
LocalDate endDate = effectiveDate.plusYears(years).minusDays(1);
LocalDate last = null;
LocalDate candidate = effectiveDate;
while (!candidate.isAfter(baseDate)) {
if (candidate.isAfter(endDate)) {
break;
}
last = candidate;
candidate = candidate.plusMonths(stepMonths);
}
return last == null
? Result.skip("基准日早于保单首期缴费日", false)
: Result.included(last);
}
/**
* 宽限期到期日 = 缴费日 + 宽限天数 - 1(含头尾:6/20 起计 31 天 = 7/20)。
*
* @param dueDate 缴费日
* @param graceDays 宽限天数(调用方保证为正数,空值已按配置兜底)
* @return 宽限期到期日;入参非法时返回 null
*/
public static LocalDate graceEndDate(LocalDate dueDate, int graceDays) {
if (dueDate == null || graceDays <= 0) {
return null;
}
return dueDate.plusDays((long) graceDays - 1);
}
/**
* 解析缴费年期为整数年,兼容 "5"、"5.0"、5、5.0(BigDecimal) 等存储形态。
*/
private static Integer parseIssueNumber(Object issueNumber) {
......
package com.yd.csf.service.utils;
import com.yd.csf.service.model.PolicyFollow;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
/**
* 续期提醒金额口径:Excel 报表 M 列与逐单邮件「应缴总保费」的唯一来源。
*/
public final class RenewalPremiumUtils {
private RenewalPremiumUtils() {
}
/**
* 应缴总保费 = 期交保费 + 保单征费。
* 期交保费与征费均无值时返回 null(调用方展示「待核对」);征费为空或非数字按 0 处理。
*/
public static BigDecimal totalPremiumAndLevy(PolicyFollow p) {
BigDecimal levy = parseAmount(p.getPolicyLevy());
if (p.getEachIssuePremium() == null && levy == null) {
return null;
}
BigDecimal premium = p.getEachIssuePremium() == null ? BigDecimal.ZERO : p.getEachIssuePremium();
return premium.add(levy == null ? BigDecimal.ZERO : levy);
}
private static BigDecimal parseAmount(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
try {
return new BigDecimal(value.trim());
} catch (NumberFormatException e) {
return null;
}
}
}
......@@ -4,7 +4,6 @@ import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.yd.csf.service.model.PolicyFollow;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import java.math.BigDecimal;
import java.util.Date;
......@@ -153,24 +152,10 @@ public class RenewalPolicyExportDTO {
/**
* 总保费及征费 = 期交保费 + 保单征费;两者均无值时留空,征费为空或非数字按 0 处理。
* 口径与逐单邮件共用 {@link com.yd.csf.service.utils.RenewalPremiumUtils}。
*/
private static String buildTotalPremiumAndLevy(PolicyFollow p) {
BigDecimal premium = p.getEachIssuePremium() == null ? BigDecimal.ZERO : p.getEachIssuePremium();
BigDecimal levy = parseAmount(p.getPolicyLevy());
if (p.getEachIssuePremium() == null && levy == null) {
return null;
}
return premium.add(levy == null ? BigDecimal.ZERO : levy).toPlainString();
}
private static BigDecimal parseAmount(String value) {
if (StringUtils.isBlank(value)) {
return null;
}
try {
return new BigDecimal(value.trim());
} catch (NumberFormatException e) {
return null;
}
BigDecimal total = com.yd.csf.service.utils.RenewalPremiumUtils.totalPremiumAndLevy(p);
return total == null ? null : total.toPlainString();
}
}
......@@ -11,6 +11,8 @@
<result property="windowStart" column="window_start"/>
<result property="windowEnd" column="window_end"/>
<result property="policyCount" column="policy_count"/>
<result property="dunCount" column="dun_count"/>
<result property="mailFailCount" column="mail_fail_count"/>
<result property="skippedCount" column="skipped_count"/>
<result property="recipients" column="recipients"/>
<result property="fileName" column="file_name"/>
......@@ -24,7 +26,7 @@
<sql id="Base_Column_List">
id
,batch_biz_id,biz_month,window_start,window_end,policy_count,
skipped_count,recipients,file_name,status,error_msg,
send_time,create_time,update_time
dun_count,mail_fail_count,skipped_count,recipients,file_name,
status,error_msg,send_time,create_time,update_time
</sql>
</mapper>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yd.csf.service.dao.RenewalRemindPolicyMailMapper">
<resultMap id="BaseResultMap" type="com.yd.csf.service.model.RenewalRemindPolicyMail">
<id property="id" column="id"/>
<result property="mailBizId" column="mail_biz_id"/>
<result property="bizMonth" column="biz_month"/>
<result property="policyFollowId" column="policy_follow_id"/>
<result property="policyNo" column="policy_no"/>
<result property="mailType" column="mail_type"/>
<result property="dueDate" column="due_date"/>
<result property="graceEnd" column="grace_end"/>
<result property="toRecipients" column="to_recipients"/>
<result property="ccRecipients" column="cc_recipients"/>
<result property="status" column="status"/>
<result property="errorMsg" column="error_msg"/>
<result property="sendTime" column="send_time"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="Base_Column_List">
id
,mail_biz_id,biz_month,policy_follow_id,policy_no,mail_type,
due_date,grace_end,to_recipients,cc_recipients,status,
error_msg,send_time,create_time,update_time
</sql>
</mapper>
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