|
|
@@ -0,0 +1,555 @@
|
|
|
+package com.skyversation.xjcy.computer;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.skyversation.xjcy.bean.Enterprise;
|
|
|
+import com.skyversation.xjcy.bean.EnterpriseEconomic;
|
|
|
+import com.skyversation.xjcy.bean.EnterpriseHealth;
|
|
|
+import lombok.Getter;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.text.DecimalFormat;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.MonthDay;
|
|
|
+import java.time.YearMonth;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+import java.util.function.Function;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.stream.IntStream;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 企业健康状况计算器
|
|
|
+ * 用于分析企业的经营状况并生成健康报告
|
|
|
+ */
|
|
|
+public class EnterpriseHealthComputer {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 企业健康类型枚举
|
|
|
+ * 定义各种健康状态及其描述
|
|
|
+ */
|
|
|
+ public enum HealthType {
|
|
|
+ /**
|
|
|
+ * 清算注销 - 企业已处于注销流程,请核查经营情况
|
|
|
+ */
|
|
|
+ LIQUIDATION(10, "清算注销", "企业已处于注销流程,请核查经营情况"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 严重失信 - 企业列入严重违法失信名单,暂停政策申请
|
|
|
+ */
|
|
|
+ SERIOUS_DISHONESTY(11, "严重失信", "企业列入严重违法失信名单,暂停政策申请"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 股权出质 - 股权已全额出质,关注经营稳定性
|
|
|
+ */
|
|
|
+ EQUITY_PLEDGE(12, "股权出质", "股权已全额出质,关注经营稳定性"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 终本案件 - 存在大额未履行终本案件,注意法律风险
|
|
|
+ */
|
|
|
+ FINAL_CASE(13, "终本案件", "存在大额未履行终本案件,注意法律风险"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 税收跳水 - 税收断崖式下跌,需核实经营情况
|
|
|
+ */
|
|
|
+ TAX_DIVE(14, "税收跳水", "税收断崖式下跌,需核实经营情况"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 能耗异常 - 能耗接近零,疑似停产或外迁
|
|
|
+ */
|
|
|
+ ENERGY_ABNORMAL(15, "能耗异常", "能耗接近零,疑似停产或外迁"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 租约到期 - 租约即将到期未续签,提前跟进招商
|
|
|
+ */
|
|
|
+ LEASE_EXPIRY(16, "租约到期", "租约即将到期未续签,提前跟进招商"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 纳税逾期 - 多次逾期申报,提醒企业及时纳税
|
|
|
+ */
|
|
|
+ TAX_OVERDUE(17, "纳税逾期", "多次逾期申报,提醒企业及时纳税"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 税收上升 - 税收稳步提升,潜力优秀
|
|
|
+ */
|
|
|
+ TAX_RISE(20, "税收上升", "税收稳步提升,潜力优秀"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 产值上升 - 产值稳步提升,潜力优秀
|
|
|
+ */
|
|
|
+ OUTPUT_RISE(21, "产值上升", "产值稳步提升,潜力优秀"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 升规成功 - 企业新晋规上,建议重点服务
|
|
|
+ */
|
|
|
+ UPGRADE_SUCCESS(22, "升规成功", "企业新晋规上,建议重点服务"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 专利爆发 - 知识产权快速积累,创新力强
|
|
|
+ */
|
|
|
+ PATENT_EXPLOSION(23, "专利爆发", "知识产权快速积累,创新力强"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 500强投资 - 背靠500强,扩张潜力大
|
|
|
+ */
|
|
|
+ TOP500_INVESTMENT(24, "500强投", "背靠500强,扩张潜力大"),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 产业新星 - 重点产业新落地,建议跟踪服务
|
|
|
+ */
|
|
|
+ INDUSTRIAL_RISING_STAR(25, "产业新星", "重点产业新落地,建议跟踪服务");
|
|
|
+
|
|
|
+ @Getter
|
|
|
+ final int value;
|
|
|
+ @Getter
|
|
|
+ final String defaultName;
|
|
|
+ @Getter
|
|
|
+ final String defaultDescribe;
|
|
|
+
|
|
|
+ HealthType(int value, String defaultName, String defaultDescribe) {
|
|
|
+ this.value = value;
|
|
|
+ this.defaultName = defaultName;
|
|
|
+ this.defaultDescribe = defaultDescribe;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 一系列月份组成的季节<br/>不可变对象
|
|
|
+ */
|
|
|
+ public static class Season {
|
|
|
+ @Getter
|
|
|
+ private final List<YearMonth> yearMonths;
|
|
|
+
|
|
|
+ private Season(List<YearMonth> yearMonths) {
|
|
|
+ this.yearMonths = new ArrayList<>(yearMonths);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取该季节的起始时间
|
|
|
+ *
|
|
|
+ * @return 第一个月的第一天的0点
|
|
|
+ */
|
|
|
+ public LocalDateTime getStartTime() {
|
|
|
+ return yearMonths.get(0).atDay(1).atStartOfDay();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取该季节的结束时间
|
|
|
+ *
|
|
|
+ * @return 最后一个月的最后一天的12点
|
|
|
+ */
|
|
|
+ public LocalDateTime getEndTime() {
|
|
|
+ return yearMonths.get(yearMonths.size() - 1).atEndOfMonth().plusDays(1).atStartOfDay();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将当前季节向前推移指定的季节数
|
|
|
+ * 每个季节包含3个月,因此实际推移的月份数为 season * 3
|
|
|
+ *
|
|
|
+ * @param season 要推移的季节数量,正数表示向过去推移,负数表示向未来推移
|
|
|
+ * @return 推移后的新Season对象
|
|
|
+ * @example // 假设当前是2024年第一季度(1-3月)
|
|
|
+ * season.minusSeasons(1) // 返回2023年第四季度(10-12月)
|
|
|
+ * season.minusSeasons(-1) // 返回2024年第二季度(4-6月)
|
|
|
+ */
|
|
|
+ public Season minusSeasons(int season) {
|
|
|
+ return new Season(yearMonths.stream().map(y -> y.minusMonths(season * 3L)).collect(Collectors.toList()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断给定的时间是否包含在当前季节的时间范围内。
|
|
|
+ * 时间范围是前闭后开区间,即 [startTime, endTime)
|
|
|
+ *
|
|
|
+ * @param time 要检查的时间
|
|
|
+ * @return 如果时间在季节范围内(即大于等于开始时间,小于结束时间)返回true,否则返回false
|
|
|
+ */
|
|
|
+ public boolean contains(LocalDateTime time) {
|
|
|
+ return !getStartTime().isAfter(time) && getEndTime().isAfter(time);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断指定年月是否在当前季节的月份范围内
|
|
|
+ * 直接检查YearMonth是否在季节的月份列表中,不涉及具体日期和时间
|
|
|
+ *
|
|
|
+ * @param yearMonth 要检查的年月
|
|
|
+ * @return 如果年月在季节的月份列表中返回true,否则返回false
|
|
|
+ * @example // 假设季节是2024年第一季度(1-3月)
|
|
|
+ * contains(YearMonth.of(2024, 1)) // true
|
|
|
+ * contains(YearMonth.of(2024, 2)) // true
|
|
|
+ * contains(YearMonth.of(2024, 3)) // true
|
|
|
+ * contains(YearMonth.of(2024, 4)) // false
|
|
|
+ * contains(YearMonth.of(2023, 12)) // false
|
|
|
+ */
|
|
|
+ public boolean contains(YearMonth yearMonth) {
|
|
|
+ return yearMonths.contains(yearMonth);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int hashCode() {
|
|
|
+ return yearMonths.hashCode();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean equals(Object obj) {
|
|
|
+ if (obj == this) return true;
|
|
|
+ if (obj == null || obj.getClass() != this.getClass()) return false;
|
|
|
+ return yearMonths.equals(((Season) obj).yearMonths);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 静态常量 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步锁对象
|
|
|
+ */
|
|
|
+ public static final Object LOCK = new Object();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 经济数据年月格式转换器
|
|
|
+ */
|
|
|
+ public static final DateTimeFormatter economicYearMonthFormat = DateTimeFormatter.ofPattern("yyyyMM");
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 简单两位小数decimalFormat
|
|
|
+ */
|
|
|
+ public static final DecimalFormat simpleDecimalFormat = new DecimalFormat("0.00%");
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 季度结束日期集合
|
|
|
+ */
|
|
|
+ private static final Set<MonthDay> SEASON_ENDS = new HashSet<>(Arrays.asList(
|
|
|
+ MonthDay.of(3, 31),
|
|
|
+ MonthDay.of(6, 30),
|
|
|
+ MonthDay.of(9, 30),
|
|
|
+ MonthDay.of(12, 31)));
|
|
|
+
|
|
|
+ // ==================== 实例变量 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算结果列表
|
|
|
+ */
|
|
|
+ @Getter
|
|
|
+ List<EnterpriseHealth> results;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 企业列表
|
|
|
+ */
|
|
|
+ List<Enterprise> enterprises;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 企业经济数据映射:企业ID -> (年月 -> 经济数据)
|
|
|
+ */
|
|
|
+ Map<String, Map<YearMonth, EnterpriseEconomic>> enterpriseEconomics;
|
|
|
+
|
|
|
+ // ==================== 构造方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造函数
|
|
|
+ *
|
|
|
+ * @param enterprises 企业列表
|
|
|
+ */
|
|
|
+ public EnterpriseHealthComputer(List<Enterprise> enterprises) {
|
|
|
+ this.enterprises = enterprises;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 公共方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取JSON格式的结果列表
|
|
|
+ *
|
|
|
+ * @return JSON对象列表
|
|
|
+ */
|
|
|
+ public List<JSONObject> getResultJsons() {
|
|
|
+ return results.stream().map(EnterpriseHealth::toJson).collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加企业经济数据
|
|
|
+ *
|
|
|
+ * @param economics 企业经济数据集合
|
|
|
+ */
|
|
|
+ public void putEnterpriseEconomics(Collection<EnterpriseEconomic> economics) {
|
|
|
+ enterpriseEconomics = economics.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ EnterpriseEconomic::getCEnterpriseId,
|
|
|
+ Collectors.toMap(
|
|
|
+ enterpriseEconomic -> YearMonth.parse(enterpriseEconomic.getCYearMonth(), economicYearMonthFormat),
|
|
|
+ Function.identity()
|
|
|
+ )
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算税收上升情况
|
|
|
+ *
|
|
|
+ * @param now 当前时间
|
|
|
+ */
|
|
|
+ public void computeTaxRise(LocalDateTime now) {
|
|
|
+ //最近四个季节
|
|
|
+ List<Season> seasons = getLastFourSeason(now);
|
|
|
+ //四个季节的上一年同季节
|
|
|
+ List<Season> previousYearSeasons = seasons.stream().map(s -> s.minusSeasons(4)).collect(Collectors.toList());
|
|
|
+ for (Enterprise enterprise : enterprises) {
|
|
|
+ List<List<EnterpriseEconomic>> seasonEconomic = getEconomicBySeasonList(enterprise, seasons);
|
|
|
+ List<List<EnterpriseEconomic>> previousYearSeasonEconomic = getEconomicBySeasonList(enterprise, previousYearSeasons);
|
|
|
+ //去年数据必须完整,避免因数据缺失异常标记
|
|
|
+ if (previousYearSeasonEconomic.stream().anyMatch(list->list.contains(null))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ //较新四个季度的税收统计
|
|
|
+ BigDecimal riseRate = checkAllRiseAndGetRiseRate(seasonEconomic, previousYearSeasonEconomic);
|
|
|
+ if (riseRate == null) continue;
|
|
|
+ //当总额上升率超过20%时提交记录
|
|
|
+ if (riseRate.compareTo(BigDecimal.valueOf(12,1)) >= 0) {
|
|
|
+ putResult(enterprise,
|
|
|
+ HealthType.TAX_RISE.getDefaultName()+simpleDecimalFormat.format(riseRate.subtract(BigDecimal.ONE)),
|
|
|
+ HealthType.TAX_RISE.getDefaultDescribe(),
|
|
|
+ HealthType.TAX_RISE,
|
|
|
+ now, seasons.get(0).getStartTime(), seasons.get(seasons.size() - 1).getEndTime());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static BigDecimal checkAllRiseAndGetRiseRate(List<List<EnterpriseEconomic>> seasonEconomic, List<List<EnterpriseEconomic>> previousYearSeasonEconomic) {
|
|
|
+ List<BigDecimal> seasonTax = seasonEconomic.stream().map(
|
|
|
+ l->l.stream()
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .map(EnterpriseEconomic::getCApprovedTax)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .reduce(BigDecimal.ZERO,BigDecimal::add)
|
|
|
+ ).collect(Collectors.toList());
|
|
|
+ //较旧四个季度的税收统计
|
|
|
+ List<BigDecimal> previousYearSeasonTax = previousYearSeasonEconomic.stream().map(
|
|
|
+ l->l.stream()
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .map(EnterpriseEconomic::getCApprovedTax)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .reduce(BigDecimal.ZERO,BigDecimal::add)
|
|
|
+ ).collect(Collectors.toList());
|
|
|
+ //检查所有季度是否同比上升
|
|
|
+ if (!isAllUpper(seasonTax, previousYearSeasonTax)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ //计算上升率
|
|
|
+ BigDecimal divide = sumAndDivide(seasonTax, previousYearSeasonTax);
|
|
|
+ if (divide == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return divide;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算产出上升情况
|
|
|
+ *
|
|
|
+ * @param now 当前时间
|
|
|
+ */
|
|
|
+ @SuppressWarnings("DuplicatedCode")
|
|
|
+ public void computeOutputRise(LocalDateTime now) {
|
|
|
+//最近四个季节
|
|
|
+ List<Season> seasons = getLastFourSeason(now);
|
|
|
+ //四个季节的上一年同季节
|
|
|
+ List<Season> previousYearSeasons = seasons.stream().map(s -> s.minusSeasons(4)).collect(Collectors.toList());
|
|
|
+ for (Enterprise enterprise : enterprises) {
|
|
|
+ List<List<EnterpriseEconomic>> seasonEconomic = getEconomicBySeasonList(enterprise, seasons);
|
|
|
+ List<List<EnterpriseEconomic>> previousYearSeasonEconomic = getEconomicBySeasonList(enterprise, previousYearSeasons);
|
|
|
+ //去年数据必须完整,避免因数据缺失异常标记
|
|
|
+ if (previousYearSeasonEconomic.stream().anyMatch(list->list.contains(null))) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ //计算上升率
|
|
|
+ BigDecimal riseRate = checkAllRiseAndGetRiseRate(seasonEconomic, previousYearSeasonEconomic);
|
|
|
+ if (riseRate == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ //当总额上升率超过20%时提交记录
|
|
|
+ if (riseRate.compareTo(BigDecimal.valueOf(12,1)) >= 0) {
|
|
|
+ putResult(enterprise,
|
|
|
+ HealthType.OUTPUT_RISE.getDefaultName()+simpleDecimalFormat.format(riseRate.subtract(BigDecimal.ONE)),
|
|
|
+ HealthType.OUTPUT_RISE.getDefaultDescribe(),
|
|
|
+ HealthType.OUTPUT_RISE,
|
|
|
+ now, seasons.get(0).getStartTime(), seasons.get(seasons.size() - 1).getEndTime());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算税收跳水情况
|
|
|
+ *
|
|
|
+ * @param now 当前时间
|
|
|
+ */
|
|
|
+ public void computeTaxDive(LocalDateTime now) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 私有方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加计算结果(自定义标题和描述)
|
|
|
+ *
|
|
|
+ * @param enterprise 企业对象
|
|
|
+ * @param title 健康标题
|
|
|
+ * @param describe 健康描述
|
|
|
+ * @param type 健康类型
|
|
|
+ * @param computeTime 计算时间
|
|
|
+ * @param healthStart 健康开始时间
|
|
|
+ * @param healthEnd 健康结束时间
|
|
|
+ * @return 企业健康对象
|
|
|
+ */
|
|
|
+ private EnterpriseHealth putResult(Enterprise enterprise, String title, String describe, HealthType type, LocalDateTime computeTime,
|
|
|
+ LocalDateTime healthStart, LocalDateTime healthEnd) {
|
|
|
+ EnterpriseHealth result = new EnterpriseHealth(title, describe, type.value, computeTime,
|
|
|
+ healthStart, healthEnd, enterprise.getCUnifiedSocialCreditCode());
|
|
|
+ results.add(result);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加计算结果(使用默认标题和描述)
|
|
|
+ *
|
|
|
+ * @param enterprise 企业对象
|
|
|
+ * @param type 健康类型
|
|
|
+ * @param computeTime 计算时间
|
|
|
+ * @param healthStart 健康开始时间
|
|
|
+ * @param healthEnd 健康结束时间
|
|
|
+ * @return 企业健康对象
|
|
|
+ */
|
|
|
+ private EnterpriseHealth putResult(Enterprise enterprise, HealthType type, LocalDateTime computeTime,
|
|
|
+ LocalDateTime healthStart, LocalDateTime healthEnd) {
|
|
|
+ EnterpriseHealth result = new EnterpriseHealth(type.defaultName, type.defaultDescribe, type.value, computeTime,
|
|
|
+ healthStart, healthEnd, enterprise.getCUnifiedSocialCreditCode());
|
|
|
+ results.add(result);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据季节列表获取企业对应的经济数据
|
|
|
+ * 按季节分组,每个季节包含该季节内各个月份的经济数据列表
|
|
|
+ *
|
|
|
+ * @param enterprise 企业对象,包含企业基本信息
|
|
|
+ * @param seasons 季节列表,每个季节包含对应的年月信息
|
|
|
+ * @return 按季节分组的企业经济数据二维列表
|
|
|
+ * 外层列表对应每个季节,内层列表对应该季节各个月份的经济数据
|
|
|
+ * 如果某个月份没有数据,对应位置可能为null
|
|
|
+ */
|
|
|
+ private List<List<EnterpriseEconomic>> getEconomicBySeasonList(Enterprise enterprise, List<Season> seasons) {
|
|
|
+ List<List<EnterpriseEconomic>> seasonEconomic = new ArrayList<>();
|
|
|
+ for (Season season : seasons) {
|
|
|
+ //企业编码
|
|
|
+ String enterpriseCode = enterprise.getCUnifiedSocialCreditCode();
|
|
|
+ Map<YearMonth, EnterpriseEconomic> yearMonthEnterpriseEconomicMap = enterpriseEconomics.get(enterpriseCode);
|
|
|
+ if (yearMonthEnterpriseEconomicMap == null) {
|
|
|
+ yearMonthEnterpriseEconomicMap = Collections.emptyMap();
|
|
|
+ }
|
|
|
+ seasonEconomic.add(season.getYearMonths().stream()
|
|
|
+ .map(yearMonthEnterpriseEconomicMap::get)
|
|
|
+ .collect(Collectors.toList()));
|
|
|
+ }
|
|
|
+ return seasonEconomic;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取以季节结束月为基准的连续月份
|
|
|
+ *
|
|
|
+ * @param now 当前时间
|
|
|
+ * @param numberOfMonths 需要的月份数量
|
|
|
+ * @return 连续的YearMonth列表,从最早到最近排序
|
|
|
+ */
|
|
|
+ private static List<YearMonth> getConsecutiveMonthsFromSeasonEnd(LocalDateTime now, int numberOfMonths) {
|
|
|
+ if (numberOfMonths <= 0) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ LocalDateTime seasonEndTime = checkAndFindSeasonEnd(now);
|
|
|
+ YearMonth endMonth = YearMonth.from(seasonEndTime);
|
|
|
+
|
|
|
+ List<YearMonth> months = new ArrayList<>();
|
|
|
+ // 从最早的月份开始添加,确保时间顺序
|
|
|
+ for (int i = numberOfMonths - 1; i >= 0; i--) {
|
|
|
+ months.add(endMonth.minusMonths(i));
|
|
|
+ }
|
|
|
+
|
|
|
+ return months;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取季度最后三个月
|
|
|
+ *
|
|
|
+ * @param now 当前时间
|
|
|
+ * @return 以输入点开始的最后一个季度Season
|
|
|
+ */
|
|
|
+ private static Season getLastSeason(LocalDateTime now) {
|
|
|
+ return new Season(getConsecutiveMonthsFromSeasonEnd(now, 3));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取季度最后十二个月
|
|
|
+ *
|
|
|
+ * @param now 当前时间
|
|
|
+ * @return 以输入点开始的最后四个季度Season
|
|
|
+ */
|
|
|
+ private static List<Season> getLastFourSeason(LocalDateTime now) {
|
|
|
+ List<YearMonth> months = getConsecutiveMonthsFromSeasonEnd(now, 12);
|
|
|
+ List<Season> seasons = new ArrayList<>();
|
|
|
+ for (int i = 0; i < 4; i++) {
|
|
|
+ seasons.add(new Season(months.subList(i * 3, (i + 1) * 3)));
|
|
|
+ }
|
|
|
+ return seasons;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查并找到最近的季度结束时间
|
|
|
+ *
|
|
|
+ * @param now 当前时间
|
|
|
+ * @return 季度结束时间
|
|
|
+ * @throws IllegalArgumentException 如果在可接受的误差范围(±1天)外
|
|
|
+ */
|
|
|
+ private static LocalDateTime checkAndFindSeasonEnd(LocalDateTime now) {
|
|
|
+ // 检查当前日期
|
|
|
+ if (isSeasonEnd(now)) return now;
|
|
|
+
|
|
|
+ // 检查后一天
|
|
|
+ LocalDateTime nextDay = now.plusDays(1);
|
|
|
+ if (isSeasonEnd(nextDay)) return nextDay;
|
|
|
+
|
|
|
+ // 检查前一天
|
|
|
+ LocalDateTime previousDay = now.minusDays(1);
|
|
|
+ if (isSeasonEnd(previousDay)) return previousDay;
|
|
|
+
|
|
|
+ throw new IllegalArgumentException("在可接受的误差范围(±1天)外");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查指定日期是否为季度结束日
|
|
|
+ *
|
|
|
+ * @param dateTime 日期时间
|
|
|
+ * @return 如果是季度结束日返回true,否则返回false
|
|
|
+ */
|
|
|
+ private static boolean isSeasonEnd(LocalDateTime dateTime) {
|
|
|
+ MonthDay monthDay = MonthDay.from(dateTime);
|
|
|
+ return SEASON_ENDS.contains(monthDay);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分别计算输入1和输入2的合计值并做除法
|
|
|
+ * @return 除法的结果,当输入2为0时返回空
|
|
|
+ */
|
|
|
+ private static BigDecimal sumAndDivide(List<BigDecimal> valueList1, List<BigDecimal> valueList2) {
|
|
|
+ BigDecimal value1 = valueList1.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ BigDecimal value2 = valueList2.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ if (value2.equals(BigDecimal.ZERO)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return value1.divide(value2, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查输入1的所有值是否都大于输入2同位置的值
|
|
|
+ */
|
|
|
+ private static boolean isAllUpper(List<BigDecimal> valueList1, List<BigDecimal> valueList2) {
|
|
|
+ return IntStream.range(0, valueList1.size()).allMatch(index -> valueList1.get(index).compareTo(valueList2.get(index)) > 0
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|