| | |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.web.domain.AjaxResult; |
| | | import com.ruoyi.production.dto.UserAccountDto; |
| | | import com.ruoyi.production.dto.UserProductionAccountingDto; |
| | | import com.ruoyi.production.bean.dto.UserAccountDto; |
| | | import com.ruoyi.production.bean.dto.UserProductionAccountingDto; |
| | | import com.ruoyi.production.service.SalesLedgerProductionAccountingService; |
| | | import com.ruoyi.project.system.domain.SysDept; |
| | | import com.ruoyi.project.system.domain.SysUser; |
| | | import com.ruoyi.project.system.mapper.SysDeptMapper; |
| | | import com.ruoyi.project.system.mapper.SysUserDeptMapper; |
| | | import com.ruoyi.project.system.mapper.SysUserMapper; |
| | | import com.ruoyi.staff.controller.TaxCalculator; |
| | | import com.ruoyi.staff.mapper.SchemeInsuranceDetailMapper; |
| | | import com.ruoyi.staff.mapper.StaffOnJobMapper; |
| | | import com.ruoyi.staff.pojo.SchemeApplicableStaff; |
| | | import com.ruoyi.staff.mapper.SchemeApplicableStaffMapper; |
| | | import com.ruoyi.staff.pojo.SchemeInsuranceDetail; |
| | | import com.ruoyi.staff.pojo.StaffOnJob; |
| | | import com.ruoyi.staff.mapper.*; |
| | | import com.ruoyi.staff.pojo.*; |
| | | import com.ruoyi.staff.service.SchemeApplicableStaffService; |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import jakarta.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | | import java.util.*; |
| | | import java.math.RoundingMode; |
| | | import java.time.YearMonth; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | |
| | | * @since 2026-03-05 11:50:17 |
| | | */ |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class SchemeApplicableStaffServiceImpl extends ServiceImpl<SchemeApplicableStaffMapper, SchemeApplicableStaff> implements SchemeApplicableStaffService { |
| | | |
| | | @Autowired |
| | | private SchemeApplicableStaffMapper schemeApplicableStaffMapper; |
| | | |
| | | @Autowired |
| | | private SchemeInsuranceDetailMapper schemeInsuranceDetailMapper; |
| | | |
| | | @Autowired |
| | | private SysUserDeptMapper sysUserDeptMapper; |
| | | |
| | | @Autowired |
| | | private SysUserMapper sysUserMapper; |
| | | |
| | | @Autowired |
| | | private SysDeptMapper sysDeptMapper; |
| | | |
| | | @Autowired |
| | | private StaffOnJobMapper staffOnJobMapper; |
| | | |
| | | @Autowired |
| | | private SalesLedgerProductionAccountingService salesLedgerProductionAccountingService; |
| | | private final SchemeApplicableStaffMapper schemeApplicableStaffMapper; |
| | | private final SchemeInsuranceDetailMapper schemeInsuranceDetailMapper; |
| | | private final SysUserMapper sysUserMapper; |
| | | private final SysDeptMapper sysDeptMapper; |
| | | private final StaffOnJobMapper staffOnJobMapper; |
| | | private final PersonalShiftMapper personalShiftMapper; |
| | | private final PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper; |
| | | private final SalesLedgerProductionAccountingService salesLedgerProductionAccountingService; |
| | | private final SubsidyConfigurationMapper subsidyConfigurationMapper; |
| | | private final StaffSalaryMainMapper staffSalaryMainMapper; |
| | | private final StaffSalaryDetailMapper staffSalaryDetailMapper; |
| | | |
| | | |
| | | @Override |
| | |
| | | schemeApplicableStaffLambdaQueryWrapper.like(SchemeApplicableStaff::getTitle, schemeApplicableStaff.getTitle()); |
| | | } |
| | | } |
| | | schemeApplicableStaffLambdaQueryWrapper.orderByDesc(SchemeApplicableStaff::getId); |
| | | Page<SchemeApplicableStaff> page1 = schemeApplicableStaffMapper.selectPage(page, schemeApplicableStaffLambdaQueryWrapper); |
| | | List<Long> collect = page1.getRecords().stream().map(SchemeApplicableStaff::getId).collect(Collectors.toList()); |
| | | if(CollectionUtils.isEmpty(collect)){ |
| | |
| | | BigDecimal basicSalary = new BigDecimal("0.00"); |
| | | map.put("fundPersonal", fundPersonal); // 公积金 |
| | | map.put("socialPersonal", socialPersonal); // 社保金额 |
| | | map.put("socialSupplementAmount", BigDecimal.ZERO); // 社保补缴金额 |
| | | map.put("basicSalary", basicSalary); // 基本工资 |
| | | // 个税金额 |
| | | BigDecimal salaryTax = new BigDecimal("0.00"); |
| | |
| | | // 实发工资 |
| | | BigDecimal netSalary = new BigDecimal("0.00"); |
| | | map.put("netSalary", netSalary); |
| | | // 调用基本工资 |
| | | // 根据排班数据计算白班天数和夜班天数 |
| | | BigDecimal dayDays = new BigDecimal("0.00"); |
| | | BigDecimal nightDays = new BigDecimal("0.00"); |
| | | |
| | | // 查询当月排班记录 |
| | | List<PersonalShift> shiftList = personalShiftMapper.selectList(new LambdaQueryWrapper<PersonalShift>() |
| | | .eq(PersonalShift::getStaffOnJobId, staffId) |
| | | .like(PersonalShift::getWorkTime, date)); |
| | | |
| | | if(!CollectionUtils.isEmpty(shiftList)){ |
| | | // 收集所有班次配置ID |
| | | List<Integer> configIds = shiftList.stream() |
| | | .map(PersonalShift::getPersonalAttendanceLocationConfigId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if(!CollectionUtils.isEmpty(configIds)){ |
| | | // 查询班次配置信息 |
| | | List<PersonalAttendanceLocationConfig> configList = personalAttendanceLocationConfigMapper.selectList( |
| | | new LambdaQueryWrapper<PersonalAttendanceLocationConfig>() |
| | | .in(PersonalAttendanceLocationConfig::getId, configIds)); |
| | | |
| | | // 构建班次配置映射 |
| | | Map<Integer, String> configMap = configList.stream() |
| | | .collect(Collectors.toMap( |
| | | PersonalAttendanceLocationConfig::getId, |
| | | PersonalAttendanceLocationConfig::getShift, |
| | | (a, b) -> a)); |
| | | |
| | | // 统计白班和夜班天数 |
| | | for (PersonalShift shift : shiftList) { |
| | | Integer configId = shift.getPersonalAttendanceLocationConfigId(); |
| | | String shiftName = configMap.get(configId); |
| | | if(shiftName != null){ |
| | | // 根据班次名称判断:包含"白"或"日"为白班,包含"夜"为夜班 |
| | | if(shiftName.contains("白") || shiftName.contains("日")){ |
| | | dayDays = dayDays.add(BigDecimal.ONE); |
| | | } else if(shiftName.contains("夜")){ |
| | | nightDays = nightDays.add(BigDecimal.ONE); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | map.put("dayDays", dayDays); // 白班天数 |
| | | map.put("nightDays", nightDays); // 夜班天数 |
| | | |
| | | // 查询餐补和夜班补贴 |
| | | // 计算餐补(回族特有)和夜班补助 |
| | | BigDecimal mealAmount = new BigDecimal("0.00"); // 餐补 |
| | | BigDecimal nightAmount = new BigDecimal("0.00"); // 夜班补助 |
| | | |
| | | // 获取餐补和夜班补贴标准(从补贴配置中获取) |
| | | SubsidyConfiguration subsidyConfig = subsidyConfigurationMapper.selectOne(null); |
| | | BigDecimal mealStandard = new BigDecimal("0.00"); // 餐补标准 |
| | | BigDecimal nightStandard = new BigDecimal("0.00"); // 夜班补贴标准 |
| | | if(subsidyConfig != null){ |
| | | mealStandard = subsidyConfig.getMealAmount() != null ? subsidyConfig.getMealAmount() : new BigDecimal("0.00"); |
| | | nightStandard = subsidyConfig.getNightAmount() != null ? subsidyConfig.getNightAmount() : new BigDecimal("0.00"); |
| | | } |
| | | |
| | | // 餐补:民族为回族特有,计算方式为(白班天数+夜班天数)*餐补标准 |
| | | StaffOnJob staffOnJobDto = staffOnJobMapper.selectById(staffId); |
| | | if(staffOnJobDto == null){ |
| | | return; |
| | | } |
| | | |
| | | String nation = staffOnJobDto.getNation(); |
| | | if("回族".equals(nation)){ |
| | | mealAmount = dayDays.add(nightDays).multiply(mealStandard); |
| | | } |
| | | |
| | | // 夜班补助:夜班天数*夜班补贴标准 |
| | | nightAmount = nightDays.multiply(nightStandard); |
| | | |
| | | map.put("mealAmount", mealAmount); // 餐补 |
| | | map.put("nightAmount", nightAmount); // 夜班补助 |
| | | |
| | | // 调用基本工资 |
| | | basicSalary = staffOnJobDto.getBasicSalary(); |
| | | map.put("basicSalary", basicSalary); |
| | | // 应发工资 |
| | | map.put("grossSalary", basicSalary); |
| | | // 应发工资(基本工资+餐补+夜班补助+其他收入) |
| | | grossSalary = basicSalary.add(mealAmount).add(nightAmount).add(otherIncome); |
| | | map.put("grossSalary", grossSalary); |
| | | // 实发工资初始值 |
| | | netSalary = grossSalary; |
| | | map.put("netSalary", netSalary); |
| | | // 个税金额(无社保版) |
| | | BigDecimal bigDecimal = TaxCalculator.calculateMonthlyTax(basicSalary, socialPersonal, fundPersonal); |
| | | map.put("salaryTax", bigDecimal); |
| | |
| | | if (CollectionUtils.isEmpty(schemeList)) { |
| | | return; // 无匹配方案,返回空列表 |
| | | } |
| | | // 3. 为每个方案关联明细列表 |
| | | for (SchemeApplicableStaff scheme : schemeList) { |
| | | List<SchemeInsuranceDetail> detailList = schemeApplicableStaffMapper.selectDetailBySchemeId(scheme.getId()); |
| | | // 根据明细列表计算社保缴费金额 |
| | | if(CollectionUtils.isEmpty(detailList)){ |
| | | continue; |
| | | YearMonth targetMonth = parseTargetMonth(date); |
| | | SchemeApplicableStaff currentScheme = resolveCurrentScheme(schemeList, targetMonth); |
| | | if (currentScheme == null) { |
| | | return; |
| | | } |
| | | List<SchemeInsuranceDetail> currentDetailList = defaultIfNull( |
| | | schemeApplicableStaffMapper.selectDetailBySchemeId(currentScheme.getId()) |
| | | ); |
| | | for (SchemeInsuranceDetail detail : currentDetailList) { |
| | | BigDecimal amount = calculateInsuranceAmount(staffOnJobDto, detail); |
| | | if ("公积金".equals(detail.getInsuranceType())) { |
| | | fundPersonal = fundPersonal.add(amount); |
| | | } else { |
| | | socialPersonal = socialPersonal.add(amount); |
| | | } |
| | | for (SchemeInsuranceDetail detail : detailList) { |
| | | if("公积金".equals(detail.getInsuranceType())){ |
| | | fundPersonal = fundPersonal.add(calculateByEmployeeIdType(detail.getInsuranceType(),fundPersonal, staffOnJobDto, detail)); |
| | | }else{ |
| | | socialPersonal = socialPersonal.add(calculateByEmployeeIdType(detail.getInsuranceType(),socialPersonal, staffOnJobDto, detail)); |
| | | } |
| | | } |
| | | } |
| | | BigDecimal socialSupplementAmount = calculateSocialSupplementAmount( |
| | | staffId, |
| | | staffOnJobDto, |
| | | targetMonth, |
| | | currentScheme, |
| | | currentDetailList, |
| | | socialPersonal |
| | | ); |
| | | if (socialSupplementAmount.compareTo(BigDecimal.ZERO) > 0) { |
| | | socialPersonal = socialPersonal.add(socialSupplementAmount); |
| | | } |
| | | map.put("socialPersonal", socialPersonal); |
| | | map.put("fundPersonal", fundPersonal); |
| | | map.put("socialSupplementAmount", socialSupplementAmount); |
| | | // 个税金额(社保版) |
| | | bigDecimal = TaxCalculator.calculateMonthlyTax(basicSalary, socialPersonal, fundPersonal); |
| | | map.put("salaryTax", bigDecimal); |
| | | // 应扣工资 |
| | | map.put("deductSalary", bigDecimal.add(fundPersonal).add(socialPersonal)); |
| | | // 实发工资 |
| | | map.put("netSalary", basicSalary.subtract(bigDecimal).subtract(fundPersonal).subtract(socialPersonal)); |
| | | |
| | | // 应扣工资 = 个税 + 公积金个人 + 社保个人 + 其他支出 |
| | | deductSalary = bigDecimal.add(fundPersonal).add(socialPersonal).add(otherDeduct); |
| | | map.put("deductSalary", deductSalary); |
| | | |
| | | // 实发工资 = 应发工资 - 应扣工资 |
| | | netSalary = grossSalary.subtract(deductSalary); |
| | | map.put("netSalary", netSalary); |
| | | |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 计算 |
| | | * @param type |
| | | * @param bigDecimal |
| | | * @param staffOnJobDto |
| | | * @param detail |
| | | * @return |
| | | * 计算单项保险金额 |
| | | */ |
| | | public BigDecimal calculateByEmployeeIdType(String type,BigDecimal bigDecimal, StaffOnJob staffOnJobDto,SchemeInsuranceDetail detail) { |
| | | // 判断是否调用基本工资 |
| | | if (detail.getUseBasicSalary() == 1) { |
| | | BigDecimal divide = detail.getPaymentBase().multiply(detail.getPersonalRatio()).divide(new BigDecimal("100"), 2); |
| | | bigDecimal = bigDecimal.add(divide); |
| | | }else{ |
| | | // 调用基本工资 |
| | | BigDecimal multiply = staffOnJobDto.getBasicSalary().multiply(detail.getPersonalRatio().divide(new BigDecimal("100"), 2)); |
| | | bigDecimal = bigDecimal.add(multiply); |
| | | public BigDecimal calculateInsuranceAmount(StaffOnJob staffOnJobDto, SchemeInsuranceDetail detail) { |
| | | if (staffOnJobDto == null || detail == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | bigDecimal = bigDecimal.add(detail.getPersonalFixed()); |
| | | return bigDecimal; |
| | | BigDecimal ratio = detail.getPersonalRatio() == null ? BigDecimal.ZERO : detail.getPersonalRatio(); |
| | | BigDecimal fixed = detail.getPersonalFixed() == null ? BigDecimal.ZERO : detail.getPersonalFixed(); |
| | | BigDecimal baseAmount; |
| | | if (detail.getUseBasicSalary() == 1) { |
| | | baseAmount = detail.getPaymentBase() == null ? BigDecimal.ZERO : detail.getPaymentBase(); |
| | | } else { |
| | | baseAmount = staffOnJobDto.getBasicSalary() == null ? BigDecimal.ZERO : staffOnJobDto.getBasicSalary(); |
| | | } |
| | | BigDecimal ratioAmount = baseAmount.multiply(ratio).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP); |
| | | return ratioAmount.add(fixed); |
| | | } |
| | | |
| | | private SchemeApplicableStaff resolveCurrentScheme(List<SchemeApplicableStaff> schemeList, YearMonth targetMonth) { |
| | | if (CollectionUtils.isEmpty(schemeList)) { |
| | | return null; |
| | | } |
| | | if (targetMonth == null) { |
| | | return schemeList.get(0); |
| | | } |
| | | for (SchemeApplicableStaff scheme : schemeList) { |
| | | if (scheme.getCreateTime() == null) { |
| | | continue; |
| | | } |
| | | YearMonth schemeMonth = YearMonth.from(scheme.getCreateTime()); |
| | | if (!schemeMonth.isAfter(targetMonth)) { |
| | | return scheme; |
| | | } |
| | | } |
| | | return schemeList.get(schemeList.size() - 1); |
| | | } |
| | | |
| | | /** |
| | | * 计算社保补缴金额 |
| | | * |
| | | * 业务逻辑: |
| | | * 1. 只有在社保方案设置新增的当月才计算补缴金额,其余月份返回0 |
| | | * 2. 根据最新社保比例计算当月及以后应缴纳的金额标准 |
| | | * 3. 统计当年1月到当前月已缴纳的社保总额 |
| | | * 4. 用最新标准计算的期望总额与实际已缴纳金额的差额即为补缴金额 |
| | | * |
| | | * @param staffId 员工ID |
| | | * @param staffOnJobDto 员工在职信息(用于计算社保金额) |
| | | * @param targetMonth 目标计算月份 |
| | | * @param currentScheme 当前社保方案 |
| | | * @param currentDetailList 当前社保方案明细列表 |
| | | * @param currentMonthSocialPersonal 当前月个人社保金额 |
| | | * @return 社保补缴金额(负数或0返回0) |
| | | */ |
| | | private BigDecimal calculateSocialSupplementAmount(Long staffId, |
| | | StaffOnJob staffOnJobDto, |
| | | YearMonth targetMonth, |
| | | SchemeApplicableStaff currentScheme, |
| | | List<SchemeInsuranceDetail> currentDetailList, |
| | | BigDecimal currentMonthSocialPersonal) { |
| | | // 1. 参数校验:任一必填参数为空,返回0 |
| | | if (staffId == null || staffOnJobDto == null || targetMonth == null || currentScheme == null || currentScheme.getCreateTime() == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | // 2. 判断是否为社保方案新增当月 |
| | | // 只有新增社保设置的当月才计算补缴,其余月份返回0 |
| | | YearMonth schemeMonth = YearMonth.from(currentScheme.getCreateTime()); |
| | | if (!schemeMonth.equals(targetMonth)) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | // 3. 计算最新月度社保个人金额(排除公积金) |
| | | BigDecimal latestMonthlySocial = defaultIfNull(currentDetailList).stream() |
| | | .filter(Objects::nonNull) |
| | | .filter(detail -> !"公积金".equals(detail.getInsuranceType())) // 排除公积金 |
| | | .map(detail -> calculateInsuranceAmount(staffOnJobDto, detail)) // 计算每项社保金额 |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); // 累加求和 |
| | | |
| | | // 如果最新月度社保金额为0或负数,无需补缴 |
| | | if (latestMonthlySocial.compareTo(BigDecimal.ZERO) <= 0) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | // 4. 统计当年1月到当前月之前已缴纳的社保总额 |
| | | BigDecimal paidYtdSocial = sumPaidSocialPersonalBeforeMonth(staffId, targetMonth); |
| | | |
| | | // 5. 计算按最新标准应缴纳的年度总额 |
| | | // 期望总额 = 最新月度社保 × 当前月份(1-12) |
| | | BigDecimal expectedYtdSocial = latestMonthlySocial.multiply(BigDecimal.valueOf(targetMonth.getMonthValue())); |
| | | |
| | | // 6. 计算补缴金额 |
| | | // 补缴金额 = 期望总额 - 已缴纳总额 - 当前月已计算的社保 |
| | | BigDecimal supplement = expectedYtdSocial.subtract(paidYtdSocial).subtract(defaultZero(currentMonthSocialPersonal)); |
| | | |
| | | // 7. 返回结果:负数或0返回0,正数保留两位小数 |
| | | return supplement.compareTo(BigDecimal.ZERO) > 0 ? supplement.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO; |
| | | } |
| | | |
| | | /** |
| | | * 统计员工当年1月到目标月份之前已缴纳的社保个人部分总额 |
| | | * |
| | | * 查询逻辑: |
| | | * 1. 根据目标月份获取当年年份前缀(如 "2024-") |
| | | * 2. 查询该年份下所有早于目标月份的薪资主表记录 |
| | | * 3. 根据薪资主表ID查询对应的薪资明细表 |
| | | * 4. 按员工ID筛选并累加社保个人部分金额 |
| | | * |
| | | * @param staffId 员工ID |
| | | * @param targetMonth 目标月份(统计截止到该月份之前) |
| | | * @return 已缴纳的社保个人部分总额 |
| | | */ |
| | | private BigDecimal sumPaidSocialPersonalBeforeMonth(Long staffId, YearMonth targetMonth) { |
| | | // 1. 参数校验 |
| | | if (staffId == null || targetMonth == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | // 2. 构建查询条件 |
| | | String yearPrefix = targetMonth.getYear() + "-"; // 年份前缀,如 "2024-" |
| | | String currentMonthStr = targetMonth.format(DateTimeFormatter.ofPattern("yyyy-MM")); // 目标月份字符串,如 "2024-03" |
| | | |
| | | // 3. 查询当年1月到目标月份之前的薪资主表 |
| | | List<StaffSalaryMain> salaryMainList = staffSalaryMainMapper.selectList( |
| | | new LambdaQueryWrapper<StaffSalaryMain>() |
| | | .likeRight(StaffSalaryMain::getSalaryMonth, yearPrefix) // 匹配当年所有月份 |
| | | .lt(StaffSalaryMain::getSalaryMonth, currentMonthStr) // 小于目标月份 |
| | | ); |
| | | if (CollectionUtils.isEmpty(salaryMainList)) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | // 4. 提取薪资主表ID列表 |
| | | List<Long> mainIds = salaryMainList.stream() |
| | | .map(StaffSalaryMain::getId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | if (CollectionUtils.isEmpty(mainIds)) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | // 5. 查询对应的薪资明细表 |
| | | List<StaffSalaryDetail> detailList = staffSalaryDetailMapper.selectList( |
| | | new LambdaQueryWrapper<StaffSalaryDetail>() |
| | | .in(StaffSalaryDetail::getMainId, mainIds) // 根据主表ID查询 |
| | | .eq(StaffSalaryDetail::getStaffOnJobId, staffId) // 按员工ID筛选 |
| | | ); |
| | | if (CollectionUtils.isEmpty(detailList)) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | // 6. 累加社保个人部分金额 |
| | | return detailList.stream() |
| | | .map(StaffSalaryDetail::getSocialPersonal) // 获取社保个人部分 |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); // 累加求和 |
| | | } |
| | | |
| | | private YearMonth parseTargetMonth(String date) { |
| | | if (StringUtils.isEmpty(date)) { |
| | | return null; |
| | | } |
| | | String normalized = date.length() >= 7 ? date.substring(0, 7) : date; |
| | | try { |
| | | return YearMonth.parse(normalized, DateTimeFormatter.ofPattern("yyyy-MM")); |
| | | } catch (DateTimeParseException ex) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private <T> List<T> defaultIfNull(List<T> list) { |
| | | return list == null ? Collections.emptyList() : list; |
| | | } |
| | | |
| | | private BigDecimal defaultZero(BigDecimal value) { |
| | | return value == null ? BigDecimal.ZERO : value; |
| | | } |
| | | } |