DeliveryFeeServiceImplTest.java 7.75 KB
package com.diligrp.rider.service.impl;

import com.diligrp.rider.dto.DeliveryPricingConfigDTO;
import com.diligrp.rider.dto.DeliveryPricingRuleDTO;
import com.diligrp.rider.dto.DeliveryFeeCalcDTO;
import com.diligrp.rider.service.CityService;
import com.diligrp.rider.util.GeoUtil;
import com.diligrp.rider.vo.DeliveryFeeResultVO;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;

class DeliveryFeeServiceImplTest {

    private final DeliveryFeeServiceImpl service = new DeliveryFeeServiceImpl(mock(CityService.class));

    @Test
    void shouldApplyMinFee() {
        DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig();
        pricingConfig.getType6().setBaseSwitch(1);
        pricingConfig.getType6().setBaseFee(new BigDecimal("2.00"));
        pricingConfig.getType6().setDistanceSwitch(0);
        pricingConfig.getType6().setWeightSwitch(0);
        pricingConfig.getType6().setMinFee(new BigDecimal("5.00"));

        DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, baseCalc());

        assertEquals(new BigDecimal("2.00"), result.getMoneyBasic());
        assertEquals(new BigDecimal("5.00"), result.getTotalFee());
        assertEquals(1, result.getMinFeeApplied());
    }

    @Test
    void shouldCalculateDistanceStepFee() {
        DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig();
        pricingConfig.getType6().setDistanceBasic(new BigDecimal("1.0"));
        pricingConfig.getType6().setDistanceBasicMoney(new BigDecimal("4.0"));
        pricingConfig.getType6().setWeightSwitch(0);
        pricingConfig.getType6().setDistanceSteps(List.of(
                distanceStep("3.0", "1.0", "2.0", 0),
                distanceStep("5.0", "1.0", "3.0", 1)
        ));

        DeliveryFeeCalcDTO calc = baseCalc();
        calc.setStartLat("31.2304");
        calc.setStartLng("121.4737");
        calc.setEndLat("31.2574");
        calc.setEndLng("121.4737");

        DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc);

        BigDecimal actualDistance = BigDecimal.valueOf(GeoUtil.calcDistanceKm(31.2304, 121.4737, 31.2574, 121.4737))
                .setScale(0, RoundingMode.CEILING)
                .setScale(1);
        BigDecimal expectedDistanceFee = actualDistance.compareTo(new BigDecimal("3.0")) <= 0
                ? new BigDecimal("4.00")
                : new BigDecimal("7.00");
        assertEquals(actualDistance, result.getDistance());
        assertEquals(expectedDistanceFee, result.getMoneyDistance());
        assertEquals(result.getMoneyBasic().add(result.getMoneyDistance()), result.getTotalFee());
    }

    @Test
    void shouldCapWeightFee() {
        DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig();
        pricingConfig.getType6().setDistanceSwitch(0);
        pricingConfig.getType6().setWeightFirst(new BigDecimal("5"));
        pricingConfig.getType6().setWeightFirstFee(new BigDecimal("3"));
        pricingConfig.getType6().setWeightUnitFee(new BigDecimal("2"));
        pricingConfig.getType6().setWeightCapFee(new BigDecimal("10"));

        DeliveryFeeCalcDTO calc = baseCalc();
        calc.setWeight(new BigDecimal("10"));

        DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc);

        assertEquals(new BigDecimal("3"), result.getMoneyBasic());
        assertEquals(new BigDecimal("7.00"), result.getMoneyWeight());
        assertEquals(new BigDecimal("10.00"), result.getTotalFee());
    }

    @Test
    void shouldApplyPieceRule() {
        DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig();
        pricingConfig.getType6().setDistanceSwitch(0);
        pricingConfig.getType6().setWeightSwitch(0);
        pricingConfig.getType6().setPieceSwitch(1);
        pricingConfig.getType6().setPieceRules(List.of(
                pieceRule(1, 2, "1.00", 0),
                pieceRule(3, 5, "3.50", 1)
        ));

        DeliveryFeeCalcDTO calc = baseCalc();
        calc.setPieces(4);

        DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc);

        assertEquals(new BigDecimal("3.50"), result.getMoneyPiece());
        assertEquals("(3-5件)", result.getMoneyPieceTxt());
        assertEquals(new BigDecimal("3.50"), result.getTotalFee());
    }

    @Test
    void shouldApplyCrossDayTimeFee() {
        DeliveryPricingConfigDTO pricingConfig = defaultPricingConfig();
        pricingConfig.getType6().setDistanceSwitch(0);
        pricingConfig.getType6().setWeightSwitch(0);
        pricingConfig.getType6().setTimes(List.of(timeRule(22 * 60, 6 * 60, "2.50", 1)));

        DeliveryFeeCalcDTO calc = baseCalc();
        calc.setServiceTime(LocalDateTime.of(2026, 4, 2, 23, 30)
                .atZone(ZoneId.of("Asia/Shanghai"))
                .toEpochSecond());

        DeliveryFeeResultVO result = service.calcFeeByConfig(pricingConfig, calc);

        assertEquals(new BigDecimal("2.50"), result.getMoneyTime());
        assertEquals(new BigDecimal("2.50"), result.getTotalFee());
    }

    private DeliveryPricingConfigDTO defaultPricingConfig() {
        DeliveryPricingConfigDTO config = new DeliveryPricingConfigDTO();
        config.setType(Arrays.asList(6));
        config.setDistanceBasic(new BigDecimal("3"));
        config.setDistanceBasicTime(30);
        config.setDistanceMoreTime(10);
        config.setRiderDistance(new BigDecimal("3"));

        DeliveryPricingRuleDTO type6 = new DeliveryPricingRuleDTO();
        type6.setFeeMode(2);
        type6.setBaseSwitch(0);
        type6.setBaseFee(BigDecimal.ZERO);
        type6.setDistanceSwitch(1);
        type6.setDistanceBasic(new BigDecimal("3"));
        type6.setDistanceBasicMoney(new BigDecimal("4"));
        type6.setDistanceType(2);
        type6.setWeightSwitch(1);
        type6.setWeightFirst(new BigDecimal("5"));
        type6.setWeightFirstFee(BigDecimal.ZERO);
        type6.setWeightUnitFee(BigDecimal.ONE);
        type6.setWeightCapFee(new BigDecimal("30"));
        type6.setPieceSwitch(0);
        config.setType6(type6);
        return config;
    }

    private DeliveryFeeCalcDTO baseCalc() {
        DeliveryFeeCalcDTO calc = new DeliveryFeeCalcDTO();
        calc.setCityId(1L);
        calc.setOrderType(6);
        calc.setStartLng("121.4737");
        calc.setStartLat("31.2304");
        calc.setEndLng("121.4737");
        calc.setEndLat("31.2304");
        calc.setWeight(BigDecimal.ZERO);
        calc.setPieces(0);
        calc.setServiceTime(0L);
        return calc;
    }

    private DeliveryPricingRuleDTO.DistanceStepDTO distanceStep(String endDistance, String unitDistance, String unitFee, int order) {
        DeliveryPricingRuleDTO.DistanceStepDTO dto = new DeliveryPricingRuleDTO.DistanceStepDTO();
        dto.setEndDistance(new BigDecimal(endDistance));
        dto.setUnitDistance(new BigDecimal(unitDistance));
        dto.setUnitFee(new BigDecimal(unitFee));
        dto.setListOrder(order);
        return dto;
    }

    private DeliveryPricingRuleDTO.PieceRuleDTO pieceRule(int start, int end, String fee, int order) {
        DeliveryPricingRuleDTO.PieceRuleDTO dto = new DeliveryPricingRuleDTO.PieceRuleDTO();
        dto.setStartPiece(start);
        dto.setEndPiece(end);
        dto.setFee(new BigDecimal(fee));
        dto.setListOrder(order);
        return dto;
    }

    private DeliveryPricingRuleDTO.TimePeriodDTO timeRule(int start, int end, String fee, int isOpen) {
        DeliveryPricingRuleDTO.TimePeriodDTO dto = new DeliveryPricingRuleDTO.TimePeriodDTO();
        dto.setStart(start);
        dto.setEnd(end);
        dto.setMoney(new BigDecimal(fee));
        dto.setIsOpen(isOpen);
        return dto;
    }
}