应付账单初始化
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
||||
package com.bms.web.controller.data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.common.annotation.Log;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
import com.bms.common.core.page.TableDataInfo;
|
||||
import com.bms.common.enums.BusinessType;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.price.domain.PayPostcodeGroup;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
import com.bms.price.service.IPayPostcodeService;
|
||||
|
||||
/**
|
||||
* 数据管理下的应付邮编配置。Excel 导入后按国家和附加费金额合并。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/data/payPostcode")
|
||||
public class PayPostcodeController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IPayPostcodeService payPostcodeService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('data:payPostcode:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(PayPostcodeGroup query)
|
||||
{
|
||||
startPage();
|
||||
List<PayPostcodeGroup> list = payPostcodeService.selectGroupList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('data:payPostcode:query')")
|
||||
@GetMapping("/{groupId}")
|
||||
public AjaxResult getInfo(@PathVariable Long groupId)
|
||||
{
|
||||
return success(payPostcodeService.selectGroupById(groupId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('data:payPostcode:edit')")
|
||||
@Log(title = "应付邮编配置", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody PayPostcodeGroup group)
|
||||
{
|
||||
return toAjax(payPostcodeService.updateGroup(group));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('data:payPostcode:query')")
|
||||
@GetMapping("/line")
|
||||
public TableDataInfo lines(PayPostcodeLine query)
|
||||
{
|
||||
startPage();
|
||||
List<PayPostcodeLine> list = payPostcodeService.selectLineList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('data:payPostcode:import')")
|
||||
@Log(title = "应付邮编配置", businessType = BusinessType.IMPORT)
|
||||
@PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public AjaxResult importExcel(MultipartFile file) throws Exception
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请选择 Excel 文件");
|
||||
}
|
||||
String name = file.getOriginalFilename() == null ? "" : file.getOriginalFilename().toLowerCase(Locale.ROOT);
|
||||
if (!name.endsWith(".xlsx"))
|
||||
{
|
||||
throw new ServiceException("只支持 xlsx 文件");
|
||||
}
|
||||
return success(payPostcodeService.importExcel(file.getBytes(), file.getOriginalFilename(), getUsername()));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('data:payPostcode:remove')")
|
||||
@Log(title = "应付邮编配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{groupIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] groupIds)
|
||||
{
|
||||
return toAjax(payPostcodeService.deleteByIds(groupIds));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.bms.web.controller.price;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
import com.bms.common.core.page.TableDataInfo;
|
||||
import com.bms.price.domain.PayBill;
|
||||
import com.bms.price.domain.PayBillCommand;
|
||||
import com.bms.price.service.IPayBillService;
|
||||
|
||||
/**
|
||||
* 账单管理下的应付账单。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/price/payBill")
|
||||
public class PayBillController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IPayBillService payBillService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(PayBill query)
|
||||
{
|
||||
startPage();
|
||||
List<PayBill> list = payBillService.selectPayBillList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:query')")
|
||||
@GetMapping("/{billId}")
|
||||
public AjaxResult detail(@PathVariable Long billId)
|
||||
{
|
||||
return success(payBillService.selectPayBillDetail(billId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:query')")
|
||||
@PostMapping("/preview")
|
||||
public AjaxResult preview(@RequestBody PayBillCommand command)
|
||||
{
|
||||
return success(payBillService.preview(command));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:add')")
|
||||
@PostMapping
|
||||
public AjaxResult generate(@RequestBody PayBillCommand command)
|
||||
{
|
||||
return success(payBillService.generate(command));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:confirm')")
|
||||
@PutMapping("/confirm/{billId}")
|
||||
public AjaxResult confirm(@PathVariable Long billId)
|
||||
{
|
||||
payBillService.confirm(billId);
|
||||
return success();
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:confirm')")
|
||||
@PutMapping("/paid/{billId}")
|
||||
public AjaxResult paid(@PathVariable Long billId)
|
||||
{
|
||||
payBillService.markPaid(billId);
|
||||
return success();
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:payBill:void')")
|
||||
@PutMapping("/void")
|
||||
public AjaxResult voidBill(@RequestBody PayBillCommand command)
|
||||
{
|
||||
payBillService.voidBill(command);
|
||||
return success();
|
||||
}
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
package com.bms.web.controller.price;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.bms.common.annotation.Log;
|
||||
import com.bms.common.core.controller.BaseController;
|
||||
import com.bms.common.core.domain.AjaxResult;
|
||||
import com.bms.common.core.page.TableDataInfo;
|
||||
import com.bms.common.enums.BusinessType;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.poi.ExcelUtil;
|
||||
import com.bms.price.domain.ChannelProductMap;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PayProductTerm;
|
||||
import com.bms.price.domain.PaySaveCommand;
|
||||
import com.bms.price.pay.PayDetailExporter;
|
||||
import com.bms.price.service.IPayOrderCostService;
|
||||
import com.bms.price.service.IPayProductService;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 价格基础下的应付配置。导入先预览,确认后才入库。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/price/pay")
|
||||
public class PayProductController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IPayProductService payProductService;
|
||||
|
||||
@Autowired
|
||||
private IPayOrderCostService payOrderCostService;
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(PayProduct query)
|
||||
{
|
||||
startPage();
|
||||
List<PayProduct> list = payProductService.selectPayProductList(query);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 一个产品的价格版本和有效期 */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:query,price:pay:edit')")
|
||||
@GetMapping("/term/list")
|
||||
public AjaxResult termList(@RequestParam Long productId)
|
||||
{
|
||||
return success(payProductService.listTerms(productId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:edit')")
|
||||
@Log(title = "应付有效期", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/term")
|
||||
public AjaxResult updateTerm(@RequestBody PayProductTerm term)
|
||||
{
|
||||
return toAjax(payProductService.updateTerm(term));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:edit')")
|
||||
@Log(title = "应付有效期", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/term/status")
|
||||
public AjaxResult changeTermStatus(@RequestBody PayProductTerm term)
|
||||
{
|
||||
return toAjax(payProductService.changeTermStatus(term));
|
||||
}
|
||||
|
||||
/** 按订单的渠道服务商和应付产品生成应付账单 */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:billing:bill,price:pay:edit')")
|
||||
@Log(title = "应付账单", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/order/{billId}")
|
||||
public AjaxResult generateOrderCost(@PathVariable String billId)
|
||||
{
|
||||
return success(payOrderCostService.generate(billId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:query')")
|
||||
@GetMapping("/{productId}")
|
||||
public AjaxResult getInfo(@PathVariable Long productId)
|
||||
{
|
||||
return success(payProductService.selectPayProductDetail(productId));
|
||||
}
|
||||
|
||||
/** 下拉:已导入的公共模板,供渠道页选择。payType 为空时按应付 AP */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:query,data:serviceChannel:list,data:serviceChannel:edit')")
|
||||
@GetMapping("/options")
|
||||
public AjaxResult options(@RequestParam(value = "payType", required = false) String payType)
|
||||
{
|
||||
return success(payProductService.selectTemplateOptions(payType));
|
||||
}
|
||||
|
||||
/** 渠道当前价格副本 */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:query,data:serviceChannel:query,data:serviceChannel:edit')")
|
||||
@GetMapping("/channel/{channelId}")
|
||||
public AjaxResult channelPrice(@PathVariable String channelId)
|
||||
{
|
||||
return success(payProductService.selectChannelPrice(channelId));
|
||||
}
|
||||
|
||||
/** 渠道只绑定产品编号,以后按有效期取价 */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:edit,data:serviceChannel:edit')")
|
||||
@Log(title = "渠道应付产品", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/channel/{channelId}/bind")
|
||||
public AjaxResult bindChannel(@PathVariable String channelId, @RequestBody ChannelProductMap link)
|
||||
{
|
||||
String payType = link == null ? null : link.getPayType();
|
||||
if (link == null || link.getProductId() == null)
|
||||
{
|
||||
payProductService.unbindChannel(channelId, payType);
|
||||
return success();
|
||||
}
|
||||
payProductService.bindChannel(channelId, link.getProductId(), link.getPriceSetId(), payType);
|
||||
return success(payProductService.selectChannelPrice(channelId));
|
||||
}
|
||||
|
||||
/** 对照两个价格版本 */
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:query')")
|
||||
@GetMapping("/compare")
|
||||
public AjaxResult compare(@RequestParam Long leftId, @RequestParam Long rightId)
|
||||
{
|
||||
return success(payProductService.compare(leftId, rightId));
|
||||
}
|
||||
|
||||
/** 给已绑定的产品追加一版,产品编号不变 */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:import,data:serviceChannel:edit')")
|
||||
@Log(title = "渠道应付价格", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/channel/{channelId}")
|
||||
public AjaxResult saveChannel(@PathVariable String channelId, @RequestBody PayProduct product)
|
||||
{
|
||||
return success(payProductService.saveChannelVersion(channelId, product));
|
||||
}
|
||||
|
||||
/** 重新导入渠道价格,生成新版本,不改模板 */
|
||||
@PreAuthorize("@ss.hasAnyPermi('price:pay:import,data:serviceChannel:edit')")
|
||||
@Log(title = "渠道应付价格", businessType = BusinessType.IMPORT)
|
||||
@PostMapping(value = "/channel/{channelId}/reimport", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public AjaxResult reimportChannel(@PathVariable String channelId, MultipartFile file) throws Exception
|
||||
{
|
||||
assertWorkbook(file);
|
||||
return success(payProductService.reimportChannel(channelId, file.getBytes(), file.getOriginalFilename()));
|
||||
}
|
||||
|
||||
/** 按字典匹配工作表。dictValue 为空时读取文件里所有能对上的表 */
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:import')")
|
||||
@Log(title = "应付配置", businessType = BusinessType.IMPORT)
|
||||
@PostMapping(value = "/preview", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public AjaxResult preview(MultipartFile file, @RequestParam(value = "dictValue", required = false) String dictValue) throws Exception
|
||||
{
|
||||
assertWorkbook(file);
|
||||
return success(payProductService.preview(file.getBytes(), file.getOriginalFilename(), dictValue));
|
||||
}
|
||||
|
||||
/** 修改已导入的公共模板 */
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:import')")
|
||||
@Log(title = "应付配置", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody PayProduct product)
|
||||
{
|
||||
return toAjax(payProductService.updateDetail(product));
|
||||
}
|
||||
|
||||
/** 保存预览中勾选的产品 */
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:import')")
|
||||
@Log(title = "应付配置", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/save")
|
||||
public AjaxResult save(@RequestBody PaySaveCommand command)
|
||||
{
|
||||
return success(payProductService.save(command));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:remove')")
|
||||
@Log(title = "应付配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{productIds}")
|
||||
public AjaxResult remove(@PathVariable Long[] productIds)
|
||||
{
|
||||
return toAjax(payProductService.deletePayProducts(productIds));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:export')")
|
||||
@Log(title = "应付配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, PayProduct query)
|
||||
{
|
||||
List<PayProduct> list = payProductService.selectPayProductList(query);
|
||||
new ExcelUtil<>(PayProduct.class).exportExcel(response, list, "应付产品");
|
||||
}
|
||||
|
||||
/** 按当前搜索条件导出产品、重量段、附加费和其他配置 */
|
||||
@PreAuthorize("@ss.hasPermi('price:pay:export')")
|
||||
@Log(title = "应付配置明细", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/exportDetail")
|
||||
public void exportDetail(HttpServletResponse response, PayProduct query) throws Exception
|
||||
{
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
PayDetailExporter.write(response.getOutputStream(), payProductService.selectPayProductDetails(query));
|
||||
}
|
||||
|
||||
private void assertWorkbook(MultipartFile file)
|
||||
{
|
||||
if (file == null || file.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请选择价卡文件");
|
||||
}
|
||||
String filename = file.getOriginalFilename();
|
||||
if (filename == null || !filename.toLowerCase(Locale.ROOT).endsWith(".xlsx"))
|
||||
{
|
||||
throw new ServiceException("请上传xlsx价卡");
|
||||
}
|
||||
if (file.getSize() > 30L * 1024 * 1024)
|
||||
{
|
||||
throw new ServiceException("价卡文件不能超过30MB");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.PayWeightBand;
|
||||
|
||||
class PayCostCalculatorTest
|
||||
{
|
||||
@Test
|
||||
void royalMailChargesTicketPriceAndMatchingSurcharges()
|
||||
{
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(royalMail(), new BigDecimal("4"),
|
||||
new BigDecimal("50"), new BigDecimal("40"), new BigDecimal("30"), "IV1 1AA", null, 1);
|
||||
assertEquals(new BigDecimal("3.42"), result.getPayTotal());
|
||||
assertEquals("GBP", result.getCurrency());
|
||||
assertTrue(hasFee(result, "基础运费", "1.62"));
|
||||
assertTrue(hasFee(result, "偏远地区附加费", "1.80"));
|
||||
assertTrue(result.getFees().stream().noneMatch(fee -> fee.getFeeName().contains("超大")));
|
||||
assertTrue(result.getSkipped().stream().anyMatch(text -> text.contains("签名费")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void overweightOutsideTheCardDoesNotInventFreight()
|
||||
{
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(royalMail(), new BigDecimal("21"),
|
||||
null, null, null, "SW1A 1AA", null, 1);
|
||||
assertTrue(result.getFees().isEmpty());
|
||||
assertTrue(result.getMessage().contains("超出"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oversizeUsesTheHigherPercentWhenBothTiersMatch()
|
||||
{
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(spainOneZone(), new BigDecimal("1"),
|
||||
new BigDecimal("130"), new BigDecimal("50"), new BigDecimal("40"), "28001", null, 1);
|
||||
assertTrue(hasFee(result, "基础运费", "2.27"));
|
||||
assertTrue(hasFee(result, "特大包裹收费", "1.59"));
|
||||
assertEquals(1, result.getFees().stream().filter(fee -> "特大包裹收费".equals(fee.getFeeName())).count());
|
||||
}
|
||||
|
||||
@Test
|
||||
void postcodeOutsideTheRemoteListUsesMainland()
|
||||
{
|
||||
PayProduct product = evri();
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(product, new BigDecimal("0.522"),
|
||||
new BigDecimal("30.1"), new BigDecimal("24"), new BigDecimal("15.1"), "LL22 8PJ",
|
||||
"UK Mainland\n英国大陆", null, 1);
|
||||
assertEquals(new BigDecimal("1.57"), result.getPayTotal());
|
||||
assertTrue(hasFee(result, "基础运费", "1.57"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void severalZonesCannotBePricedFromTheOrder()
|
||||
{
|
||||
PayProduct product = spainOneZone();
|
||||
PayWeightBand rural = band("0.01", "30", "3.12", "Rural Resto");
|
||||
product.getWeightBands().add(rural);
|
||||
PayOrderCostResult result = PayCostCalculator.calculate(product, new BigDecimal("1"),
|
||||
null, null, null, "28001", null, 1);
|
||||
assertTrue(result.getFees().isEmpty());
|
||||
assertTrue(result.getMessage().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 PayProduct royalMail()
|
||||
{
|
||||
PayProduct product = new PayProduct();
|
||||
product.setProductName("英国RM");
|
||||
product.setWeightBands(List.of(
|
||||
band("0", "5", "1.62", ""),
|
||||
band("5.001", "10", "2.26", ""),
|
||||
band("10.001", "15", "3.51", ""),
|
||||
band("15.001", "20", "5.18", "")));
|
||||
product.getWeightBands().forEach(band -> {
|
||||
band.setChargeName("每票");
|
||||
band.setPriceUnit("GBP/票");
|
||||
});
|
||||
product.setSurcharges(List.of(
|
||||
fee(11L, "偏远地区附加费", "1.80", "GBP", "POSTCODE", "IV,HS,KA27-28"),
|
||||
fee(12L, "偏远地区附加费", "1.00", "GBP", "POSTCODE", "BT,TR21-25"),
|
||||
fee(13L, "签名费", "0.70", "GBP", "MANUAL", null),
|
||||
fee(14L, "超大附加费(包裹)", "12.50", "GBP", "SIZE", "box:61*46*46;volume:0.129"),
|
||||
fee(15L, "超重附加费(包裹)", "12.50", "GBP", "WEIGHT", "20")));
|
||||
return product;
|
||||
}
|
||||
|
||||
private static PayProduct spainOneZone()
|
||||
{
|
||||
PayProduct product = new PayProduct();
|
||||
product.setProductName("西邮-西班牙(主岛)");
|
||||
PayWeightBand band = band("0.01", "30", "2.27", "MAD-BCN Dense");
|
||||
band.setChargeName("每件");
|
||||
band.setPriceUnit("EUR/件");
|
||||
product.setWeightBands(new ArrayList<>(List.of(band)));
|
||||
product.setSurcharges(List.of(
|
||||
fee(21L, "特大包裹收费", "35", "%", "SIZE", "side:120;sum:210;join:or"),
|
||||
fee(22L, "特大包裹收费", "70", "%", "SIZE", "side:120;sum:210;join:and")));
|
||||
product.getSurcharges().get(0).setChargeText("运费另加35%");
|
||||
product.getSurcharges().get(1).setChargeText("运费另加70%");
|
||||
return product;
|
||||
}
|
||||
|
||||
private static PayProduct evri()
|
||||
{
|
||||
PayProduct product = new PayProduct();
|
||||
product.setProductName("英国evri");
|
||||
product.setCurrency("GBP");
|
||||
product.setWeightBands(new ArrayList<>(List.of(
|
||||
band("0", "2", "1.57", "UK Mainland\n英国大陆"),
|
||||
band("0", "2", "4.78", "Highlands& Islands\n高地和岛屿"))));
|
||||
product.getWeightBands().forEach(row -> row.setPriceUnit("GBP/ITEM"));
|
||||
return product;
|
||||
}
|
||||
|
||||
private static PayWeightBand band(String min, String max, String price, String zone)
|
||||
{
|
||||
PayWeightBand band = new PayWeightBand();
|
||||
band.setWeightMin(new BigDecimal(min));
|
||||
band.setWeightMax(new BigDecimal(max));
|
||||
band.setWeightLabel(min + "-" + max);
|
||||
band.setPrice(new BigDecimal(price));
|
||||
band.setZoneName(zone);
|
||||
return band;
|
||||
}
|
||||
|
||||
private static PaySurcharge fee(Long id, String name, String amount, String currency, String rule, String param)
|
||||
{
|
||||
PaySurcharge fee = new PaySurcharge();
|
||||
fee.setSurchargeId(id);
|
||||
fee.setFeeName(name);
|
||||
fee.setAmount(new BigDecimal(amount));
|
||||
fee.setCurrency(currency);
|
||||
fee.setChargeText(amount + " " + currency);
|
||||
fee.setCalcRule(rule);
|
||||
fee.setCalcParam(param);
|
||||
return fee;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.bms.price.domain.PayPostcodeGroup;
|
||||
|
||||
/**
|
||||
* 用 10 月价卡核对邮编星号、附加费合并和分区原文。
|
||||
*/
|
||||
class PayPostcodeImporterTest
|
||||
{
|
||||
private final PayPostcodeImporter importer = new PayPostcodeImporter();
|
||||
|
||||
@Test
|
||||
void stripsStarAndMergesSameFee() throws Exception
|
||||
{
|
||||
List<PayPostcodeGroup> groups = importer.parse(Files.readAllBytes(file("32、")), "32.xlsx");
|
||||
List<PayPostcodeGroup> fees = groups.stream().filter(item -> "FEE".equals(item.getConfigKind()) && "英国附加费".equals(item.getSheetName())).toList();
|
||||
assertEquals(4, fees.size(), fees.stream().map(item -> item.getFeeName() + "=" + item.getFeeText() + " x" + item.getLineCount()).toList().toString());
|
||||
PayPostcodeGroup remote195 = one(fees, "偏远附加费", "1.95GBP/票");
|
||||
PayPostcodeGroup remote115 = one(fees, "偏远附加费", "1.15GBP/票");
|
||||
PayPostcodeGroup evri = one(fees, "偏远附加费", "5.95GBP/票");
|
||||
PayPostcodeGroup air = one(fees, "空气减排附加费", "0.45GBP/票");
|
||||
assertEquals("Royal Mail", remote195.getChannelName());
|
||||
assertEquals("Royal Mail", remote115.getChannelName());
|
||||
assertEquals("EVRI", evri.getChannelName());
|
||||
assertEquals("EVRI", air.getChannelName());
|
||||
assertEquals(15, remote195.getLineCount());
|
||||
assertTrue(remote195.getLines().stream().anyMatch(line -> "PA60-78".equals(line.getPostcode())));
|
||||
assertTrue(remote195.getLines().stream().anyMatch(line -> "PA80".equals(line.getPostcode())));
|
||||
assertTrue(remote195.getLines().stream().anyMatch(line -> "KA27-28".equals(line.getPostcode())));
|
||||
assertEquals(6, remote115.getLineCount());
|
||||
assertTrue(remote115.getLines().stream().anyMatch(line -> "AB35-38".equals(line.getPostcode())));
|
||||
assertTrue(remote115.getLines().stream().anyMatch(line -> "AB53-56".equals(line.getPostcode())));
|
||||
assertTrue(evri.getLineCount() > 100 && evri.getLineCount() < 500);
|
||||
assertTrue(evri.getLines().stream().anyMatch(line -> "GY".equals(line.getPostcode()) && "GY*".equals(line.getPostcodeRaw())));
|
||||
assertTrue(air.getLineCount() > 70000);
|
||||
assertEquals("英国", air.getCountryName());
|
||||
assertEquals("REMOTE", remote195.getCoverage());
|
||||
assertEquals("REMOTE", remote115.getCoverage());
|
||||
assertEquals("REMOTE", evri.getCoverage());
|
||||
assertEquals("", air.getCoverage());
|
||||
assertTrue(groups.stream().noneMatch(item -> "Evri 48H".equals(item.getSheetName())));
|
||||
|
||||
PayPostcodeGroup italy = groups.stream().filter(item -> "iMile邮编分区".equals(item.getSheetName()) && "IT".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertEquals("ZONE", italy.getConfigKind());
|
||||
assertEquals("", italy.getCoverage());
|
||||
assertEquals("IT-iMile", italy.getChannelName());
|
||||
assertTrue(italy.getLines().stream().anyMatch(line -> "00010".equals(line.getPostcode()) && "province(no dense)".equals(line.getZoneName()) && "Lazio".equals(line.getRegionName()) && "ROMA".equals(line.getCityName())));
|
||||
assertTrue(groups.stream().anyMatch(item -> "iMile邮编分区".equals(item.getSheetName()) && "DE".equals(item.getCountryCode()) && "DE-iMile".equals(item.getChannelName()) && item.getLines().stream().anyMatch(line -> "01001".equals(line.getPostcode()))));
|
||||
PayPostcodeGroup spain = groups.stream().filter(item -> "CTT邮编分区".equals(item.getSheetName())).findFirst().orElseThrow();
|
||||
assertEquals("西班牙", spain.getCountryName());
|
||||
assertEquals("CTT", spain.getChannelName());
|
||||
assertTrue(spain.getLines().stream().anyMatch(line -> "01110".equals(line.getPostcode()) && "Low".equals(line.getZoneName()) && "SANTIKURUTZE KANPEZU".equals(line.getCityName())));
|
||||
PayPostcodeGroup france = groups.stream().filter(item -> "Colissimo邮编分区".equals(item.getSheetName())).findFirst().orElseThrow();
|
||||
assertEquals("Colissimo", france.getChannelName());
|
||||
assertTrue(france.getLines().stream().anyMatch(line -> "77244".equals(line.getPostcode()) && "Moyenne Densité".equals(line.getZoneName())));
|
||||
PayPostcodeGroup poste = groups.stream().filter(item -> "PosteItaliane邮编分区".equals(item.getSheetName()) && item.getLines().stream().anyMatch(line -> "City (dense)".equals(line.getZoneName()))).findFirst().orElseThrow();
|
||||
assertTrue(poste.getLines().stream().noneMatch(line -> "City".equals(line.getZoneName())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stripStarOnlyRemovesTrailingMark()
|
||||
{
|
||||
assertEquals("GY", PayPostcodeImporter.stripStar("GY*"));
|
||||
assertEquals("PO30", PayPostcodeImporter.stripStar("PO30*"));
|
||||
assertEquals("KA27-28", PayPostcodeImporter.stripStar("KA27-28"));
|
||||
assertEquals("Royal Mail", PayPostcodeImporter.channelOf(Arrays.asList("Royal Mail偏远地区附加费")));
|
||||
assertEquals("EVRI", PayPostcodeImporter.channelOf(Arrays.asList("EVRI偏远&空气减排地区附加费")));
|
||||
assertEquals("CTT", PayPostcodeImporter.channelOf(Arrays.asList("Código Postal", "CTT Density")));
|
||||
assertEquals("GOFO", PayPostcodeImporter.channelOf(Arrays.asList("法国GOFO可达邮编")));
|
||||
assertEquals("REACH", PayPostcodeImporter.coverageOf("法国GOFO可达邮编", "", ""));
|
||||
assertEquals("UNREACH", PayPostcodeImporter.coverageOf("德国DPD挂号(DEDPD)不可达邮编", "", ""));
|
||||
assertEquals("REMOTE", PayPostcodeImporter.coverageOf("evri 偏远", "", ""));
|
||||
assertEquals("", PayPostcodeImporter.coverageOf("英国附加费", "EVRI偏远&空气减排地区附加费", "空气减排附加费"));
|
||||
List<PayPostcodeImporter.PostcodeToken> kept = PayPostcodeImporter.expandPostcode("KA27-28");
|
||||
assertEquals("KA27-28", kept.get(0).postcode);
|
||||
assertEquals("", kept.get(0).end);
|
||||
List<PayPostcodeImporter.PostcodeToken> range = PayPostcodeImporter.expandPostcode("57030-57039");
|
||||
assertEquals("57030", range.get(0).postcode);
|
||||
assertEquals("57039", range.get(0).end);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reachableUnreachableAndRemoteKeepTheirOwnCountry() throws Exception
|
||||
{
|
||||
List<PayPostcodeGroup> may = importer.parse(Files.readAllBytes(file("20、")), "20.xlsx");
|
||||
PayPostcodeGroup cirro = may.stream().filter(item -> item.getSheetName().contains("Cirro可派")).findFirst().orElseThrow();
|
||||
assertEquals("REACH", cirro.getCoverage());
|
||||
assertEquals("法国", cirro.getCountryName());
|
||||
assertEquals("Cirro", cirro.getChannelName());
|
||||
assertTrue(cirro.getLines().stream().anyMatch(line -> "94350".equals(line.getPostcode()) && "Zone1".equals(line.getZoneName()) && "Paris".equals(line.getRegionName())));
|
||||
|
||||
PayPostcodeGroup germany = may.stream().filter(item -> "西班牙GLS 偏远邮编".equals(item.getSheetName()) && "DE".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertEquals("REMOTE", germany.getCoverage());
|
||||
assertEquals("德国", germany.getCountryName());
|
||||
assertEquals("GLS", germany.getChannelName());
|
||||
assertTrue(germany.getLines().stream().anyMatch(line -> "18565".equals(line.getPostcode()) && "Hiddensee".equals(line.getRegionName())));
|
||||
PayPostcodeGroup italyRemote = may.stream().filter(item -> "西班牙GLS 偏远邮编".equals(item.getSheetName()) && "IT".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertTrue(italyRemote.getLines().stream().anyMatch(line -> "57030".equals(line.getPostcode()) && "57039".equals(line.getPostcodeEnd())));
|
||||
|
||||
PayPostcodeGroup blocked = may.stream().filter(item -> item.getSheetName().contains("不可达")).findFirst().orElseThrow();
|
||||
assertEquals("UNREACH", blocked.getCoverage());
|
||||
assertEquals("德国", blocked.getCountryName());
|
||||
assertEquals("DPD", blocked.getChannelName());
|
||||
assertTrue(blocked.getLines().stream().anyMatch(line -> "18565".equals(line.getPostcode()) && "18565".equals(line.getPostcodeEnd())));
|
||||
|
||||
PayPostcodeGroup denmark = may.stream().filter(item -> item.getSheetName().contains("岛屿") && "DK".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertEquals("REMOTE", denmark.getCoverage());
|
||||
assertEquals("丹麦", denmark.getCountryName());
|
||||
assertEquals("DPD", denmark.getChannelName());
|
||||
assertTrue(denmark.getLines().stream().anyMatch(line -> "4592".equals(line.getPostcode())));
|
||||
PayPostcodeGroup estonia = may.stream().filter(item -> item.getSheetName().contains("岛屿") && "EE".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertEquals("爱沙尼亚", estonia.getCountryName());
|
||||
assertEquals("REMOTE", estonia.getCoverage());
|
||||
assertTrue(estonia.getLines().stream().anyMatch(line -> "88001".equals(line.getPostcode()) && "88005".equals(line.getPostcodeEnd())));
|
||||
|
||||
List<PayPostcodeGroup> yide = importer.parse(Files.readAllBytes(file("26、")), "26.xlsx");
|
||||
PayPostcodeGroup gofo = yide.stream().filter(item -> item.getSheetName().contains("GOFO可达")).findFirst().orElseThrow();
|
||||
assertEquals("REACH", gofo.getCoverage());
|
||||
assertEquals("法国", gofo.getCountryName());
|
||||
assertEquals("GOFO", gofo.getChannelName());
|
||||
assertTrue(gofo.getLines().stream().anyMatch(line -> "94350".equals(line.getPostcode()) && "Paris".equals(line.getRegionName())));
|
||||
|
||||
PayPostcodeGroup evriRemote = yide.stream().filter(item -> "evri 偏远".equals(item.getSheetName())).findFirst().orElseThrow();
|
||||
assertEquals("REMOTE", evriRemote.getCoverage());
|
||||
assertEquals("英国", evriRemote.getCountryName());
|
||||
assertEquals("evri", evriRemote.getChannelName());
|
||||
assertTrue(evriRemote.getLines().stream().anyMatch(line -> "GY".equals(line.getPostcode()) && "GY*".equals(line.getPostcodeRaw())));
|
||||
|
||||
PayPostcodeGroup peuItaly = yide.stream().filter(item -> item.getSheetName().contains("不可达") && "IT".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertEquals("UNREACH", peuItaly.getCoverage());
|
||||
assertEquals("意大利", peuItaly.getCountryName());
|
||||
assertTrue(peuItaly.getLines().stream().anyMatch(line -> "23041".equals(line.getPostcode())));
|
||||
List<PayPostcodeGroup> weishi = importer.parse(Files.readAllBytes(file("8、")), "8.xlsx");
|
||||
PayPostcodeGroup cttZone = weishi.stream().filter(item -> "西班牙CTT邮编分区".equals(item.getSheetName())).findFirst().orElseThrow();
|
||||
assertEquals("末端", cttZone.getServiceType());
|
||||
assertEquals("西班牙", cttZone.getCatalogCountry());
|
||||
assertEquals("CTT", cttZone.getProductName());
|
||||
assertEquals("西班牙CTT", cttZone.getQuoteSheet());
|
||||
assertEquals("", cttZone.getCoverage() == null ? "" : cttZone.getCoverage());
|
||||
assertTrue(cttZone.getLines().stream().anyMatch(line -> "01110".equals(line.getPostcode()) && "Low".equals(line.getZoneName()) && "ALAVA".equals(line.getRegionName())));
|
||||
PayPostcodeGroup cirroMay = weishi.stream().filter(item -> item.getSheetName().contains("Cirro可派")).findFirst().orElseThrow();
|
||||
assertEquals("末端", cirroMay.getServiceType());
|
||||
assertEquals("法国", cirroMay.getCatalogCountry());
|
||||
assertEquals("cirro", cirroMay.getProductName());
|
||||
assertEquals("法国Cirro", cirroMay.getQuoteSheet());
|
||||
assertEquals("REACH", cirroMay.getCoverage());
|
||||
PayPostcodeGroup glsMay = weishi.stream().filter(item -> "西班牙GLS 偏远邮编".equals(item.getSheetName()) && "DE".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertEquals("西班牙", glsMay.getCatalogCountry());
|
||||
assertEquals("GLS", glsMay.getProductName());
|
||||
assertEquals("西班牙GLS", glsMay.getQuoteSheet());
|
||||
assertEquals("德国", glsMay.getCountryName());
|
||||
assertEquals("REMOTE", glsMay.getCoverage());
|
||||
PayPostcodeGroup blockedMay = weishi.stream().filter(item -> item.getSheetName().contains("不可达")).findFirst().orElseThrow();
|
||||
assertEquals("德国", blockedMay.getCatalogCountry());
|
||||
assertEquals("DPD", blockedMay.getProductName());
|
||||
assertTrue(blockedMay.getQuoteSheet().contains("DEDPD"));
|
||||
assertEquals("UNREACH", blockedMay.getCoverage());
|
||||
PayPostcodeGroup islandMay = weishi.stream().filter(item -> item.getSheetName().contains("岛屿") && "DK".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertEquals("DPD国际件", islandMay.getProductName());
|
||||
assertEquals("德国DPD挂号-国际件", islandMay.getQuoteSheet());
|
||||
assertEquals("REMOTE", islandMay.getCoverage());
|
||||
assertTrue(weishi.stream().noneMatch(item -> item.getSheetName().contains("危机燃油") || item.getSheetName().contains("高风险")));
|
||||
|
||||
PayPostcodeGroup peuFrance = yide.stream().filter(item -> item.getSheetName().contains("不可达") && "FR".equals(item.getCountryCode())).findFirst().orElseThrow();
|
||||
assertTrue(peuFrance.getLines().stream().anyMatch(line -> "98700".equals(line.getPostcode()) && "98799".equals(line.getPostcodeEnd())));
|
||||
}
|
||||
|
||||
private static PayPostcodeGroup one(List<PayPostcodeGroup> fees, String name, String text)
|
||||
{
|
||||
return fees.stream().filter(item -> name.equals(item.getFeeName()) && text.equals(item.getFeeText())).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
private Path file(String prefix) throws Exception
|
||||
{
|
||||
try (Stream<Path> files = Files.list(Path.of("D:/应付供应商")))
|
||||
{
|
||||
return files.filter(path -> path.getFileName().toString().startsWith(prefix) && path.toString().endsWith(".xlsx")).findFirst().orElseThrow();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
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.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.bms.price.domain.PayPreview;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
import com.bms.price.domain.PaySurcharge;
|
||||
import com.bms.price.domain.PayWeightBand;
|
||||
|
||||
/**
|
||||
* 用现有应付价卡核对目录产品和重量段、附加费是否被读出来。
|
||||
*/
|
||||
class PayWorkbookParserTest
|
||||
{
|
||||
private final PayWorkbookParser parser = new PayWorkbookParser();
|
||||
|
||||
@Test
|
||||
void octoberCatalogKeepsTwelveProductSheets() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("9、");
|
||||
assertEquals(12, preview.getProducts().size());
|
||||
PayProduct evri = product(preview, "Evri 48H");
|
||||
assertTrue(evri.getWeightBands().stream().anyMatch(band -> decimal(band, "1.51") && "英国大陆".equals(zoneTail(band))));
|
||||
assertTrue(evri.getSurcharges().stream().anyMatch(fee -> "战争附加费".equals(fee.getFeeName()) && fee.getChargeText().contains("2.5")));
|
||||
PayProduct ctt = product(preview, "CTT 48H");
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> band.getWeightLabel() != null && band.getWeightLabel().contains("首重") && decimal(band, "2.05")));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "每增加 1Kg".equals(band.getWeightLabel()) || (band.getWeightLabel() != null && band.getWeightLabel().contains("每增加"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mayCardReadsDhlAndDpdWithoutZoneSheets() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("8、");
|
||||
assertEquals(8, preview.getProducts().size());
|
||||
PayProduct dhl = product(preview, "DHL泛欧");
|
||||
assertEquals("威狮", dhl.getSupplierName());
|
||||
assertTrue(dhl.getRelatedSheet() != null && dhl.getRelatedSheet().contains("高风险"));
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> decimal(band, "9.71") && band.getCountryName() != null && band.getCountryName().contains("丹麦")),
|
||||
dhl.getParseStatus() + " " + dhl.getSheetName() + " " + dhl.getWeightBands().stream().limit(6)
|
||||
.map(band -> band.getCountryName() + "/" + band.getChargeName() + "/" + band.getPrice()).toList());
|
||||
assertEquals("末端", dhl.getServiceType());
|
||||
assertEquals("德国", dhl.getCountryScope());
|
||||
assertEquals("DHL泛欧", dhl.getSheetName());
|
||||
PaySurcharge afghan = dhl.getSurcharges().stream()
|
||||
.filter(fee -> "高风险地区附加费".equals(fee.getFeeName()) && "AF".equals(fee.getCalcParam())).findFirst().orElseThrow();
|
||||
assertEquals("Afghanistan", afghan.getZoneName());
|
||||
assertEquals("RMB", afghan.getCurrency());
|
||||
assertTrue(afghan.getChargeText().contains("10.4") && afghan.getChargeText().contains("RMB/kg"));
|
||||
assertTrue(decimalFee(afghan, "10.4"));
|
||||
assertEquals("MANUAL", afghan.getCalcRule());
|
||||
assertTrue(afghan.getFeeDesc().contains("非欧洲"));
|
||||
PayProduct dpd = preview.getProducts().stream().filter(item -> "DPD".equals(item.getProductName())).findFirst().orElseThrow();
|
||||
assertEquals("末端", dpd.getServiceType());
|
||||
assertEquals("德国", dpd.getCountryScope());
|
||||
assertTrue(dpd.getSheetName().contains("DEDPD"));
|
||||
assertTrue(dpd.getRelatedSheet().contains("不可达"));
|
||||
assertTrue(dpd.getRelatedSheet().contains("危机燃油"));
|
||||
assertTrue(dpd.getWeightBands().stream().anyMatch(band -> decimal(band, "3.29")));
|
||||
assertTrue(dpd.getSurcharges().stream().anyMatch(fee -> "道路通行费".equals(fee.getFeeName()) && decimalFee(fee, "0.14")));
|
||||
assertTrue(dpd.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("旺季") && fee.getEffectiveStart() != null));
|
||||
PaySurcharge bracket = dpd.getSurcharges().stream()
|
||||
.filter(fee -> "危机燃油附加费".equals(fee.getFeeName()) && "1.95|2".equals(fee.getCalcParam())).findFirst().orElseThrow();
|
||||
assertEquals("1.95 ≤ 油价 < 2", bracket.getChargeText());
|
||||
assertTrue(decimalFee(bracket, "0.0379"));
|
||||
assertEquals("MANUAL", bracket.getCalcRule());
|
||||
PaySurcharge zero = dpd.getSurcharges().stream()
|
||||
.filter(fee -> "危机燃油附加费".equals(fee.getFeeName()) && "|1.35".equals(fee.getCalcParam())).findFirst().orElseThrow();
|
||||
assertEquals("油价 < 1.35", zero.getChargeText());
|
||||
assertTrue(decimalFee(zero, "0"));
|
||||
PayProduct ctt = product(preview, "CTT");
|
||||
assertEquals("末端", ctt.getServiceType());
|
||||
assertEquals("西班牙", ctt.getCountryScope());
|
||||
assertEquals("西班牙CTT", ctt.getSheetName());
|
||||
assertTrue(ctt.getRelatedSheet().contains("西班牙CTT邮编分区"));
|
||||
PayProduct international = product(preview, "DPD国际件");
|
||||
assertEquals("德国", international.getCountryScope());
|
||||
assertTrue(international.getRelatedSheet().contains("岛屿"));
|
||||
assertTrue(international.getSurcharges().stream().noneMatch(fee -> "1.95|2".equals(fee.getCalcParam())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upsZonesStayLeftToRightAndColissimoFuelIsMarked() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("32、");
|
||||
PayProduct ups = product(preview, "德国UPS");
|
||||
assertEquals("Zone 1", ups.getWeightBands().get(0).getZoneName());
|
||||
assertEquals("DE", ups.getWeightBands().get(0).getCountryCode());
|
||||
assertTrue(decimal(ups.getWeightBands().get(0), "3.9"));
|
||||
assertEquals("Zone 2", ups.getWeightBands().get(1).getZoneName());
|
||||
assertTrue(ups.getWeightBands().get(1).getCountryCode().startsWith("BE,LU"));
|
||||
assertTrue(decimal(ups.getWeightBands().get(1), "7.58"));
|
||||
assertTrue(ups.getWeightBands().get(4).getZoneName().contains("Zone 5"));
|
||||
assertTrue(decimal(ups.getWeightBands().get(4), "9.45"));
|
||||
assertTrue(ups.getSurcharges().stream().anyMatch(fee -> "报价说明".equals(fee.getFeeName())
|
||||
&& fee.getChargeText().contains("不含管理费") && fee.getChargeText().contains("不含燃油")));
|
||||
PaySurcharge handling = ups.getSurcharges().stream()
|
||||
.filter(fee -> fee.getFeeName().startsWith("额外处理费") && (fee.getSectionName() == null || !fee.getSectionName().contains("高峰")))
|
||||
.findFirst().orElseThrow();
|
||||
assertTrue(handling.getChargeText().contains("23.65") && handling.getChargeText().contains("26.7"));
|
||||
assertTrue(handling.getFeeDesc().contains("是否加收燃油:是"));
|
||||
PaySurcharge peak = ups.getSurcharges().stream()
|
||||
.filter(fee -> "高峰附加费".equals(fee.getSectionName()) && fee.getFeeName().startsWith("额外处理费"))
|
||||
.findFirst().orElseThrow();
|
||||
assertTrue(peak.getChargeText().contains("7.35"));
|
||||
assertEquals(java.time.LocalDate.of(2026, 9, 28),
|
||||
peak.getEffectiveStart().toInstant().atZone(java.time.ZoneId.of("GMT+8")).toLocalDate());
|
||||
assertEquals(java.time.LocalDate.of(2027, 1, 17),
|
||||
peak.getEffectiveEnd().toInstant().atZone(java.time.ZoneId.of("GMT+8")).toLocalDate());
|
||||
|
||||
PayProduct colissimo = product(preview, "Colissimo");
|
||||
PaySurcharge fuel = colissimo.getSurcharges().stream().filter(fee -> fee.getFeeName().contains("燃油")).findFirst().orElseThrow();
|
||||
assertTrue(fuel.getChargeText().contains("不含燃油"));
|
||||
assertTrue(fuel.getChargeText().contains("不含去碳"));
|
||||
assertTrue(colissimo.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().startsWith("去碳附加费") && decimalFee(fee, "0.05")));
|
||||
assertTrue(colissimo.getWeightBands().stream().anyMatch(band -> decimal(band, "3.03")));
|
||||
assertTrue(colissimo.getWeightBands().stream().noneMatch(band -> band.getChargeName() != null && band.getChargeName().contains("燃油")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void imilePostcodesResolveZoneAndGermanCoverage() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("32、");
|
||||
PayProduct italy = product(preview, "IT-iMile");
|
||||
assertTrue(italy.getRelatedSheet().contains("iMile邮编分区"));
|
||||
assertTrue(italy.getExtraLines().size() > 4000);
|
||||
assertTrue(italy.getExtraLines().stream().anyMatch(line -> "邮编".equals(line.getLineType()) && line.getLineText().contains("00010") && line.getLineText().contains("Province") && line.getLineText().contains("ROMA")));
|
||||
assertTrue(italy.getExtraLines().stream().anyMatch(line -> line.getLineText().endsWith(" | City")));
|
||||
assertTrue(italy.getExtraLines().stream().anyMatch(line -> line.getLineText().endsWith(" | Rural")));
|
||||
assertTrue(italy.getWeightBands().stream().anyMatch(band -> "City".equals(band.getZoneName()) && decimal(band, "2.85")));
|
||||
assertTrue(italy.getWeightBands().stream().anyMatch(band -> "Rural".equals(band.getZoneName()) && decimal(band, "3.68")));
|
||||
|
||||
PayProduct germany = product(preview, "DE-iMile");
|
||||
assertTrue(germany.getRelatedSheet().contains("iMile邮编分区"));
|
||||
assertTrue(germany.getExtraLines().size() > 7000);
|
||||
assertTrue(germany.getExtraLines().stream().anyMatch(line -> line.getLineText().contains("01001") && line.getLineText().contains("Active")));
|
||||
assertTrue(germany.getExtraLines().stream().noneMatch(line -> line.getLineText().contains("Lazio") || line.getLineText().contains("City")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void gweFileOverridesOnlyPanEuroSupplier() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("8.1");
|
||||
PayProduct dhl = product(preview, "DHL泛欧");
|
||||
assertEquals("GWE", dhl.getSupplierName());
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> decimal(band, "9.79")),
|
||||
dhl.getParseStatus() + " sur=" + dhl.getSurchargeCount() + " extra=" + dhl.getExtraCount() + " " + dhl.getRemark() + " "
|
||||
+ dhl.getWeightBands().stream().limit(8)
|
||||
.map(band -> band.getCountryName() + "/" + band.getWeightLabel() + "/" + band.getChargeName() + "/" + band.getPrice()).toList());
|
||||
PayProduct ctt = product(preview, "CTT");
|
||||
assertEquals("威狮", ctt.getSupplierName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void wholeWorkbookPreviewSerializes() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("9、");
|
||||
String json = tools.jackson.databind.json.JsonMapper.builder().build().writeValueAsString(preview);
|
||||
assertTrue(json.contains("\"productName\":\"Evri 48H\""));
|
||||
assertTrue(json.contains("2026-10-01") || json.contains("effectiveDate"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void yideCatalogIncludesPostcodeProducts() throws Exception
|
||||
{
|
||||
PayPreview preview = parse("26、");
|
||||
assertEquals(10, preview.getProducts().size());
|
||||
assertTrue(preview.getProducts().stream().anyMatch(item -> item.getExtraCount() != null && item.getExtraCount() > 0));
|
||||
PayProduct evri = preview.getProducts().stream().filter(item -> item.getSheetName().contains("英国evri")).findFirst().orElseThrow();
|
||||
assertFalse(evri.getWeightBands().isEmpty());
|
||||
assertFalse(evri.getSurcharges().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dictMatchReadsNamedSheetAndSkipsOthers() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/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"));
|
||||
assertEquals(1, preview.getProducts().size());
|
||||
PayProduct evri = preview.getProducts().get(0);
|
||||
assertEquals("Evri 48H", evri.getDictValue());
|
||||
assertEquals("Evri 48H", evri.getSheetName());
|
||||
assertFalse(evri.getWeightBands().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void panEuroKeepsTwoChargesPerCountryAndEverySurcharge() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/DHL泛欧.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayProduct dhl = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "DHL泛欧",
|
||||
List.of("DHL泛欧")).getProducts().get(0);
|
||||
assertPanEuroPrices(dhl);
|
||||
assertEquals(15, dhl.getSurcharges().size(), dhl.getSurcharges().stream()
|
||||
.map(fee -> fee.getFeeName() + "/" + fee.getChargeText()).toList().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void panEuroSheetInCatalogDoesNotCopyPricesAndKeepsHighRisk() throws Exception
|
||||
{
|
||||
Path dir = Path.of("D:/应付供应商");
|
||||
Assumptions.assumeTrue(Files.isDirectory(dir));
|
||||
Path file;
|
||||
try (Stream<Path> files = Files.list(dir))
|
||||
{
|
||||
file = files.filter(path -> path.getFileName().toString().startsWith("8、") && path.toString().endsWith(".xlsx"))
|
||||
.findFirst().orElseThrow();
|
||||
}
|
||||
PayProduct dhl = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "DHL泛欧",
|
||||
List.of("DHL泛欧")).getProducts().get(0);
|
||||
assertPanEuroPrices(dhl);
|
||||
assertTrue(dhl.getRelatedSheet() != null && dhl.getRelatedSheet().contains("高风险"));
|
||||
PaySurcharge afghan = dhl.getSurcharges().stream()
|
||||
.filter(fee -> "高风险地区附加费".equals(fee.getFeeName()) && "AF".equals(fee.getCalcParam())).findFirst().orElseThrow();
|
||||
assertEquals("Afghanistan", afghan.getZoneName());
|
||||
assertTrue(decimalFee(afghan, "10.4"));
|
||||
assertTrue(dhl.getSurcharges().stream().filter(fee -> "高风险地区附加费".equals(fee.getFeeName()) && "AF".equals(fee.getCalcParam())).count() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nlDhlVolumeStaysOnTheWeightBand() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/NL-DHL.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "NL-DHL",
|
||||
List.of("NL-DHL"));
|
||||
PayProduct dhl = preview.getProducts().get(0);
|
||||
assertEquals(10, dhl.getWeightBands().size(), dhl.getWeightBands().stream()
|
||||
.map(band -> band.getWeightLabel() + "/" + band.getChargeName() + "/" + band.getZoneName() + "/" + band.getPrice()
|
||||
+ "/" + band.getVolumeText())
|
||||
.toList().toString());
|
||||
assertTrue(dhl.getWeightBands().stream().noneMatch(band -> textOf(band.getChargeName()).contains("长")
|
||||
|| textOf(band.getZoneName()).contains("长")));
|
||||
assertTrue(dhl.getWeightBands().stream().allMatch(band -> band.getVolumeText() != null && band.getVolumeText().endsWith("cm³")));
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> "0<W≤5".equals(band.getWeightLabel())
|
||||
&& decimal(band, "3.49") && "10000cm³".equals(band.getVolumeText())
|
||||
&& "处理费".equals(band.getChargeName()) && "EUR/ITEM".equals(band.getPriceUnit())));
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> "20<W≤31.5".equals(band.getWeightLabel())
|
||||
&& decimal(band, "11.38") && "432000cm³".equals(band.getVolumeText())));
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> "0<W≤5".equals(band.getWeightLabel())
|
||||
&& decimal(band, "4.93") && "10000cm³".equals(band.getVolumeText())
|
||||
&& band.getCountryName().contains("比利时") && "BE".equals(band.getCountryCode())));
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> "20<W≤31.5".equals(band.getWeightLabel())
|
||||
&& decimal(band, "19.88") && "432000cm³".equals(band.getVolumeText())));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().startsWith("超尺寸附加费")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void royalMailReadsRemoteAreaTableAndSkipsRemarks() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/英国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());
|
||||
PayProduct rm = preview.getProducts().get(0);
|
||||
assertEquals(4, rm.getWeightBands().size());
|
||||
assertTrue(rm.getWeightBands().stream().anyMatch(band -> "0-5".equals(band.getWeightLabel()) && decimal(band, "1.62")
|
||||
&& band.getWeightMin() != null && band.getWeightMin().compareTo(java.math.BigDecimal.ZERO) == 0
|
||||
&& band.getWeightMax() != null && band.getWeightMax().compareTo(new java.math.BigDecimal("5")) == 0
|
||||
&& "每票".equals(band.getChargeName()) && "GBP/票".equals(band.getPriceUnit())));
|
||||
assertTrue(rm.getWeightBands().stream().anyMatch(band -> "15.001-20".equals(band.getWeightLabel()) && decimal(band, "5.18")
|
||||
&& band.getWeightMin() != null && band.getWeightMin().compareTo(new java.math.BigDecimal("15.001")) == 0
|
||||
&& band.getWeightMax() != null && band.getWeightMax().compareTo(new java.math.BigDecimal("20")) == 0));
|
||||
List<PaySurcharge> remote = rm.getSurcharges().stream().filter(fee -> "偏远地区附加费".equals(fee.getFeeName())).toList();
|
||||
assertEquals(3, remote.size(), remote.stream().map(fee -> fee.getChargeText() + " / " + fee.getFeeDesc()).toList().toString());
|
||||
assertTrue(remote.stream().anyMatch(fee -> fee.getChargeText().contains("1.80") && fee.getFeeDesc().contains("Area A")
|
||||
&& fee.getFeeDesc().contains("Highlands") && fee.getFeeDesc().contains("IV")));
|
||||
assertTrue(remote.stream().anyMatch(fee -> fee.getChargeText().contains("1.80") && fee.getFeeDesc().contains("Isle of Man")
|
||||
&& fee.getFeeDesc().contains("ZE")));
|
||||
assertTrue(remote.stream().anyMatch(fee -> fee.getChargeText().contains("1.00") && fee.getFeeDesc().contains("Area B")
|
||||
&& fee.getFeeDesc().contains("BT")));
|
||||
assertTrue(remote.stream().allMatch(fee -> "POSTCODE".equals(fee.getCalcRule())));
|
||||
assertTrue(remote.stream().anyMatch(fee -> fee.getCalcParam() != null && fee.getCalcParam().contains("IV")));
|
||||
assertTrue(remote.stream().anyMatch(fee -> fee.getCalcParam() != null && fee.getCalcParam().contains("BT")));
|
||||
assertTrue(rm.getSurcharges().stream().anyMatch(fee -> "签名费".equals(fee.getFeeName()) && fee.getChargeText().contains("0.7")
|
||||
&& "MANUAL".equals(fee.getCalcRule())));
|
||||
assertTrue(rm.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("超大附加费") && fee.getFeeDesc().contains("61")
|
||||
&& "SIZE".equals(fee.getCalcRule()) && fee.getCalcParam() != null && fee.getCalcParam().contains("box:61")));
|
||||
assertTrue(rm.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("超重")
|
||||
&& "WEIGHT".equals(fee.getCalcRule()) && "20".equals(fee.getCalcParam())));
|
||||
assertTrue(rm.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("重复单号") && fee.getChargeText().contains("23")));
|
||||
assertTrue(rm.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("未预报") && fee.getChargeText().contains("1.50")));
|
||||
assertTrue(rm.getSurcharges().stream().noneMatch(fee -> fee.getFeeDesc() != null && (fee.getFeeDesc().contains("计费重量")
|
||||
|| fee.getFeeDesc().contains("赔偿条款") || fee.getFeeDesc().contains("royalmail.com"))));
|
||||
assertTrue(rm.getServiceNote() == null || rm.getServiceNote().isBlank());
|
||||
assertTrue(rm.getExtraLines().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void evriOtherFeesKeepOneRowPerRegion() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/英国evri.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "英国evri", List.of("英国evri"));
|
||||
PayProduct evri = preview.getProducts().get(0);
|
||||
List<PaySurcharge> oversize = evri.getSurcharges().stream()
|
||||
.filter(fee -> "超尺寸费".equals(fee.getFeeName())).toList();
|
||||
assertEquals(6, oversize.size(), oversize.stream().map(fee -> fee.getZoneName() + "=" + fee.getChargeText()).toList().toString());
|
||||
assertTrue(oversize.stream().anyMatch(fee -> fee.getZoneName().contains("英国大陆") && fee.getChargeText().contains("6.99")));
|
||||
assertTrue(oversize.stream().anyMatch(fee -> fee.getZoneName().contains("高地") && fee.getChargeText().contains("9.67")));
|
||||
assertTrue(oversize.stream().anyMatch(fee -> fee.getZoneName().contains("马恩岛") && fee.getChargeText().contains("9.67")));
|
||||
assertTrue(evri.getSurcharges().stream().anyMatch(fee -> "周末服务费".equals(fee.getFeeName())
|
||||
&& fee.getZoneName().contains("北爱尔兰") && fee.getChargeText().contains("3.02")));
|
||||
assertTrue(evri.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().startsWith("旺季附加费")
|
||||
&& fee.getZoneName().contains("英国大陆") && fee.getChargeText().contains("0.15")));
|
||||
assertTrue(evri.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().startsWith("旺季附加费")
|
||||
&& fee.getZoneName().contains("马恩岛") && fee.getChargeText().contains("5.5")));
|
||||
assertTrue(evri.getSurcharges().stream().noneMatch(fee -> fee.getFeeName() != null
|
||||
&& (fee.getFeeName().contains("服务说明") || fee.getFeeName().contains("索赔"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spainMainlandKeepsAreasAndTwoSurcharges() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/西邮-西班牙(主岛).xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "西邮-西班牙(主岛)",
|
||||
List.of("西邮-西班牙(主岛)"));
|
||||
PayProduct spain = preview.getProducts().get(0);
|
||||
assertEquals(160, spain.getWeightBands().size());
|
||||
assertTrue(spain.getWeightBands().stream().anyMatch(band -> band.getWeightLabel().contains("0.01-0.25")
|
||||
&& "MAD-BCN Dense".equals(band.getZoneName()) && decimal(band, "2.27") && "每件".equals(band.getChargeName())
|
||||
&& "EUR/件".equals(band.getPriceUnit())));
|
||||
assertTrue(spain.getWeightBands().stream().anyMatch(band -> band.getWeightLabel().contains("0.01-0.25")
|
||||
&& "Rural Resto".equals(band.getZoneName()) && decimal(band, "3.12")));
|
||||
assertTrue(spain.getWeightBands().stream().anyMatch(band -> band.getWeightLabel().contains("29-30")
|
||||
&& "Denso Resto".equals(band.getZoneName()) && decimal(band, "10.08")));
|
||||
assertTrue(spain.getSurcharges().stream().anyMatch(fee -> "重新贴尾程标签附加费".equals(fee.getFeeName())
|
||||
&& fee.getChargeText().contains("0.35") && fee.getFeeDesc().contains("重新贴尾程标签")));
|
||||
List<PaySurcharge> oversize = spain.getSurcharges().stream().filter(fee -> "特大包裹收费".equals(fee.getFeeName())).toList();
|
||||
assertEquals(2, oversize.size(), oversize.stream().map(PaySurcharge::getChargeText).toList().toString());
|
||||
assertTrue(oversize.stream().anyMatch(fee -> fee.getChargeText().contains("35%") && fee.getFeeDesc().contains("120")
|
||||
&& "SIZE".equals(fee.getCalcRule()) && fee.getCalcParam() != null && fee.getCalcParam().contains("join:or")));
|
||||
assertTrue(oversize.stream().anyMatch(fee -> fee.getChargeText().contains("70%")
|
||||
&& "SIZE".equals(fee.getCalcRule()) && fee.getCalcParam() != null && fee.getCalcParam().contains("join:and")));
|
||||
assertTrue(spain.getSurcharges().stream().anyMatch(fee -> "重新贴尾程标签附加费".equals(fee.getFeeName())
|
||||
&& "MANUAL".equals(fee.getCalcRule())));
|
||||
assertTrue(spain.getSurcharges().stream().noneMatch(fee -> fee.getFeeName() != null
|
||||
&& (fee.getFeeName().contains("禁运") || fee.getFeeName().contains("条款"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spainGlsSplitsCountryCodesAndServiceFees() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/西班牙GLS.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "西班牙GLS",
|
||||
List.of("西班牙GLS"));
|
||||
PayProduct gls = preview.getProducts().get(0);
|
||||
assertEquals("OK", gls.getParseStatus());
|
||||
assertEquals(476, gls.getWeightBands().size());
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "DE".equals(band.getCountryCode()) && "德国".equals(band.getCountryName())
|
||||
&& "A".equals(band.getZoneName()) && "1KG".equals(band.getWeightLabel()) && decimal(band, "5.12")
|
||||
&& band.getWeightMin().compareTo(java.math.BigDecimal.ZERO) == 0
|
||||
&& band.getWeightMax().compareTo(java.math.BigDecimal.ONE) == 0
|
||||
&& "每件".equals(band.getChargeName()) && "EUR/件".equals(band.getPriceUnit())));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "NL".equals(band.getCountryCode()) && "荷兰".equals(band.getCountryName())
|
||||
&& "A".equals(band.getZoneName()) && decimal(band, "5.12")));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "PL".equals(band.getCountryCode()) && "波兰".equals(band.getCountryName())));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "FR".equals(band.getCountryCode()) && "法国".equals(band.getCountryName())
|
||||
&& "B".equals(band.getZoneName()) && "1KG".equals(band.getWeightLabel()) && decimal(band, "6.38")));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "HU".equals(band.getCountryCode()) && "匈牙利".equals(band.getCountryName())
|
||||
&& "D".equals(band.getZoneName()) && "3KG".equals(band.getWeightLabel()) && decimal(band, "8.15")));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "GB".equals(band.getCountryCode()) && "英国".equals(band.getCountryName())
|
||||
&& "E".equals(band.getZoneName()) && "1KG".equals(band.getWeightLabel()) && decimal(band, "7.11")));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "FR2".equals(band.getCountryCode()) && "法国-科西嘉岛".equals(band.getCountryName())
|
||||
&& "G".equals(band.getZoneName()) && "1KG".equals(band.getWeightLabel()) && decimal(band, "16.01")));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "CY".equals(band.getCountryCode()) && "塞浦路斯".equals(band.getCountryName())
|
||||
&& "H".equals(band.getZoneName()) && decimal(band, "13.01")));
|
||||
assertTrue(gls.getWeightBands().stream().anyMatch(band -> "IT".equals(band.getCountryCode()) && "Ad.kg.".equals(band.getWeightLabel())
|
||||
&& decimal(band, "1.07") && "每公斤".equals(band.getChargeName()) && "EUR/公斤".equals(band.getPriceUnit())
|
||||
&& band.getWeightMin() == null));
|
||||
assertEquals(4, gls.getSurcharges().size());
|
||||
assertTrue(gls.getSurcharges().stream().anyMatch(fee -> "取还服务".equals(fee.getFeeName()) && fee.getChargeText().contains("7")
|
||||
&& fee.getFeeDesc().contains("送货上门") && decimalFee(fee, "7")));
|
||||
assertTrue(gls.getSurcharges().stream().anyMatch(fee -> "取货发货服务".equals(fee.getFeeName()) && decimalFee(fee, "7")));
|
||||
assertTrue(gls.getSurcharges().stream().anyMatch(fee -> "现金服务".equals(fee.getFeeName()) && fee.getFeeDesc().contains("交货付款")
|
||||
&& decimalFee(fee, "6")));
|
||||
assertTrue(gls.getSurcharges().stream().anyMatch(fee -> "灵活配送服务".equals(fee.getFeeName()) && "free".equalsIgnoreCase(fee.getChargeText())
|
||||
&& decimalFee(fee, "0") && fee.getFeeDesc().contains("灵活性")));
|
||||
assertTrue(gls.getSurcharges().stream().noneMatch(fee -> fee.getFeeName() != null && (fee.getFeeName().contains("格陵兰")
|
||||
|| fee.getFeeName().contains("转账") || fee.getFeeName().contains("备注") || fee.getFeeName().contains("能源"))));
|
||||
assertTrue(gls.getServiceNote() == null || gls.getServiceNote().isBlank());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ctt48hFileKeepsSurchargeTablesOutOfWeightBands() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/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);
|
||||
assertTrue(ctt.getWeightBands().stream().allMatch(band -> "派送费".equals(band.getChargeName())),
|
||||
ctt.getWeightBands().stream().map(band -> band.getChargeName() + "/" + band.getZoneName() + "/" + band.getPrice()).toList().toString());
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> decimal(band, "2.05") && band.getZoneName().contains("马德里")));
|
||||
assertTrue(ctt.getWeightBands().stream().noneMatch(band -> bandText(band).contains("偏远") || bandText(band).contains("额外")
|
||||
|| bandText(band).toLowerCase().contains("line-haul")));
|
||||
PaySurcharge balearic = ctt.getSurcharges().stream()
|
||||
.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")));
|
||||
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公里")));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> "超重附加费".equals(fee.getFeeName()) && decimalFee(fee, "5.98")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spainCttMainlandReadsBillableConcepts() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/西班牙CTT带电专线(主要地区).xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(),
|
||||
"西班牙CTT带电专线(主要地区)", List.of("西班牙CTT带电专线(主要地区)"));
|
||||
PayProduct ctt = preview.getProducts().get(0);
|
||||
assertEquals("OK", ctt.getParseStatus());
|
||||
assertEquals("单票价", ctt.getChargeType());
|
||||
assertTrue(ctt.getExtraPerKg() == null || ctt.getExtraPerKg().isBlank());
|
||||
assertEquals(150, ctt.getWeightBands().size());
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "1".equals(band.getWeightLabel())
|
||||
&& "Mad High Density".equals(band.getZoneName()) && decimal(band, "2.11")));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "30".equals(band.getWeightLabel())
|
||||
&& "Portugal".equals(band.getZoneName()) && decimal(band, "11.96")));
|
||||
assertEquals(15, ctt.getSurcharges().size());
|
||||
assertEquals(5, ctt.getSurcharges().stream().filter(fee -> "Extra Line-Haul (*)".equals(fee.getFeeName())).count());
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> "Extra Line-Haul (*)".equals(fee.getFeeName())
|
||||
&& "Mad High Density".equals(fee.getZoneName()) && decimalFee(fee, "0.12") && "EUR".equals(fee.getCurrency())
|
||||
&& "MANUAL".equals(fee.getCalcRule())));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> "Extra Line-Haul Additional Kg (**)".equals(fee.getFeeName())
|
||||
&& "Portugal".equals(fee.getZoneName()) && decimalFee(fee, "0.08")));
|
||||
List<PaySurcharge> pudo = ctt.getSurcharges().stream().filter(fee -> "Pudo".equals(fee.getFeeName())).toList();
|
||||
assertEquals(1, pudo.size());
|
||||
assertTrue(pudo.get(0).getChargeText().contains("0.10") && pudo.get(0).getChargeText().contains("欧元"));
|
||||
assertTrue(decimalFee(pudo.get(0), "0.10"));
|
||||
assertEquals("", pudo.get(0).getZoneName() == null ? "" : pudo.get(0).getZoneName());
|
||||
assertEquals(1, ctt.getSurcharges().stream().filter(fee -> fee.getFeeName().contains("COD")).count());
|
||||
assertEquals(1, ctt.getSurcharges().stream().filter(fee -> "Reverse Logistics".equals(fee.getFeeName())).count());
|
||||
assertEquals(1, ctt.getSurcharges().stream().filter(fee -> "Fuel rate".equals(fee.getFeeName())).count());
|
||||
assertEquals(1, ctt.getSurcharges().stream().filter(fee -> "Volumetric weight".equals(fee.getFeeName())).count());
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> fee.getFeeName().contains("COD")
|
||||
&& fee.getChargeText().contains("1%") && fee.getAmount() == null));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> "Volumetric weight".equals(fee.getFeeName())
|
||||
&& fee.getChargeText().contains("90cm") && fee.getAmount() == null && "MANUAL".equals(fee.getCalcRule())));
|
||||
assertTrue(ctt.getSurcharges().stream().noneMatch(fee -> fee.getFeeName() != null && (fee.getFeeName().contains("计费重量")
|
||||
|| fee.getFeeName().contains("4.95") || fee.getFeeName().contains("丢货") || fee.getFeeName().contains("退件"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spainCttIslandsReadsFiveRateTables() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/西班牙CTT带电专线(岛屿+其他地区).xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(),
|
||||
"西班牙CTT带电专线(岛屿+其他地区)", List.of("西班牙CTT带电专线(岛屿+其他地区)"));
|
||||
PayProduct ctt = preview.getProducts().get(0);
|
||||
assertEquals("OK", ctt.getParseStatus());
|
||||
assertEquals("EUR", ctt.getCurrency());
|
||||
assertEquals("续重计费", ctt.getChargeType());
|
||||
assertTrue(covers(ctt.getExtraPerKg(), "1.86"));
|
||||
assertTrue(covers(ctt.getExtraPerKg(), "5.79"));
|
||||
assertEquals(48, ctt.getWeightBands().size());
|
||||
assertEquals(4, ctt.getSurcharges().size());
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "2. INSULAR RATES / STANDARD".equals(band.getSectionName())
|
||||
&& "Balearic Islands Economy 48H / Majorca".equals(band.getZoneName()) && "西班牙".equals(band.getCountryName()) && "ES".equals(band.getCountryCode())
|
||||
&& "Up to 1 kg".equals(band.getWeightLabel()) && decimal(band, "3.39") && "每件".equals(band.getChargeName())
|
||||
&& "EUR/件".equals(band.getPriceUnit()) && band.getWeightMax().compareTo(java.math.BigDecimal.ONE) == 0));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "2. INSULAR RATES / STANDARD".equals(band.getSectionName())
|
||||
&& "Canary Islands / Minor Islands".equals(band.getZoneName()) && "Additional Kg".equals(band.getWeightLabel())
|
||||
&& decimal(band, "1.86") && "每公斤".equals(band.getChargeName()) && band.getWeightMin() == null));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "2. INSULAR RATES / EXPRESS".equals(band.getSectionName())
|
||||
&& "Canary Islands / Mayor Islands".equals(band.getZoneName()) && "Up to 1 kg".equals(band.getWeightLabel())
|
||||
&& decimal(band, "7.46")));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "Price WITH CUSTOMS CLEARANCE / STANDARD".equals(band.getSectionName())
|
||||
&& "Canary Islands (*) / Mayor Islands".equals(band.getZoneName()) && decimal(band, "5.33")));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "Price WITH CUSTOMS CLEARANCE / EXPRESS".equals(band.getSectionName())
|
||||
&& "Portuguese Island / Island".equals(band.getZoneName()) && "Additional Kg".equals(band.getWeightLabel())
|
||||
&& decimal(band, "5.79")));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> band.getSectionName().contains("CEUTA")
|
||||
&& "Ceuta".equals(band.getZoneName()) && "Up to 1 kg".equals(band.getWeightLabel()) && decimal(band, "7.61")));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> band.getSectionName().contains("CEUTA")
|
||||
&& "Andorra".equals(band.getZoneName()) && "Additional Kg".equals(band.getWeightLabel()) && decimal(band, "2.85")));
|
||||
assertTrue(ctt.getSurcharges().stream().allMatch(fee -> "Cubic Capacity".equals(fee.getFeeName()) && fee.getAmount() == null
|
||||
&& "MANUAL".equals(fee.getCalcRule())));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> "2. INSULAR RATES / STANDARD".equals(fee.getSectionName())
|
||||
&& fee.getChargeText().contains("167 kg/m3") && fee.getChargeText().contains("333 kg/m3")
|
||||
&& fee.getFeeDesc().contains("Balearic Islands Economy 48H / Majorca")
|
||||
&& fee.getFeeDesc().contains("Canary Islands / Interislands")));
|
||||
assertTrue(ctt.getSurcharges().stream().anyMatch(fee -> "2. INSULAR RATES / EXPRESS".equals(fee.getSectionName())
|
||||
&& fee.getFeeDesc().contains("Portuguese Island / Island")));
|
||||
assertTrue(ctt.getSurcharges().stream().noneMatch(fee -> fee.getSectionName() != null && fee.getSectionName().contains("CEUTA")));
|
||||
assertTrue(ctt.getWeightBands().stream().noneMatch(band -> band.getZoneName() != null && band.getZoneName().contains("19E")));
|
||||
assertTrue(ctt.getSurcharges().stream().noneMatch(fee -> fee.getFeeName() != null && (fee.getFeeName().contains("计费重量")
|
||||
|| fee.getFeeName().contains("4.95") || fee.getFeeName().contains("退货"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void spainCttFillsCountryAndContinuedWeight() throws Exception
|
||||
{
|
||||
Path file = Path.of("D:/payment/西班牙CTT.xlsx");
|
||||
Assumptions.assumeTrue(Files.exists(file));
|
||||
PayPreview preview = parser.parseMatched(Files.readAllBytes(file), file.getFileName().toString(), "西班牙CTT",
|
||||
List.of("西班牙CTT"));
|
||||
PayProduct ctt = preview.getProducts().get(0);
|
||||
assertEquals("OK", ctt.getParseStatus());
|
||||
assertEquals("西班牙", ctt.getCountryScope());
|
||||
assertEquals("续重计费", ctt.getChargeType());
|
||||
assertTrue(covers(ctt.getExtraPerKg(), "0.82"));
|
||||
assertEquals(72, ctt.getWeightBands().size());
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "西班牙".equals(band.getCountryName()) && "ES".equals(band.getCountryCode())
|
||||
&& "Mainland / Mad High Density".equals(band.getZoneName()) && "1kg".equals(band.getWeightLabel())
|
||||
&& decimal(band, "2.1") && "每件".equals(band.getChargeName())
|
||||
&& band.getWeightMin().compareTo(java.math.BigDecimal.ZERO) == 0
|
||||
&& band.getWeightMax().compareTo(java.math.BigDecimal.ONE) == 0));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "Mainland / Portugal".equals(band.getZoneName())
|
||||
&& "5kg".equals(band.getWeightLabel()) && decimal(band, "3.95")
|
||||
&& band.getWeightMin().compareTo(new java.math.BigDecimal("4")) == 0));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "Balearic Islands Economy 48H / Majorca".equals(band.getZoneName())
|
||||
&& "Kg.ad.".equals(band.getWeightLabel()) && decimal(band, "0.82") && "每公斤".equals(band.getChargeName())
|
||||
&& band.getWeightMin() == null));
|
||||
assertTrue(ctt.getWeightBands().stream().anyMatch(band -> "Canary Islands / Minor Islands".equals(band.getZoneName())
|
||||
&& "1kg".equals(band.getWeightLabel()) && decimal(band, "13.11")));
|
||||
assertTrue(ctt.getWeightBands().stream().noneMatch(band -> band.getWeightLabel() != null && band.getWeightLabel().contains("预估")));
|
||||
}
|
||||
|
||||
private PayPreview parse(String prefix) throws Exception
|
||||
{
|
||||
Path dir = Path.of("D:/应付供应商");
|
||||
Path file;
|
||||
try (Stream<Path> files = Files.list(dir))
|
||||
{
|
||||
file = files.filter(path -> path.getFileName().toString().startsWith(prefix) && path.toString().endsWith(".xlsx"))
|
||||
.findFirst().orElseThrow();
|
||||
}
|
||||
return parser.parse(Files.readAllBytes(file), file.getFileName().toString());
|
||||
}
|
||||
|
||||
private void assertPanEuroPrices(PayProduct dhl)
|
||||
{
|
||||
assertEquals(52, dhl.getWeightBands().size(), dhl.getWeightBands().stream()
|
||||
.map(band -> band.getCountryCode() + "/" + band.getChargeName() + "/" + band.getPrice()).toList().toString());
|
||||
assertEquals(26, dhl.getWeightBands().stream().map(PayWeightBand::getCountryCode).distinct().count());
|
||||
assertEquals(26, dhl.getWeightBands().stream().filter(band -> "处理费".equals(band.getChargeName())).count());
|
||||
assertEquals(26, dhl.getWeightBands().stream().filter(band -> "速递运费".equals(band.getChargeName())).count());
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> "DK".equals(band.getCountryCode())
|
||||
&& "处理费".equals(band.getChargeName()) && decimal(band, "9.71") && "EUR/ITEM".equals(band.getPriceUnit())));
|
||||
assertTrue(dhl.getWeightBands().stream().anyMatch(band -> "DK".equals(band.getCountryCode())
|
||||
&& "速递运费".equals(band.getChargeName()) && decimal(band, "0.23") && "EUR/KG".equals(band.getPriceUnit())));
|
||||
assertTrue(dhl.getServiceNote() != null && dhl.getServiceNote().contains("报价已含燃油"));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "其他费用".equals(fee.getSectionName())
|
||||
&& "道路通行费".equals(fee.getFeeName()) && decimalFee(fee, "0.19")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "旺季附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.19")
|
||||
&& fee.getEffectiveStart() != null));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "高峰旺季附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.5")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "超尺寸超重费".equals(fee.getFeeName()) && decimalFee(fee, "14")
|
||||
&& "产生时收取".equals(fee.getChargeTiming())));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "路由码选择附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.49")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "重量信息修正附加费".equals(fee.getFeeName()) && decimalFee(fee, "0.99")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "未预报附加费".equals(fee.getFeeName())
|
||||
&& fee.getChargeText().contains("31.5kg")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "托收附加费".equals(fee.getFeeName()) && decimalFee(fee, "2.2")
|
||||
&& textOf(fee.getFeeDesc()).contains("Supplemental")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "提供虚假信息罚款费".equals(fee.getFeeName()) && decimalFee(fee, "18")
|
||||
&& textOf(fee.getFeeDesc()).contains("故意")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "高风险地区附加费".equals(fee.getFeeName())
|
||||
&& textOf(fee.getChargeText()).contains("高风险")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "退件费".equals(fee.getFeeName()) && decimalFee(fee, "20")
|
||||
&& textOf(fee.getFeeDesc()).contains("空运")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "退件费".equals(fee.getFeeName()) && decimalFee(fee, "10")
|
||||
&& textOf(fee.getFeeDesc()).contains("陆运")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "重派费".equals(fee.getFeeName())
|
||||
&& textOf(fee.getChargeText()).contains("同正向运费")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "退件操作费".equals(fee.getFeeName()) && decimalFee(fee, "0.5")));
|
||||
assertTrue(dhl.getSurcharges().stream().anyMatch(fee -> "重派操作费".equals(fee.getFeeName()) && decimalFee(fee, "1")));
|
||||
}
|
||||
|
||||
private PayProduct product(PayPreview preview, String name)
|
||||
{
|
||||
return preview.getProducts().stream().filter(item -> name.equals(item.getProductName())).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
private boolean covers(String extraPerKg, String price)
|
||||
{
|
||||
if (extraPerKg == null || extraPerKg.isBlank())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
java.math.BigDecimal value = new java.math.BigDecimal(price);
|
||||
String[] parts = extraPerKg.split("~");
|
||||
java.math.BigDecimal low = new java.math.BigDecimal(parts[0]);
|
||||
java.math.BigDecimal high = new java.math.BigDecimal(parts[parts.length - 1]);
|
||||
return low.compareTo(value) <= 0 && high.compareTo(value) >= 0;
|
||||
}
|
||||
|
||||
private String textOf(String value)
|
||||
{
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
private boolean decimal(PayWeightBand band, String price)
|
||||
{
|
||||
return band.getPrice() != null && band.getPrice().compareTo(new java.math.BigDecimal(price)) == 0;
|
||||
}
|
||||
|
||||
private boolean decimalFee(PaySurcharge fee, String amount)
|
||||
{
|
||||
return fee.getAmount() != null && fee.getAmount().compareTo(new java.math.BigDecimal(amount)) == 0;
|
||||
}
|
||||
|
||||
private String bandText(PayWeightBand band)
|
||||
{
|
||||
return (band.getChargeName() == null ? "" : band.getChargeName()) + " "
|
||||
+ (band.getSectionName() == null ? "" : band.getSectionName()) + " "
|
||||
+ (band.getZoneName() == null ? "" : band.getZoneName());
|
||||
}
|
||||
|
||||
private String zoneTail(PayWeightBand band)
|
||||
{
|
||||
String zone = band.getZoneName() == null ? "" : band.getZoneName();
|
||||
return zone.contains("英国大陆") ? "英国大陆" : zone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.bms.price.pay;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.bms.price.domain.PayPostcodeLine;
|
||||
|
||||
class PayZoneMatcherTest
|
||||
{
|
||||
private static final List<String> ZONES = List.of(
|
||||
"UK Mainland\n英国大陆",
|
||||
"Highlands& Islands\n高地和岛屿",
|
||||
"Channel Islands\n(海峡群岛,包括Isle of Scilly锡利岛)",
|
||||
"Northern Ireland\n北爱尔兰",
|
||||
"BFPO\n(军事地址)",
|
||||
"Isle of Man\n马恩岛");
|
||||
|
||||
@Test
|
||||
void welshPostcodeFallsBackToMainland()
|
||||
{
|
||||
PayZoneMatcher.Decision decision = PayZoneMatcher.resolve(ZONES, evriLines(), "LL22 8PJ");
|
||||
assertNull(decision.getMessage());
|
||||
assertEquals("UK Mainland\n英国大陆", decision.getZoneName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void northernIrelandAndHighlandsComeFromThePostcodeBook()
|
||||
{
|
||||
assertEquals("Northern Ireland\n北爱尔兰", PayZoneMatcher.resolve(ZONES, evriLines(), "BT1 1AA").getZoneName());
|
||||
assertEquals("Highlands& Islands\n高地和岛屿", PayZoneMatcher.resolve(ZONES, evriLines(), "IV1 1AA").getZoneName());
|
||||
assertEquals("Channel Islands\n(海峡群岛,包括Isle of Scilly锡利岛)",
|
||||
PayZoneMatcher.resolve(ZONES, evriLines(), "TR21 1AA").getZoneName());
|
||||
assertEquals("Isle of Man\n马恩岛", PayZoneMatcher.resolve(ZONES, evriLines(), "IM1 1AA").getZoneName());
|
||||
assertEquals("BFPO\n(军事地址)", PayZoneMatcher.resolve(ZONES, evriLines(), "BF1 0AA").getZoneName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void productNameKeepsTheCarrierToken()
|
||||
{
|
||||
assertEquals(List.of("evri"), PayZoneMatcher.searchTokens("英国evri"));
|
||||
assertEquals(List.of("ubi", "evri"), PayZoneMatcher.searchTokens("UBI英国EVRi普"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingPostcodeIsRejected()
|
||||
{
|
||||
PayZoneMatcher.Decision decision = PayZoneMatcher.resolve(ZONES, evriLines(), " ");
|
||||
assertTrue(decision.getMessage().contains("没有邮编"));
|
||||
}
|
||||
|
||||
private static List<PayPostcodeLine> evriLines()
|
||||
{
|
||||
return List.of(
|
||||
line("Channel Islands", "GY"),
|
||||
line("Isle of Man", "IM"),
|
||||
line("Northern Ireland", "BT"),
|
||||
line("Scilly Isles", "TR21"),
|
||||
line("Scottish H&I (Finance)", "IV"),
|
||||
line("Scottish H&I (Finance)", "HS"),
|
||||
line("Shetlands", "ZE"));
|
||||
}
|
||||
|
||||
private static PayPostcodeLine line(String region, String postcode)
|
||||
{
|
||||
PayPostcodeLine line = new PayPostcodeLine();
|
||||
line.setRegionName(region);
|
||||
line.setPostcode(postcode);
|
||||
return line;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user