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
e9254ee5
Commit
e9254ee5
authored
Sep 14, 2026
by
jianan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
保费到期提醒8
parent
f8230861
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
130 additions
and
0 deletions
+130
-0
yd-csf-service/src/main/java/com/yd/csf/service/utils/RenewalDueDateCalculator.java
+130
-0
No files found.
yd-csf-service/src/main/java/com/yd/csf/service/utils/RenewalDueDateCalculator.java
0 → 100644
View file @
e9254ee5
package
com
.
yd
.
csf
.
service
.
utils
;
import
com.yd.csf.feign.enums.PaymentFrequencyEnum
;
import
lombok.Getter
;
import
org.apache.commons.lang3.StringUtils
;
import
java.math.BigDecimal
;
import
java.time.LocalDate
;
import
java.util.Objects
;
/**
* 保单续期保费到期日计算器。
*
* <p>规则对齐现有 Excel 模板「保费到期日」公式:</p>
* <ul>
* <li>年缴每 12 个月、季缴每 3 个月、月缴每 1 个月为一个缴费日,以生效日为锚点;</li>
* <li>取第一个严格晚于今天的缴费日;</li>
* <li>缴费期满日 = 生效日 + 缴费年期年数 - 1 天,下一缴费日晚于期满日表示已缴完;</li>
* <li>闰日(2/29)在非闰年由 LocalDate.plusMonths/plusYears 自动钳为 2/28。</li>
* </ul>
*/
public
final
class
RenewalDueDateCalculator
{
private
RenewalDueDateCalculator
()
{
}
/**
* 计算单张保单的下一期保费到期日。
*
* @param effectiveDate 生效日
* @param frequency 缴费频率(PaymentFrequencyEnum 的 itemValue:YEAR/SEASON/MONTH/FULL_PAYMENT)
* @param issueNumber 缴费年期(年),库中类型不规整,可能为 Integer/String/BigDecimal
* @param today 当前日期(参数化便于测试)
* @return 计算结果;不纳入时 dueDate 为空并带跳过原因
*/
public
static
Result
calculate
(
LocalDate
effectiveDate
,
String
frequency
,
Object
issueNumber
,
LocalDate
today
)
{
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
);
}
Integer
years
=
parseIssueNumber
(
issueNumber
);
if
(
years
==
null
)
{
return
Result
.
skip
(
"缴费年期为空或无法解析: "
+
issueNumber
,
true
);
}
if
(
years
<=
0
)
{
return
Result
.
skip
(
"缴费年期非法: "
+
years
,
true
);
}
LocalDate
endDate
=
effectiveDate
.
plusYears
(
years
).
minusDays
(
1
);
LocalDate
candidate
=
effectiveDate
;
while
(!
candidate
.
isAfter
(
today
))
{
candidate
=
candidate
.
plusMonths
(
stepMonths
);
if
(
candidate
.
isAfter
(
endDate
))
{
return
Result
.
skip
(
"保单缴费期已满"
,
false
);
}
}
return
Result
.
included
(
candidate
);
}
/**
* 解析缴费年期为整数年,兼容 "5"、"5.0"、5、5.0(BigDecimal) 等存储形态。
*/
private
static
Integer
parseIssueNumber
(
Object
issueNumber
)
{
if
(
Objects
.
isNull
(
issueNumber
))
{
return
null
;
}
if
(
issueNumber
instanceof
Number
)
{
BigDecimal
bd
=
new
BigDecimal
(
issueNumber
.
toString
());
return
bd
.
signum
()
>
0
?
bd
.
intValue
()
:
null
;
}
String
s
=
StringUtils
.
trimToNull
(
issueNumber
.
toString
());
if
(
s
==
null
)
{
return
null
;
}
try
{
BigDecimal
bd
=
new
BigDecimal
(
s
);
return
bd
.
signum
()
>
0
?
bd
.
intValue
()
:
null
;
}
catch
(
NumberFormatException
e
)
{
return
null
;
}
}
/**
* 计算结果:dueDate 非空表示纳入提醒,为空时 skipReason 说明原因。
*/
@Getter
public
static
class
Result
{
private
final
LocalDate
dueDate
;
private
final
String
skipReason
;
/**
* 是否为异常数据跳过(缺字段/无法解析);整付、缴完等正常排除为 false
*/
private
final
boolean
abnormal
;
private
Result
(
LocalDate
dueDate
,
String
skipReason
,
boolean
abnormal
)
{
this
.
dueDate
=
dueDate
;
this
.
skipReason
=
skipReason
;
this
.
abnormal
=
abnormal
;
}
public
static
Result
included
(
LocalDate
dueDate
)
{
return
new
Result
(
dueDate
,
null
,
false
);
}
public
static
Result
skip
(
String
reason
,
boolean
abnormal
)
{
return
new
Result
(
null
,
reason
,
abnormal
);
}
public
boolean
isIncluded
()
{
return
dueDate
!=
null
;
}
}
}
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