package com.nq.utils; import com.nq.utils.stock.GetStayDays; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.Date; public final class StayFeeUtil { private static final BigDecimal WAN_UNIT = new BigDecimal("10000"); public static final LocalTime CHARGE_TIME = LocalTime.of(14, 50); private StayFeeUtil() { } /** * 递延费计费基数:不足1万按1万;超过1万按每1万向上取整(如5999→10000,35999→40000)。 */ public static BigDecimal calcBillingMarketValue(BigDecimal marketValue) { BigDecimal mv = marketValue == null ? BigDecimal.ZERO : marketValue; if (mv.compareTo(BigDecimal.ZERO) <= 0) { return WAN_UNIT; } return mv.divide(WAN_UNIT, 0, RoundingMode.CEILING).multiply(WAN_UNIT); } public static BigDecimal calcDailyStayFee(BigDecimal marketValue, BigDecimal stayFeeRate) { BigDecimal base = calcBillingMarketValue(marketValue); BigDecimal rate = stayFeeRate == null ? BigDecimal.ZERO : stayFeeRate; return base.multiply(rate).setScale(2, RoundingMode.HALF_UP); } public static LocalDate toLocalDate(Date date) { if (date == null) { return LocalDate.now(); } return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } /** 自然日:买入日不计,自次日起每个自然日计 1 天(含周末、法定假日本身) */ public static int getCalendarDaysSinceBuy(Date buyOrderTime) { LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime)); LocalDate today = LocalDate.now(); if (!buy.isBefore(today)) { return 0; } return (int) ChronoUnit.DAYS.between(buy, today); } /** 交易日:买入日不计,仅统计非周末且非法定假日本身的工作日 */ public static int getTradingDaysSinceBuy(Date buyOrderTime) { LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime)); LocalDate today = LocalDate.now(); if (!buy.isBefore(today)) { return 0; } int count = 0; LocalDate d = buy.plusDays(1); while (!d.isAfter(today)) { if (TradingDayUtil.isTradingDay(d)) { count++; } d = d.plusDays(1); } return count; } /** * 应计费天数(买入日不计): * 节假日休市开 → 仅交易日;关 → 每个自然日(不管节假日)。 */ public static int getChargeableDaysSinceBuy(Date buyOrderTime, boolean respectHoliday) { return respectHoliday ? getTradingDaysSinceBuy(buyOrderTime) : getCalendarDaysSinceBuy(buyOrderTime); } /** * 本次定时任务应扣天数(最多 1 天,不补扣历史): * 开 → 仅交易日扣 1 天;关 → 每个自然日扣 1 天(节假日也扣,不补扣周末/假日前遗漏的天数)。 */ public static int getDaysToChargeThisRun(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) { int chargeableDays = getChargeableDaysSinceBuy(buyOrderTime, respectHoliday); int billed = orderStayDays == null ? 0 : orderStayDays; if (chargeableDays < 1 || billed >= chargeableDays) { return 0; } if (respectHoliday && !TradingDayUtil.isTradingDay(LocalDate.now())) { return 0; } return 1; } public static boolean isEligibleForCharge(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) { return getDaysToChargeThisRun(buyOrderTime, orderStayDays, respectHoliday) > 0; } public static long getNextStayFeeTimestamp(Date buyOrderTime, Integer orderStayDays, boolean respectHoliday) { LocalDate buy = toLocalDate(GetStayDays.getBeginDate(buyOrderTime)); LocalDate today = LocalDate.now(); LocalDateTime now = LocalDateTime.now(); int chargeableDays = getChargeableDaysSinceBuy(buyOrderTime, respectHoliday); int billed = orderStayDays == null ? 0 : orderStayDays; LocalDate targetDate; if (respectHoliday) { if (chargeableDays < 1) { targetDate = firstTradingDayOnOrAfter(buy.plusDays(1)); } else if (billed < chargeableDays && TradingDayUtil.isTradingDay(today) && now.toLocalTime().isBefore(CHARGE_TIME)) { targetDate = today; } else { targetDate = firstTradingDayOnOrAfter(today.plusDays(1)); } } else { if (chargeableDays < 1) { targetDate = buy.plusDays(1); } else if (billed < chargeableDays && now.toLocalTime().isBefore(CHARGE_TIME)) { targetDate = today; } else { targetDate = today.plusDays(1); } } LocalDateTime next = LocalDateTime.of(targetDate, CHARGE_TIME); return next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); } public static long getStayFeeCountdownSec(long nextStayFeeTimestamp) { long diff = nextStayFeeTimestamp - System.currentTimeMillis(); return diff <= 0 ? 0 : diff / 1000; } /** 节假日休市开:非交易日不跑递延费任务 */ public static boolean shouldSkipStayFeeTaskToday(boolean respectHoliday) { return respectHoliday && !TradingDayUtil.isTradingDay(LocalDate.now()); } private static LocalDate firstTradingDayOnOrAfter(LocalDate from) { LocalDate d = from; while (!TradingDayUtil.isTradingDay(d)) { d = d.plusDays(1); } return d; } }