新增提单管理和提单计费页面

This commit is contained in:
liuminghui
2026-11-11 16:36:17 +08:00
parent 7869205afc
commit efd92b5663
47 changed files with 1712 additions and 369 deletions
@@ -19,7 +19,7 @@ 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.enums.PaymentMesEnum;
import com.bms.price.domain.PayPostcodeGroup;
import com.bms.price.domain.PayPostcodeLine;
import com.bms.price.service.IPayPostcodeService;
@@ -74,12 +74,12 @@ public class PayPostcodeController extends BaseController
{
if (file == null || file.isEmpty())
{
throw new ServiceException("请选择 Excel 文件");
throw PaymentMesEnum.BMS_PAY_PICK_XLS.exception();
}
String name = file.getOriginalFilename() == null ? "" : file.getOriginalFilename().toLowerCase(Locale.ROOT);
if (!name.endsWith(".xlsx"))
{
throw new ServiceException("只支持 xlsx 文件");
throw PaymentMesEnum.BMS_PAY_XLSX_ONLY.exception();
}
return success(payPostcodeService.importExcel(file.getBytes(), file.getOriginalFilename(), getUsername()));
}
@@ -0,0 +1,126 @@
package com.bms.web.controller.price;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
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.enums.BusinessType;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.airbill.domain.SrvBillAirFeeImportRow;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeCommand;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeQuery;
import com.bms.price.domain.vo.SrvBillAirQuery;
import com.bms.price.service.ISrvBillAirFeeService;
import com.bms.price.service.ISrvBillAirService;
/**
* 空运、报关、清关三个角色往提单上添加费用。
*/
@RestController
@RequestMapping("/price/airFee")
public class AirFeeRoleController extends BaseController
{
private static final int MAX_BILLS = 50;
@Autowired
private ISrvBillAirFeeService srvBillAirFeeService;
@Autowired
private ISrvBillAirService srvBillAirService;
/** 按提单号查出可绑定的提单,支持一次多选。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@GetMapping("/bills")
public AjaxResult bills(@RequestParam(value = "billCodes", required = false) String billCodes)
{
List<String> codes = splitCodes(billCodes);
if (codes.isEmpty())
{
return success(List.of());
}
if (codes.size() > MAX_BILLS)
{
return error("单次最多选择" + MAX_BILLS + "个提单");
}
SrvBillAirQuery query = new SrvBillAirQuery();
query.setBillCodes(codes);
return success(srvBillAirService.selectSrvBillAirList(query));
}
/** 查询已绑定到这些提单、且属于当前入口的费用。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@GetMapping("/list")
public AjaxResult list(@RequestParam("scene") String scene, @RequestParam(value = "billCodes", required = false) String billCodes)
{
SrvBillAirRoleFeeQuery query = new SrvBillAirRoleFeeQuery();
query.setScene(scene);
query.setBillCodes(splitCodes(billCodes));
return success(srvBillAirFeeService.listRoleFees(query));
}
/** 保存当前入口的费用。空运写入 b 段,报关和清关写入 c 段。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@Log(title = "提单角色费用", businessType = BusinessType.UPDATE)
@PostMapping("/save")
public AjaxResult save(@RequestBody SrvBillAirRoleFeeCommand command)
{
return success(srvBillAirFeeService.saveRole(command));
}
/** 下载导入模板,模板中带一行示例。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response)
{
SrvBillAirFeeImportRow example = new SrvBillAirFeeImportRow();
example.setFeeName("燃油附加费");
example.setPrice(new BigDecimal("1000"));
example.setCurrency("EUR");
example.setRemark("导入时需清空这行示例数据");
List<SrvBillAirFeeImportRow> rows = new ArrayList<>();
rows.add(example);
new ExcelUtil<>(SrvBillAirFeeImportRow.class).exportExcel(response, rows, "提单费用导入模板");
}
/** 解析导入文件,不直接落库。示例行会被跳过。 */
@PreAuthorize("@ss.hasAnyPermi('price:airFee:air,price:airFee:declare,price:airFee:clear')")
@Log(title = "提单角色费用导入", businessType = BusinessType.IMPORT)
@PostMapping("/import")
public AjaxResult importFee(MultipartFile file, @RequestParam("scene") String scene) throws Exception
{
if (file == null || file.isEmpty())
{
return error("请选择导入文件");
}
return success(srvBillAirFeeService.parseImport(scene, file.getInputStream()));
}
private static List<String> splitCodes(String billCodes)
{
LinkedHashSet<String> codes = new LinkedHashSet<>();
if (billCodes != null)
{
for (String part : billCodes.split("[,,\\s]+"))
{
if (part != null && !part.isBlank())
{
codes.add(part.trim());
}
}
}
return new ArrayList<>(codes);
}
}
@@ -22,7 +22,7 @@ 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.enums.PaymentMesEnum;
import com.bms.common.utils.file.FileUtils;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.domain.ChannelProductMap;
@@ -86,7 +86,8 @@ public class PayProductController extends BaseController
@PostMapping("/order/{billId}")
public AjaxResult generateOrderCost(@PathVariable String billId)
{
return success(payOrderCostService.generate(billId));
payOrderCostService.generate(billId);
return success("操作成功");
}
/** 下载某一版导入时上传的源文件 */
@@ -260,16 +261,16 @@ public class PayProductController extends BaseController
{
if (file == null || file.isEmpty())
{
throw new ServiceException("请选择价卡文件");
throw PaymentMesEnum.BMS_PAY_PICK_FILE.exception();
}
String filename = file.getOriginalFilename();
if (filename == null || !filename.toLowerCase(Locale.ROOT).endsWith(".xlsx"))
{
throw new ServiceException("请上传xlsx价卡");
throw PaymentMesEnum.BMS_PAY_XLSX.exception();
}
if (file.getSize() > 30L * 1024 * 1024)
{
throw new ServiceException("价卡文件不能超过30MB");
throw PaymentMesEnum.BMS_PAY_FILE_30M.exception();
}
}
@@ -0,0 +1,194 @@
package com.bms.common.enums;
import java.util.HashMap;
import java.util.Map;
import com.bms.common.exception.ServiceException;
/**
* 应付报错。只收没有拼接变量的固定提示。
* code 以 BMS_PAY 开头,name 是这条提示用在哪一步,cnMes 是弹出的中文,enMes 先留空。
*/
public enum PaymentMesEnum
{
BMS_PAY_PICK_ORDER("BMS_PAY_PICK_ORDER", "生成应付校验订单", "请选择订单"),
BMS_PAY_ORDER_MISS("BMS_PAY_ORDER_MISS", "生成应付校验订单", "订单不存在"),
BMS_PAY_NO_COST("BMS_PAY_NO_COST", "生成应付计算结果", "没有算出应付费用"),
BMS_PAY_NO_ZONE("BMS_PAY_NO_ZONE", "生成应付匹配邮编", "邮编没有对上价卡地区"),
BMS_PAY_NO_CHANNEL("BMS_PAY_NO_CHANNEL", "生成应付匹配渠道", "订单没有渠道服务商"),
BMS_PAY_CH_SYNC("BMS_PAY_CH_SYNC", "生成应付匹配渠道", "订单没有渠道服务商,或渠道尚未同步"),
BMS_PAY_NO_PRODUCT("BMS_PAY_NO_PRODUCT", "应付计费匹配产品", "没有应付产品"),
BMS_PAY_NO_WEIGHT("BMS_PAY_NO_WEIGHT", "订单判断重量", "订单没有计费重"),
BMS_PAY_PICK_PROD("BMS_PAY_PICK_PROD", "应付产品保存", "请先勾选要保存的产品"),
BMS_PAY_PROD_LIMIT("BMS_PAY_PROD_LIMIT", "应付产品保存", "一次最多保存80个产品"),
BMS_PAY_PROD_FIELD("BMS_PAY_PROD_FIELD", "应付产品保存", "产品名称、工作表和文件摘要不能为空"),
BMS_PAY_PICK_EDIT("BMS_PAY_PICK_EDIT", "应付产品修改", "请选择要修改的产品"),
BMS_PAY_PROD_MISS("BMS_PAY_PROD_MISS", "应付产品修改", "产品不存在或已删除"),
BMS_PAY_TPL_ONLY("BMS_PAY_TPL_ONLY", "应付产品修改", "渠道价格请在渠道页面保存,这里只改公共模板"),
BMS_PAY_SAVE_CH("BMS_PAY_SAVE_CH", "渠道应付价格", "请先保存渠道,再维护价格"),
BMS_PAY_NO_PRICE("BMS_PAY_NO_PRICE", "渠道应付价格", "没有可保存的价格"),
BMS_PAY_REIMPORT("BMS_PAY_REIMPORT", "应付价卡导入", "重新导入时请使用单个产品文件,或先选定渠道上的应付产品"),
BMS_PAY_PICK_IMP("BMS_PAY_PICK_IMP", "应付价卡导入", "请先选择一个已导入的应付产品"),
BMS_PAY_DICT_EMPTY("BMS_PAY_DICT_EMPTY", "应付产品字典", "应付产品字典还没有数据"),
BMS_PAY_TERM_START("BMS_PAY_TERM_START", "应付有效期", "请填写生效开始"),
BMS_PAY_TERM_END("BMS_PAY_TERM_END", "应付有效期", "生效结束不能早于生效开始"),
BMS_PAY_PICK_VER("BMS_PAY_PICK_VER", "应付有效期", "请选择要修改的版本"),
BMS_PAY_TERM_MISS("BMS_PAY_TERM_MISS", "应付有效期", "有效期不存在"),
BMS_PAY_TERM_STAT("BMS_PAY_TERM_STAT", "应付有效期", "状态只能是待生效、生效或已失效"),
BMS_PAY_FILE_DOWN("BMS_PAY_FILE_DOWN", "应付价卡源文件", "源文件下载失败"),
BMS_PAY_NO_SOURCE("BMS_PAY_NO_SOURCE", "应付价卡源文件", "这一版没有可下载的源文件"),
BMS_PAY_FILE_UP("BMS_PAY_FILE_UP", "应付价卡源文件", "价卡文件上传失败"),
BMS_PAY_FILE_WAIT("BMS_PAY_FILE_WAIT", "应付价卡源文件", "源文件还没有上传成功,请重新选择价卡后再保存"),
BMS_PAY_CH_FIRST("BMS_PAY_CH_FIRST", "渠道绑定产品", "请先保存渠道,再选择产品"),
BMS_PAY_CH_MISS("BMS_PAY_CH_MISS", "渠道绑定产品", "渠道不存在或渠道编码为空"),
BMS_PAY_PICK_CH("BMS_PAY_PICK_CH", "渠道绑定产品", "请先勾选渠道"),
BMS_PAY_PICK_SIDE("BMS_PAY_PICK_SIDE", "渠道绑定产品", "请至少选择应付或应收产品"),
BMS_PAY_BIND_NONE("BMS_PAY_BIND_NONE", "渠道绑定产品", "没有可绑定的渠道"),
BMS_PAY_CH_PROD("BMS_PAY_CH_PROD", "渠道应付产品", "渠道还没有选择应付产品"),
BMS_PAY_OUT_TERM("BMS_PAY_OUT_TERM", "应付价格有效期", "订单时间没有落在价格有效期内"),
BMS_PAY_TWO_VER("BMS_PAY_TWO_VER", "应付价格对比", "请选择两个价格版本"),
BMS_PAY_AR_AP("BMS_PAY_AR_AP", "收付类型", "收付类型只能是应收AR或应付AP"),
BMS_PAY_AP_MISS("BMS_PAY_AP_MISS", "应付产品查询", "应付产品不存在或已删除"),
BMS_PAY_PICK_FILE("BMS_PAY_PICK_FILE", "应付价卡文件", "请选择价卡文件"),
BMS_PAY_XLSX("BMS_PAY_XLSX", "应付价卡文件", "请上传xlsx价卡"),
BMS_PAY_FILE_30M("BMS_PAY_FILE_30M", "应付价卡文件", "价卡文件不能超过30MB"),
BMS_PAY_NO_SHEET("BMS_PAY_NO_SHEET", "应付价卡解析", "文件里没有工作表"),
BMS_PAY_NO_QUOTE("BMS_PAY_NO_QUOTE", "应付价卡解析", "目录里没有找到产品报价表"),
BMS_PAY_BAD_XLS("BMS_PAY_BAD_XLS", "应付价卡解析", "价卡无法读取,请确认是 Excel 报价文件"),
BMS_PAY_PICK_XLS("BMS_PAY_PICK_XLS", "应付邮编导入", "请选择 Excel 文件"),
BMS_PAY_XLS_30M("BMS_PAY_XLS_30M", "应付邮编导入", "文件不能超过30MB"),
BMS_PAY_ZIP_READ("BMS_PAY_ZIP_READ", "应付邮编导入", "邮编表无法读取,请确认是 Excel 文件"),
BMS_PAY_XLSX_ONLY("BMS_PAY_XLSX_ONLY", "应付邮编导入", "只支持 xlsx 文件"),
BMS_PAY_ZIP_EDIT("BMS_PAY_ZIP_EDIT", "应付邮编配置", "请选择要修改的邮编配置"),
BMS_PAY_ZIP_MISS("BMS_PAY_ZIP_MISS", "应付邮编配置", "邮编配置不存在或已删除"),
BMS_PAY_ZIP_TYPE("BMS_PAY_ZIP_TYPE", "应付邮编配置", "类型只能是附加费或邮编分区"),
BMS_PAY_ZIP_HEAD("BMS_PAY_ZIP_HEAD", "应付邮编导入", "没有识别到邮编或附加费邮编,请确认表头里有邮编、ZIP 或附加费"),
BMS_PAY_ZIP_ATTR("BMS_PAY_ZIP_ATTR", "应付邮编配置", "邮编属性只能是可达、不可达或偏远"),
BMS_PAY_BILL_MISS("BMS_PAY_BILL_MISS", "应付账单查询", "应付账单不存在"),
BMS_PAY_PICK_SUP("BMS_PAY_PICK_SUP", "应付账单生成", "请选择供应商"),
BMS_PAY_SUP_MISS("BMS_PAY_SUP_MISS", "应付账单生成", "供应商不存在"),
BMS_PAY_NO_GEN("BMS_PAY_NO_GEN", "应付账单生成", "没有可生成的订单"),
BMS_PAY_BILL_FEE("BMS_PAY_BILL_FEE", "应付账单生成", "订单没有应付费用"),
BMS_PAY_CURRENCY("BMS_PAY_CURRENCY", "应付账单生成", "所选订单币种不一致,请按币种分开生成"),
BMS_PAY_DRAFT("BMS_PAY_DRAFT", "应付账单确认", "只有草稿可以确认"),
BMS_PAY_CONFIRM("BMS_PAY_CONFIRM", "应付账单付款", "请先确认账单,再登记付款"),
BMS_PAY_PICK_BILL("BMS_PAY_PICK_BILL", "应付账单作废", "请选择应付账单"),
BMS_PAY_VOID_WHY("BMS_PAY_VOID_WHY", "应付账单作废", "请填写作废原因"),
BMS_PAY_VOIDED("BMS_PAY_VOIDED", "应付账单作废", "账单已经作废"),
BMS_PAY_PAID("BMS_PAY_PAID", "应付账单作废", "已付款的账单不能直接作废,请先冲回付款"),
BMS_PAY_ORDER_NO("BMS_PAY_ORDER_NO", "应付账单查询", "请填写订单号"),
BMS_PAY_BIZ_DATE("BMS_PAY_BIZ_DATE", "应付账单查询", "请选择计费日起止"),
BMS_PAY_LADING_NO("BMS_PAY_LADING_NO", "应付账单查询", "请填写提单号"),
BMS_PAY_QUERY("BMS_PAY_QUERY", "应付账单查询", "请选择查询方式"),
BMS_PAY_NO_ALLOC("BMS_PAY_NO_ALLOC", "提单费用分摊", "所选提单没有需要分摊的费用"),
BMS_PAY_ALLOC_ST("BMS_PAY_ALLOC_ST", "提单费用分摊", "费用状态已变化,请重新打开后再分摊"),
BMS_PAY_PICK_LAD("BMS_PAY_PICK_LAD", "提单费用分摊", "请勾选提单"),
BMS_PAY_LAD_LIMIT("BMS_PAY_LAD_LIMIT", "提单费用分摊", "单次最多处理5条提单"),
BMS_PAY_NO_CHANGE("BMS_PAY_NO_CHANGE", "提单费用保存", "没有需要保存的变更"),
BMS_PAY_IMP_EMPTY("BMS_PAY_IMP_EMPTY", "提单费用导入", "导入文件没有费用数据"),
BMS_PAY_IMP_DEMO("BMS_PAY_IMP_DEMO", "提单费用导入", "请删除示例行后再导入有效费用"),
BMS_PAY_FEE_BODY("BMS_PAY_FEE_BODY", "提单费用保存", "请提交要保存的费用"),
BMS_PAY_DEL_ID("BMS_PAY_DEL_ID", "提单费用删除", "要删除的费用缺少编号"),
BMS_PAY_FEE_DUP("BMS_PAY_FEE_DUP", "提单费用保存", "同一条费用不能重复提交"),
BMS_PAY_FEE_GONE("BMS_PAY_FEE_GONE", "提单费用保存", "费用已删除或不属于该提单"),
BMS_PAY_FEE_LOCK("BMS_PAY_FEE_LOCK", "提单费用删除", "已分摊或已确定的费用不能删除,请使用作废"),
BMS_PAY_EDIT_ID("BMS_PAY_EDIT_ID", "提单费用修改", "要修改的费用缺少编号"),
BMS_PAY_FEE_EDIT("BMS_PAY_FEE_EDIT", "提单费用修改", "已分摊或已确定的费用不能修改"),
BMS_PAY_PICK_OK("BMS_PAY_PICK_OK", "提单费用确定", "请选择要确定的费用"),
BMS_PAY_OK_STAT("BMS_PAY_OK_STAT", "提单费用确定", "只有已分摊的费用可以确定"),
BMS_PAY_OK_CHG("BMS_PAY_OK_CHG", "提单费用确定", "费用状态已变化,请刷新后再确定"),
BMS_PAY_PICK_VOID("BMS_PAY_PICK_VOID", "提单费用作废", "请选择要作废的费用"),
BMS_PAY_VOID_STAT("BMS_PAY_VOID_STAT", "提单费用作废", "只有已分摊或已确定的费用可以作废"),
BMS_PAY_VOID_CHG("BMS_PAY_VOID_CHG", "提单费用作废", "费用状态已变化,请刷新后再作废"),
BMS_PAY_NO_PERM("BMS_PAY_NO_PERM", "提单费用权限", "没有权限修改该费用"),
BMS_PAY_FEE_ENTRY("BMS_PAY_FEE_ENTRY", "提单费用保存", "费用入口不正确"),
BMS_PAY_FORBIDDEN("BMS_PAY_FORBIDDEN", "提单费用权限", "没有权限"),
BMS_PAY_PICK_ITEM("BMS_PAY_PICK_ITEM", "提单费用校验", "请选择费用项"),
BMS_PAY_ITEM_MISS("BMS_PAY_ITEM_MISS", "提单费用校验", "费用项不存在"),
BMS_PAY_LEVEL("BMS_PAY_LEVEL", "提单费用校验", "费用阶段不在字典中"),
BMS_PAY_PRICE("BMS_PAY_PRICE", "提单费用校验价格", "请填写价格"),
BMS_PAY_PRICE_NEG("BMS_PAY_PRICE_NEG", "提单费用校验价格", "价格不能小于0"),
BMS_PAY_PRICE_LEN("BMS_PAY_PRICE_LEN", "提单费用校验价格", "价格整数部分不能超过16位"),
BMS_PAY_CUR_PICK("BMS_PAY_CUR_PICK", "提单费用校验币种", "请选择收费币种"),
BMS_PAY_CUR_DICT("BMS_PAY_CUR_DICT", "提单费用校验币种", "收费币种不在币种字典中"),
BMS_PAY_REMARK("BMS_PAY_REMARK", "提单费用校验备注", "备注不能超过500字"),
BMS_PAY_LAD_BLANK("BMS_PAY_LAD_BLANK", "提单费用校验提单", "提单号不能为空"),
BMS_PAY_LAD_MISS("BMS_PAY_LAD_MISS", "提单费用校验提单", "提单不存在");
private static final Map<String, PaymentMesEnum> BY_CODE = new HashMap<>();
static
{
for (PaymentMesEnum item : values())
{
BY_CODE.put(item.code, item);
}
}
private final String code;
private final String name;
private final String cnMes;
private final String enMes;
PaymentMesEnum(String code, String name, String cnMes)
{
this(code, name, cnMes, "");
}
PaymentMesEnum(String code, String name, String cnMes, String enMes)
{
this.code = code;
this.name = name;
this.cnMes = cnMes;
this.enMes = enMes == null ? "" : enMes;
}
public String getCode()
{
return code;
}
public String getName()
{
return name;
}
public String getCnMes()
{
return cnMes;
}
public String getEnMes()
{
return enMes;
}
/** 按编码取出提示。没有对应编码时返回 null。 */
public static PaymentMesEnum of(String code)
{
if (code == null || code.isBlank())
{
return null;
}
return BY_CODE.get(code.trim());
}
/** 弹出给用户的中文提示。英文未配置时仍返回中文。 */
public String text()
{
return cnMes == null || cnMes.isBlank() ? enMes : cnMes;
}
/** 转成业务异常,页面只看到中文提示。 */
public ServiceException exception()
{
return new ServiceException(text()).setDetailMessage(code);
}
}
@@ -1,7 +1,6 @@
package com.bms.price.airbill.domain;
import java.math.BigDecimal;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
/** 待分摊的一条提单费用,供费用处理弹框展示。 */
@@ -30,14 +29,6 @@ public class SrvBillAirAllocFee
/** 分摊维度:1订单 2重量 */
private String allocDim;
/** 提单实际重量。只在按重量分摊时按提单号填入,不返回页面 */
@JsonIgnore
private BigDecimal actualWeight;
/** 干线打板重量。只在按重量分摊时按提单号填入,不返回页面 */
@JsonIgnore
private BigDecimal bandBoxWeight;
private Integer orderCount;
/** 不能分摊时的原因。空表示可以分摊 */
@@ -21,9 +21,12 @@ public class SrvBillAirFee
/** 附加费名称,仅展示,不入库 */
private String feeName;
/** 费用阶段,字典 bms_fee_level */
/** 费用阶段,字典 bms_fee_level。空运为 2(b段),报关和清关为 3(c段) */
private String feeLevel;
/** 费用入口:AIR 空运,DECLARE 报关,CLEAR 清关。页面不展示 */
private String feeScene;
/** 提单费用状态,字典 bill_fee_status。只随分摊、作废操作变化 */
private String billFeeStatus;
@@ -0,0 +1,22 @@
package com.bms.price.airbill.domain;
import java.math.BigDecimal;
import com.bms.common.annotation.Excel;
import lombok.Data;
/** 空运、报关、清关费用导入行。 */
@Data
public class SrvBillAirFeeImportRow
{
@Excel(name = "费用名称")
private String feeName;
@Excel(name = "价格")
private BigDecimal price;
@Excel(name = "币种")
private String currency;
@Excel(name = "备注")
private String remark;
}
@@ -0,0 +1,27 @@
package com.bms.price.airbill.domain;
import java.util.ArrayList;
import java.util.List;
import com.bms.common.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 空运、报关、清关页面保存费用。
* scene:air 空运,declare 报关,clear 清关。数据权限写在 params.dataScope。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SrvBillAirRoleFeeCommand extends BaseEntity
{
private static final long serialVersionUID = 1L;
private String scene;
/** 入库入口,仅服务端填写 */
@JsonIgnore
private String feeScene;
private List<SrvBillAirFeeSave> bills = new ArrayList<>();
}
@@ -0,0 +1,25 @@
package com.bms.price.airbill.domain;
import java.util.ArrayList;
import java.util.List;
import com.bms.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 空运、报关、清关费用查询。数据权限写在 params.dataScope。
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SrvBillAirRoleFeeQuery extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** air 空运,declare 报关,clear 清关 */
private String scene;
/** 入库入口:AIR、DECLARE、CLEAR */
private String feeScene;
private List<String> billCodes = new ArrayList<>();
}
@@ -75,6 +75,12 @@ public class SrvBillAirVo
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date firstLandingTime;
/** 板号 */
private String boardNo;
/** 打板重量 */
private BigDecimal bandBoxWeight;
/** 航班号 */
private String airNo;
@@ -96,6 +102,9 @@ public class SrvBillAirVo
/** 清关代理 */
private String qingguanAgent;
/** 清关操作人工号 */
private String clearanceOpCode;
/** 出仓仓库地址 */
private String stockAddr;
@@ -138,6 +138,9 @@ public interface PrcOrderBillMapper
/** 只更新应付合计,不改应收。 */
int updatePayTotal(@Param("billId") String billId, @Param("payTotal") BigDecimal payTotal);
/** 按未删除应付明细合计回写订单上的应付总额。单次不超过 1000 个订单。 */
int refreshPayTotalFromFees(@Param("billIds") List<String> billIds);
/** 统计引用该销售套餐版本的订单账单数。 */
int countByPsvId(@Param("psvId") Long psvId);
}
@@ -47,6 +47,12 @@ public interface PrcPayBillFeeMapper
*/
public int markLadingFeeDeleted(@Param("feeId") Long feeId);
/** 这条提单费用已经分摊到的订单。作废前取出,用来重算应付总额。 */
public List<String> selectBillIdsByLadingFee(@Param("feeId") Long feeId);
/** 明细还没挂上应付主表时,按订单补上主表 ID。单次不超过 1000 个订单。 */
public int bindPayBillId(@Param("billIds") List<String> billIds);
/**
* 这条提单费用分摊到的订单里,取一个已经生成应付账单的订单号。
* 是否出账看 prc_order_bill_ext.is_meet:1 已生成。
@@ -1,5 +1,7 @@
package com.bms.price.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.price.domain.PrcPayBill;
/** 订单应付主表 prc_pay_bill。一票计费订单对应一行。 */
@@ -52,4 +54,15 @@ public interface PrcPayBillMapper
* @return 影响行数
*/
int deleteByBillId(String billId);
/**
* 按未删除的应付明细合计,回写这些订单的应付总额。
*
* @param billIds 计费订单 ID,单次不超过 1000 个
* @return 影响行数
*/
int refreshPayTotal(@Param("billIds") List<String> billIds);
/** 订单还没有应付主表时补一行,总额随后按明细重算。单次不超过 1000 个订单。 */
int insertMissingPayBills(@Param("billIds") List<String> billIds, @Param("operator") String operator);
}
@@ -15,6 +15,9 @@ public interface PrcSurchargeMapper
{
public PrcSurcharge selectPrcSurchargeById(Long scId);
/** 按费用名称精确匹配未删除的附加费。 */
public List<PrcSurcharge> selectByExactName(@Param("scName") String scName);
public List<PrcSurcharge> selectPrcSurchargeList(PrcSurcharge surcharge);
public int insertPrcSurcharge(PrcSurcharge surcharge);
@@ -20,14 +20,6 @@ public interface SrvBillAirAllocMapper
*/
List<SrvBillAirAllocFee> selectPendingFees(@Param("billCodes") List<String> billCodes);
/**
* 按提单号取出实际重量和打板重量,只供按重量分摊计算。
*
* @param billCodes 提单号
* @return 提单号、实际重量、打板重量
*/
List<SrvBillAirAllocFee> selectLadingWeight(@Param("billCodes") List<String> billCodes);
/**
* 按提单号查出关联订单。订单号在扩展表,计费单号和重量在订单表。
*
@@ -4,6 +4,8 @@ import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.price.airbill.domain.SrvBillAirFee;
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeCommand;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeQuery;
/**
* 空运提单费用及操作日志。
@@ -12,6 +14,13 @@ public interface SrvBillAirFeeMapper
{
List<SrvBillAirFee> selectByBillCode(String billCode);
/** 按提单号和费用入口查询仍有效的费用,并套用数据权限。 */
List<SrvBillAirFee> selectRoleFees(SrvBillAirRoleFeeQuery query);
/** 当前数据权限下仍可修改的费用。看不到时返回空。 */
SrvBillAirFee selectRoleFeeVisible(@Param("id") Long id, @Param("billCode") String billCode,
@Param("query") SrvBillAirRoleFeeCommand query);
/** 只取这一票提单上仍有效的费用,供修改和删除前核对。 */
SrvBillAirFee selectLive(@Param("id") Long id, @Param("billCode") String billCode);
@@ -10,6 +10,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.price.domain.PayCostLine;
import com.bms.price.domain.PayOrderCostResult;
import com.bms.price.domain.PayProduct;
@@ -93,14 +94,14 @@ public final class PayCostCalculator
result.setSkipped(new ArrayList<>());
if (product == null)
{
result.setMessage("没有应付产品");
result.setMessage(PaymentMesEnum.BMS_PAY_NO_PRODUCT.getCnMes());
return result;
}
result.setProductId(product.getProductId());
result.setProductName(product.getProductName());
if (weight == null || weight.compareTo(BigDecimal.ZERO) <= 0)
{
result.setMessage("订单没有计费重");
result.setMessage(PaymentMesEnum.BMS_PAY_NO_WEIGHT.getCnMes());
return result;
}
List<PayWeightBand> usable = usableBands(product.getWeightBands());
@@ -390,7 +391,7 @@ public final class PayCostCalculator
}
else if (!name.isEmpty() && !name.contains("报价说明"))
{
result.getSkipped().add(name + ":价卡没有可计算的条件,需人工确认");
result.getSkipped().add(name + ":价卡没有可计算的条件,暂不计费");
}
}
result.getFees().addAll(keepHighestByName(sized));
@@ -14,6 +14,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.util.DigestUtils;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.poi.ExcelGrid;
import com.bms.price.domain.PayPostcodeGroup;
@@ -78,11 +79,11 @@ public class PayPostcodeImporter
{
if (bytes == null || bytes.length == 0)
{
throw new ServiceException("请选择 Excel 文件");
throw PaymentMesEnum.BMS_PAY_PICK_XLS.exception();
}
if (bytes.length > MAX_BYTES)
{
throw new ServiceException("文件不能超过30MB");
throw PaymentMesEnum.BMS_PAY_XLS_30M.exception();
}
String safeName = filename == null ? "邮编.xlsx" : filename;
String md5 = DigestUtils.md5DigestAsHex(bytes);
@@ -115,7 +116,7 @@ public class PayPostcodeImporter
}
catch (Exception ex)
{
throw new ServiceException("邮编表无法读取,请确认是 Excel 文件");
throw PaymentMesEnum.BMS_PAY_ZIP_READ.exception();
}
}
@@ -10,7 +10,7 @@ import java.util.regex.Pattern;
import com.bms.price.domain.PaySurcharge;
/**
* 把附加费说明收成可计算的条件。认不出的保持人工,避免把签名、重复单号当成每票必收。
* 把附加费说明收成可计算的条件。认不出的标为暂不计费,避免把签名、重复单号当成每票必收。
*/
public final class PaySurchargeRules
{
@@ -17,6 +17,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.util.DigestUtils;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.poi.ExcelGrid;
import com.bms.price.domain.PayExtraLine;
@@ -64,25 +65,25 @@ public class PayWorkbookParser
{
if (bytes == null || bytes.length == 0)
{
throw new ServiceException("请选择价卡文件");
throw PaymentMesEnum.BMS_PAY_PICK_FILE.exception();
}
if (bytes.length > MAX_BYTES)
{
throw new ServiceException("价卡文件不能超过30MB");
throw PaymentMesEnum.BMS_PAY_FILE_30M.exception();
}
String safeName = filename == null ? "价卡.xlsx" : filename;
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(bytes)))
{
if (workbook.getNumberOfSheets() == 0)
{
throw new ServiceException("文件里没有工作表");
throw PaymentMesEnum.BMS_PAY_NO_SHEET.exception();
}
ExcelGrid catalog = ExcelGrid.read(workbook.getSheetAt(0));
FileMeta meta = fileMeta(safeName);
List<CatalogItem> items = readCatalog(catalog, workbook, meta);
if (items.isEmpty())
{
throw new ServiceException("目录里没有找到产品报价表");
throw PaymentMesEnum.BMS_PAY_NO_QUOTE.exception();
}
PayPreview preview = new PayPreview();
preview.setSourceFile(safeName);
@@ -103,7 +104,7 @@ public class PayWorkbookParser
}
catch (Exception ex)
{
throw new ServiceException("价卡无法读取,请确认是 Excel 报价文件");
throw PaymentMesEnum.BMS_PAY_BAD_XLS.exception();
}
}
@@ -124,16 +125,16 @@ public class PayWorkbookParser
{
if (bytes == null || bytes.length == 0)
{
throw new ServiceException("请选择价卡文件");
throw PaymentMesEnum.BMS_PAY_PICK_FILE.exception();
}
if (bytes.length > MAX_BYTES)
{
throw new ServiceException("价卡文件不能超过30MB");
throw PaymentMesEnum.BMS_PAY_FILE_30M.exception();
}
Map<String, String> dict = dictIndex(dictValues);
if (dict.isEmpty())
{
throw new ServiceException("应付产品字典还没有数据");
throw PaymentMesEnum.BMS_PAY_DICT_EMPTY.exception();
}
String safeName = filename == null ? "价卡.xlsx" : filename;
String wanted = norm(selected);
@@ -142,7 +143,7 @@ public class PayWorkbookParser
{
if (workbook.getNumberOfSheets() == 0)
{
throw new ServiceException("文件里没有工作表");
throw PaymentMesEnum.BMS_PAY_NO_SHEET.exception();
}
FileMeta meta = fileMeta(safeName);
PayPreview preview = new PayPreview();
@@ -208,7 +209,7 @@ public class PayWorkbookParser
}
catch (Exception ex)
{
throw new ServiceException("价卡无法读取,请确认是 Excel 报价文件");
throw PaymentMesEnum.BMS_PAY_BAD_XLS.exception();
}
}
@@ -0,0 +1,25 @@
package com.bms.price.service;
import java.util.List;
import com.bms.price.domain.PrcPayBillFee;
/**
* 订单应付明细。写入明细后,按未删除金额合计回写应付主表。
*/
public interface IPrcPayBillFeeService
{
/**
* 分批写入应付明细,并回写这些订单的应付总额。
* 明细每批最多 1000 条。总额是该订单未删除明细的金额之和,同时写入 prc_pay_bill.pay_total 和订单应付总额。
*
* @param fees 本次新增的应付明细
*/
void insertBatchAndRefreshTotals(List<PrcPayBillFee> fees);
/**
* 按未删除应付明细重算应付总额。订单每批最多 1000 个。
*
* @param billIds 计费订单 ID
*/
void refreshTotals(List<String> billIds);
}
@@ -1,9 +1,12 @@
package com.bms.price.service;
import java.io.InputStream;
import java.util.List;
import com.bms.price.airbill.domain.SrvBillAirFee;
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeCommand;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeQuery;
/**
* 空运提单费用维护。
@@ -12,6 +15,18 @@ public interface ISrvBillAirFeeService
{
List<SrvBillAirFee> listFees(String billCode);
/**
* 按费用入口和提单号查询仍有效的费用。入口:air 空运,declare 报关,clear 清关。
* 按角色数据权限过滤:本部门看本部门创建的费用,仅本人看自己创建的费用,超级管理员和全部数据权限看全部。
*/
List<SrvBillAirFee> listRoleFees(SrvBillAirRoleFeeQuery query);
/** 把空运、报关或清关页面提交的费用写入提单。费用阶段按入口写入字典值,分摊固定为重量。 */
String saveRole(SrvBillAirRoleFeeCommand command);
/** 解析导入文件。示例行不入库,费用名称按附加费名称匹配。 */
List<SrvBillAirFee> parseImport(String scene, InputStream input) throws Exception;
List<SrvBillAirFeeLog> listLogs(String billCode);
/** 只写入有变动的行,并按行记录修改前后。返回给页面的提示。 */
@@ -14,6 +14,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.SecurityUtils;
import com.bms.common.utils.uuid.IdUtils;
@@ -72,7 +73,7 @@ public class PayBillServiceImpl implements IPayBillService
PayBill bill = payBillMapper.selectPayBillById(billId);
if (bill == null)
{
throw new ServiceException("应付账单不存在");
throw PaymentMesEnum.BMS_PAY_BILL_MISS.exception();
}
bill.setLines(payBillMapper.selectLines(billId));
bill.setLogs(payBillMapper.selectLogs(billId));
@@ -104,18 +105,18 @@ public class PayBillServiceImpl implements IPayBillService
{
if (command.getSupplierId() == null)
{
throw new ServiceException("请选择供应商");
throw PaymentMesEnum.BMS_PAY_PICK_SUP.exception();
}
BmsSupplier supplier = supplierMapper.selectBmsSupplierBySupplierId(command.getSupplierId());
if (supplier == null)
{
throw new ServiceException("供应商不存在");
throw PaymentMesEnum.BMS_PAY_SUP_MISS.exception();
}
PayBillPreview preview = collect(command, true);
List<PayBillOrder> orders = freeOrders(preview);
if (orders.isEmpty())
{
throw new ServiceException(preview.getMissingNos().isEmpty() ? "没有可生成的订单" : "订单号没有对上:" + String.join("、", preview.getMissingNos()));
throw new ServiceException(preview.getMissingNos().isEmpty() ? PaymentMesEnum.BMS_PAY_NO_GEN.getCnMes() : "订单号没有对上:" + String.join("、", preview.getMissingNos()));
}
String currency = currencyOf(orders);
Map<String, Integer> versions = new HashMap<>();
@@ -155,7 +156,7 @@ public class PayBillServiceImpl implements IPayBillService
}
if (lines.isEmpty())
{
throw new ServiceException("订单没有应付费用");
throw PaymentMesEnum.BMS_PAY_BILL_FEE.exception();
}
BigDecimal total = BigDecimal.ZERO;
for (PayBillLine line : lines)
@@ -163,7 +164,7 @@ public class PayBillServiceImpl implements IPayBillService
String lineCurrency = line.getCurrency() == null ? "" : line.getCurrency();
if (!currency.equals(lineCurrency))
{
throw new ServiceException("所选订单币种不一致,请按币种分开生成");
throw PaymentMesEnum.BMS_PAY_CURRENCY.exception();
}
total = total.add(line.getAmount() == null ? BigDecimal.ZERO : line.getAmount());
}
@@ -203,7 +204,7 @@ public class PayBillServiceImpl implements IPayBillService
PayBill bill = required(billId);
if (!"DRAFT".equals(bill.getStatus()))
{
throw new ServiceException("只有草稿可以确认");
throw PaymentMesEnum.BMS_PAY_DRAFT.exception();
}
String operator = SecurityUtils.getUsername();
bill.setStatus("CONFIRMED");
@@ -226,7 +227,7 @@ public class PayBillServiceImpl implements IPayBillService
PayBill bill = required(billId);
if (!"CONFIRMED".equals(bill.getStatus()))
{
throw new ServiceException("请先确认账单,再登记付款");
throw PaymentMesEnum.BMS_PAY_CONFIRM.exception();
}
String operator = SecurityUtils.getUsername();
bill.setStatus("PAID");
@@ -248,20 +249,20 @@ public class PayBillServiceImpl implements IPayBillService
{
if (command == null || command.getBillId() == null)
{
throw new ServiceException("请选择应付账单");
throw PaymentMesEnum.BMS_PAY_PICK_BILL.exception();
}
if (command.getVoidReason() == null || command.getVoidReason().isBlank())
{
throw new ServiceException("请填写作废原因");
throw PaymentMesEnum.BMS_PAY_VOID_WHY.exception();
}
PayBill bill = required(command.getBillId());
if ("VOID".equals(bill.getStatus()))
{
throw new ServiceException("账单已经作废");
throw PaymentMesEnum.BMS_PAY_VOIDED.exception();
}
if ("PAID".equals(bill.getStatus()))
{
throw new ServiceException("已付款的账单不能直接作废,请先冲回付款");
throw PaymentMesEnum.BMS_PAY_PAID.exception();
}
String operator = SecurityUtils.getUsername();
String from = bill.getStatus();
@@ -289,16 +290,16 @@ public class PayBillServiceImpl implements IPayBillService
List<String> ladingNos = "LADING".equals(mode) ? tokens(command.getLadingText()) : new ArrayList<>();
if ("ORDER".equals(mode) && orderNos.isEmpty())
{
throw new ServiceException("请填写订单号");
throw PaymentMesEnum.BMS_PAY_ORDER_NO.exception();
}
if ("TIME".equals(mode) && (command.getBeginDate() == null || command.getBeginDate().isBlank()
|| command.getEndDate() == null || command.getEndDate().isBlank()))
{
throw new ServiceException("请选择计费日起止");
throw PaymentMesEnum.BMS_PAY_BIZ_DATE.exception();
}
if ("LADING".equals(mode) && ladingNos.isEmpty())
{
throw new ServiceException("请填写提单号");
throw PaymentMesEnum.BMS_PAY_LADING_NO.exception();
}
List<PayBillOrder> found = payBillMapper.selectCandidateOrders(
orderNos.isEmpty() ? null : orderNos,
@@ -412,7 +413,7 @@ public class PayBillServiceImpl implements IPayBillService
PayBill bill = payBillMapper.selectPayBillById(billId);
if (bill == null)
{
throw new ServiceException("应付账单不存在");
throw PaymentMesEnum.BMS_PAY_BILL_MISS.exception();
}
return bill;
}
@@ -476,7 +477,7 @@ public class PayBillServiceImpl implements IPayBillService
{
if (command == null || command.getSourceType() == null || command.getSourceType().isBlank())
{
throw new ServiceException("请选择查询方式");
throw PaymentMesEnum.BMS_PAY_QUERY.exception();
}
return command.getSourceType().trim().toUpperCase(Locale.ROOT);
}
@@ -6,6 +6,7 @@ import java.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.SecurityUtils;
import com.bms.masterdata.domain.ServiceChannel;
@@ -32,6 +33,7 @@ import com.bms.price.pay.PaySurchargeRules;
import com.bms.price.pay.PayZoneMatcher;
import com.bms.price.service.IPayOrderCostService;
import com.bms.price.service.IPayProductService;
import com.bms.price.service.IPrcPayBillFeeService;
/**
* 订单找到渠道,渠道找到当前应付产品,再按重量、尺寸、邮编计算应付并落库。
@@ -57,6 +59,9 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
@Autowired
private PrcPayBillFeeMapper payBillFeeMapper;
@Autowired
private IPrcPayBillFeeService payBillFeeService;
@Autowired
private PayPostcodeMapper payPostcodeMapper;
@@ -73,12 +78,12 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
{
if (billId == null || billId.isBlank())
{
throw new ServiceException("请选择订单");
throw PaymentMesEnum.BMS_PAY_PICK_ORDER.exception();
}
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(billId.trim());
if (bill == null)
{
throw new ServiceException("订单不存在");
throw PaymentMesEnum.BMS_PAY_ORDER_MISS.exception();
}
PrcOrderBillExt ext = orderBillExtMapper.selectByBillId(bill.getBillId());
ServiceChannel channel = resolveChannel(ext);
@@ -98,12 +103,11 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
result.setVersionNo(product.getVersionNo());
if (result.getFees() == null || result.getFees().isEmpty())
{
throw new ServiceException(result.getMessage() == null ? "没有算出应付费用" : result.getMessage());
throw new ServiceException(result.getMessage() == null ? PaymentMesEnum.BMS_PAY_NO_COST.getCnMes() : result.getMessage());
}
String operator = SecurityUtils.getUsername();
PrcPayBill payBill = saveBill(bill, result, operator);
saveFees(bill.getBillId(), payBill.getPayBillId(), result.getFees());
orderBillMapper.updatePayTotal(bill.getBillId(), result.getPayTotal());
String channelName = channel.getChannelName() == null ? channel.getChannelCode() : channel.getChannelName();
String end = product.getTermEnd() == null ? "不限" : day(product.getTermEnd());
result.setMessage("渠道「" + channelName + "」产品编号 " + product.getOriginId() + " 第 " + product.getVersionNo()
@@ -273,7 +277,7 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
}
if (decision.getZoneName() == null)
{
throw new ServiceException("邮编没有对上价卡地区");
throw PaymentMesEnum.BMS_PAY_NO_ZONE.exception();
}
return decision.getZoneName();
}
@@ -379,7 +383,7 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
{
if (ext == null)
{
throw new ServiceException("订单没有渠道服务商");
throw PaymentMesEnum.BMS_PAY_NO_CHANNEL.exception();
}
ServiceChannel channel = blank(ext.getProductChannelId()) ? null : serviceChannelMapper.selectServiceChannelById(ext.getProductChannelId().trim());
if (channel == null && !blank(ext.getProductFloorCode()))
@@ -397,13 +401,13 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
}
if (channel == null)
{
throw new ServiceException("订单没有渠道服务商,或渠道尚未同步");
throw PaymentMesEnum.BMS_PAY_CH_SYNC.exception();
}
return channel;
}
/**
* 保存订单应付主表。已有记录则更新合计,没有则新建。合计只写本次尾程结果。
* 保存订单应付主表。已有记录则更新,没有则新建。成本价写本次尾程结果,应付总额随后按全部未删除明细重算。
*
* @param bill 计费订单
* @param result 本次尾程计算结果
@@ -473,7 +477,11 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
}
if (!rows.isEmpty())
{
payBillFeeMapper.insertBatch(rows);
payBillFeeService.insertBatchAndRefreshTotals(rows);
}
else
{
payBillFeeService.refreshTotals(java.util.List.of(billId));
}
}
@@ -7,7 +7,7 @@ import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.exception.ServiceException;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.price.domain.PayPostcodeGroup;
import com.bms.price.domain.PayPostcodeImportResult;
import com.bms.price.domain.PayPostcodeLine;
@@ -45,11 +45,11 @@ public class PayPostcodeServiceImpl implements IPayPostcodeService
{
if (group == null || group.getGroupId() == null)
{
throw new ServiceException("请选择要修改的邮编配置");
throw PaymentMesEnum.BMS_PAY_ZIP_EDIT.exception();
}
if (payPostcodeMapper.selectGroupById(group.getGroupId()) == null)
{
throw new ServiceException("邮编配置不存在或已删除");
throw PaymentMesEnum.BMS_PAY_ZIP_MISS.exception();
}
group.setCoverage(normalizeCoverage(group.getCoverage()));
group.setChannelName(trimToEmpty(group.getChannelName()));
@@ -65,7 +65,7 @@ public class PayPostcodeServiceImpl implements IPayPostcodeService
group.setRemark(trimToEmpty(group.getRemark()));
if (!"FEE".equals(group.getConfigKind()) && !"ZONE".equals(group.getConfigKind()))
{
throw new ServiceException("类型只能是附加费或邮编分区");
throw PaymentMesEnum.BMS_PAY_ZIP_TYPE.exception();
}
return payPostcodeMapper.updateGroup(group);
}
@@ -83,7 +83,7 @@ public class PayPostcodeServiceImpl implements IPayPostcodeService
List<PayPostcodeGroup> groups = importer.parse(bytes, filename);
if (groups.isEmpty())
{
throw new ServiceException("没有识别到邮编或附加费邮编,请确认表头里有邮编、ZIP 或附加费");
throw PaymentMesEnum.BMS_PAY_ZIP_HEAD.exception();
}
Set<String> sheets = new LinkedHashSet<>();
for (PayPostcodeGroup group : groups)
@@ -172,6 +172,6 @@ public class PayPostcodeServiceImpl implements IPayPostcodeService
{
return coverage;
}
throw new ServiceException("邮编属性只能是可达、不可达或偏远");
throw PaymentMesEnum.BMS_PAY_ZIP_ATTR.exception();
}
}
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.core.domain.entity.SysDictData;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.SecurityUtils;
@@ -247,11 +248,11 @@ public class PayProductServiceImpl implements IPayProductService
{
if (command == null || command.getProducts() == null || command.getProducts().isEmpty())
{
throw new ServiceException("请先勾选要保存的产品");
throw PaymentMesEnum.BMS_PAY_PICK_PROD.exception();
}
if (command.getProducts().size() > 80)
{
throw new ServiceException("一次最多保存80个产品");
throw PaymentMesEnum.BMS_PAY_PROD_LIMIT.exception();
}
String operator = SecurityUtils.getUsername();
int saved = 0;
@@ -261,7 +262,7 @@ public class PayProductServiceImpl implements IPayProductService
{
if (product == null || isBlank(product.getProductName()) || isBlank(product.getSheetName()) || isBlank(product.getFileMd5()))
{
throw new ServiceException("产品名称、工作表和文件摘要不能为空");
throw PaymentMesEnum.BMS_PAY_PROD_FIELD.exception();
}
Long existsId = templateId(product);
if (existsId != null && !command.isOverwrite())
@@ -303,16 +304,16 @@ public class PayProductServiceImpl implements IPayProductService
{
if (product == null || product.getProductId() == null)
{
throw new ServiceException("请选择要修改的产品");
throw PaymentMesEnum.BMS_PAY_PICK_EDIT.exception();
}
PayProduct existing = payProductMapper.selectPayProductById(product.getProductId());
if (existing == null)
{
throw new ServiceException("产品不存在或已删除");
throw PaymentMesEnum.BMS_PAY_PROD_MISS.exception();
}
if (!isBlank(existing.getChannelId()))
{
throw new ServiceException("渠道价格请在渠道页面保存,这里只改公共模板");
throw PaymentMesEnum.BMS_PAY_TPL_ONLY.exception();
}
product.setDictValue(blankTo(product.getDictValue(), existing.getDictValue()));
product.setOriginId(existing.getOriginId());
@@ -364,11 +365,11 @@ public class PayProductServiceImpl implements IPayProductService
{
if (isBlank(channelId))
{
throw new ServiceException("请先保存渠道,再维护价格");
throw PaymentMesEnum.BMS_PAY_SAVE_CH.exception();
}
if (edited == null)
{
throw new ServiceException("没有可保存的价格");
throw PaymentMesEnum.BMS_PAY_NO_PRICE.exception();
}
Long root = resolveRoot(edited.getOriginId() != null ? edited.getOriginId() : edited.getProductId());
String operator = SecurityUtils.getUsername();
@@ -387,7 +388,7 @@ public class PayProductServiceImpl implements IPayProductService
PayPreview preview = preview(bytes, filename, dictValue);
if (preview.getProducts().size() != 1)
{
throw new ServiceException("重新导入时请使用单个产品文件,或先选定渠道上的应付产品");
throw PaymentMesEnum.BMS_PAY_REIMPORT.exception();
}
PayProduct parsed = preview.getProducts().get(0);
if (root == null)
@@ -396,7 +397,7 @@ public class PayProductServiceImpl implements IPayProductService
}
if (root == null)
{
throw new ServiceException("请先选择一个已导入的应付产品");
throw PaymentMesEnum.BMS_PAY_PICK_IMP.exception();
}
PayProduct saved = appendVersion(root, parsed, SecurityUtils.getUsername());
saved.setOriginId(root);
@@ -424,7 +425,7 @@ public class PayProductServiceImpl implements IPayProductService
}
if (values.isEmpty())
{
throw new ServiceException("应付产品字典还没有数据");
throw PaymentMesEnum.BMS_PAY_DICT_EMPTY.exception();
}
return values;
}
@@ -544,7 +545,7 @@ public class PayProductServiceImpl implements IPayProductService
PayProduct product = productId == null ? null : payProductMapper.selectPayProductById(productId);
if (product == null)
{
throw new ServiceException("产品不存在或已删除");
throw PaymentMesEnum.BMS_PAY_PROD_MISS.exception();
}
return payProductTermMapper.selectByDict(termKey(product));
}
@@ -555,23 +556,23 @@ public class PayProductServiceImpl implements IPayProductService
{
if (term == null || term.getEffectiveStart() == null)
{
throw new ServiceException("请填写生效开始");
throw PaymentMesEnum.BMS_PAY_TERM_START.exception();
}
if (term.getEffectiveEnd() != null && term.getEffectiveEnd().before(term.getEffectiveStart()))
{
throw new ServiceException("生效结束不能早于生效开始");
throw PaymentMesEnum.BMS_PAY_TERM_END.exception();
}
String operator = SecurityUtils.getUsername();
if (term.getTermId() == null)
{
if (term.getProductId() == null)
{
throw new ServiceException("请选择要修改的版本");
throw PaymentMesEnum.BMS_PAY_PICK_VER.exception();
}
PayProduct product = payProductMapper.selectPayProductById(term.getProductId());
if (product == null)
{
throw new ServiceException("产品不存在或已删除");
throw PaymentMesEnum.BMS_PAY_PROD_MISS.exception();
}
PayProductTerm exists = payProductTermMapper.selectByProductId(product.getProductId());
if (exists != null)
@@ -592,7 +593,7 @@ public class PayProductServiceImpl implements IPayProductService
PayProductTerm existing = payProductTermMapper.selectById(term.getTermId());
if (existing == null)
{
throw new ServiceException("有效期不存在");
throw PaymentMesEnum.BMS_PAY_TERM_MISS.exception();
}
term.setProductId(existing.getProductId());
term.setUpdateBy(operator);
@@ -609,7 +610,7 @@ public class PayProductServiceImpl implements IPayProductService
if (term == null || (!TERM_PENDING.equals(term.getStatus()) && !TERM_EFFECT.equals(term.getStatus())
&& !TERM_EXPIRED.equals(term.getStatus())))
{
throw new ServiceException("状态只能是待生效、生效或已失效");
throw PaymentMesEnum.BMS_PAY_TERM_STAT.exception();
}
if (term.getTermId() == null && term.getProductId() != null)
{
@@ -621,7 +622,7 @@ public class PayProductServiceImpl implements IPayProductService
}
if (term.getTermId() == null)
{
throw new ServiceException("请选择要修改的版本");
throw PaymentMesEnum.BMS_PAY_PICK_VER.exception();
}
term.setUpdateBy(SecurityUtils.getUsername());
return payProductTermMapper.updateStatus(term);
@@ -706,7 +707,7 @@ public class PayProductServiceImpl implements IPayProductService
}
catch (IOException ex)
{
throw new ServiceException("源文件下载失败");
throw PaymentMesEnum.BMS_PAY_FILE_DOWN.exception();
}
}
@@ -715,12 +716,12 @@ public class PayProductServiceImpl implements IPayProductService
PayProduct product = productId == null ? null : payProductMapper.selectPayProductById(productId);
if (product == null)
{
throw new ServiceException("产品不存在或已删除");
throw PaymentMesEnum.BMS_PAY_PROD_MISS.exception();
}
String path = sourcePath(product.getFilePath());
if (path == null)
{
throw new ServiceException("这一版没有可下载的源文件");
throw PaymentMesEnum.BMS_PAY_NO_SOURCE.exception();
}
product.setFilePath(path);
if (isBlank(product.getSourceFile()))
@@ -754,7 +755,7 @@ public class PayProductServiceImpl implements IPayProductService
}
catch (IOException ex)
{
throw new ServiceException("价卡文件上传失败");
throw PaymentMesEnum.BMS_PAY_FILE_UP.exception();
}
}
@@ -768,7 +769,7 @@ public class PayProductServiceImpl implements IPayProductService
}
if (path == null)
{
throw new ServiceException("源文件还没有上传成功,请重新选择价卡后再保存");
throw PaymentMesEnum.BMS_PAY_FILE_WAIT.exception();
}
return path;
}
@@ -883,7 +884,7 @@ public class PayProductServiceImpl implements IPayProductService
{
if (command == null || command.getProducts() == null || command.getProducts().isEmpty())
{
throw new ServiceException("请先勾选要保存的产品");
throw PaymentMesEnum.BMS_PAY_PICK_PROD.exception();
}
Long priceSetId = ensureLabelPriceSet(command);
String operator = SecurityUtils.getUsername();
@@ -912,7 +913,7 @@ public class PayProductServiceImpl implements IPayProductService
{
if (product == null || isBlank(product.getProductName()) || isBlank(product.getSheetName()) || isBlank(product.getFileMd5()))
{
throw new ServiceException("产品名称、工作表和文件摘要不能为空");
throw PaymentMesEnum.BMS_PAY_PROD_FIELD.exception();
}
product.setProductType(PAY_TYPE_AR);
String dict = blankTo(product.getDictValue(), product.getProductName());
@@ -1740,12 +1741,12 @@ public class PayProductServiceImpl implements IPayProductService
{
if (isBlank(channelId) || productId == null)
{
throw new ServiceException("请先保存渠道,再选择产品");
throw PaymentMesEnum.BMS_PAY_CH_FIRST.exception();
}
ServiceChannel channel = serviceChannelMapper.selectServiceChannelById(channelId);
if (channel == null || isBlank(channel.getChannelCode()))
{
throw new ServiceException("渠道不存在或渠道编码为空");
throw PaymentMesEnum.BMS_PAY_CH_MISS.exception();
}
String type = payTypeOf(payType);
Long storedId = resolveRoot(productId);
@@ -1766,11 +1767,11 @@ public class PayProductServiceImpl implements IPayProductService
{
if (channelIds == null || channelIds.isEmpty())
{
throw new ServiceException("请先勾选渠道");
throw PaymentMesEnum.BMS_PAY_PICK_CH.exception();
}
if (apProductId == null && arProductId == null)
{
throw new ServiceException("请至少选择应付或应收产品");
throw PaymentMesEnum.BMS_PAY_PICK_SIDE.exception();
}
int n = 0;
for (String channelId : channelIds)
@@ -1791,7 +1792,7 @@ public class PayProductServiceImpl implements IPayProductService
}
if (n <= 0)
{
throw new ServiceException("没有可绑定的渠道");
throw PaymentMesEnum.BMS_PAY_BIND_NONE.exception();
}
return "已为 " + n + " 个渠道绑定产品";
}
@@ -1859,18 +1860,18 @@ public class PayProductServiceImpl implements IPayProductService
{
if (isBlank(channelId))
{
throw new ServiceException("订单没有渠道服务商");
throw PaymentMesEnum.BMS_PAY_NO_CHANNEL.exception();
}
Long root = channelProductRoot(channelId);
if (root == null)
{
throw new ServiceException("渠道还没有选择应付产品");
throw PaymentMesEnum.BMS_PAY_CH_PROD.exception();
}
Date day = bizDate == null ? today() : bizDate;
PayProduct version = loadVersion(root, day, true);
if (version == null)
{
throw new ServiceException("订单时间没有落在价格有效期内");
throw PaymentMesEnum.BMS_PAY_OUT_TERM.exception();
}
return version;
}
@@ -1912,7 +1913,7 @@ public class PayProductServiceImpl implements IPayProductService
PayProduct right = selectPayProductDetail(rightId);
if (left == null || right == null)
{
throw new ServiceException("请选择两个价格版本");
throw PaymentMesEnum.BMS_PAY_TWO_VER.exception();
}
int leftNo = left.getVersionNo() == null ? 1 : left.getVersionNo();
int rightNo = right.getVersionNo() == null ? 1 : right.getVersionNo();
@@ -1991,7 +1992,7 @@ public class PayProductServiceImpl implements IPayProductService
String type = payType.trim().toUpperCase();
if (!PAY_TYPE_AP.equals(type) && !PAY_TYPE_AR.equals(type))
{
throw new ServiceException("收付类型只能是应收AR或应付AP");
throw PaymentMesEnum.BMS_PAY_AR_AP.exception();
}
return type;
}
@@ -2000,7 +2001,7 @@ public class PayProductServiceImpl implements IPayProductService
{
if (productId == null)
{
throw new ServiceException("请先选择一个已导入的应付产品");
throw PaymentMesEnum.BMS_PAY_PICK_IMP.exception();
}
return stableId(productId);
}
@@ -2010,7 +2011,7 @@ public class PayProductServiceImpl implements IPayProductService
PayProduct product = payProductMapper.selectPayProductById(productId);
if (product == null)
{
throw new ServiceException("应付产品不存在或已删除");
throw PaymentMesEnum.BMS_PAY_AP_MISS.exception();
}
return product.getOriginId() == null ? product.getProductId() : product.getOriginId();
}
@@ -0,0 +1,96 @@
package com.bms.price.service.impl;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bms.common.utils.SecurityUtils;
import com.bms.price.domain.PrcPayBillFee;
import com.bms.price.mapper.PrcOrderBillMapper;
import com.bms.price.mapper.PrcPayBillFeeMapper;
import com.bms.price.mapper.PrcPayBillMapper;
import com.bms.price.service.IPrcPayBillFeeService;
/**
* 应付明细批量落库,并把合计回写到应付主表。
*/
@Service
public class PrcPayBillFeeServiceImpl implements IPrcPayBillFeeService
{
private static final int BATCH_SIZE = 1000;
@Autowired
private PrcPayBillFeeMapper payBillFeeMapper;
@Autowired
private PrcPayBillMapper payBillMapper;
@Autowired
private PrcOrderBillMapper orderBillMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void insertBatchAndRefreshTotals(List<PrcPayBillFee> fees)
{
if (fees == null || fees.isEmpty())
{
return;
}
LinkedHashSet<String> billIds = new LinkedHashSet<>();
for (int from = 0; from < fees.size(); from += BATCH_SIZE)
{
List<PrcPayBillFee> chunk = fees.subList(from, Math.min(from + BATCH_SIZE, fees.size()));
payBillFeeMapper.insertBatch(chunk);
for (PrcPayBillFee fee : chunk)
{
if (fee != null && fee.getBillId() != null && !fee.getBillId().isBlank())
{
billIds.add(fee.getBillId().trim());
}
}
}
refreshTotals(new ArrayList<>(billIds));
}
@Override
public void refreshTotals(List<String> billIds)
{
if (billIds == null || billIds.isEmpty())
{
return;
}
LinkedHashSet<String> unique = new LinkedHashSet<>();
for (String billId : billIds)
{
if (billId != null && !billId.isBlank())
{
unique.add(billId.trim());
}
}
List<String> ids = new ArrayList<>(unique);
String operator = currentOperator();
for (int from = 0; from < ids.size(); from += BATCH_SIZE)
{
List<String> chunk = ids.subList(from, Math.min(from + BATCH_SIZE, ids.size()));
payBillMapper.insertMissingPayBills(chunk, operator);
payBillFeeMapper.bindPayBillId(chunk);
payBillMapper.refreshPayTotal(chunk);
orderBillMapper.refreshPayTotalFromFees(chunk);
}
}
private static String currentOperator()
{
try
{
String name = SecurityUtils.getUsername();
return name == null || name.isBlank() ? "system" : name;
}
catch (Exception ex)
{
return "system";
}
}
}
@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson2.JSON;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.SecurityUtils;
@@ -25,7 +26,7 @@ import com.bms.price.domain.PrcPayBillFee;
import com.bms.price.mapper.SrvBillAirAllocMapper;
import com.bms.price.mapper.SrvBillAirFeeMapper;
import com.bms.price.mapper.SrvBillAirMapper;
import com.bms.price.mapper.PrcPayBillFeeMapper;
import com.bms.price.service.IPrcPayBillFeeService;
import com.bms.price.service.ISrvBillAirAllocService;
/**
@@ -42,8 +43,6 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
private static final String DIM_WEIGHT = "2";
private static final String FEE_TYPE = "LADING";
private static final String FEE_SOURCE = "LADING";
private static final BigDecimal ZERO = BigDecimal.ZERO;
@@ -58,14 +57,13 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
private SrvBillAirFeeMapper feeMapper;
@Autowired
private PrcPayBillFeeMapper payBillFeeMapper;
private IPrcPayBillFeeService payBillFeeService;
@Override
public List<SrvBillAirAllocFee> preview(List<String> billCodes)
{
List<String> codes = normalize(billCodes);
List<SrvBillAirAllocFee> fees = dedupe(allocMapper.selectPendingFees(codes));
fillWeight(fees);
Map<String, List<SrvBillAirAllocOrder>> orders = ordersOf(codes);
for (SrvBillAirAllocFee fee : fees)
{
@@ -82,10 +80,9 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
{
List<String> codes = normalize(billCodes);
List<SrvBillAirAllocFee> fees = dedupe(allocMapper.selectPendingFees(codes));
fillWeight(fees);
if (fees.isEmpty())
{
throw new ServiceException("所选提单没有需要分摊的费用");
throw PaymentMesEnum.BMS_PAY_NO_ALLOC.exception();
}
Map<String, List<SrvBillAirAllocOrder>> orders = ordersOf(codes);
for (SrvBillAirAllocFee fee : fees)
@@ -105,8 +102,7 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
{
List<SrvBillAirAllocOrder> rows = orders.get(fee.getBillCode());
fee.setOrderCount(rows.size());
boolean byWeight = DIM_WEIGHT.equals(fee.getAllocDim());
List<BigDecimal> shares = split(byWeight ? weightAmount(fee) : fee.getPrice(), rows, byWeight);
List<BigDecimal> shares = split(fee.getPrice(), rows, true);
for (int i = 0; i < rows.size(); i++)
{
batch.add(toOrderFee(fee, rows.get(i), shares.get(i), payBillIds));
@@ -122,54 +118,12 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
int updated = allocMapper.markAllocated(feeIds(fees), operator, now);
if (updated != fees.size())
{
throw new ServiceException("费用状态已变化,请重新打开后再分摊");
throw PaymentMesEnum.BMS_PAY_ALLOC_ST.exception();
}
writeLogs(fees, operator, now);
return "已将 " + fees.size() + " 条费用分摊为 " + lineCount + " 笔订单费用";
}
/**
* 按重量分摊的费用,按提单号补上实际重量和打板重量。按订单数量分摊的不查、不填。
* 这两个值只参与计算,不返回给页面。
*/
private void fillWeight(List<SrvBillAirAllocFee> fees)
{
List<String> codes = new ArrayList<>();
for (SrvBillAirAllocFee fee : fees)
{
if (fee != null && DIM_WEIGHT.equals(fee.getAllocDim()) && fee.getBillCode() != null && !codes.contains(fee.getBillCode()))
{
codes.add(fee.getBillCode());
}
}
if (codes.isEmpty())
{
return;
}
Map<String, SrvBillAirAllocFee> weights = new LinkedHashMap<>();
for (SrvBillAirAllocFee row : allocMapper.selectLadingWeight(codes))
{
if (row != null && row.getBillCode() != null)
{
weights.put(row.getBillCode(), row);
}
}
for (SrvBillAirAllocFee fee : fees)
{
if (fee == null || !DIM_WEIGHT.equals(fee.getAllocDim()))
{
continue;
}
SrvBillAirAllocFee weight = weights.get(fee.getBillCode());
if (weight == null)
{
continue;
}
fee.setActualWeight(weight.getActualWeight());
fee.setBandBoxWeight(weight.getBandBoxWeight());
}
}
/** 提单号去重、去空白,并限制单次 5 条。 */
private List<String> normalize(List<String> billCodes)
{
@@ -193,11 +147,11 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
}
if (codes.isEmpty())
{
throw new ServiceException("请勾选提单");
throw PaymentMesEnum.BMS_PAY_PICK_LAD.exception();
}
if (codes.size() > MAX_BILLS)
{
throw new ServiceException("单次最多处理5条提单");
throw PaymentMesEnum.BMS_PAY_LAD_LIMIT.exception();
}
for (String code : codes)
{
@@ -266,28 +220,13 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
{
return "提单缺少主键";
}
if (!DIM_ORDER.equals(fee.getAllocDim()) && !DIM_WEIGHT.equals(fee.getAllocDim()))
{
return "未选择分摊维度";
}
if (orders == null || orders.isEmpty())
{
return "提单下没有关联订单";
}
if (DIM_WEIGHT.equals(fee.getAllocDim()))
if (totalWeight(orders).signum() <= 0)
{
if (fee.getActualWeight() == null || fee.getActualWeight().signum() <= 0)
{
return "提单没有实际重量,无法按重量分摊";
}
if (fee.getBandBoxWeight() == null || fee.getBandBoxWeight().signum() <= 0)
{
return "干线没有打板重量,无法按重量分摊";
}
if (totalWeight(orders).signum() <= 0)
{
return "订单没有可用重量,无法按重量分摊";
}
return "订单没有可用重量,无法按重量分摊";
}
return "";
}
@@ -298,8 +237,9 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
row.setFeeId(UUID.randomUUID().toString());
row.setBillId(order.getBillId());
row.setPayBillId(payBillIds.get(order.getBillId()));
row.setFeeType(FEE_TYPE);
row.setFeeName(blankName(fee));
String name = blankName(fee);
row.setFeeType(name.length() <= 30 ? name : name.substring(0, 30));
row.setFeeName(name);
row.setScId(fee.getId() == null ? 0L : fee.getId());
row.setFeeLevel(fee.getFeeLevel());
row.setAmount(amount);
@@ -319,7 +259,7 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
return 0;
}
int rows = batch.size();
payBillFeeMapper.insertBatch(batch);
payBillFeeService.insertBatchAndRefreshTotals(new ArrayList<>(batch));
batch.clear();
return rows;
}
@@ -345,28 +285,12 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
}
/**
* 按重量分摊时,先算出这一票提单实际可分摊的金额。
* 百分比 = 提单实际重量 srv_bill_air.actual_weight / 干线打板重量 srv_bill_air_flight.band_box_weight。
* 可分摊金额 = 费用价格 × 百分比,保留两位,四舍五入。
* 调用前须已确认实际重量和打板重量都大于 0。
*
* @param fee 待分摊的提单费用,带有实际重量和打板重量
* @return 按百分比算出的可分摊金额
*/
private static BigDecimal weightAmount(SrvBillAirAllocFee fee)
{
BigDecimal price = fee.getPrice().setScale(2, RoundingMode.HALF_UP);
return price.multiply(fee.getActualWeight()).divide(fee.getBandBoxWeight(), 2, RoundingMode.HALF_UP);
}
/**
* 把金额拆到每一票订单,结果保留两位。
* 按订单数量:每一票先向下取整,差额补在最后一票,合计等于费用价格。
* 按重量:传入的金额已是「费用价格 × 提单实际重量 / 干线打板重量」。
* 再按订单重量占这些订单总重量的比例分摊;前面的订单向下取整,差额补在最后一票有重量的订单上,合计等于这笔金额。
* 把费用价格拆到每一票订单,结果保留两位,合计等于费用价格。
* 按订单数量:每一票先向下取整,差额补在最后一票。
* 按重量:按订单重量占这些订单总重量的比例分摊。前面的订单向下取整,差额补在最后一票有重量的订单上。
* 订单重量优先取计费重,没有计费重时取实际重量,再没有取收货重量。
*
* @param price 本次要拆开的金额。按重量时是百分比计算后的可分摊金额
* @param price 费用价格,全额分摊
* @param orders 该提单下的订单
* @param byWeight true 按订单重量占比,false 按订单数量均分
* @return 与订单顺序一致的分摊金额
@@ -1,5 +1,6 @@
package com.bms.price.service.impl;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
@@ -12,19 +13,27 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson2.JSON;
import com.bms.common.annotation.DataScope;
import com.bms.common.core.domain.entity.SysDictData;
import com.bms.common.core.domain.entity.SysUser;
import com.bms.common.enums.PaymentMesEnum;
import com.bms.common.exception.ServiceException;
import com.bms.common.utils.DateUtils;
import com.bms.common.utils.PageUtils;
import com.bms.common.utils.SecurityUtils;
import com.bms.common.utils.poi.ExcelUtil;
import com.bms.price.airbill.domain.SrvBillAirFee;
import com.bms.price.airbill.domain.SrvBillAirFeeImportRow;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeCommand;
import com.bms.price.airbill.domain.SrvBillAirRoleFeeQuery;
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
import com.bms.price.domain.PrcSurcharge;
import com.bms.price.mapper.PrcPayBillFeeMapper;
import com.bms.price.mapper.PrcSurchargeMapper;
import com.bms.price.mapper.SrvBillAirFeeMapper;
import com.bms.price.mapper.SrvBillAirMapper;
import com.bms.price.service.IPrcPayBillFeeService;
import com.bms.price.service.IPrcSurchargeService;
import com.bms.price.service.ISrvBillAirFeeService;
import com.bms.system.service.ISysDictTypeService;
@@ -49,15 +58,24 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
private static final String CURRENCY_DICT = "bms_currency";
/** 三个费用页的权限字符,用来匹配角色上的数据范围 */
private static final String ROLE_FEE_PERMS = "price:airFee:air,price:airFee:declare,price:airFee:clear";
@Autowired
private SrvBillAirFeeMapper feeMapper;
@Autowired
private PrcPayBillFeeMapper payBillFeeMapper;
@Autowired
private IPrcPayBillFeeService payBillFeeService;
@Autowired
private IPrcSurchargeService surchargeService;
@Autowired
private PrcSurchargeMapper surchargeMapper;
@Autowired
private SrvBillAirMapper billMapper;
@@ -71,6 +89,142 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
return feeMapper.selectByBillCode(billCode.trim());
}
@Override
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = ROLE_FEE_PERMS)
public List<SrvBillAirFee> listRoleFees(SrvBillAirRoleFeeQuery query)
{
if (query == null)
{
return List.of();
}
String normalized = normalizeScene(query.getScene());
requireScenePerm(normalized);
if (query.getBillCodes() == null || query.getBillCodes().isEmpty())
{
return List.of();
}
query.setFeeScene(sceneCode(normalized));
return feeMapper.selectRoleFees(query);
}
@Override
@DataScope(deptAlias = "su", deptField = "dept_id", userAlias = "su", userField = "user_id", permission = ROLE_FEE_PERMS)
@Transactional(rollbackFor = Exception.class)
public String saveRole(SrvBillAirRoleFeeCommand command)
{
if (command == null || command.getBills() == null || command.getBills().isEmpty())
{
throw PaymentMesEnum.BMS_PAY_NO_CHANGE.exception();
}
String scene = normalizeScene(command.getScene());
requireScenePerm(scene);
String level = levelOf(scene);
String code = sceneCode(scene);
command.setFeeScene(code);
int saved = 0;
for (SrvBillAirFeeSave bill : command.getBills())
{
if (bill == null)
{
continue;
}
requireVisible(command, bill);
stamp(bill.getInserts(), level, code);
stamp(bill.getUpdates(), level, code);
save(bill);
saved++;
}
if (saved == 0)
{
throw PaymentMesEnum.BMS_PAY_NO_CHANGE.exception();
}
return "已保存 " + saved + " 个提单的费用";
}
@Override
public List<SrvBillAirFee> parseImport(String scene, InputStream input) throws Exception
{
String normalized = normalizeScene(scene);
requireScenePerm(normalized);
String level = levelOf(normalized);
ExcelUtil<SrvBillAirFeeImportRow> excel = new ExcelUtil<>(SrvBillAirFeeImportRow.class);
List<SrvBillAirFeeImportRow> rows = excel.importExcel(input);
if (rows == null || rows.isEmpty())
{
throw PaymentMesEnum.BMS_PAY_IMP_EMPTY.exception();
}
Map<String, String> currencies = dictLabels(CURRENCY_DICT, "币种");
List<SrvBillAirFee> fees = new ArrayList<>();
int line = 1;
for (SrvBillAirFeeImportRow row : rows)
{
line++;
if (row == null || exampleRow(row) || blankImport(row))
{
continue;
}
String name = trim(row.getFeeName());
if (name.isEmpty())
{
throw new ServiceException("第" + line + "行请填写费用名称");
}
List<PrcSurcharge> found = surchargeMapper.selectByExactName(name);
if (found == null || found.isEmpty())
{
throw new ServiceException("第" + line + "行费用名称不存在:" + name);
}
PrcSurcharge chosen = null;
int enabled = 0;
for (PrcSurcharge item : found)
{
if (item != null && "on".equalsIgnoreCase(trim(item.getSsCode())))
{
enabled++;
chosen = item;
}
}
if (enabled > 1)
{
throw new ServiceException("第" + line + "行费用名称对应多条附加费:" + name);
}
if (chosen == null)
{
chosen = found.get(0);
}
if (row.getPrice() == null)
{
throw new ServiceException("第" + line + "行请填写价格");
}
if (row.getPrice().signum() < 0)
{
throw new ServiceException("第" + line + "行价格不能小于0");
}
String currency = trim(row.getCurrency());
if (currency.isEmpty() || !currencies.containsKey(currency))
{
throw new ServiceException("第" + line + "行币种不在币种字典中");
}
String remark = trim(row.getRemark());
if (remark.length() > 500)
{
throw new ServiceException("第" + line + "行备注不能超过500字");
}
SrvBillAirFee fee = new SrvBillAirFee();
fee.setFeeType(String.valueOf(chosen.getScId()));
fee.setFeeName(chosen.getScName());
fee.setFeeLevel(level);
fee.setPrice(row.getPrice());
fee.setCurrency(currency);
fee.setRemark(remark);
fees.add(fee);
}
if (fees.isEmpty())
{
throw PaymentMesEnum.BMS_PAY_IMP_DEMO.exception();
}
return fees;
}
@Override
public List<SrvBillAirFeeLog> listLogs(String billCode)
{
@@ -93,7 +247,7 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
{
if (command == null)
{
throw new ServiceException("请提交要保存的费用");
throw PaymentMesEnum.BMS_PAY_FEE_BODY.exception();
}
String billCode = requireBill(command.getBillCode());
List<SrvBillAirFee> inserts = command.getInserts() == null ? List.of() : command.getInserts();
@@ -101,7 +255,7 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
List<Long> deletes = command.getDeletes() == null ? List.of() : command.getDeletes();
if (inserts.isEmpty() && updates.isEmpty() && deletes.isEmpty())
{
throw new ServiceException("没有需要保存的变更");
throw PaymentMesEnum.BMS_PAY_NO_CHANGE.exception();
}
Map<String, String> levelLabels = dictLabels(LEVEL_DICT, "费用阶段");
Map<String, String> allocLabels = dictLabels(ALLOC_DICT, "是否分摊");
@@ -118,20 +272,20 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
{
if (id == null)
{
throw new ServiceException("要删除的费用缺少编号");
throw PaymentMesEnum.BMS_PAY_DEL_ID.exception();
}
if (!seen.add(id))
{
throw new ServiceException("同一条费用不能重复提交");
throw PaymentMesEnum.BMS_PAY_FEE_DUP.exception();
}
SrvBillAirFee current = liveFee(id, billCode);
if (current == null)
{
throw new ServiceException("费用已删除或不属于该提单");
throw PaymentMesEnum.BMS_PAY_FEE_GONE.exception();
}
if (locked(current))
{
throw new ServiceException("已分摊或已确定的费用不能删除,请使用作废");
throw PaymentMesEnum.BMS_PAY_FEE_LOCK.exception();
}
int rows = feeMapper.deleteFee(current.getId(), billCode, operator.account, now);
if (rows > 0)
@@ -144,20 +298,24 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
{
if (incoming == null || incoming.getId() == null)
{
throw new ServiceException("要修改的费用缺少编号");
throw PaymentMesEnum.BMS_PAY_EDIT_ID.exception();
}
if (!seen.add(incoming.getId()))
{
throw new ServiceException("同一条费用不能重复提交");
throw PaymentMesEnum.BMS_PAY_FEE_DUP.exception();
}
SrvBillAirFee current = liveFee(incoming.getId(), billCode);
if (current == null)
{
throw new ServiceException("费用已删除或不属于该提单");
throw PaymentMesEnum.BMS_PAY_FEE_GONE.exception();
}
if (locked(current))
{
throw new ServiceException("已分摊或已确定的费用不能修改");
throw PaymentMesEnum.BMS_PAY_FEE_EDIT.exception();
}
if (blank(incoming.getFeeLevel()))
{
incoming.setFeeLevel(current.getFeeLevel());
}
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
next.setBillFeeStatus(current.getBillFeeStatus() == null || current.getBillFeeStatus().isBlank() ? "0" : current.getBillFeeStatus());
@@ -198,7 +356,7 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
}
if (inserted + updated + removed == 0)
{
throw new ServiceException("没有需要保存的变更");
throw PaymentMesEnum.BMS_PAY_NO_CHANGE.exception();
}
return "已保存:新增 " + inserted + " 条,修改 " + updated + " 条,删除 " + removed + " 条";
}
@@ -210,22 +368,22 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
String code = requireBill(billCode);
if (feeId == null)
{
throw new ServiceException("请选择要确定的费用");
throw PaymentMesEnum.BMS_PAY_PICK_OK.exception();
}
SrvBillAirFee current = liveFee(feeId, code);
if (current == null)
{
throw new ServiceException("费用已删除或不属于该提单");
throw PaymentMesEnum.BMS_PAY_FEE_GONE.exception();
}
if (!STATUS_ALLOCATED.equals(current.getBillFeeStatus()))
{
throw new ServiceException("只有已分摊的费用可以确定");
throw PaymentMesEnum.BMS_PAY_OK_STAT.exception();
}
Operator operator = operator();
Date now = DateUtils.getNowDate();
if (feeMapper.confirmFee(feeId, code, operator.account, now) != 1)
{
throw new ServiceException("费用状态已变化,请刷新后再确定");
throw PaymentMesEnum.BMS_PAY_OK_CHG.exception();
}
writePlainLog(code, feeId, SrvBillAirFeeLog.CONFIRM, "确定费用", operator, now);
return "费用已确定";
@@ -238,33 +396,25 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
String code = requireBill(billCode);
if (feeId == null)
{
throw new ServiceException("请选择要作废的费用");
throw PaymentMesEnum.BMS_PAY_PICK_VOID.exception();
}
SrvBillAirFee current = liveFee(feeId, code);
if (current == null)
{
throw new ServiceException("费用已删除或不属于该提单");
throw PaymentMesEnum.BMS_PAY_FEE_GONE.exception();
}
if (!locked(current))
{
throw new ServiceException("只有已分摊或已确定的费用可以作废");
}
String billedOrderNo = payBillFeeMapper.selectBilledOrderNo(feeId);
if (billedOrderNo != null)
{
String orderNo = billedOrderNo.trim();
if (orderNo.isEmpty())
{
orderNo = "未知订单";
}
throw new ServiceException("该费用存在单笔或多笔订单已出账单-如:" + orderNo + ",不可作废");
throw PaymentMesEnum.BMS_PAY_VOID_STAT.exception();
}
Operator operator = operator();
Date now = DateUtils.getNowDate();
List<String> affectedBills = payBillFeeMapper.selectBillIdsByLadingFee(feeId);
payBillFeeMapper.markLadingFeeDeleted(feeId);
payBillFeeService.refreshTotals(affectedBills);
if (feeMapper.voidFee(feeId, code, operator.account, now) != 1)
{
throw new ServiceException("费用状态已变化,请刷新后再作废");
throw PaymentMesEnum.BMS_PAY_VOID_CHG.exception();
}
Map<String, String> levelLabels = dictLabels(LEVEL_DICT, "费用阶段");
Map<String, String> allocLabels = dictLabels(ALLOC_DICT, "是否分摊");
@@ -276,6 +426,118 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
return "费用已作废";
}
private static void stamp(List<SrvBillAirFee> rows, String feeLevel, String feeScene)
{
if (rows == null)
{
return;
}
for (SrvBillAirFee row : rows)
{
if (row == null)
{
continue;
}
row.setFeeLevel(feeLevel);
row.setFeeScene(feeScene);
row.setAllocFlag("1");
row.setAllocDim("2");
}
}
/** 修改和删除必须落在当前角色的数据权限内。新增由当前用户创建,不在这里拦截。 */
private void requireVisible(SrvBillAirRoleFeeCommand command, SrvBillAirFeeSave bill)
{
Object rawScope = command.getParams().get("dataScope");
if (rawScope == null || String.valueOf(rawScope).isBlank())
{
return;
}
if (bill.getBillCode() == null || bill.getBillCode().isBlank())
{
return;
}
List<Long> ids = new ArrayList<>();
if (bill.getDeletes() != null)
{
ids.addAll(bill.getDeletes());
}
if (bill.getUpdates() != null)
{
for (SrvBillAirFee fee : bill.getUpdates())
{
if (fee != null)
{
ids.add(fee.getId());
}
}
}
for (Long id : ids)
{
if (id == null)
{
continue;
}
if (feeMapper.selectRoleFeeVisible(id, bill.getBillCode(), command) == null)
{
throw PaymentMesEnum.BMS_PAY_NO_PERM.exception();
}
}
}
/** 空运对应字典 b 段,报关和清关对应字典 c 段。 */
private static String levelOf(String scene)
{
return "air".equals(scene) ? "2" : "3";
}
private static String sceneCode(String scene)
{
if ("air".equals(scene))
{
return "AIR";
}
if ("declare".equals(scene))
{
return "DECLARE";
}
return "CLEAR";
}
private static String normalizeScene(String scene)
{
String value = scene == null ? "" : scene.trim().toLowerCase();
if (!"air".equals(value) && !"declare".equals(value) && !"clear".equals(value))
{
throw PaymentMesEnum.BMS_PAY_FEE_ENTRY.exception();
}
return value;
}
private static void requireScenePerm(String scene)
{
String perm = "air".equals(scene) ? "price:airFee:air"
: "declare".equals(scene) ? "price:airFee:declare" : "price:airFee:clear";
if (!SecurityUtils.hasPermi(perm))
{
throw PaymentMesEnum.BMS_PAY_FORBIDDEN.exception();
}
}
private static boolean exampleRow(SrvBillAirFeeImportRow row)
{
return "燃油附加费".equals(trim(row.getFeeName()))
&& row.getPrice() != null
&& row.getPrice().compareTo(new BigDecimal("1000")) == 0
&& "EUR".equalsIgnoreCase(trim(row.getCurrency()))
&& "导入时需清空这行示例数据".equals(trim(row.getRemark()));
}
private static boolean blankImport(SrvBillAirFeeImportRow row)
{
return blank(row.getFeeName()) && row.getPrice() == null && blank(row.getCurrency()) && blank(row.getRemark());
}
private SrvBillAirFee liveFee(Long id, String billCode)
{
return feeMapper.selectLive(id, billCode);
@@ -292,73 +554,53 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
{
String feeType = trim(raw.getFeeType());
String feeLevel = trim(raw.getFeeLevel());
String allocFlag = trim(raw.getAllocFlag());
String allocDim = trim(raw.getAllocDim());
String currency = trim(raw.getCurrency());
String remark = trim(raw.getRemark());
Long scId = parseScId(feeType);
if (scId == null)
{
throw new ServiceException("请选择费用项");
throw PaymentMesEnum.BMS_PAY_PICK_ITEM.exception();
}
PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId);
if (surcharge == null)
{
throw new ServiceException("费用项不存在");
throw PaymentMesEnum.BMS_PAY_ITEM_MISS.exception();
}
feeType = String.valueOf(scId);
if (allocFlag.isEmpty() || !allocLabels.containsKey(allocFlag))
{
throw new ServiceException("请选择是否分摊");
}
if (!feeLevel.isEmpty() && (feeLevel.length() > 32 || !levelLabels.containsKey(feeLevel)))
{
throw new ServiceException("费用阶段不在字典中");
}
if ("1".equals(allocFlag) && feeLevel.isEmpty())
{
throw new ServiceException("如需分摊费用至订单,请选择费用所属阶段");
}
if ("1".equals(allocFlag))
{
if (allocDim.isEmpty() || !dimLabels.containsKey(allocDim))
{
throw new ServiceException("如需分摊费用至订单,请选择分摊维度");
}
}
else
{
allocDim = "";
throw PaymentMesEnum.BMS_PAY_LEVEL.exception();
}
if (raw.getPrice() == null)
{
throw new ServiceException("请填写价格");
throw PaymentMesEnum.BMS_PAY_PRICE.exception();
}
if (raw.getPrice().signum() < 0)
{
throw new ServiceException("价格不能小于0");
throw PaymentMesEnum.BMS_PAY_PRICE_NEG.exception();
}
if (raw.getPrice().precision() - raw.getPrice().scale() > 16)
{
throw new ServiceException("价格整数部分不能超过16位");
throw PaymentMesEnum.BMS_PAY_PRICE_LEN.exception();
}
if (currency.isEmpty())
{
throw new ServiceException("请选择收费币种");
throw PaymentMesEnum.BMS_PAY_CUR_PICK.exception();
}
if (currency.length() > 16 || !currencyLabels.containsKey(currency))
{
throw new ServiceException("收费币种不在币种字典中");
throw PaymentMesEnum.BMS_PAY_CUR_DICT.exception();
}
if (remark.length() > 500)
{
throw new ServiceException("备注不能超过500字");
throw PaymentMesEnum.BMS_PAY_REMARK.exception();
}
SrvBillAirFee fee = new SrvBillAirFee();
fee.setFeeType(feeType);
fee.setFeeLevel(feeLevel);
fee.setAllocFlag(allocFlag);
fee.setAllocDim(allocDim);
fee.setFeeScene(trim(raw.getFeeScene()));
fee.setAllocFlag("1");
fee.setAllocDim("2");
fee.setPrice(raw.getPrice());
fee.setCurrency(currency);
fee.setRemark(remark);
@@ -420,14 +662,6 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
List<SrvBillAirFeeLog.FieldChange> changes = new ArrayList<>();
addChange(changes, "feeType", "费用项", label(feeLabels, value(before, SrvBillAirFee::getFeeType)),
label(feeLabels, value(after, SrvBillAirFee::getFeeType)));
addChange(changes, "feeLevel", "费用阶段", label(levelLabels, value(before, SrvBillAirFee::getFeeLevel)),
label(levelLabels, value(after, SrvBillAirFee::getFeeLevel)));
addChange(changes, "allocFlag", "是否分摊", label(allocLabels, value(before, SrvBillAirFee::getAllocFlag)),
label(allocLabels, value(after, SrvBillAirFee::getAllocFlag)));
addChange(changes, "allocDim", "分摊维度(票)", label(dimLabels, value(before, SrvBillAirFee::getAllocDim)),
label(dimLabels, value(after, SrvBillAirFee::getAllocDim)));
addChange(changes, "billFeeStatus", "费用状态", label(statusLabels, value(before, SrvBillAirFee::getBillFeeStatus)),
label(statusLabels, value(after, SrvBillAirFee::getBillFeeStatus)));
addChange(changes, "price", "价格", priceText(before), priceText(after));
addChange(changes, "currency", "收费币种", label(currencyLabels, value(before, SrvBillAirFee::getCurrency)),
label(currencyLabels, value(after, SrvBillAirFee::getCurrency)));
@@ -486,12 +720,12 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
{
if (billCode == null || billCode.isBlank())
{
throw new ServiceException("提单号不能为空");
throw PaymentMesEnum.BMS_PAY_LAD_BLANK.exception();
}
String code = billCode.trim();
if (billMapper.selectSrvBillAirByBillCode(code) == null)
{
throw new ServiceException("提单不存在");
throw PaymentMesEnum.BMS_PAY_LAD_MISS.exception();
}
return code;
}
@@ -949,6 +949,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where bill_id = #{billId}
</update>
<update id="refreshPayTotalFromFees">
update prc_order_bill o
left join (
select bill_id, ifnull(sum(amount), 0) as total
from prc_pay_bill_fee
where ifnull(is_deleted, '0') = '0'
and bill_id in
<foreach collection="billIds" item="billId" open="(" separator="," close=")">#{billId}</foreach>
group by bill_id
) s on s.bill_id = o.bill_id
set o.pay_total = ifnull(s.total, 0),
o.update_time = sysdate()
where o.bill_id in
<foreach collection="billIds" item="billId" open="(" separator="," close=")">#{billId}</foreach>
</update>
<update id="updateOrderBillAmounts">
update prc_order_bill
set surcharge_amount = #{surchargeAmount},
@@ -65,6 +65,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
limit 1
</select>
<update id="bindPayBillId">
update prc_pay_bill_fee f
inner join prc_pay_bill b on b.bill_id = f.bill_id
set f.pay_bill_id = b.pay_bill_id
where (f.pay_bill_id is null or f.pay_bill_id = '')
and f.bill_id in
<foreach collection="billIds" item="billId" open="(" separator="," close=")">#{billId}</foreach>
</update>
<select id="selectBillIdsByLadingFee" resultType="String">
select distinct bill_id
from prc_pay_bill_fee
where fee_source = 'LADING'
and sc_id = #{feeId}
and ifnull(is_deleted, '0') = '0'
and bill_id is not null
and bill_id != ''
</select>
<update id="markLadingFeeDeleted">
update prc_pay_bill_fee
set is_deleted = '1'
@@ -96,6 +96,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
update_time = sysdate()
</insert>
<insert id="insertMissingPayBills">
insert into prc_pay_bill (
pay_bill_id, bill_id, currency, pay_total,
belong_saler, belong_saler_code, settlement_man, settlement_man_code,
create_by, create_time, update_by, update_time
)
select replace(uuid(), '-', ''), o.bill_id, ifnull(nullif(o.currency, ''), 'CNY'), 0,
o.belong_saler, o.belong_saler_code, o.settlement_man, o.settlement_man_code,
#{operator}, sysdate(), #{operator}, sysdate()
from prc_order_bill o
where o.bill_id in
<foreach collection="billIds" item="billId" open="(" separator="," close=")">#{billId}</foreach>
and not exists (
select 1 from prc_pay_bill b where b.bill_id = o.bill_id
)
</insert>
<update id="refreshPayTotal">
update prc_pay_bill b
left join (
select bill_id, ifnull(sum(amount), 0) as total
from prc_pay_bill_fee
where ifnull(is_deleted, '0') = '0'
and bill_id in
<foreach collection="billIds" item="billId" open="(" separator="," close=")">#{billId}</foreach>
group by bill_id
) s on s.bill_id = b.bill_id
set b.pay_total = ifnull(s.total, 0),
b.update_time = sysdate()
where b.bill_id in
<foreach collection="billIds" item="billId" open="(" separator="," close=")">#{billId}</foreach>
</update>
<delete id="deleteByBillId" parameterType="String">
delete from prc_pay_bill where bill_id = #{billId}
</delete>
@@ -35,6 +35,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and ifnull(is_del, '0') = '0'
</select>
<select id="selectByExactName" resultMap="PrcSurchargeResult">
<include refid="selectPrcSurchargeVo"/>
where ifnull(is_del, '0') = '0'
and sc_name = #{scName}
and lower(ifnull(ss_code, '')) &lt;&gt; 'vd'
</select>
<select id="selectPrcSurchargeList" parameterType="PrcSurcharge" resultMap="PrcSurchargeResult">
<include refid="selectPrcSurchargeVo"/>
<where>
@@ -10,21 +10,12 @@
left join srv_bill_air a on a.bill_code = f.bill_code
left join prc_surcharge sc on sc.sc_id = f.fee_type and ifnull(sc.is_del, '0') = '0'
where f.del_flag = '0'
and f.alloc_flag = '1'
and f.bill_fee_status = '0'
and f.bill_code in
<foreach collection="billCodes" item="code" open="(" separator="," close=")">#{code}</foreach>
order by f.bill_code, f.id
</select>
<select id="selectLadingWeight" resultType="com.bms.price.airbill.domain.SrvBillAirAllocFee">
select a.bill_code as billCode, a.actual_weight as actualWeight, fl.band_box_weight as bandBoxWeight
from srv_bill_air a
left join srv_bill_air_flight fl on fl.bill_code = a.bill_code
where a.bill_code in
<foreach collection="billCodes" item="code" open="(" separator="," close=")">#{code}</foreach>
</select>
<select id="selectOrders" resultType="com.bms.price.airbill.domain.SrvBillAirAllocOrder">
select trim(e.air_no_old) as billCode, e.order_no as orderNo, e.bill_id as billId,
case
@@ -50,10 +41,11 @@
<update id="markAllocated">
update srv_bill_air_fee
set bill_fee_status = '1',
alloc_flag = '1',
alloc_dim = '2',
update_by = #{updateBy},
update_time = #{updateTime}
where del_flag = '0'
and alloc_flag = '1'
and bill_fee_status = '0'
and id in
<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
@@ -16,6 +16,53 @@
order by f.id
</select>
<sql id="roleFeeDataScope">
<if test="params != null and params.dataScope != null and params.dataScope != ''">
AND EXISTS (
SELECT 1 FROM sys_user su
WHERE su.del_flag = '0'
AND su.user_name COLLATE utf8mb4_general_ci = f.create_by COLLATE utf8mb4_general_ci
${params.dataScope}
)
</if>
</sql>
<select id="selectRoleFees" parameterType="com.bms.price.airbill.domain.SrvBillAirRoleFeeQuery" resultType="com.bms.price.airbill.domain.SrvBillAirFee">
select f.id, f.bill_code as billCode, f.fee_type as feeType, sc.sc_name as feeName,
f.fee_level as feeLevel, f.fee_scene as feeScene, f.bill_fee_status as billFeeStatus,
f.alloc_flag as allocFlag, f.alloc_dim as allocDim, f.price, f.currency, f.remark,
f.create_by as createBy, f.create_time as createTime, f.update_by as updateBy,
f.update_time as updateTime, f.del_flag as delFlag
from srv_bill_air_fee f
left join prc_surcharge sc on sc.sc_id = f.fee_type and ifnull(sc.is_del, '0') = '0'
where f.del_flag = '0'
and ifnull(f.bill_fee_status, '0') &lt;&gt; '2'
and f.fee_scene = #{feeScene}
and f.bill_code in
<foreach collection="billCodes" item="code" open="(" separator="," close=")">#{code}</foreach>
<include refid="roleFeeDataScope"/>
order by f.bill_code, f.id
</select>
<select id="selectRoleFeeVisible" resultType="com.bms.price.airbill.domain.SrvBillAirFee">
select f.id, f.bill_code as billCode, f.fee_scene as feeScene, f.create_by as createBy
from srv_bill_air_fee f
where f.id = #{id}
and f.bill_code = #{billCode}
and f.del_flag = '0'
and ifnull(f.bill_fee_status, '0') &lt;&gt; '2'
and f.fee_scene = #{query.feeScene}
<if test="query.params != null and query.params.dataScope != null and query.params.dataScope != ''">
AND EXISTS (
SELECT 1 FROM sys_user su
WHERE su.del_flag = '0'
AND su.user_name COLLATE utf8mb4_general_ci = f.create_by COLLATE utf8mb4_general_ci
${query.params.dataScope}
)
</if>
limit 1
</select>
<select id="selectLive" resultType="com.bms.price.airbill.domain.SrvBillAirFee">
select f.id, f.bill_code as billCode, f.fee_type as feeType, sc.sc_name as feeName,
f.fee_level as feeLevel, f.bill_fee_status as billFeeStatus,
@@ -32,10 +79,10 @@
<insert id="insertFee" useGeneratedKeys="true" keyProperty="id">
insert into srv_bill_air_fee (
bill_code, fee_type, fee_level, bill_fee_status, alloc_flag, alloc_dim, price, currency, remark,
bill_code, fee_type, fee_level, fee_scene, bill_fee_status, alloc_flag, alloc_dim, price, currency, remark,
create_by, create_time, update_by, update_time, del_flag
) values (
#{billCode}, #{feeType}, #{feeLevel}, '0', #{allocFlag}, #{allocDim}, #{price}, #{currency}, #{remark},
#{billCode}, #{feeType}, #{feeLevel}, #{feeScene}, '0', #{allocFlag}, #{allocDim}, #{price}, #{currency}, #{remark},
#{createBy}, #{createTime}, #{updateBy}, #{updateTime}, '0'
)
</insert>
@@ -26,6 +26,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="actualVolume" column="actual_volume"/>
<result property="volumeWeight" column="volume_weight"/>
<result property="firstLandingTime" column="first_landing_time"/>
<result property="boardNo" column="board_no"/>
<result property="bandBoxWeight" column="band_box_weight"/>
<result property="airNo" column="air_no"/>
<result property="actualAirNo" column="actual_air_no"/>
<result property="airCompanyAgent" column="air_ompany_agent"/>
@@ -33,6 +35,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="declareCode" column="declare_code"/>
<result property="clearanceCode" column="clearance_code"/>
<result property="qingguanAgent" column="qingguan_agent"/>
<result property="clearanceOpCode" column="clearance_op_code"/>
<result property="stockAddr" column="stock_addr"/>
<result property="shippingDate" column="shipping_date"/>
<result property="startTime" column="start_time"/>
@@ -66,6 +69,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
a.actual_volume,
a.volume_weight,
fl.first_landing_time,
fl.board_no,
fl.band_box_weight,
fl.air_no,
fl.actual_air_no,
fl.air_ompany_agent,
@@ -73,6 +78,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
o.declare_code,
o.clearance_code,
o.qingguan_agent,
o.clearance_op_code,
o.stock_addr,
t.shipping_date,
t.start_time,
+2 -2
View File
@@ -1,8 +1,8 @@
-- 空运提单列表: under 计费中心
-- 空运提单列表: under 提单管理
SET NAMES utf8mb4;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT 2520, N'提单列表', 2300, 2, 'airBill', 'price/billing/airBill', '', 'AirBill', 1, 0, 'C', '0', '0',
SELECT 2520, N'提单列表', 2650, 1, 'airBill', 'price/billing/airBill', '', 'AirBill', 1, 0, 'C', '0', '0',
'price:airBill:list', 'list', 'admin', sysdate(), N'查看空运提单列表与详情(srv_bill_air*)'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2520 OR perms = 'price:airBill:list');
+56
View File
@@ -0,0 +1,56 @@
-- 提单管理:空运费用、报关费用、清关费用
SET NAMES utf8mb4;
SET @sql = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE srv_bill_air_fee ADD COLUMN fee_scene varchar(16) NULL DEFAULT NULL COMMENT ''费用入口:AIR空运 DECLARE报关 CLEAR清关。空运费用阶段为2(b段),报关和清关为3(c段)'' AFTER fee_level',
'SELECT 1'
)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'srv_bill_air_fee' AND COLUMN_NAME = 'fee_scene'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT 2650, N'提单管理', 0, 4, 'ladingFee', NULL, '', '', 1, 0, 'M', '0', '0', '', 'money', 'admin', sysdate(), N'提单列表、空运、报关、清关费用'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2650 OR perms = 'price:airFee:menu');
UPDATE sys_menu
SET parent_id = 0, order_num = 4
WHERE menu_id = 2650;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT 2651, N'空运费用', 2650, 2, 'air', 'price/billing/airFee/air/index', '', 'AirLadingFee', 1, 0, 'C', '0', '0', 'price:airFee:air', 'edit', 'admin', sysdate(), N'空运费用,费用阶段写入b段'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2651 OR perms = 'price:airFee:air');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT 2652, N'报关费用', 2650, 3, 'declare', 'price/billing/airFee/declare/index', '', 'DeclareLadingFee', 1, 0, 'C', '0', '0', 'price:airFee:declare', 'edit', 'admin', sysdate(), N'报关费用,费用阶段写入c段'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2652 OR perms = 'price:airFee:declare');
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT 2653, N'清关费用', 2650, 4, 'clear', 'price/billing/airFee/clear/index', '', 'ClearLadingFee', 1, 0, 'C', '0', '0', 'price:airFee:clear', 'edit', 'admin', sysdate(), N'清关费用,费用阶段写入c段'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2653 OR perms = 'price:airFee:clear');
UPDATE sys_menu SET parent_id = 2650, order_num = 1 WHERE menu_id = 2520;
UPDATE sys_menu SET order_num = 2 WHERE menu_id = 2651;
UPDATE sys_menu SET order_num = 3 WHERE menu_id = 2652;
UPDATE sys_menu SET order_num = 4 WHERE menu_id = 2653;
UPDATE sys_menu SET remark = N'提单列表、空运、报关、清关费用' WHERE menu_id = 2650;
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT 1, m.menu_id
FROM sys_menu m
WHERE m.menu_id IN (2650, 2651, 2652, 2653)
AND NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = m.menu_id);
INSERT INTO sys_role_menu (role_id, menu_id)
SELECT DISTINCT rm.role_id, m.menu_id
FROM sys_role_menu rm
JOIN sys_menu m ON m.menu_id IN (2650, 2651, 2652, 2653)
WHERE rm.menu_id IN (2300, 2520)
AND NOT EXISTS (
SELECT 1 FROM sys_role_menu x WHERE x.role_id = rm.role_id AND x.menu_id = m.menu_id
);