偏远地区邮编判断调整
This commit is contained in:
@@ -27,6 +27,12 @@ public class PayBillController extends BaseController
|
||||
@Autowired
|
||||
private IPayBillService payBillService;
|
||||
|
||||
/**
|
||||
* 分页查询应付账单。
|
||||
*
|
||||
* @param query 账单号、供应商、状态、币种,空着的条件不过滤
|
||||
* @return 账单列表,不含费用明细
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(PayBill query)
|
||||
@@ -36,6 +42,12 @@ public class PayBillController extends BaseController
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询一张应付账单的明细和操作日志。
|
||||
*
|
||||
* @param billId 应付账单ID
|
||||
* @return 账单头、费用行和日志
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:query')")
|
||||
@GetMapping("/{billId}")
|
||||
public AjaxResult detail(@PathVariable Long billId)
|
||||
@@ -43,6 +55,12 @@ public class PayBillController extends BaseController
|
||||
return success(payBillService.selectPayBillDetail(billId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览本次能生成账单的订单。只查询,不重算费用,也不写账单。
|
||||
*
|
||||
* @param command 查询方式,以及订单号、计费日或提单号
|
||||
* @return 可生成订单、未找到的单号、已经入账的订单
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:query')")
|
||||
@PostMapping("/preview")
|
||||
public AjaxResult preview(@RequestBody PayBillCommand command)
|
||||
@@ -50,6 +68,12 @@ public class PayBillController extends BaseController
|
||||
return success(payBillService.preview(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成应付账单。先重算每票订单的尾程应付,再把当时的费用抄进草稿账单。
|
||||
*
|
||||
* @param command 供应商,以及按订单号、计费日或提单号划定的范围
|
||||
* @return 刚生成的草稿账单
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:add')")
|
||||
@PostMapping
|
||||
public AjaxResult generate(@RequestBody PayBillCommand command)
|
||||
@@ -57,6 +81,11 @@ public class PayBillController extends BaseController
|
||||
return success(payBillService.generate(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认草稿账单。确认后才可以登记付款。
|
||||
*
|
||||
* @param billId 应付账单ID
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:confirm')")
|
||||
@PutMapping("/confirm/{billId}")
|
||||
public AjaxResult confirm(@PathVariable Long billId)
|
||||
@@ -65,6 +94,11 @@ public class PayBillController extends BaseController
|
||||
return success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 把已确认的账单登记为已付款。已付款的账单不能直接作废。
|
||||
*
|
||||
* @param billId 应付账单ID
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:confirm')")
|
||||
@PutMapping("/paid/{billId}")
|
||||
public AjaxResult paid(@PathVariable Long billId)
|
||||
@@ -73,6 +107,11 @@ public class PayBillController extends BaseController
|
||||
return success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 作废应付账单。账单和费用行保留,只改状态并记下作废原因。
|
||||
*
|
||||
* @param command 账单ID和作废原因
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:void')")
|
||||
@PutMapping("/void")
|
||||
public AjaxResult voidBill(@RequestBody PayBillCommand command)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.bms.price.domain.PayOrderCostResult;
|
||||
import com.bms.price.domain.PayPostcodeGroup;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PaySurcharge;
|
||||
import com.bms.price.domain.PayWeightBand;
|
||||
@@ -113,6 +116,133 @@ class PayCostCalculatorTest
|
||||
assertTrue(hasFee(result, "基础运费", "1.57"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void remoteZoneUsesMainlandFreightAndItsOwnSurcharge()
|
||||
{
|
||||
PayProduct product = new PayProduct();
|
||||
product.setProductName("Evri 48H");
|
||||
product.setCurrency("GBP");
|
||||
product.setWeightBands(new ArrayList<>(List.of(
|
||||
band("0", "1", "1.51", "UK Mainland"),
|
||||
band("1", "2", "1.72", "UK Mainland"))));
|
||||
PaySurcharge light = fee(1L, "偏远附加费", "5.95", "GBP", "ZONE", null);
|
||||
light.setZoneName("Highlands& Islands");
|
||||
light.setWeightMin(BigDecimal.ZERO);
|
||||
light.setWeightMax(BigDecimal.ONE);
|
||||
PaySurcharge heavy = fee(2L, "偏远附加费", "6.20", "GBP", "ZONE", null);
|
||||
heavy.setZoneName("Highlands& Islands");
|
||||
heavy.setWeightMin(BigDecimal.ONE);
|
||||
heavy.setWeightMax(new BigDecimal("2"));
|
||||
product.setSurcharges(List.of(light, heavy));
|
||||
|
||||
PayOrderCostResult remote = PayCostCalculator.calculate(product, new BigDecimal("0.5"),
|
||||
null, null, null, "IV1 1AA", "Highlands& Islands", null, 1);
|
||||
assertTrue(hasFee(remote, "基础运费", "1.51"));
|
||||
assertTrue(hasFee(remote, "偏远附加费", "5.95"));
|
||||
assertEquals(1, remote.getFees().stream().filter(fee -> "偏远附加费".equals(fee.getFeeName())).count());
|
||||
|
||||
PayOrderCostResult mainland = PayCostCalculator.calculate(product, new BigDecimal("0.5"),
|
||||
null, null, null, "LL22 8PJ", "UK Mainland", null, 1);
|
||||
assertTrue(hasFee(mainland, "基础运费", "1.51"));
|
||||
assertTrue(mainland.getFees().stream().noneMatch(fee -> "偏远附加费".equals(fee.getFeeName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spanishPrefixChargesFirstWeightThenExtraKg()
|
||||
{
|
||||
PayProduct product = new PayProduct();
|
||||
product.setProductName("CTT 48H");
|
||||
product.setCurrency("EUR");
|
||||
product.setWeightBands(new ArrayList<>(List.of(band("0", "5", "2.05", "马德里"))));
|
||||
PaySurcharge first = fee(1L, "偏远附加费", "7.1", "EUR", "POSTCODE", "07");
|
||||
first.setZoneName("Balearic Islands");
|
||||
first.setPostcodeParam("07");
|
||||
first.setWeightMin(BigDecimal.ZERO);
|
||||
first.setWeightMax(BigDecimal.ONE);
|
||||
PaySurcharge extra = fee(2L, "偏远附加费", "4", "EUR", "EXTRA_KG", "1");
|
||||
extra.setZoneName("Balearic Islands");
|
||||
extra.setPostcodeParam("07");
|
||||
extra.setPriceUnit("EUR/KG");
|
||||
product.setSurcharges(List.of(first, extra));
|
||||
|
||||
PayOrderCostResult light = PayCostCalculator.calculate(product, new BigDecimal("0.8"),
|
||||
null, null, null, "07008", "马德里", null, 1);
|
||||
assertTrue(hasFee(light, "基础运费", "2.05"));
|
||||
assertTrue(hasFee(light, "偏远附加费", "7.1"));
|
||||
assertEquals(1, light.getFees().stream().filter(fee -> "偏远附加费".equals(fee.getFeeName())).count());
|
||||
|
||||
PayOrderCostResult heavy = PayCostCalculator.calculate(product, new BigDecimal("3"),
|
||||
null, null, null, "07008", "马德里", null, 1);
|
||||
assertTrue(hasFee(heavy, "基础运费", "2.05"));
|
||||
assertTrue(hasFee(heavy, "偏远附加费", "7.1"));
|
||||
assertTrue(hasFee(heavy, "偏远附加费", "8"));
|
||||
|
||||
PayOrderCostResult mainland = PayCostCalculator.calculate(product, new BigDecimal("3"),
|
||||
null, null, null, "28001", "马德里", null, 1);
|
||||
assertTrue(mainland.getFees().stream().noneMatch(fee -> "偏远附加费".equals(fee.getFeeName())));
|
||||
assertTrue(PaySurchargeRules.matchesPostcode("07,35,38,51,52,9", "35001"));
|
||||
assertTrue(PaySurchargeRules.matchesPostcode("07,35,38,51,52,9", "9000"));
|
||||
assertFalse(PaySurchargeRules.matchesPostcode("07,35,38,51,52,9", "28001"));
|
||||
assertTrue(PaySurchargeRules.matchesPostcode("IV,HS,KA27-28", "KA27 8AA"));
|
||||
assertEquals("07", PaySurchargeRules.remotePrefixes("Balearic Islands 07邮编开头"));
|
||||
assertEquals("35,38", PaySurchargeRules.remotePrefixes("Canary Islands 35、38邮编开头"));
|
||||
assertEquals("9", PaySurchargeRules.remotePrefixes("Portuguese Island 9邮编开头"));
|
||||
assertEquals("", PaySurchargeRules.remotePrefixes("岛屿邮编地区"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void productPostcodeBookChoosesTheMatchingRemoteFee()
|
||||
{
|
||||
List<PayRemoteBook.Hit> hits = List.of(new PayRemoteBook.Hit("偏远附加费", new BigDecimal("1.80"), "", "Highlands"));
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(royalMail(), new BigDecimal("4"),
|
||||
null, null, null, "IV1 1AA", null, null, 1, hits);
|
||||
assertTrue(hasFee(result, "偏远地区附加费", "1.80"));
|
||||
assertTrue(result.getFees().stream().noneMatch(fee -> "偏远地区附加费".equals(fee.getFeeName())
|
||||
&& fee.getAmount().compareTo(new BigDecimal("1.00")) == 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkedRemoteBookWithoutAHitDoesNotUseThePriceCardList()
|
||||
{
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(royalMail(), new BigDecimal("4"),
|
||||
null, null, null, "IV1 1AA", null, null, 1, List.of());
|
||||
assertTrue(result.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("偏远")));
|
||||
assertTrue(hasFee(result, "基础运费", "1.62"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void remoteBookChargesAManualIslandFeeAndPerKg()
|
||||
{
|
||||
PayProduct product = new PayProduct();
|
||||
product.setProductName("德国DPD挂号-国际件");
|
||||
product.setCurrency("EUR");
|
||||
product.setWeightBands(new ArrayList<>(List.of(band("0", "5", "3.20", ""))));
|
||||
PaySurcharge island = fee(31L, "岛屿附加费", "12", "EUR", "MANUAL", null);
|
||||
PaySurcharge perKg = fee(32L, "偏远附加费", "0.35", "EUR", "MANUAL", null);
|
||||
perKg.setPriceUnit("EUR/KG");
|
||||
product.setSurcharges(List.of(island, perKg));
|
||||
List<PayRemoteBook.Hit> hits = List.of(new PayRemoteBook.Hit("", null, "", "岛屿"));
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(product, new BigDecimal("2"),
|
||||
null, null, null, "1234", null, null, 1, hits);
|
||||
assertTrue(hasFee(result, "岛屿附加费", "12"));
|
||||
assertTrue(hasFee(result, "偏远附加费", "0.70"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void remoteHitsFollowTheProductGroupAndCountry()
|
||||
{
|
||||
PayPostcodeGroup germany = group(44L, "西班牙GLS", "REMOTE", "", "DE");
|
||||
PayPostcodeGroup france = group(45L, "西班牙GLS", "REMOTE", "", "FR");
|
||||
PayPostcodeLine berlin = line(44L, "10115");
|
||||
PayPostcodeLine paris = line(45L, "75001");
|
||||
List<PayRemoteBook.Hit> de = PayRemoteBook.hits(List.of(germany, france), List.of(berlin, paris), "10115", "DE", "德国");
|
||||
assertEquals(1, de.size());
|
||||
List<PayRemoteBook.Hit> none = PayRemoteBook.hits(List.of(germany, france), List.of(berlin, paris), "28001", "ES", "西班牙");
|
||||
assertTrue(none.isEmpty());
|
||||
PayPostcodeGroup delivery = group(42L, "西班牙CTT", "", "", "ES");
|
||||
assertTrue(PayRemoteBook.hits(List.of(delivery), List.of(line(42L, "28001")), "28001", "ES", "西班牙") == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void severalZonesCannotBePricedFromTheOrder()
|
||||
{
|
||||
@@ -192,6 +322,26 @@ class PayCostCalculatorTest
|
||||
return band;
|
||||
}
|
||||
|
||||
private static PayPostcodeGroup group(Long id, String quoteSheet, String coverage, String feeName, String country)
|
||||
{
|
||||
PayPostcodeGroup group = new PayPostcodeGroup();
|
||||
group.setGroupId(id);
|
||||
group.setQuoteSheet(quoteSheet);
|
||||
group.setCoverage(coverage);
|
||||
group.setFeeName(feeName);
|
||||
group.setCountryCode(country);
|
||||
group.setConfigKind("ZONE");
|
||||
return group;
|
||||
}
|
||||
|
||||
private static PayPostcodeLine line(Long groupId, String postcode)
|
||||
{
|
||||
PayPostcodeLine line = new PayPostcodeLine();
|
||||
line.setGroupId(groupId);
|
||||
line.setPostcode(postcode);
|
||||
return line;
|
||||
}
|
||||
|
||||
private static PaySurcharge fee(Long id, String name, String amount, String currency, String rule, String param)
|
||||
{
|
||||
PaySurcharge fee = new PaySurcharge();
|
||||
|
||||
@@ -204,7 +204,7 @@ class PayWorkbookParserTest
|
||||
@Test
|
||||
void dictMatchReadsNamedSheetAndSkipsOthers() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/Evri 48H.xlsx");
|
||||
Path file = priceCard("Evri 48H.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "Evri 48H",
|
||||
List.of("Evri 48H", "DHL Paket"));
|
||||
@@ -213,6 +213,14 @@ class PayWorkbookParserTest
|
||||
assertEquals("Evri 48H", evri.getDictValue());
|
||||
assertEquals("Evri 48H", evri.getSheetName());
|
||||
assertFalse(evri.getWeightBands().isEmpty());
|
||||
assertTrue(evri.getWeightBands().stream().allMatch(band -> "派送费".equals(band.getChargeName())));
|
||||
assertTrue(evri.getWeightBands().stream().noneMatch(band -> bandText(band).contains("偏远") || bandText(band).contains("Highlands")));
|
||||
assertTrue(evri.getWeightBands().stream().anyMatch(band -> decimal(band, "1.51") && band.getZoneName().contains("Mainland")));
|
||||
PaySurcharge highlands = evri.getSurcharges().stream()
|
||||
.filter(fee -> "偏远附加费".equals(fee.getFeeName()) && fee.getZoneName().contains("Highlands") && decimalFee(fee, "5.95"))
|
||||
.findFirst().orElseThrow();
|
||||
assertEquals("ZONE", highlands.getCalcRule());
|
||||
assertTrue(highlands.getWeightMax() != null && highlands.getWeightMax().compareTo(java.math.BigDecimal.ONE) == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -280,7 +288,7 @@ class PayWorkbookParserTest
|
||||
@Test
|
||||
void royalMailReadsRemoteAreaTableAndSkipsRemarks() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/英国RM.xlsx");
|
||||
Path file = priceCard("英国RM.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "英国RM", List.of("英国RM"));
|
||||
assertEquals(1, preview.getProducts().size());
|
||||
@@ -318,6 +326,21 @@ class PayWorkbookParserTest
|
||||
assertTrue(rm.getExtraLines().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void royalMail48hRemoteRowsMatchPostcodePrefixes() throws Exception
|
||||
{
|
||||
Path file = priceCard("Royal Mail 48H.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayProduct rm = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "Royal Mail 48H",
|
||||
List.of("Royal Mail 48H")).getProducts().get(0);
|
||||
List<PaySurcharge> remote = rm.getSurcharges().stream().filter(fee -> fee.getFeeName() != null && fee.getFeeName().contains("偏远")).toList();
|
||||
assertFalse(remote.isEmpty(), rm.getSurcharges().stream().map(fee -> fee.getFeeName() + "/" + fee.getCalcRule() + "/" + fee.getCalcParam()).toList().toString());
|
||||
assertTrue(remote.stream().allMatch(fee -> "POSTCODE".equals(fee.getCalcRule())),
|
||||
remote.stream().map(fee -> fee.getFeeName() + "/" + fee.getCalcParam() + "/" + fee.getFeeDesc()).toList().toString());
|
||||
assertTrue(remote.stream().anyMatch(fee -> fee.getCalcParam() != null && fee.getCalcParam().contains("IV") && !fee.getCalcParam().contains("邮编")));
|
||||
assertTrue(rm.getWeightBands().stream().noneMatch(band -> bandText(band).contains("偏远")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void evriOtherFeesKeepOneRowPerRegion() throws Exception
|
||||
{
|
||||
@@ -418,7 +441,7 @@ class PayWorkbookParserTest
|
||||
@Test
|
||||
void ctt48hFileKeepsSurchargeTablesOutOfWeightBands() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/CTT 48H.xlsx");
|
||||
Path file = priceCard("CTT 48H.xlsx");
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "CTT 48H",
|
||||
List.of("CTT 48H"));
|
||||
PayProduct ctt = preview.getProducts().get(0);
|
||||
@@ -431,8 +454,17 @@ class PayWorkbookParserTest
|
||||
.filter(fee -> "偏远附加费".equals(fee.getFeeName()) && fee.getZoneName().contains("Balearic") && decimalFee(fee, "7.1"))
|
||||
.findFirst().orElseThrow();
|
||||
assertTrue(balearic.getFeeDesc().contains("首重"));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> "偏远附加费".equals(fee.getFeeName())
|
||||
&& fee.getZoneName().contains("Canary") && decimalFee(fee, "4.5") && fee.getChargeText().contains("KG")));
|
||||
assertEquals("POSTCODE", balearic.getCalcRule());
|
||||
assertTrue(balearic.getCalcParam() != null && balearic.getCalcParam().contains("07"));
|
||||
assertTrue(balearic.getWeightMax() != null && balearic.getWeightMax().compareTo(new java.math.BigDecimal("1")) == 0);
|
||||
PaySurcharge canary = ctt.getSurcharges().stream()
|
||||
.filter(fee -> "偏远附加费".equals(fee.getFeeName()) && fee.getZoneName().contains("Canary") && decimalFee(fee, "4.5")
|
||||
&& fee.getChargeText().contains("KG"))
|
||||
.findFirst().orElseThrow();
|
||||
assertEquals("EXTRA_KG", canary.getCalcRule());
|
||||
assertEquals("1", canary.getCalcParam());
|
||||
assertTrue(canary.getPostcodeParam() != null && canary.getPostcodeParam().contains("35") && canary.getPostcodeParam().contains("38"));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("额外线路运输费") && "MANUAL".equals(fee.getCalcRule())));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("额外线路运输费") && decimalFee(fee, "0.09")));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("额外线路运输费")
|
||||
&& fee.getFeeDesc().contains("800公里")));
|
||||
@@ -442,7 +474,7 @@ class PayWorkbookParserTest
|
||||
@Test
|
||||
void spainCttMainlandReadsBillableConcepts() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/西班牙CTT带电专线(主要地区).xlsx");
|
||||
Path file = priceCard("西班牙CTT带电专线(主要地区).xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(),
|
||||
"西班牙CTT带电专线(主要地区)", List.of("西班牙CTT带电专线(主要地区)"));
|
||||
@@ -711,6 +743,16 @@ class PayWorkbookParserTest
|
||||
return fee.getAmount() != null && fee.getAmount().compareTo(new java.math.BigDecimal(amount)) == 0;
|
||||
}
|
||||
|
||||
private static Path priceCard(String name)
|
||||
{
|
||||
Path current = Path.of("D:/A-payment").resolve(name);
|
||||
if (Files.exists(current))
|
||||
{
|
||||
return current;
|
||||
}
|
||||
return Path.of("D:/payment").resolve(name);
|
||||
}
|
||||
|
||||
private String bandText(PayWeightBand band)
|
||||
{
|
||||
return (band.getChargeName() == null ? "" : band.getChargeName()) + " "
|
||||
|
||||
Reference in New Issue
Block a user