Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yd-csf
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
xingmin
yd-csf
Commits
d640c5c3
Commit
d640c5c3
authored
Sep 17, 2026
by
jianan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
保费到期提醒24-保单邮件逐条通知
parent
bf7d94c2
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
114 additions
and
76 deletions
+114
-76
yd-csf-api/src/main/java/com/yd/csf/api/service/impl/RenewalDispatchServiceImpl.java
+33
-21
yd-csf-service/src/main/java/com/yd/csf/service/component/renewal/RenewalDueDateCalculator.java
+20
-9
yd-csf-service/src/main/java/com/yd/csf/service/component/renewal/RenewalPolicyMailBuilder.java
+13
-18
yd-csf-service/src/main/java/com/yd/csf/service/component/renewal/RenewalReminderData.java
+2
-2
yd-csf-service/src/main/java/com/yd/csf/service/service/impl/RenewalReminderQueryServiceImpl.java
+46
-26
No files found.
yd-csf-api/src/main/java/com/yd/csf/api/service/impl/RenewalDispatchServiceImpl.java
View file @
d640c5c3
...
@@ -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
();
}
}
...
...
yd-csf-service/src/main/java/com/yd/csf/service/component/renewal/RenewalDueDateCalculator.java
View file @
d640c5c3
...
@@ -145,24 +145,35 @@ public final class RenewalDueDateCalculator {
...
@@ -145,24 +145,35 @@ 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
());
if
(
s
==
null
)
{
return
null
;
}
try
{
bd
=
new
BigDecimal
(
s
);
}
catch
(
NumberFormatException
e
)
{
return
null
;
}
}
}
String
s
=
StringUtils
.
trimToNull
(
issueNumber
.
toString
());
if
(
bd
.
signum
()
<=
0
||
bd
.
stripTrailingZeros
().
scale
()
>
0
)
{
if
(
s
==
null
)
{
return
null
;
return
null
;
}
}
try
{
try
{
BigDecimal
bd
=
new
BigDecimal
(
s
);
return
bd
.
intValueExact
();
return
bd
.
signum
()
>
0
?
bd
.
intValue
()
:
null
;
}
catch
(
ArithmeticException
e
)
{
}
catch
(
NumberFormatException
e
)
{
return
null
;
return
null
;
}
}
}
}
...
...
yd-csf-service/src/main/java/com/yd/csf/service/component/renewal/RenewalPolicyMailBuilder.java
View file @
d640c5c3
...
@@ -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
()
?
"(宽限天数待核对(按默认 "
+
resolveDefaul
tGraceDays
()
+
" 天估算))"
?
"(宽限天数待核对(按默认 "
+
item
.
ge
tGraceDays
()
+
" 天估算))"
:
"(获取保费到期日起计"
+
item
.
getGraceDays
()
+
"天)"
;
:
"(获取保费到期日起计"
+
item
.
getGraceDays
()
+
"天)"
;
String
graceNoteTc
=
item
.
isGraceFallback
()
String
graceNoteTc
=
item
.
isGraceFallback
()
?
"(寬限天數待核對(按預設 "
+
resolveDefaul
tGraceDays
()
+
" 天估算))"
?
"(寬限天數待核對(按預設 "
+
item
.
ge
tGraceDays
()
+
" 天估算))"
:
"(获取保费到期日起计"
+
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}"
,
""
)
;
}
}
/**
/**
...
...
yd-csf-service/src/main/java/com/yd/csf/service/component/renewal/RenewalReminderData.java
View file @
d640c5c3
...
@@ -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
;
...
...
yd-csf-service/src/main/java/com/yd/csf/service/service/impl/RenewalReminderQueryServiceImpl.java
View file @
d640c5c3
...
@@ -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
);
}
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment