提单费用均摊处理
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.core.page.TableDataInfo;
|
||||||
import com.bms.common.enums.BusinessType;
|
import com.bms.common.enums.BusinessType;
|
||||||
import com.bms.common.utils.StringUtils;
|
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.SrvBillAirFeeLog;
|
||||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||||
import com.bms.price.domain.vo.SrvBillAirQuery;
|
import com.bms.price.domain.vo.SrvBillAirQuery;
|
||||||
import com.bms.price.domain.vo.SrvBillAirVo;
|
import com.bms.price.domain.vo.SrvBillAirVo;
|
||||||
|
import com.bms.price.service.ISrvBillAirAllocService;
|
||||||
import com.bms.price.service.ISrvBillAirFeeService;
|
import com.bms.price.service.ISrvBillAirFeeService;
|
||||||
import com.bms.price.service.ISrvBillAirService;
|
import com.bms.price.service.ISrvBillAirService;
|
||||||
|
|
||||||
@@ -36,6 +38,9 @@ public class SrvBillAirController extends BaseController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ISrvBillAirFeeService srvBillAirFeeService;
|
private ISrvBillAirFeeService srvBillAirFeeService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISrvBillAirAllocService srvBillAirAllocService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提单列表
|
* 提单列表
|
||||||
*/
|
*/
|
||||||
@@ -94,4 +99,57 @@ public class SrvBillAirController extends BaseController
|
|||||||
{
|
{
|
||||||
return success(srvBillAirFeeService.save(command));
|
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 DELETE = "3";
|
||||||
|
|
||||||
|
/** 已分摊费用确定。日志不记录字段变化。 */
|
||||||
|
public static final String CONFIRM = "4";
|
||||||
|
|
||||||
|
/** 已分摊或已确定费用作废。 */
|
||||||
|
public static final String VOID = "5";
|
||||||
|
|
||||||
private Long logId;
|
private Long logId;
|
||||||
|
|
||||||
private String billCode;
|
private String billCode;
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ public class PrcPayBillFee implements Serializable
|
|||||||
private String feeType;
|
private String feeType;
|
||||||
private String feeName;
|
private String feeName;
|
||||||
private Long scId;
|
private Long scId;
|
||||||
|
|
||||||
|
/** 费用所属阶段:1 a段,2 b段,3 c段,4 d段 */
|
||||||
|
private String feeLevel;
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
private String currency;
|
private String currency;
|
||||||
private String roundMode;
|
private String roundMode;
|
||||||
|
|||||||
@@ -38,4 +38,21 @@ public interface PrcPayBillFeeMapper
|
|||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
public int deleteByBillId(String billId);
|
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,
|
int deleteFee(@Param("id") Long id, @Param("billCode") String billCode, @Param("updateBy") String updateBy,
|
||||||
@Param("updateTime") java.util.Date updateTime);
|
@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);
|
int insertLog(SrvBillAirFeeLog log);
|
||||||
|
|
||||||
List<SrvBillAirFeeLog> selectLogs(String billCode);
|
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);
|
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.SrvBillAirFeeLog;
|
||||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||||
import com.bms.price.domain.PrcSurcharge;
|
import com.bms.price.domain.PrcSurcharge;
|
||||||
|
import com.bms.price.mapper.PrcPayBillFeeMapper;
|
||||||
import com.bms.price.mapper.SrvBillAirFeeMapper;
|
import com.bms.price.mapper.SrvBillAirFeeMapper;
|
||||||
import com.bms.price.mapper.SrvBillAirMapper;
|
import com.bms.price.mapper.SrvBillAirMapper;
|
||||||
import com.bms.price.service.IPrcSurchargeService;
|
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_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";
|
private static final String CURRENCY_DICT = "bms_currency";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SrvBillAirFeeMapper feeMapper;
|
private SrvBillAirFeeMapper feeMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PrcPayBillFeeMapper payBillFeeMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IPrcSurchargeService surchargeService;
|
private IPrcSurchargeService surchargeService;
|
||||||
|
|
||||||
@@ -121,6 +129,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("费用已删除或不属于该提单");
|
throw new ServiceException("费用已删除或不属于该提单");
|
||||||
}
|
}
|
||||||
|
if (locked(current))
|
||||||
|
{
|
||||||
|
throw new ServiceException("已分摊或已确定的费用不能删除,请使用作废");
|
||||||
|
}
|
||||||
int rows = feeMapper.deleteFee(current.getId(), billCode, operator.account, now);
|
int rows = feeMapper.deleteFee(current.getId(), billCode, operator.account, now);
|
||||||
if (rows > 0)
|
if (rows > 0)
|
||||||
{
|
{
|
||||||
@@ -143,6 +155,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
|||||||
{
|
{
|
||||||
throw new ServiceException("费用已删除或不属于该提单");
|
throw new ServiceException("费用已删除或不属于该提单");
|
||||||
}
|
}
|
||||||
|
if (locked(current))
|
||||||
|
{
|
||||||
|
throw new ServiceException("已分摊或已确定的费用不能修改");
|
||||||
|
}
|
||||||
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
|
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
|
||||||
next.setBillFeeStatus(current.getBillFeeStatus() == null || current.getBillFeeStatus().isBlank() ? "0" : current.getBillFeeStatus());
|
next.setBillFeeStatus(current.getBillFeeStatus() == null || current.getBillFeeStatus().isBlank() ? "0" : current.getBillFeeStatus());
|
||||||
Map<String, String> feeNames = feeNames(current, next);
|
Map<String, String> feeNames = feeNames(current, next);
|
||||||
@@ -187,11 +203,90 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
|||||||
return "已保存:新增 " + inserted + " 条,修改 " + updated + " 条,删除 " + removed + " 条";
|
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)
|
private SrvBillAirFee liveFee(Long id, String billCode)
|
||||||
{
|
{
|
||||||
return feeMapper.selectLive(id, 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,
|
private SrvBillAirFee normalize(SrvBillAirFee raw, Map<String, String> levelLabels,
|
||||||
Map<String, String> allocLabels, Map<String, String> dimLabels, Map<String, String> currencyLabels)
|
Map<String, String> allocLabels, Map<String, String> dimLabels, Map<String, String> currencyLabels)
|
||||||
{
|
{
|
||||||
@@ -270,6 +365,22 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
|||||||
return fee;
|
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,
|
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> 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)
|
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);
|
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,
|
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> feeLabels, Map<String, String> levelLabels, Map<String, String> allocLabels,
|
||||||
Map<String, String> dimLabels, Map<String, String> statusLabels, Map<String, String> currencyLabels)
|
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)
|
private static String summary(String operation, List<SrvBillAirFeeLog.FieldChange> changes)
|
||||||
{
|
{
|
||||||
String action = SrvBillAirFeeLog.INSERT.equals(operation) ? "新增费用"
|
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);
|
StringBuilder text = new StringBuilder(action);
|
||||||
for (SrvBillAirFeeLog.FieldChange change : changes)
|
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="feeType" column="fee_type" />
|
||||||
<result property="feeName" column="fee_name" />
|
<result property="feeName" column="fee_name" />
|
||||||
<result property="scId" column="sc_id" />
|
<result property="scId" column="sc_id" />
|
||||||
|
<result property="feeLevel" column="fee_level" />
|
||||||
<result property="amount" column="amount" />
|
<result property="amount" column="amount" />
|
||||||
|
<result property="feeSource" column="fee_source" />
|
||||||
<result property="currency" column="currency" />
|
<result property="currency" column="currency" />
|
||||||
<result property="roundMode" column="round_mode" />
|
<result property="roundMode" column="round_mode" />
|
||||||
<result property="remark" column="remark" />
|
<result property="remark" column="remark" />
|
||||||
@@ -21,8 +23,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<select id="selectByBillId" parameterType="String" resultMap="PrcPayBillFeeResult">
|
<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,
|
select fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, fee_level, amount, currency, round_mode, remark,
|
||||||
source_id, is_deleted, create_time
|
fee_source, source_id, is_deleted, create_time
|
||||||
from prc_pay_bill_fee
|
from prc_pay_bill_fee
|
||||||
where bill_id = #{billId}
|
where bill_id = #{billId}
|
||||||
and is_deleted = '0'
|
and is_deleted = '0'
|
||||||
@@ -31,25 +33,46 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
|
|
||||||
<insert id="insertPrcPayBillFee" parameterType="PrcPayBillFee">
|
<insert id="insertPrcPayBillFee" parameterType="PrcPayBillFee">
|
||||||
insert into prc_pay_bill_fee (
|
insert into prc_pay_bill_fee (
|
||||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, fee_level, amount, currency, round_mode, remark,
|
||||||
source_id, is_deleted, create_time
|
fee_source, source_id, is_deleted, create_time
|
||||||
) values (
|
) values (
|
||||||
#{feeId}, #{billId}, #{payBillId}, #{feeType}, #{feeName}, #{scId}, #{amount}, #{currency}, #{roundMode}, #{remark},
|
#{feeId}, #{billId}, #{payBillId}, #{feeType}, #{feeName}, #{scId}, #{feeLevel}, #{amount}, #{currency}, #{roundMode}, #{remark},
|
||||||
#{sourceId}, ifnull(#{isDeleted}, '0'), sysdate()
|
ifnull(#{feeSource}, 'CALC'), #{sourceId}, ifnull(#{isDeleted}, '0'), sysdate()
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<insert id="insertBatch" parameterType="java.util.List">
|
<insert id="insertBatch" parameterType="java.util.List">
|
||||||
insert into prc_pay_bill_fee (
|
insert into prc_pay_bill_fee (
|
||||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, fee_level, amount, currency, round_mode, remark,
|
||||||
source_id, is_deleted, create_time
|
fee_source, source_id, is_deleted, create_time
|
||||||
) values
|
) values
|
||||||
<foreach collection="list" item="item" separator=",">
|
<foreach collection="list" item="item" separator=",">
|
||||||
(#{item.feeId}, #{item.billId}, #{item.payBillId}, #{item.feeType}, #{item.feeName}, #{item.scId}, #{item.amount},
|
(#{item.feeId}, #{item.billId}, #{item.payBillId}, #{item.feeType}, #{item.feeName}, #{item.scId}, #{item.feeLevel},
|
||||||
#{item.currency}, #{item.roundMode}, #{item.remark}, #{item.sourceId}, ifnull(#{item.isDeleted}, '0'), sysdate())
|
#{item.amount}, #{item.currency}, #{item.roundMode}, #{item.remark}, ifnull(#{item.feeSource}, 'CALC'),
|
||||||
|
#{item.sourceId}, ifnull(#{item.isDeleted}, '0'), sysdate())
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</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 id="deleteByBillId" parameterType="String">
|
||||||
delete from prc_pay_bill_fee
|
delete from prc_pay_bill_fee
|
||||||
where bill_id = #{billId}
|
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'
|
left join prc_surcharge sc on sc.sc_id = f.fee_type and ifnull(sc.is_del, '0') = '0'
|
||||||
where f.bill_code = #{billCode}
|
where f.bill_code = #{billCode}
|
||||||
and f.del_flag = '0'
|
and f.del_flag = '0'
|
||||||
|
and ifnull(f.bill_fee_status, '0') <> '2'
|
||||||
order by f.id
|
order by f.id
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
where f.id = #{id}
|
where f.id = #{id}
|
||||||
and f.bill_code = #{billCode}
|
and f.bill_code = #{billCode}
|
||||||
and f.del_flag = '0'
|
and f.del_flag = '0'
|
||||||
|
and ifnull(f.bill_fee_status, '0') <> '2'
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<insert id="insertFee" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertFee" useGeneratedKeys="true" keyProperty="id">
|
||||||
@@ -52,6 +54,30 @@
|
|||||||
where id = #{id}
|
where id = #{id}
|
||||||
and bill_code = #{billCode}
|
and bill_code = #{billCode}
|
||||||
and del_flag = '0'
|
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>
|
||||||
|
|
||||||
<update id="deleteFee">
|
<update id="deleteFee">
|
||||||
@@ -62,6 +88,7 @@
|
|||||||
where id = #{id}
|
where id = #{id}
|
||||||
and bill_code = #{billCode}
|
and bill_code = #{billCode}
|
||||||
and del_flag = '0'
|
and del_flag = '0'
|
||||||
|
and ifnull(bill_fee_status, '0') not in ('1', '3')
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<insert id="insertLog" useGeneratedKeys="true" keyProperty="logId">
|
<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
|
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 = (
|
SET @sql = (
|
||||||
SELECT IF(
|
SELECT IF(
|
||||||
COUNT(*) = 0,
|
COUNT(*) = 0,
|
||||||
|
|||||||
@@ -38,3 +38,26 @@ WHERE rm.menu_id IN (2520, 2522)
|
|||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1 FROM sys_role_menu x WHERE x.role_id = rm.role_id AND x.menu_id = fee.menu_id
|
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) {
|
export function saveAirBillFee(data) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -238,6 +238,11 @@ const props = defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
|
// 最多勾选条数,0 表示不限制
|
||||||
|
selectionMax: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
// 是否显示序号列
|
// 是否显示序号列
|
||||||
showIndex: {
|
showIndex: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -436,10 +441,30 @@ function resetQuery() {
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选中变化
|
// 选中变化。超过上限时只保留先勾到的行,并同步虚拟表和普通表的勾选状态。
|
||||||
|
let cappingSelection = false
|
||||||
function handleSelectionChange(val) {
|
function handleSelectionChange(val) {
|
||||||
selectionList.value = val
|
if (cappingSelection) return
|
||||||
emit('selection-change', val)
|
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(() =>
|
const selectionIds = computed(() =>
|
||||||
selectionList.value.map((r) => (props.rowKey ? r[props.rowKey] : r.id)).filter((v) => v !== undefined)
|
selectionList.value.map((r) => (props.rowKey ? r[props.rowKey] : r.id)).filter((v) => v !== undefined)
|
||||||
|
|||||||
@@ -11,7 +11,20 @@
|
|||||||
:page-size="100"
|
:page-size="100"
|
||||||
:height="tableHeight"
|
:height="tableHeight"
|
||||||
virtual
|
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 }">
|
<template #search-billCodes="{ model }">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="model.billCodes"
|
v-model="model.billCodes"
|
||||||
@@ -55,6 +68,7 @@
|
|||||||
|
|
||||||
<air-bill-detail v-model="open" :title="title" :loading="loading" :detail="detail" />
|
<air-bill-detail v-model="open" :title="title" :loading="loading" :detail="detail" />
|
||||||
<air-bill-fee-dialog ref="feeRef" />
|
<air-bill-fee-dialog ref="feeRef" />
|
||||||
|
<air-bill-alloc-dialog ref="allocRef" @done="tableRef?.refresh()" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -62,9 +76,11 @@
|
|||||||
import { listAirBill, getAirBill } from '@/api/price/airBill'
|
import { listAirBill, getAirBill } from '@/api/price/airBill'
|
||||||
import AirBillDetail from './components/AirBillDetail.vue'
|
import AirBillDetail from './components/AirBillDetail.vue'
|
||||||
import AirBillFeeDialog from './components/AirBillFeeDialog.vue'
|
import AirBillFeeDialog from './components/AirBillFeeDialog.vue'
|
||||||
|
import AirBillAllocDialog from './components/AirBillAllocDialog.vue'
|
||||||
|
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const feeRef = ref()
|
const feeRef = ref()
|
||||||
|
const allocRef = ref()
|
||||||
const tableHeight = ref(480)
|
const tableHeight = ref(480)
|
||||||
let tableResizeObserver = null
|
let tableResizeObserver = null
|
||||||
|
|
||||||
@@ -171,6 +187,10 @@ function openFee(row) {
|
|||||||
feeRef.value?.open(row)
|
feeRef.value?.open(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAlloc(rows) {
|
||||||
|
allocRef.value?.open((rows || []).map(row => row.billCode))
|
||||||
|
}
|
||||||
|
|
||||||
function openDetail(row) {
|
function openDetail(row) {
|
||||||
if (!row?.billCode) return
|
if (!row?.billCode) return
|
||||||
open.value = true
|
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">
|
<el-dialog v-model="visible" :title="title" width="1360px" append-to-body destroy-on-close @closed="reset">
|
||||||
<div v-loading="loading">
|
<div v-loading="loading">
|
||||||
<div class="fee-toolbar">
|
<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>
|
<span class="fee-hint">只保存本次新增、修改和删除的费用,未改动的行不会重写。</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="rows" border size="small" max-height="320" row-key="rowKey" empty-text="暂无费用,点击新增">
|
<el-table :data="rows" border size="small" max-height="320" row-key="rowKey" empty-text="暂无费用,点击新增">
|
||||||
<el-table-column min-width="180">
|
<el-table-column min-width="180">
|
||||||
<template #header><span class="col-label"><i v-if="allocRequired" class="req-star">*</i>费用阶段</span></template>
|
<template #header><span class="col-label"><i v-if="allocRequired" class="req-star">*</i>费用阶段</span></template>
|
||||||
<template #default="{ row }">
|
<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-option v-for="item in bms_fee_level" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<el-table-column width="120">
|
<el-table-column width="120">
|
||||||
<template #header><span class="col-label"><i class="req-star">*</i>是否分摊</span></template>
|
<template #header><span class="col-label"><i class="req-star">*</i>是否分摊</span></template>
|
||||||
<template #default="{ row }">
|
<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-option v-for="item in bill_fee_alloc" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<el-table-column v-if="allocRequired" min-width="150">
|
<el-table-column v-if="allocRequired" min-width="150">
|
||||||
<template #header><span class="col-label"><i class="req-star">*</i>分摊维度(票)</span></template>
|
<template #header><span class="col-label"><i class="req-star">*</i>分摊维度(票)</span></template>
|
||||||
<template #default="{ row }">
|
<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-option v-for="item in bill_fee_alloc_dim" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<template #header><span class="col-label"><i class="req-star">*</i>费用项</span></template>
|
<template #header><span class="col-label"><i class="req-star">*</i>费用项</span></template>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select v-model="row.feeType" filterable remote clearable reserve-keyword placeholder="输入费用名称"
|
<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-option v-for="item in surchargeOptions" :key="item.scId" :label="item.scName" :value="item.scId" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,25 +47,27 @@
|
|||||||
<el-table-column width="150">
|
<el-table-column width="150">
|
||||||
<template #header><span class="col-label"><i class="req-star">*</i>价格</span></template>
|
<template #header><span class="col-label"><i class="req-star">*</i>价格</span></template>
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column width="140">
|
<el-table-column width="140">
|
||||||
<template #header><span class="col-label"><i class="req-star">*</i>收费币种</span></template>
|
<template #header><span class="col-label"><i class="req-star">*</i>收费币种</span></template>
|
||||||
<template #default="{ row }">
|
<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-option v-for="item in bms_currency" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="备注" min-width="180">
|
<el-table-column label="备注" min-width="180">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="80" align="center">
|
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||||
<template #default="{ row }">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -77,14 +79,14 @@
|
|||||||
<el-table-column label="操作时间" width="168">
|
<el-table-column label="操作时间" width="168">
|
||||||
<template #default="{ row }">{{ logTime(row) }}</template>
|
<template #default="{ row }">{{ logTime(row) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作类型" width="90" align="center">
|
<el-table-column label="操作类型" width="110" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag size="small" :type="logType(row.operationType)">{{ logAction(row.operationType) }}</el-tag>
|
<el-tag size="small" :type="logType(row.operationType)">{{ logAction(row.operationType) }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="更改前" min-width="200">
|
<el-table-column label="更改前" min-width="200">
|
||||||
<template #default="{ row }">
|
<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-else class="log-change">
|
||||||
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.oldValue) }}</div>
|
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.oldValue) }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,7 +94,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="更改后" min-width="200">
|
<el-table-column label="更改后" min-width="200">
|
||||||
<template #default="{ row }">
|
<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-else class="log-change">
|
||||||
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.newValue) }}</div>
|
<div v-for="change in row.changes" :key="change.field">{{ change.fieldLabel || change.field }}:{{ showValue(change.newValue) }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,13 +119,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<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>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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 { listSurchargeOptions } from '@/api/price/surcharge'
|
||||||
import { parseTime } from '@/utils/bms'
|
import { parseTime } from '@/utils/bms'
|
||||||
|
|
||||||
@@ -134,6 +136,7 @@ const visible = ref(false)
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const logLoading = ref(false)
|
const logLoading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
const acting = ref(false)
|
||||||
const billCode = ref('')
|
const billCode = ref('')
|
||||||
const rows = ref([])
|
const rows = ref([])
|
||||||
const deletedIds = ref([])
|
const deletedIds = ref([])
|
||||||
@@ -172,6 +175,7 @@ function reset() {
|
|||||||
surchargeOptions.value = []
|
surchargeOptions.value = []
|
||||||
surchargeLoading.value = false
|
surchargeLoading.value = false
|
||||||
saving.value = false
|
saving.value = false
|
||||||
|
acting.value = false
|
||||||
loading.value = false
|
loading.value = false
|
||||||
logLoading.value = false
|
logLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -307,6 +311,37 @@ function changeAlloc(row) {
|
|||||||
if (String(row.allocFlag) !== '1') row.allocDim = ''
|
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) {
|
function removeRow(row) {
|
||||||
if (row.id != null && !deletedIds.value.some(id => String(id) === String(row.id))) {
|
if (row.id != null && !deletedIds.value.some(id => String(id) === String(row.id))) {
|
||||||
deletedIds.value.push(row.id)
|
deletedIds.value.push(row.id)
|
||||||
@@ -404,15 +439,21 @@ function save() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function logAction(type) {
|
function logAction(type) {
|
||||||
return { 1: '新增', 2: '修改', 3: '删除' }[String(type)] || '维护'
|
return { 1: '新增', 2: '修改', 3: '删除', 4: '确定费用', 5: '作废' }[String(type)] || '维护'
|
||||||
}
|
}
|
||||||
|
|
||||||
function logType(type) {
|
function logType(type) {
|
||||||
if (String(type) === '1') return 'success'
|
const value = String(type)
|
||||||
if (String(type) === '3') return 'danger'
|
if (value === '1' || value === '4') return 'success'
|
||||||
|
if (value === '3' || value === '5') return 'danger'
|
||||||
return 'warning'
|
return 'warning'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emptyText(row) {
|
||||||
|
const value = String(row?.operationType)
|
||||||
|
return value === '4' || value === '5' ? '无' : '空'
|
||||||
|
}
|
||||||
|
|
||||||
function logTime(item) {
|
function logTime(item) {
|
||||||
return parseTime(item.createTime) || ''
|
return parseTime(item.createTime) || ''
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user