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

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
@@ -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,