提单费用均摊处理
This commit is contained in:
+58
@@ -16,10 +16,12 @@ import com.bms.common.core.domain.AjaxResult;
|
||||
import com.bms.common.core.page.TableDataInfo;
|
||||
import com.bms.common.enums.BusinessType;
|
||||
import com.bms.common.utils.StringUtils;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocCommand;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||
import com.bms.price.domain.vo.SrvBillAirQuery;
|
||||
import com.bms.price.domain.vo.SrvBillAirVo;
|
||||
import com.bms.price.service.ISrvBillAirAllocService;
|
||||
import com.bms.price.service.ISrvBillAirFeeService;
|
||||
import com.bms.price.service.ISrvBillAirService;
|
||||
|
||||
@@ -36,6 +38,9 @@ public class SrvBillAirController extends BaseController
|
||||
@Autowired
|
||||
private ISrvBillAirFeeService srvBillAirFeeService;
|
||||
|
||||
@Autowired
|
||||
private ISrvBillAirAllocService srvBillAirAllocService;
|
||||
|
||||
/**
|
||||
* 提单列表
|
||||
*/
|
||||
@@ -94,4 +99,57 @@ public class SrvBillAirController extends BaseController
|
||||
{
|
||||
return success(srvBillAirFeeService.save(command));
|
||||
}
|
||||
|
||||
/** 已分摊的费用改为已确定 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:feeConfirm')")
|
||||
@Log(title = "提单费用确定", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/fee/confirm")
|
||||
public AjaxResult confirmFee(@RequestParam("billCode") String billCode, @RequestParam("feeId") Long feeId)
|
||||
{
|
||||
return success(srvBillAirFeeService.confirm(billCode, feeId));
|
||||
}
|
||||
|
||||
/** 作废已分摊或已确定的费用,并逻辑删除已分摊到订单的明细 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:feeVoid')")
|
||||
@Log(title = "提单费用作废", businessType = BusinessType.DELETE)
|
||||
@PostMapping("/fee/void")
|
||||
public AjaxResult voidFee(@RequestParam("billCode") String billCode, @RequestParam("feeId") Long feeId)
|
||||
{
|
||||
return success(srvBillAirFeeService.voidFee(billCode, feeId));
|
||||
}
|
||||
|
||||
/** 勾选提单后,查出需要分摊到订单的费用 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:fee')")
|
||||
@GetMapping("/fee/alloc/preview")
|
||||
public AjaxResult allocPreview(@RequestParam("billCodes") String billCodes)
|
||||
{
|
||||
return success(srvBillAirAllocService.preview(splitCodes(billCodes)));
|
||||
}
|
||||
|
||||
/** 按重量或订单数把费用分摊到订单,并把费用状态改为已分摊 */
|
||||
@PreAuthorize("@ss.hasPermi('price:airBill:fee')")
|
||||
@Log(title = "提单费用分摊", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/fee/alloc")
|
||||
public AjaxResult allocate(@RequestBody SrvBillAirAllocCommand command)
|
||||
{
|
||||
List<String> billCodes = command == null ? null : command.getBillCodes();
|
||||
return success(srvBillAirAllocService.allocate(billCodes));
|
||||
}
|
||||
|
||||
private static List<String> splitCodes(String billCodes)
|
||||
{
|
||||
List<String> codes = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(billCodes))
|
||||
{
|
||||
return codes;
|
||||
}
|
||||
for (String part : billCodes.split("[,,\\s]+"))
|
||||
{
|
||||
if (StringUtils.isNotEmpty(part))
|
||||
{
|
||||
codes.add(part.trim());
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/** 提单费用分摊请求。一次最多 5 个提单号。 */
|
||||
@Data
|
||||
public class SrvBillAirAllocCommand
|
||||
{
|
||||
private List<String> billCodes;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
/** 待分摊的一条提单费用,供费用处理弹框展示。 */
|
||||
@Data
|
||||
public class SrvBillAirAllocFee
|
||||
{
|
||||
private Long id;
|
||||
|
||||
private String billCode;
|
||||
|
||||
/** 提单主键 srv_bill_air.guid,写入订单费用的 source_id */
|
||||
private String ladingId;
|
||||
|
||||
/** 费用阶段,字典 bms_fee_level */
|
||||
private String feeLevel;
|
||||
|
||||
/** 费用状态,字典 bill_fee_status */
|
||||
private String billFeeStatus;
|
||||
|
||||
private String feeName;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
private String currency;
|
||||
|
||||
/** 分摊维度:1订单 2重量 */
|
||||
private String allocDim;
|
||||
|
||||
/** 提单实际重量。只在按重量分摊时按提单号填入,不返回页面 */
|
||||
@JsonIgnore
|
||||
private BigDecimal actualWeight;
|
||||
|
||||
/** 干线打板重量。只在按重量分摊时按提单号填入,不返回页面 */
|
||||
@JsonIgnore
|
||||
private BigDecimal bandBoxWeight;
|
||||
|
||||
private Integer orderCount;
|
||||
|
||||
/** 不能分摊时的原因。空表示可以分摊 */
|
||||
private String blockReason;
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.bms.price.airbill.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/** 提单下参与分摊的一票订单。 */
|
||||
@Data
|
||||
public class SrvBillAirAllocOrder
|
||||
{
|
||||
private String billCode;
|
||||
|
||||
private String orderNo;
|
||||
|
||||
/** 计费订单 ID,对应 prc_order_bill.bill_id */
|
||||
private String billId;
|
||||
|
||||
/** 计费重,没有计费重时依次取实际重量、收货重量 */
|
||||
private BigDecimal chargeWeight;
|
||||
}
|
||||
@@ -17,6 +17,12 @@ public class SrvBillAirFeeLog
|
||||
|
||||
public static final String DELETE = "3";
|
||||
|
||||
/** 已分摊费用确定。日志不记录字段变化。 */
|
||||
public static final String CONFIRM = "4";
|
||||
|
||||
/** 已分摊或已确定费用作废。 */
|
||||
public static final String VOID = "5";
|
||||
|
||||
private Long logId;
|
||||
|
||||
private String billCode;
|
||||
|
||||
@@ -19,6 +19,9 @@ public class PrcPayBillFee implements Serializable
|
||||
private String feeType;
|
||||
private String feeName;
|
||||
private Long scId;
|
||||
|
||||
/** 费用所属阶段:1 a段,2 b段,3 c段,4 d段 */
|
||||
private String feeLevel;
|
||||
private BigDecimal amount;
|
||||
private String currency;
|
||||
private String roundMode;
|
||||
|
||||
@@ -38,4 +38,21 @@ public interface PrcPayBillFeeMapper
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int deleteByBillId(String billId);
|
||||
|
||||
/**
|
||||
* 逻辑删除某一条提单费用分摊到订单上的明细。
|
||||
*
|
||||
* @param feeId 提单费用 ID,保存在 sc_id
|
||||
* @return 影响行数
|
||||
*/
|
||||
public int markLadingFeeDeleted(@Param("feeId") Long feeId);
|
||||
|
||||
/**
|
||||
* 这条提单费用分摊到的订单里,取一个已经生成应付账单的订单号。
|
||||
* 是否出账看 prc_order_bill_ext.is_meet:1 已生成。
|
||||
*
|
||||
* @param feeId 提单费用 ID,保存在 sc_id
|
||||
* @return 订单号,没有已出账订单时为空
|
||||
*/
|
||||
public String selectBilledOrderNo(@Param("feeId") Long feeId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocOrder;
|
||||
import com.bms.price.domain.PrcPayBill;
|
||||
|
||||
/**
|
||||
* 提单费用分摊到订单。
|
||||
*/
|
||||
public interface SrvBillAirAllocMapper
|
||||
{
|
||||
/**
|
||||
* 查询这些提单上尚未分摊、且标记为需要分摊的费用。
|
||||
*
|
||||
* @param billCodes 提单号
|
||||
* @return 待分摊费用
|
||||
*/
|
||||
List<SrvBillAirAllocFee> selectPendingFees(@Param("billCodes") List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按提单号取出实际重量和打板重量,只供按重量分摊计算。
|
||||
*
|
||||
* @param billCodes 提单号
|
||||
* @return 提单号、实际重量、打板重量
|
||||
*/
|
||||
List<SrvBillAirAllocFee> selectLadingWeight(@Param("billCodes") List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按提单号查出关联订单。订单号在扩展表,计费单号和重量在订单表。
|
||||
*
|
||||
* @param billCodes 提单号,对应 prc_order_bill_ext.air_no_old
|
||||
* @return 订单
|
||||
*/
|
||||
List<SrvBillAirAllocOrder> selectOrders(@Param("billCodes") List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按计费订单号取出已有的应付主表。
|
||||
*
|
||||
* @param billIds 计费订单 ID
|
||||
* @return 应付主表编号
|
||||
*/
|
||||
List<PrcPayBill> selectPayBills(@Param("billIds") List<String> billIds);
|
||||
|
||||
/**
|
||||
* 把仍是未分摊的费用改为已分摊。已经不是未分摊的行不会被改到。
|
||||
*
|
||||
* @param ids 提单费用 ID
|
||||
* @param updateBy 操作人
|
||||
* @param updateTime 操作时间
|
||||
* @return 实际改到的行数
|
||||
*/
|
||||
int markAllocated(@Param("ids") List<Long> ids, @Param("updateBy") String updateBy, @Param("updateTime") Date updateTime);
|
||||
}
|
||||
@@ -22,6 +22,14 @@ public interface SrvBillAirFeeMapper
|
||||
int deleteFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||
@Param("updateTime") java.util.Date updateTime);
|
||||
|
||||
/** 已分摊改为已确定。 */
|
||||
int confirmFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||
@Param("updateTime") java.util.Date updateTime);
|
||||
|
||||
/** 已分摊或已确定的费用作废:状态改为已作废,并逻辑删除。 */
|
||||
int voidFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||
@Param("updateTime") java.util.Date updateTime);
|
||||
|
||||
int insertLog(SrvBillAirFeeLog log);
|
||||
|
||||
List<SrvBillAirFeeLog> selectLogs(String billCode);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.bms.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocFee;
|
||||
|
||||
/**
|
||||
* 把提单上待分摊的费用均摊到关联订单。
|
||||
*/
|
||||
public interface ISrvBillAirAllocService
|
||||
{
|
||||
/**
|
||||
* 查出勾选提单上需要分摊、且尚未分摊的费用。
|
||||
*
|
||||
* @param billCodes 提单号,最多 5 个
|
||||
* @return 费用和不能分摊的原因
|
||||
*/
|
||||
List<SrvBillAirAllocFee> preview(List<String> billCodes);
|
||||
|
||||
/**
|
||||
* 按费用上的分摊维度把金额摊到每个订单,并改费用状态为已分摊。整次处理在一个事务里,写入按 1000 条一批。
|
||||
*
|
||||
* @param billCodes 提单号,最多 5 个
|
||||
* @return 给页面的处理结果
|
||||
*/
|
||||
String allocate(List<String> billCodes);
|
||||
}
|
||||
@@ -16,4 +16,22 @@ public interface ISrvBillAirFeeService
|
||||
|
||||
/** 只写入有变动的行,并按行记录修改前后。返回给页面的提示。 */
|
||||
String save(SrvBillAirFeeSave command);
|
||||
|
||||
/**
|
||||
* 已分摊的费用确认为已确定。
|
||||
*
|
||||
* @param billCode 提单号
|
||||
* @param feeId 费用 ID
|
||||
* @return 给页面的提示
|
||||
*/
|
||||
String confirm(String billCode, Long feeId);
|
||||
|
||||
/**
|
||||
* 作废已分摊或已确定的费用。订单上的分摊明细逻辑删除,这条提单费用也逻辑删除。
|
||||
*
|
||||
* @param billCode 提单号
|
||||
* @param feeId 费用 ID
|
||||
* @return 给页面的提示
|
||||
*/
|
||||
String voidFee(String billCode, Long feeId);
|
||||
}
|
||||
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
package com.bms.price.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
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.core.domain.entity.SysUser;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.DateUtils;
|
||||
import com.bms.common.utils.SecurityUtils;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirAllocOrder;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.domain.PrcPayBill;
|
||||
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.ISrvBillAirAllocService;
|
||||
|
||||
/**
|
||||
* 提单费用分摊。只处理标记为分摊、状态仍是未分摊的费用。
|
||||
*/
|
||||
@Service
|
||||
public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
|
||||
{
|
||||
private static final int MAX_BILLS = 5;
|
||||
|
||||
private static final int BATCH_SIZE = 1000;
|
||||
|
||||
private static final String DIM_ORDER = "1";
|
||||
|
||||
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;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirAllocMapper allocMapper;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirMapper billMapper;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirFeeMapper feeMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcPayBillFeeMapper payBillFeeMapper;
|
||||
|
||||
@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)
|
||||
{
|
||||
List<SrvBillAirAllocOrder> rows = orders.getOrDefault(fee.getBillCode(), List.of());
|
||||
fee.setOrderCount(rows.size());
|
||||
fee.setBlockReason(blockReason(fee, rows));
|
||||
}
|
||||
return fees;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String allocate(List<String> billCodes)
|
||||
{
|
||||
List<String> codes = normalize(billCodes);
|
||||
List<SrvBillAirAllocFee> fees = dedupe(allocMapper.selectPendingFees(codes));
|
||||
fillWeight(fees);
|
||||
if (fees.isEmpty())
|
||||
{
|
||||
throw new ServiceException("所选提单没有需要分摊的费用");
|
||||
}
|
||||
Map<String, List<SrvBillAirAllocOrder>> orders = ordersOf(codes);
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
List<SrvBillAirAllocOrder> rows = orders.getOrDefault(fee.getBillCode(), List.of());
|
||||
fee.setOrderCount(rows.size());
|
||||
String reason = blockReason(fee, rows);
|
||||
if (!reason.isEmpty())
|
||||
{
|
||||
throw new ServiceException(fee.getBillCode() + " " + blankName(fee) + ":" + reason);
|
||||
}
|
||||
}
|
||||
Map<String, String> payBillIds = payBillIds(orders);
|
||||
List<PrcPayBillFee> batch = new ArrayList<>(BATCH_SIZE);
|
||||
int lineCount = 0;
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
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);
|
||||
for (int i = 0; i < rows.size(); i++)
|
||||
{
|
||||
batch.add(toOrderFee(fee, rows.get(i), shares.get(i), payBillIds));
|
||||
if (batch.size() == BATCH_SIZE)
|
||||
{
|
||||
lineCount += flush(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
lineCount += flush(batch);
|
||||
Date now = DateUtils.getNowDate();
|
||||
String operator = SecurityUtils.getUsername();
|
||||
int updated = allocMapper.markAllocated(feeIds(fees), operator, now);
|
||||
if (updated != fees.size())
|
||||
{
|
||||
throw new ServiceException("费用状态已变化,请重新打开后再分摊");
|
||||
}
|
||||
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)
|
||||
{
|
||||
LinkedHashSet<String> codes = new LinkedHashSet<>();
|
||||
if (billCodes != null)
|
||||
{
|
||||
for (String raw : billCodes)
|
||||
{
|
||||
if (raw == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (String part : raw.split("[,,\\s]+"))
|
||||
{
|
||||
if (!part.isBlank())
|
||||
{
|
||||
codes.add(part.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (codes.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请勾选提单");
|
||||
}
|
||||
if (codes.size() > MAX_BILLS)
|
||||
{
|
||||
throw new ServiceException("单次最多处理5条提单");
|
||||
}
|
||||
for (String code : codes)
|
||||
{
|
||||
if (billMapper.selectSrvBillAirByBillCode(code) == null)
|
||||
{
|
||||
throw new ServiceException("提单不存在:" + code);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(codes);
|
||||
}
|
||||
|
||||
private Map<String, List<SrvBillAirAllocOrder>> ordersOf(List<String> codes)
|
||||
{
|
||||
Map<String, List<SrvBillAirAllocOrder>> grouped = new LinkedHashMap<>();
|
||||
for (SrvBillAirAllocOrder order : allocMapper.selectOrders(codes))
|
||||
{
|
||||
if (order.getBillId() == null || order.getBillId().isBlank())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
grouped.computeIfAbsent(order.getBillCode(), key -> new ArrayList<>()).add(order);
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
private Map<String, String> payBillIds(Map<String, List<SrvBillAirAllocOrder>> orders)
|
||||
{
|
||||
List<String> billIds = new ArrayList<>();
|
||||
for (List<SrvBillAirAllocOrder> rows : orders.values())
|
||||
{
|
||||
for (SrvBillAirAllocOrder order : rows)
|
||||
{
|
||||
billIds.add(order.getBillId());
|
||||
}
|
||||
}
|
||||
Map<String, String> payIds = new LinkedHashMap<>();
|
||||
for (int from = 0; from < billIds.size(); from += BATCH_SIZE)
|
||||
{
|
||||
List<String> chunk = billIds.subList(from, Math.min(from + BATCH_SIZE, billIds.size()));
|
||||
for (PrcPayBill payBill : allocMapper.selectPayBills(chunk))
|
||||
{
|
||||
if (payBill.getBillId() != null && payBill.getPayBillId() != null)
|
||||
{
|
||||
payIds.put(payBill.getBillId(), payBill.getPayBillId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return payIds;
|
||||
}
|
||||
|
||||
private String blockReason(SrvBillAirAllocFee fee, List<SrvBillAirAllocOrder> orders)
|
||||
{
|
||||
if (fee.getPrice() == null)
|
||||
{
|
||||
return "费用没有价格";
|
||||
}
|
||||
if (fee.getPrice().signum() < 0)
|
||||
{
|
||||
return "费用价格不能小于0";
|
||||
}
|
||||
if (fee.getCurrency() == null || fee.getCurrency().isBlank())
|
||||
{
|
||||
return "费用没有币种";
|
||||
}
|
||||
if (fee.getLadingId() == null || fee.getLadingId().isBlank())
|
||||
{
|
||||
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 (fee.getActualWeight() == null || fee.getActualWeight().signum() <= 0)
|
||||
{
|
||||
return "提单没有实际重量,无法按重量分摊";
|
||||
}
|
||||
if (fee.getBandBoxWeight() == null || fee.getBandBoxWeight().signum() <= 0)
|
||||
{
|
||||
return "干线没有打板重量,无法按重量分摊";
|
||||
}
|
||||
if (totalWeight(orders).signum() <= 0)
|
||||
{
|
||||
return "订单没有可用重量,无法按重量分摊";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private PrcPayBillFee toOrderFee(SrvBillAirAllocFee fee, SrvBillAirAllocOrder order, BigDecimal amount, Map<String, String> payBillIds)
|
||||
{
|
||||
PrcPayBillFee row = new PrcPayBillFee();
|
||||
row.setFeeId(UUID.randomUUID().toString());
|
||||
row.setBillId(order.getBillId());
|
||||
row.setPayBillId(payBillIds.get(order.getBillId()));
|
||||
row.setFeeType(FEE_TYPE);
|
||||
row.setFeeName(blankName(fee));
|
||||
row.setScId(fee.getId() == null ? 0L : fee.getId());
|
||||
row.setFeeLevel(fee.getFeeLevel());
|
||||
row.setAmount(amount);
|
||||
row.setCurrency(fee.getCurrency().trim());
|
||||
row.setRoundMode("HALF_UP");
|
||||
row.setRemark(remark(fee));
|
||||
row.setFeeSource(FEE_SOURCE);
|
||||
row.setSourceId(fee.getLadingId().trim());
|
||||
row.setIsDeleted("0");
|
||||
return row;
|
||||
}
|
||||
|
||||
private int flush(List<PrcPayBillFee> batch)
|
||||
{
|
||||
if (batch.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int rows = batch.size();
|
||||
payBillFeeMapper.insertBatch(batch);
|
||||
batch.clear();
|
||||
return rows;
|
||||
}
|
||||
|
||||
private void writeLogs(List<SrvBillAirAllocFee> fees, String operator, Date now)
|
||||
{
|
||||
SysUser user = SecurityUtils.getLoginUser().getUser();
|
||||
String name = user.getNickName() == null || user.getNickName().isBlank() ? operator : user.getNickName();
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
SrvBillAirFeeLog log = new SrvBillAirFeeLog();
|
||||
log.setBillCode(fee.getBillCode());
|
||||
log.setFeeId(fee.getId());
|
||||
log.setOperationType(SrvBillAirFeeLog.UPDATE);
|
||||
log.setSummary("分摊到 " + fee.getOrderCount() + " 票订单");
|
||||
log.setChangeContent(JSON.toJSONString(List.of(
|
||||
new SrvBillAirFeeLog.FieldChange("billFeeStatus", "费用状态", "未分摊", "已分摊"))));
|
||||
log.setCreateBy(operator);
|
||||
log.setCreateByName(name);
|
||||
log.setCreateTime(now);
|
||||
feeMapper.insertLog(log);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按重量分摊时,先算出这一票提单实际可分摊的金额。
|
||||
* 百分比 = 提单实际重量 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 orders 该提单下的订单
|
||||
* @param byWeight true 按订单重量占比,false 按订单数量均分
|
||||
* @return 与订单顺序一致的分摊金额
|
||||
*/
|
||||
private static List<BigDecimal> split(BigDecimal price, List<SrvBillAirAllocOrder> orders, boolean byWeight)
|
||||
{
|
||||
BigDecimal total = price.setScale(2, RoundingMode.HALF_UP);
|
||||
int count = orders.size();
|
||||
List<BigDecimal> shares = new ArrayList<>(count);
|
||||
if (!byWeight)
|
||||
{
|
||||
BigDecimal each = total.divide(BigDecimal.valueOf(count), 2, RoundingMode.DOWN);
|
||||
BigDecimal used = ZERO;
|
||||
for (int i = 0; i < count - 1; i++)
|
||||
{
|
||||
shares.add(each);
|
||||
used = used.add(each);
|
||||
}
|
||||
shares.add(total.subtract(used));
|
||||
return shares;
|
||||
}
|
||||
BigDecimal sum = totalWeight(orders);
|
||||
int last = count - 1;
|
||||
for (int i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (weight(orders.get(i)).signum() > 0)
|
||||
{
|
||||
last = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BigDecimal used = ZERO;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (i == last)
|
||||
{
|
||||
shares.add(null);
|
||||
continue;
|
||||
}
|
||||
BigDecimal share = total.multiply(weight(orders.get(i))).divide(sum, 2, RoundingMode.DOWN);
|
||||
shares.add(share);
|
||||
used = used.add(share);
|
||||
}
|
||||
shares.set(last, total.subtract(used));
|
||||
return shares;
|
||||
}
|
||||
|
||||
private static BigDecimal totalWeight(List<SrvBillAirAllocOrder> orders)
|
||||
{
|
||||
BigDecimal sum = ZERO;
|
||||
for (SrvBillAirAllocOrder order : orders)
|
||||
{
|
||||
sum = sum.add(weight(order));
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private static BigDecimal weight(SrvBillAirAllocOrder order)
|
||||
{
|
||||
return order.getChargeWeight() == null ? ZERO : order.getChargeWeight();
|
||||
}
|
||||
|
||||
private static List<SrvBillAirAllocFee> dedupe(List<SrvBillAirAllocFee> fees)
|
||||
{
|
||||
Map<Long, SrvBillAirAllocFee> unique = new LinkedHashMap<>();
|
||||
if (fees != null)
|
||||
{
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
if (fee != null && fee.getId() != null)
|
||||
{
|
||||
unique.putIfAbsent(fee.getId(), fee);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(unique.values());
|
||||
}
|
||||
|
||||
private static List<Long> feeIds(List<SrvBillAirAllocFee> fees)
|
||||
{
|
||||
List<Long> ids = new ArrayList<>();
|
||||
for (SrvBillAirAllocFee fee : fees)
|
||||
{
|
||||
ids.add(fee.getId());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private static String blankName(SrvBillAirAllocFee fee)
|
||||
{
|
||||
if (fee.getFeeName() != null && !fee.getFeeName().isBlank())
|
||||
{
|
||||
return fee.getFeeName().trim();
|
||||
}
|
||||
return "提单费用";
|
||||
}
|
||||
|
||||
private static String remark(SrvBillAirAllocFee fee)
|
||||
{
|
||||
String dim = DIM_WEIGHT.equals(fee.getAllocDim()) ? "重量" : "订单";
|
||||
return "提单" + fee.getBillCode() + "按" + dim + "分摊";
|
||||
}
|
||||
}
|
||||
+129
-1
@@ -22,6 +22,7 @@ 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.domain.PrcSurcharge;
|
||||
import com.bms.price.mapper.PrcPayBillFeeMapper;
|
||||
import com.bms.price.mapper.SrvBillAirFeeMapper;
|
||||
import com.bms.price.mapper.SrvBillAirMapper;
|
||||
import com.bms.price.service.IPrcSurchargeService;
|
||||
@@ -42,11 +43,18 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
|
||||
private static final String STATUS_DICT = "bill_fee_status";
|
||||
|
||||
private static final String STATUS_ALLOCATED = "1";
|
||||
|
||||
private static final String STATUS_CONFIRMED = "3";
|
||||
|
||||
private static final String CURRENCY_DICT = "bms_currency";
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirFeeMapper feeMapper;
|
||||
|
||||
@Autowired
|
||||
private PrcPayBillFeeMapper payBillFeeMapper;
|
||||
|
||||
@Autowired
|
||||
private IPrcSurchargeService surchargeService;
|
||||
|
||||
@@ -121,6 +129,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
if (locked(current))
|
||||
{
|
||||
throw new ServiceException("已分摊或已确定的费用不能删除,请使用作废");
|
||||
}
|
||||
int rows = feeMapper.deleteFee(current.getId(), billCode, operator.account, now);
|
||||
if (rows > 0)
|
||||
{
|
||||
@@ -143,6 +155,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
if (locked(current))
|
||||
{
|
||||
throw new ServiceException("已分摊或已确定的费用不能修改");
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
|
||||
next.setBillFeeStatus(current.getBillFeeStatus() == null || current.getBillFeeStatus().isBlank() ? "0" : current.getBillFeeStatus());
|
||||
Map<String, String> feeNames = feeNames(current, next);
|
||||
@@ -187,11 +203,90 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
return "已保存:新增 " + inserted + " 条,修改 " + updated + " 条,删除 " + removed + " 条";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String confirm(String billCode, Long feeId)
|
||||
{
|
||||
String code = requireBill(billCode);
|
||||
if (feeId == null)
|
||||
{
|
||||
throw new ServiceException("请选择要确定的费用");
|
||||
}
|
||||
SrvBillAirFee current = liveFee(feeId, code);
|
||||
if (current == null)
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
if (!STATUS_ALLOCATED.equals(current.getBillFeeStatus()))
|
||||
{
|
||||
throw new ServiceException("只有已分摊的费用可以确定");
|
||||
}
|
||||
Operator operator = operator();
|
||||
Date now = DateUtils.getNowDate();
|
||||
if (feeMapper.confirmFee(feeId, code, operator.account, now) != 1)
|
||||
{
|
||||
throw new ServiceException("费用状态已变化,请刷新后再确定");
|
||||
}
|
||||
writePlainLog(code, feeId, SrvBillAirFeeLog.CONFIRM, "确定费用", operator, now);
|
||||
return "费用已确定";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String voidFee(String billCode, Long feeId)
|
||||
{
|
||||
String code = requireBill(billCode);
|
||||
if (feeId == null)
|
||||
{
|
||||
throw new ServiceException("请选择要作废的费用");
|
||||
}
|
||||
SrvBillAirFee current = liveFee(feeId, code);
|
||||
if (current == null)
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
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 + ",不可作废");
|
||||
}
|
||||
Operator operator = operator();
|
||||
Date now = DateUtils.getNowDate();
|
||||
payBillFeeMapper.markLadingFeeDeleted(feeId);
|
||||
if (feeMapper.voidFee(feeId, code, operator.account, now) != 1)
|
||||
{
|
||||
throw new ServiceException("费用状态已变化,请刷新后再作废");
|
||||
}
|
||||
Map<String, String> levelLabels = dictLabels(LEVEL_DICT, "费用阶段");
|
||||
Map<String, String> allocLabels = dictLabels(ALLOC_DICT, "是否分摊");
|
||||
Map<String, String> dimLabels = dictLabels(ALLOC_DIM_DICT, "分摊维度");
|
||||
Map<String, String> statusLabels = dictLabels(STATUS_DICT, "费用状态");
|
||||
Map<String, String> currencyLabels = dictLabels(CURRENCY_DICT, "币种");
|
||||
writeLog(code, feeId, SrvBillAirFeeLog.VOID, current, null, feeNames(current, null),
|
||||
levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
return "费用已作废";
|
||||
}
|
||||
|
||||
private SrvBillAirFee liveFee(Long id, String billCode)
|
||||
{
|
||||
return feeMapper.selectLive(id, billCode);
|
||||
}
|
||||
|
||||
private static boolean locked(SrvBillAirFee fee)
|
||||
{
|
||||
String status = fee == null ? "" : text(fee.getBillFeeStatus());
|
||||
return STATUS_ALLOCATED.equals(status) || STATUS_CONFIRMED.equals(status);
|
||||
}
|
||||
|
||||
private SrvBillAirFee normalize(SrvBillAirFee raw, Map<String, String> levelLabels,
|
||||
Map<String, String> allocLabels, Map<String, String> dimLabels, Map<String, String> currencyLabels)
|
||||
{
|
||||
@@ -270,6 +365,22 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
return fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用日志组装
|
||||
* @param billCode
|
||||
* @param feeId
|
||||
* @param operation
|
||||
* @param before
|
||||
* @param after
|
||||
* @param feeLabels
|
||||
* @param levelLabels
|
||||
* @param allocLabels
|
||||
* @param dimLabels
|
||||
* @param statusLabels
|
||||
* @param currencyLabels
|
||||
* @param operator
|
||||
* @param now
|
||||
*/
|
||||
private void writeLog(String billCode, Long feeId, String operation, SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> levelLabels, Map<String, String> allocLabels,
|
||||
Map<String, String> dimLabels, Map<String, String> statusLabels, Map<String, String> currencyLabels, Operator operator, Date now)
|
||||
@@ -287,6 +398,21 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
feeMapper.insertLog(log);
|
||||
}
|
||||
|
||||
/** 确定费用不比较字段,更改前和更改后都留空。 */
|
||||
private void writePlainLog(String billCode, Long feeId, String operation, String summary, Operator operator, Date now)
|
||||
{
|
||||
SrvBillAirFeeLog log = new SrvBillAirFeeLog();
|
||||
log.setBillCode(billCode);
|
||||
log.setFeeId(feeId);
|
||||
log.setOperationType(operation);
|
||||
log.setSummary(summary);
|
||||
log.setChangeContent("[]");
|
||||
log.setCreateBy(operator.account);
|
||||
log.setCreateByName(operator.name);
|
||||
log.setCreateTime(now);
|
||||
feeMapper.insertLog(log);
|
||||
}
|
||||
|
||||
private static List<SrvBillAirFeeLog.FieldChange> diff(SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> levelLabels, Map<String, String> allocLabels,
|
||||
Map<String, String> dimLabels, Map<String, String> statusLabels, Map<String, String> currencyLabels)
|
||||
@@ -323,7 +449,9 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
private static String summary(String operation, List<SrvBillAirFeeLog.FieldChange> changes)
|
||||
{
|
||||
String action = SrvBillAirFeeLog.INSERT.equals(operation) ? "新增费用"
|
||||
: SrvBillAirFeeLog.DELETE.equals(operation) ? "删除费用" : "修改费用";
|
||||
: SrvBillAirFeeLog.DELETE.equals(operation) ? "删除费用"
|
||||
: SrvBillAirFeeLog.CONFIRM.equals(operation) ? "确定费用"
|
||||
: SrvBillAirFeeLog.VOID.equals(operation) ? "作废费用" : "修改费用";
|
||||
StringBuilder text = new StringBuilder(action);
|
||||
for (SrvBillAirFeeLog.FieldChange change : changes)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="feeType" column="fee_type" />
|
||||
<result property="feeName" column="fee_name" />
|
||||
<result property="scId" column="sc_id" />
|
||||
<result property="feeLevel" column="fee_level" />
|
||||
<result property="amount" column="amount" />
|
||||
<result property="feeSource" column="fee_source" />
|
||||
<result property="currency" column="currency" />
|
||||
<result property="roundMode" column="round_mode" />
|
||||
<result property="remark" column="remark" />
|
||||
@@ -21,8 +23,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByBillId" parameterType="String" resultMap="PrcPayBillFeeResult">
|
||||
select fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
source_id, is_deleted, create_time
|
||||
select fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, fee_level, amount, currency, round_mode, remark,
|
||||
fee_source, source_id, is_deleted, create_time
|
||||
from prc_pay_bill_fee
|
||||
where bill_id = #{billId}
|
||||
and is_deleted = '0'
|
||||
@@ -31,25 +33,46 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<insert id="insertPrcPayBillFee" parameterType="PrcPayBillFee">
|
||||
insert into prc_pay_bill_fee (
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
source_id, is_deleted, create_time
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, fee_level, amount, currency, round_mode, remark,
|
||||
fee_source, source_id, is_deleted, create_time
|
||||
) values (
|
||||
#{feeId}, #{billId}, #{payBillId}, #{feeType}, #{feeName}, #{scId}, #{amount}, #{currency}, #{roundMode}, #{remark},
|
||||
#{sourceId}, ifnull(#{isDeleted}, '0'), sysdate()
|
||||
#{feeId}, #{billId}, #{payBillId}, #{feeType}, #{feeName}, #{scId}, #{feeLevel}, #{amount}, #{currency}, #{roundMode}, #{remark},
|
||||
ifnull(#{feeSource}, 'CALC'), #{sourceId}, ifnull(#{isDeleted}, '0'), sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertBatch" parameterType="java.util.List">
|
||||
insert into prc_pay_bill_fee (
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
source_id, is_deleted, create_time
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, fee_level, amount, currency, round_mode, remark,
|
||||
fee_source, source_id, is_deleted, create_time
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.feeId}, #{item.billId}, #{item.payBillId}, #{item.feeType}, #{item.feeName}, #{item.scId}, #{item.amount},
|
||||
#{item.currency}, #{item.roundMode}, #{item.remark}, #{item.sourceId}, ifnull(#{item.isDeleted}, '0'), sysdate())
|
||||
(#{item.feeId}, #{item.billId}, #{item.payBillId}, #{item.feeType}, #{item.feeName}, #{item.scId}, #{item.feeLevel},
|
||||
#{item.amount}, #{item.currency}, #{item.roundMode}, #{item.remark}, ifnull(#{item.feeSource}, 'CALC'),
|
||||
#{item.sourceId}, ifnull(#{item.isDeleted}, '0'), sysdate())
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<select id="selectBilledOrderNo" resultType="String">
|
||||
select e.order_no
|
||||
from prc_pay_bill_fee f
|
||||
inner join prc_order_bill_ext e on e.bill_id = f.bill_id
|
||||
where f.fee_source = 'LADING'
|
||||
and f.sc_id = #{feeId}
|
||||
and ifnull(f.is_deleted, '0') = '0'
|
||||
and e.is_meet = 1
|
||||
order by e.order_no
|
||||
limit 1
|
||||
</select>
|
||||
|
||||
<update id="markLadingFeeDeleted">
|
||||
update prc_pay_bill_fee
|
||||
set is_deleted = '1'
|
||||
where fee_source = 'LADING'
|
||||
and sc_id = #{feeId}
|
||||
and ifnull(is_deleted, '0') = '0'
|
||||
</update>
|
||||
|
||||
<delete id="deleteByBillId" parameterType="String">
|
||||
delete from prc_pay_bill_fee
|
||||
where bill_id = #{billId}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.SrvBillAirAllocMapper">
|
||||
|
||||
<select id="selectPendingFees" resultType="com.bms.price.airbill.domain.SrvBillAirAllocFee">
|
||||
select f.id, f.bill_code as billCode, a.guid as ladingId, f.fee_level as feeLevel,
|
||||
f.bill_fee_status as billFeeStatus, sc.sc_name as feeName, f.price, f.currency,
|
||||
f.alloc_dim as allocDim
|
||||
from srv_bill_air_fee f
|
||||
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
|
||||
when ifnull(b.charge_weight, 0) > 0 then b.charge_weight
|
||||
when ifnull(b.actual_weight, 0) > 0 then b.actual_weight
|
||||
when ifnull(b.receive_weight, 0) > 0 then b.receive_weight
|
||||
else 0
|
||||
end as chargeWeight
|
||||
from prc_order_bill_ext e
|
||||
inner join prc_order_bill b on b.bill_id = e.bill_id
|
||||
where trim(e.air_no_old) in
|
||||
<foreach collection="billCodes" item="code" open="(" separator="," close=")">#{code}</foreach>
|
||||
order by e.air_no_old, e.order_no, e.bill_id
|
||||
</select>
|
||||
|
||||
<select id="selectPayBills" resultType="com.bms.price.domain.PrcPayBill">
|
||||
select bill_id as billId, pay_bill_id as payBillId
|
||||
from prc_pay_bill
|
||||
where bill_id in
|
||||
<foreach collection="billIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</select>
|
||||
|
||||
<update id="markAllocated">
|
||||
update srv_bill_air_fee
|
||||
set bill_fee_status = '1',
|
||||
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>
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -12,6 +12,7 @@
|
||||
left join prc_surcharge sc on sc.sc_id = f.fee_type and ifnull(sc.is_del, '0') = '0'
|
||||
where f.bill_code = #{billCode}
|
||||
and f.del_flag = '0'
|
||||
and ifnull(f.bill_fee_status, '0') <> '2'
|
||||
order by f.id
|
||||
</select>
|
||||
|
||||
@@ -26,6 +27,7 @@
|
||||
where f.id = #{id}
|
||||
and f.bill_code = #{billCode}
|
||||
and f.del_flag = '0'
|
||||
and ifnull(f.bill_fee_status, '0') <> '2'
|
||||
</select>
|
||||
|
||||
<insert id="insertFee" useGeneratedKeys="true" keyProperty="id">
|
||||
@@ -52,6 +54,30 @@
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
and ifnull(bill_fee_status, '0') not in ('1', '3')
|
||||
</update>
|
||||
|
||||
<update id="confirmFee">
|
||||
update srv_bill_air_fee
|
||||
set bill_fee_status = '3',
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
and bill_fee_status = '1'
|
||||
</update>
|
||||
|
||||
<update id="voidFee">
|
||||
update srv_bill_air_fee
|
||||
set del_flag = '1',
|
||||
bill_fee_status = '2',
|
||||
update_by = #{updateBy},
|
||||
update_time = #{updateTime}
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
and bill_fee_status in ('1', '3')
|
||||
</update>
|
||||
|
||||
<update id="deleteFee">
|
||||
@@ -62,6 +88,7 @@
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
and ifnull(bill_fee_status, '0') not in ('1', '3')
|
||||
</update>
|
||||
|
||||
<insert id="insertLog" useGeneratedKeys="true" keyProperty="logId">
|
||||
|
||||
@@ -32,6 +32,51 @@ WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_dict_data d WHERE d.dict_type = 'bill_fee_status' AND d.dict_value = v.dict_value
|
||||
);
|
||||
|
||||
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
|
||||
SELECT 4, N'已确定', '3', 'bill_fee_status', '', 'primary', 'N', '0', 'admin', sysdate(), N'分摊结果确认后'
|
||||
FROM DUAL WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_dict_data d WHERE d.dict_type = 'bill_fee_status' AND d.dict_value = '3'
|
||||
);
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) > 0,
|
||||
'ALTER TABLE srv_bill_air_fee MODIFY COLUMN bill_fee_status char(1) NOT NULL DEFAULT ''0'' COMMENT ''提单费用状态,字典 bill_fee_status:0未分摊 1已分摊 2已作废 3已确定''',
|
||||
'SELECT 1'
|
||||
)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'srv_bill_air_fee' AND COLUMN_NAME = 'bill_fee_status'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
'ALTER TABLE srv_bill_air_fee ADD COLUMN del_flag char(1) NOT NULL DEFAULT ''0'' COMMENT ''逻辑删除:0未删除 1已删除''',
|
||||
'SELECT 1'
|
||||
)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'srv_bill_air_fee' AND COLUMN_NAME = 'del_flag'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
'ALTER TABLE prc_pay_bill_fee ADD COLUMN is_deleted char(1) NOT NULL DEFAULT ''0'' COMMENT ''是否删除:0未删除 1已删除''',
|
||||
'SELECT 1'
|
||||
)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_pay_bill_fee' AND COLUMN_NAME = 'is_deleted'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
|
||||
@@ -38,3 +38,26 @@ WHERE rm.menu_id IN (2520, 2522)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_role_menu x WHERE x.role_id = rm.role_id AND x.menu_id = fee.menu_id
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2630, N'确定费用', 2520, 4, '', NULL, 1, 0, 'F', '0', '0', 'price:airBill:feeConfirm', '#', 'admin', sysdate(), N'已分摊的提单费用确定'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2630 OR perms = 'price:airBill:feeConfirm');
|
||||
|
||||
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT 2631, N'作废费用', 2520, 5, '', NULL, 1, 0, 'F', '0', '0', 'price:airBill:feeVoid', '#', 'admin', sysdate(), N'作废已分摊或已确定的提单费用'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_id = 2631 OR perms = 'price:airBill:feeVoid');
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, m.menu_id
|
||||
FROM sys_menu m
|
||||
WHERE m.perms IN ('price:airBill:feeConfirm', 'price:airBill:feeVoid')
|
||||
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, btn.menu_id
|
||||
FROM sys_role_menu rm
|
||||
JOIN sys_menu fee ON fee.menu_id = rm.menu_id AND fee.perms = 'price:airBill:fee'
|
||||
JOIN sys_menu btn ON btn.perms IN ('price:airBill:feeConfirm', 'price:airBill:feeVoid')
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_role_menu x WHERE x.role_id = rm.role_id AND x.menu_id = btn.menu_id
|
||||
);
|
||||
|
||||
@@ -36,6 +36,43 @@ export function listAirBillFeeLog(query) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 勾选提单后,查询需要分摊到订单的费用 */
|
||||
export function previewAirBillAlloc(billCodes) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/alloc/preview',
|
||||
method: 'get',
|
||||
params: { billCodes: (billCodes || []).join(',') }
|
||||
})
|
||||
}
|
||||
|
||||
/** 按重量或订单数把费用分摊到订单 */
|
||||
export function allocateAirBillFee(billCodes) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/alloc',
|
||||
method: 'post',
|
||||
data: { billCodes },
|
||||
timeout: 120000
|
||||
})
|
||||
}
|
||||
|
||||
/** 已分摊的费用改为已确定 */
|
||||
export function confirmAirBillFee(billCode, feeId) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/confirm',
|
||||
method: 'post',
|
||||
params: { billCode, feeId }
|
||||
})
|
||||
}
|
||||
|
||||
/** 作废已分摊或已确定的费用,并逻辑删除订单上的分摊明细 */
|
||||
export function voidAirBillFee(billCode, feeId) {
|
||||
return request({
|
||||
url: '/price/airBill/fee/void',
|
||||
method: 'post',
|
||||
params: { billCode, feeId }
|
||||
})
|
||||
}
|
||||
|
||||
/** 只提交新增、修改、删除的费用 */
|
||||
export function saveAirBillFee(data) {
|
||||
return request({
|
||||
|
||||
@@ -238,6 +238,11 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 最多勾选条数,0 表示不限制
|
||||
selectionMax: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否显示序号列
|
||||
showIndex: {
|
||||
type: Boolean,
|
||||
@@ -436,10 +441,30 @@ function resetQuery() {
|
||||
getList()
|
||||
}
|
||||
|
||||
// 选中变化
|
||||
// 选中变化。超过上限时只保留先勾到的行,并同步虚拟表和普通表的勾选状态。
|
||||
let cappingSelection = false
|
||||
function handleSelectionChange(val) {
|
||||
selectionList.value = val
|
||||
emit('selection-change', val)
|
||||
if (cappingSelection) return
|
||||
const rows = Array.isArray(val) ? val.slice() : []
|
||||
const max = Number(props.selectionMax) || 0
|
||||
if (max > 0 && rows.length > max) {
|
||||
const kept = rows.slice(0, max)
|
||||
cappingSelection = true
|
||||
selectionList.value = kept
|
||||
emit('selection-change', kept)
|
||||
proxy.$modal.msgWarning(`单次最多勾选 ${max} 条`)
|
||||
nextTick(() => {
|
||||
const table = tableRef.value
|
||||
if (table?.toggleRowSelection) {
|
||||
const keys = new Set(kept.map((row) => virtualRowId(row)))
|
||||
tableData.value.forEach((row) => table.toggleRowSelection(row, keys.has(virtualRowId(row))))
|
||||
}
|
||||
cappingSelection = false
|
||||
})
|
||||
return
|
||||
}
|
||||
selectionList.value = rows
|
||||
emit('selection-change', rows)
|
||||
}
|
||||
const selectionIds = computed(() =>
|
||||
selectionList.value.map((r) => (props.rowKey ? r[props.rowKey] : r.id)).filter((v) => v !== undefined)
|
||||
|
||||
@@ -11,7 +11,20 @@
|
||||
:page-size="100"
|
||||
:height="tableHeight"
|
||||
virtual
|
||||
selection
|
||||
:selection-max="5"
|
||||
>
|
||||
<template #toolbar="{ selection }">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
:disabled="!selection.length"
|
||||
v-hasPermi="['price:airBill:fee']"
|
||||
@click="openAlloc(selection)"
|
||||
>费用处理</el-button>
|
||||
</el-col>
|
||||
</template>
|
||||
<template #search-billCodes="{ model }">
|
||||
<el-input
|
||||
v-model="model.billCodes"
|
||||
@@ -55,6 +68,7 @@
|
||||
|
||||
<air-bill-detail v-model="open" :title="title" :loading="loading" :detail="detail" />
|
||||
<air-bill-fee-dialog ref="feeRef" />
|
||||
<air-bill-alloc-dialog ref="allocRef" @done="tableRef?.refresh()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -62,9 +76,11 @@
|
||||
import { listAirBill, getAirBill } from '@/api/price/airBill'
|
||||
import AirBillDetail from './components/AirBillDetail.vue'
|
||||
import AirBillFeeDialog from './components/AirBillFeeDialog.vue'
|
||||
import AirBillAllocDialog from './components/AirBillAllocDialog.vue'
|
||||
|
||||
const tableRef = ref()
|
||||
const feeRef = ref()
|
||||
const allocRef = ref()
|
||||
const tableHeight = ref(480)
|
||||
let tableResizeObserver = null
|
||||
|
||||
@@ -171,6 +187,10 @@ function openFee(row) {
|
||||
feeRef.value?.open(row)
|
||||
}
|
||||
|
||||
function openAlloc(rows) {
|
||||
allocRef.value?.open((rows || []).map(row => row.billCode))
|
||||
}
|
||||
|
||||
function openDetail(row) {
|
||||
if (!row?.billCode) return
|
||||
open.value = true
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" width="980px" append-to-body destroy-on-close @closed="reset">
|
||||
<template #header>
|
||||
<div class="alloc-title">
|
||||
<span>费用处理</span>
|
||||
<span class="alloc-hint">本次操作是将费用均摊到订单</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="loading">
|
||||
<div class="alloc-summary">
|
||||
已选 {{ billCodes.length }} 个提单,待分摊 {{ rows.length }} 条费用
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="blockText"
|
||||
:title="blockText"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="alloc-alert"
|
||||
/>
|
||||
<el-table :data="rows" border size="small" max-height="420" empty-text="所选提单没有需要分摊的费用">
|
||||
<el-table-column prop="billCode" label="提单号" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="费用阶段" width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bms_fee_level" :value="row.feeLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="费用状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bill_fee_status" :value="row.billFeeStatus || '0'" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="feeName" label="费用项" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="价格" width="110" align="right">
|
||||
<template #default="{ row }">{{ priceText(row.price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="币种" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag v-if="row.currency" :options="bms_currency" :value="row.currency" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分摊维度" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<dict-tag :options="bill_fee_alloc_dim" :value="row.allocDim" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderCount" label="订单数" width="80" align="center" />
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!canSubmit" :loading="saving" @click="submit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { previewAirBillAlloc, allocateAirBillFee } from '@/api/price/airBill'
|
||||
import { useDict } from '@/utils/dict'
|
||||
|
||||
const emit = defineEmits(['done'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_fee_level, bill_fee_status, bms_currency, bill_fee_alloc_dim } = useDict(
|
||||
'bms_fee_level', 'bill_fee_status', 'bms_currency', 'bill_fee_alloc_dim'
|
||||
)
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const billCodes = ref([])
|
||||
const rows = ref([])
|
||||
|
||||
const blockText = computed(() => {
|
||||
const blocked = rows.value.filter(row => row.blockReason)
|
||||
if (!blocked.length) return ''
|
||||
return blocked.slice(0, 3).map(row => `${row.billCode || '-'} ${row.feeName || '费用'}:${row.blockReason}`).join(';')
|
||||
})
|
||||
const canSubmit = computed(() => !loading.value && rows.value.length > 0 && !blockText.value)
|
||||
|
||||
function open(codes) {
|
||||
const picked = [...new Set((codes || []).map(code => String(code || '').trim()).filter(Boolean))]
|
||||
if (!picked.length) {
|
||||
proxy.$modal.msgWarning('请先勾选提单')
|
||||
return
|
||||
}
|
||||
if (picked.length > 5) {
|
||||
proxy.$modal.msgWarning('单次最多勾选 5 条提单')
|
||||
return
|
||||
}
|
||||
billCodes.value = picked
|
||||
rows.value = []
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
previewAirBillAlloc(picked).then(res => {
|
||||
rows.value = res.data || []
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function priceText(value) {
|
||||
if (value === null || value === undefined || value === '') return '-'
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) ? number.toFixed(2) : value
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (!canSubmit.value) return
|
||||
try {
|
||||
await proxy.$modal.confirm('确认将费用分摊到订单吗?确定后会按重量或订单数量摊到每一票,费用状态改为已分摊。')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await allocateAirBillFee(billCodes.value)
|
||||
proxy.$modal.msgSuccess(res.msg || '分摊完成')
|
||||
visible.value = false
|
||||
emit('done')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
loading.value = false
|
||||
saving.value = false
|
||||
billCodes.value = []
|
||||
rows.value = []
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.alloc-title {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
}
|
||||
.alloc-hint {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.alloc-summary {
|
||||
margin-bottom: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
.alloc-alert {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,14 +2,14 @@
|
||||
<el-dialog v-model="visible" :title="title" width="1360px" append-to-body destroy-on-close @closed="reset">
|
||||
<div v-loading="loading">
|
||||
<div class="fee-toolbar">
|
||||
<el-button type="primary" plain icon="Plus" @click="addRow">新增</el-button>
|
||||
<el-button type="primary" plain icon="Plus" v-hasPermi="['price:airBill:fee']" @click="addRow">新增</el-button>
|
||||
<span class="fee-hint">只保存本次新增、修改和删除的费用,未改动的行不会重写。</span>
|
||||
</div>
|
||||
<el-table :data="rows" border size="small" max-height="320" row-key="rowKey" empty-text="暂无费用,点击新增">
|
||||
<el-table-column min-width="180">
|
||||
<template #header><span class="col-label"><i v-if="allocRequired" class="req-star">*</i>费用阶段</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeLevel" filterable clearable placeholder="请选择" style="width: 100%">
|
||||
<el-select v-model="row.feeLevel" filterable clearable placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bms_fee_level" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -17,7 +17,7 @@
|
||||
<el-table-column width="120">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>是否分摊</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.allocFlag" placeholder="请选择" style="width: 100%" @change="changeAlloc(row)">
|
||||
<el-select v-model="row.allocFlag" placeholder="请选择" style="width: 100%" :disabled="locked(row)" @change="changeAlloc(row)">
|
||||
<el-option v-for="item in bill_fee_alloc" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -25,7 +25,7 @@
|
||||
<el-table-column v-if="allocRequired" min-width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>分摊维度(票)</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-if="String(row.allocFlag) === '1'" v-model="row.allocDim" placeholder="请选择" style="width: 100%">
|
||||
<el-select v-if="String(row.allocFlag) === '1'" v-model="row.allocDim" placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bill_fee_alloc_dim" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -39,7 +39,7 @@
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>费用项</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeType" filterable remote clearable reserve-keyword placeholder="输入费用名称"
|
||||
:remote-method="searchSurcharge" :loading="surchargeLoading" style="width: 100%" @change="name => onFeeChange(row, name)">
|
||||
:remote-method="searchSurcharge" :loading="surchargeLoading" :disabled="locked(row)" style="width: 100%" @change="name => onFeeChange(row, name)">
|
||||
<el-option v-for="item in surchargeOptions" :key="item.scId" :label="item.scName" :value="item.scId" />
|
||||
</el-select>
|
||||
</template>
|
||||
@@ -47,25 +47,27 @@
|
||||
<el-table-column width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>价格</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.price" :min="0" :precision="4" :controls="false" :value-on-clear="null" placeholder="价格" style="width: 100%" />
|
||||
<el-input-number v-model="row.price" :min="0" :precision="4" :controls="false" :value-on-clear="null" :disabled="locked(row)" placeholder="价格" style="width: 100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="140">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>收费币种</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.currency" filterable clearable placeholder="请选择" style="width: 100%">
|
||||
<el-select v-model="row.currency" filterable clearable placeholder="请选择" style="width: 100%" :disabled="locked(row)">
|
||||
<el-option v-for="item in bms_currency" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" maxlength="500" clearable placeholder="备注" />
|
||||
<el-input v-model="row.remark" maxlength="500" clearable placeholder="备注" :disabled="locked(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" @click="removeRow(row)">删除</el-button>
|
||||
<el-button v-if="row.billFeeStatus === '1'" v-hasPermi="['price:airBill:feeConfirm']" link type="primary" :disabled="acting" @click="confirmRow(row)">确定</el-button>
|
||||
<el-button v-if="row.billFeeStatus === '1' || row.billFeeStatus === '3'" v-hasPermi="['price:airBill:feeVoid']" link type="danger" :disabled="acting" @click="voidRow(row)">作废</el-button>
|
||||
<el-button v-if="!locked(row)" v-hasPermi="['price:airBill:fee']" link type="danger" @click="removeRow(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -77,14 +79,14 @@
|
||||
<el-table-column label="操作时间" width="168">
|
||||
<template #default="{ row }">{{ logTime(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作类型" width="90" align="center">
|
||||
<el-table-column label="操作类型" width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="logType(row.operationType)">{{ logAction(row.operationType) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更改前" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div v-if="blankSide(row, 'old')" class="log-empty">空</div>
|
||||
<div v-if="blankSide(row, 'old')" class="log-empty">{{ emptyText(row) }}</div>
|
||||
<div v-else class="log-change">
|
||||
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.oldValue) }}</div>
|
||||
</div>
|
||||
@@ -92,7 +94,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="更改后" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div v-if="blankSide(row, 'new')" class="log-empty">空</div>
|
||||
<div v-if="blankSide(row, 'new')" class="log-empty">{{ emptyText(row) }}</div>
|
||||
<div v-else class="log-change">
|
||||
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.newValue) }}</div>
|
||||
</div>
|
||||
@@ -117,13 +119,13 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!dirty" :loading="saving" @click="save">保存</el-button>
|
||||
<el-button type="primary" v-hasPermi="['price:airBill:fee']" :disabled="!dirty" :loading="saving" @click="save">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listAirBillFee, listAirBillFeeLog, saveAirBillFee } from '@/api/price/airBill'
|
||||
import { listAirBillFee, listAirBillFeeLog, saveAirBillFee, confirmAirBillFee, voidAirBillFee } from '@/api/price/airBill'
|
||||
import { listSurchargeOptions } from '@/api/price/surcharge'
|
||||
import { parseTime } from '@/utils/bms'
|
||||
|
||||
@@ -134,6 +136,7 @@ const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const logLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const acting = ref(false)
|
||||
const billCode = ref('')
|
||||
const rows = ref([])
|
||||
const deletedIds = ref([])
|
||||
@@ -172,6 +175,7 @@ function reset() {
|
||||
surchargeOptions.value = []
|
||||
surchargeLoading.value = false
|
||||
saving.value = false
|
||||
acting.value = false
|
||||
loading.value = false
|
||||
logLoading.value = false
|
||||
}
|
||||
@@ -307,6 +311,37 @@ function changeAlloc(row) {
|
||||
if (String(row.allocFlag) !== '1') row.allocDim = ''
|
||||
}
|
||||
|
||||
function locked(row) {
|
||||
return row?.billFeeStatus === '1' || row?.billFeeStatus === '3'
|
||||
}
|
||||
|
||||
function confirmRow(row) {
|
||||
if (!row?.id || acting.value) return
|
||||
acting.value = true
|
||||
confirmAirBillFee(billCode.value, row.id).then(res => {
|
||||
proxy.$modal.msgSuccess(res.msg || '费用已确定')
|
||||
load()
|
||||
}).finally(() => {
|
||||
acting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
async function voidRow(row) {
|
||||
if (!row?.id || acting.value) return
|
||||
try {
|
||||
await proxy.$modal.confirm('确认作废这条费用吗?已分摊到订单的费用明细会逻辑删除,这条提单费用也会逻辑删除。')
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
acting.value = true
|
||||
voidAirBillFee(billCode.value, row.id).then(res => {
|
||||
proxy.$modal.msgSuccess(res.msg || '费用已作废')
|
||||
load()
|
||||
}).finally(() => {
|
||||
acting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function removeRow(row) {
|
||||
if (row.id != null && !deletedIds.value.some(id => String(id) === String(row.id))) {
|
||||
deletedIds.value.push(row.id)
|
||||
@@ -404,15 +439,21 @@ function save() {
|
||||
}
|
||||
|
||||
function logAction(type) {
|
||||
return { 1: '新增', 2: '修改', 3: '删除' }[String(type)] || '维护'
|
||||
return { 1: '新增', 2: '修改', 3: '删除', 4: '确定费用', 5: '作废' }[String(type)] || '维护'
|
||||
}
|
||||
|
||||
function logType(type) {
|
||||
if (String(type) === '1') return 'success'
|
||||
if (String(type) === '3') return 'danger'
|
||||
const value = String(type)
|
||||
if (value === '1' || value === '4') return 'success'
|
||||
if (value === '3' || value === '5') return 'danger'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
function emptyText(row) {
|
||||
const value = String(row?.operationType)
|
||||
return value === '4' || value === '5' ? '无' : '空'
|
||||
}
|
||||
|
||||
function logTime(item) {
|
||||
return parseTime(item.createTime) || ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user