应付配置增加附加费配置,应付账单新增核销、对账、凭证、审核功能

This commit is contained in:
liuminghui
2026-11-12 18:20:32 +08:00
parent 1ba431540d
commit 6675808dfc
55 changed files with 5898 additions and 185 deletions
@@ -0,0 +1,172 @@
package com.bms.price.pay;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import com.bms.price.domain.PayOrderCostResult;
import com.bms.price.domain.PayProduct;
import com.bms.price.domain.PaySurcharge;
import com.bms.price.domain.PaySurchargeRule;
import com.bms.price.domain.PayWeightBand;
/** 用 DE-iMile 价卡和附加费配置,按测试订单的重量、尺寸、日期测算。 */
class PaySurchargeConfigCalcTest
{
@Test
void lightOctoberOrderChargesFreightAndCarbonOnly()
{
PayOrderCostResult result = PayCostCalculator.calculate(imile(), new BigDecimal("0.451"),
new BigDecimal("35.70"), new BigDecimal("32.80"), new BigDecimal("4.00"),
null, null, day(2026, 10, 17), 8);
assertTrue(hasFee(result, "基础运费", "2.99"));
assertTrue(hasFee(result, "CO₂附加费", "0.03"));
assertTrue(result.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("超尺寸")));
assertTrue(result.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("旺季")));
assertEquals(new BigDecimal("3.02"), result.getPayTotal());
}
@Test
void heavierOctoberOrderUsesTheNextWeightBand()
{
PayOrderCostResult result = PayCostCalculator.calculate(imile(), new BigDecimal("1.246"),
new BigDecimal("41.10"), new BigDecimal("34.20"), new BigDecimal("6.10"),
null, null, day(2026, 10, 17), 5);
assertTrue(hasFee(result, "基础运费", "3.12"));
assertTrue(hasFee(result, "CO₂附加费", "0.03"));
assertEquals(new BigDecimal("3.15"), result.getPayTotal());
}
@Test
void oversizeAndPeakSeasonUseTheConfiguredRules()
{
PayOrderCostResult oversize = PayCostCalculator.calculate(imile(), new BigDecimal("1.246"),
new BigDecimal("130"), new BigDecimal("70"), new BigDecimal("40"),
null, null, day(2026, 11, 12), 5);
assertTrue(hasFee(oversize, "基础运费", "3.12"));
assertTrue(hasFee(oversize, "CO₂附加费", "0.03"));
assertTrue(hasFee(oversize, "异形件/超尺寸处理费", "3.60"));
assertTrue(hasFee(oversize, "旺季附加费", "0.19"));
assertEquals(new BigDecimal("6.94"), oversize.getPayTotal());
PayOrderCostResult peak = PayCostCalculator.calculate(dhl(), new BigDecimal("0.664"),
new BigDecimal("25.50"), new BigDecimal("18.90"), new BigDecimal("7.80"),
null, null, day(2026, 11, 4), 11);
assertTrue(hasFee(peak, "基础运费", "3.13"));
assertTrue(hasFee(peak, "燃油附加费", "0.15"));
assertTrue(hasFee(peak, "旺季附加费", "0.19"));
assertTrue(peak.getFees().stream().noneMatch(fee -> "高峰旺季附加费".equals(fee.getFeeName())));
assertTrue(peak.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("超尺寸")));
}
private static boolean hasFee(PayOrderCostResult result, String name, String amount)
{
return result.getFees().stream().anyMatch(fee -> name.equals(fee.getFeeName())
&& fee.getAmount().compareTo(new BigDecimal(amount)) == 0);
}
private static Date day(int year, int month, int day)
{
return Date.from(LocalDate.of(year, month, day).atStartOfDay(ZoneId.systemDefault()).toInstant());
}
private static PayProduct imile()
{
PayProduct product = new PayProduct();
product.setProductName("DE-iMile");
product.setCurrency("EUR");
product.setWeightBands(List.of(
band("0", "1", "2.99"),
band("1", "2", "3.12")));
product.setSurcharges(List.of(
fee("CO₂附加费", "0.03", "ALWAYS"),
fee("异形件/超尺寸处理费", "3.60", "SIZE"),
fee("旺季附加费", "0.19", "ALWAYS")));
product.setBillingRules(List.of(
always(null, null, null),
always("旺季附加费", "11-01", "12-31"),
sizeBox()));
return product;
}
private static PayProduct dhl()
{
PayProduct product = new PayProduct();
product.setProductName("德国DHL");
product.setCurrency("EUR");
product.setWeightBands(List.of(band("0", "1", "3.13")));
product.setSurcharges(List.of(
fee("燃油附加费", "0.15", "ALWAYS"),
fee("超尺寸超重费", "14.00", "SIZE"),
fee("旺季附加费", "0.19", "ALWAYS"),
fee("高峰旺季附加费", "0.50", "ALWAYS")));
product.setBillingRules(List.of(
always(null, null, null),
always("旺季附加费", "11-01", "12-31"),
always("高峰旺季附加费", "11-24", "12-07"),
sizeBox()));
return product;
}
private static PaySurchargeRule always(String feeName, String start, String end)
{
PaySurchargeRule rule = new PaySurchargeRule();
rule.setCalcRule("ALWAYS");
rule.setEnabled("1");
Map<String, Object> detail = new LinkedHashMap<>();
if (feeName != null)
{
detail.put("feeName", feeName);
detail.put("periodStart", start);
detail.put("periodEnd", end);
}
rule.setDetail(detail);
return rule;
}
private static PaySurchargeRule sizeBox()
{
PaySurchargeRule rule = new PaySurchargeRule();
rule.setCalcRule("SIZE");
rule.setEnabled("1");
Map<String, Object> detail = new LinkedHashMap<>();
detail.put("matchMode", "ANY");
detail.put("boxMode", "OVER");
detail.put("boxLength", new BigDecimal("120"));
detail.put("boxWidth", new BigDecimal("60"));
detail.put("boxHeight", new BigDecimal("60"));
rule.setDetail(detail);
return rule;
}
private static PayWeightBand band(String min, String max, String price)
{
PayWeightBand band = new PayWeightBand();
band.setWeightMin(new BigDecimal(min));
band.setWeightMax(new BigDecimal(max));
band.setWeightLabel(min + "-" + max);
band.setPrice(new BigDecimal(price));
band.setPriceUnit("EUR/ITEM");
return band;
}
private static PaySurcharge fee(String name, String amount, String rule)
{
PaySurcharge fee = new PaySurcharge();
fee.setFeeName(name);
fee.setAmount(new BigDecimal(amount));
fee.setCurrency("EUR");
fee.setCalcRule(rule);
fee.setChargeText(amount + " EUR/ITEM");
return fee;
}
}