费用明细可以增加费用
This commit is contained in:
+30
@@ -4,18 +4,24 @@ import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.price.domain.PrcOrderBill;
|
||||
import com.ruoyi.price.domain.PrcQuote;
|
||||
import com.ruoyi.price.domain.vo.AddIncomeBillFeeRequest;
|
||||
import com.ruoyi.price.domain.vo.UpdateIncomeBillFeeRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
@@ -110,6 +116,30 @@ public class BillingController extends BaseController
|
||||
return success(prcOrderBillService.getBill(billId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:billing:feeAdd')")
|
||||
@Log(title = "Billing income fee", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/bill/fee")
|
||||
public AjaxResult addIncomeFee(@Validated @RequestBody AddIncomeBillFeeRequest request)
|
||||
{
|
||||
return success(prcOrderBillService.addIncomeFee(request));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:billing:feeEdit')")
|
||||
@Log(title = "Billing income fee", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/bill/fee")
|
||||
public AjaxResult updateIncomeFee(@Validated @RequestBody UpdateIncomeBillFeeRequest request)
|
||||
{
|
||||
return success(prcOrderBillService.updateIncomeFee(request));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:billing:feeRemove')")
|
||||
@Log(title = "Billing income fee", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/bill/fee/{feeId}")
|
||||
public AjaxResult deleteIncomeFee(@PathVariable String feeId)
|
||||
{
|
||||
return success(prcOrderBillService.deleteIncomeFee(feeId));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('price:billing:bill')")
|
||||
@PostMapping("/bill")
|
||||
public AjaxResult bill(@Validated @RequestBody TmsPriceBillRequest request)
|
||||
|
||||
+2
-1
@@ -69,7 +69,8 @@ public class SurchargeController extends BaseController
|
||||
* 不要求 price:surcharge:list 权限,套餐维护角色(price:set:*)也能读,避免套餐页拿到 403 空列表。
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('price:surcharge:list') or @ss.hasPermi('price:surcharge:query')"
|
||||
+ " or @ss.hasPermi('price:set:query') or @ss.hasPermi('price:set:add') or @ss.hasPermi('price:set:edit')")
|
||||
+ " or @ss.hasPermi('price:set:query') or @ss.hasPermi('price:set:add') or @ss.hasPermi('price:set:edit')"
|
||||
+ " or @ss.hasPermi('price:billing:bill')")
|
||||
@GetMapping("/options")
|
||||
public AjaxResult listOptions(PrcSurcharge surcharge)
|
||||
{
|
||||
|
||||
@@ -153,6 +153,12 @@ price.bill.notFound=计费记录不存在
|
||||
price.bill.customerRequired=订单同步缺少有效客户
|
||||
price.bill.alreadyInArBill=订单{0}已生成应收账单,作废后方可重新计费
|
||||
price.bill.bizDateRequired=业务日期为空
|
||||
price.bill.fee.scIdRequired=附加费不能为空
|
||||
price.bill.fee.surchargeNotFound=附加费配置不存在或已停用
|
||||
price.bill.fee.notFound=费用明细不存在
|
||||
price.bill.fee.notManual=仅手动增加的费用可编辑或删除
|
||||
price.bill.fee.alreadyBilled=费用已生成账单,不可编辑或删除
|
||||
price.bill.fee.idRequired=费用ID不能为空
|
||||
|
||||
price.set.version.effectiveTime.empty=版本生效时间不能为空
|
||||
price.set.version.notExists=套餐版本不存在
|
||||
|
||||
@@ -568,6 +568,8 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
if (claimedCount != currentSourceBillIds.size()) {
|
||||
throw new ServiceException("部分计费订单已被其他账单占用,请重新查询后生成");
|
||||
}
|
||||
sourceFeeMapper.updateArBillStatusByBillIds(currentSourceBillIds,
|
||||
ArOrderBillStatus.UNBILLED.getCode(), ArOrderBillStatus.BILLED.getCode());
|
||||
|
||||
int detailNo = (task.getProcessedCount() == null ? 0 : task.getProcessedCount()) + 1;
|
||||
List<ArBillDetail> details = new ArrayList<>(sources.size());
|
||||
@@ -623,8 +625,18 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
*/
|
||||
private void cleanupGenerateTask(ArBillGenerateTask task, String message, boolean canceled,
|
||||
String operator, String requestId) {
|
||||
List<String> sourceBillIds = new ArrayList<>();
|
||||
for (ArBillDetail detail : detailMapper.selectByBillId(task.getArBillId())) {
|
||||
if (!StringUtils.isEmpty(detail.getPrcOrderBillId())) {
|
||||
sourceBillIds.add(detail.getPrcOrderBillId());
|
||||
}
|
||||
}
|
||||
sourceBillMapper.releaseArBillOrdersByArBillId(task.getArBillId(), operator,
|
||||
ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode());
|
||||
if (!sourceBillIds.isEmpty()) {
|
||||
sourceFeeMapper.updateArBillStatusByBillIds(sourceBillIds,
|
||||
ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode());
|
||||
}
|
||||
detailFeeMapper.deleteByBillId(task.getArBillId());
|
||||
detailMapper.deleteByBillId(task.getArBillId());
|
||||
billMapper.updateArBillGenerationStatus(task.getArBillId(), ArBillGenerationStatus.FAILED, operator, message);
|
||||
@@ -788,6 +800,8 @@ public class ArBillServiceImpl implements IArBillService {
|
||||
bill.getStatus(),
|
||||
"部分来源订单状态异常,账单未作废,请刷新后重试", operator, requestId);
|
||||
}
|
||||
sourceFeeMapper.updateArBillStatusByBillIds(sourceBillIds,
|
||||
ArOrderBillStatus.BILLED.getCode(), ArOrderBillStatus.UNBILLED.getCode());
|
||||
}
|
||||
recordSuccess(bill, ArLogObjectType.BILL, ArLogOperationType.VOID,
|
||||
"作废应收账单:" + remark, bill.getStatus(), ArBillStatus.VOID.getCode(), requestId, operator);
|
||||
|
||||
@@ -23,6 +23,10 @@ public class PrcIncomeBillFee implements Serializable
|
||||
private String currency;
|
||||
private String roundMode;
|
||||
private String remark;
|
||||
/** 是否生成账单状态:UNBILLED/BILLED */
|
||||
private String arBillStatus;
|
||||
/** 是否手动增加:0否 1是 */
|
||||
private String manualFlag;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.ruoyi.price.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/** Manual add receivable fee line on billing detail. */
|
||||
@Data
|
||||
public class AddIncomeBillFeeRequest implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "billId required")
|
||||
private String billId;
|
||||
|
||||
/** Surcharge config id (prc_surcharge.sc_id). */
|
||||
@NotNull(message = "scId required")
|
||||
private Long scId;
|
||||
|
||||
/** Dict bms_fee_type; empty = use surcharge.feeType. */
|
||||
private String feeType;
|
||||
|
||||
/** Empty = calculate from surcharge chargeType. */
|
||||
private BigDecimal amount;
|
||||
|
||||
/** Default CNY. */
|
||||
private String currency;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
+5
@@ -10,6 +10,7 @@ public class TmsPriceQuoteFeeLine implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String feeId;
|
||||
private String feeType;
|
||||
private String feeName;
|
||||
/** surcharge config id, 0 for freight/registration */
|
||||
@@ -18,4 +19,8 @@ public class TmsPriceQuoteFeeLine implements Serializable
|
||||
private String currency;
|
||||
private String roundMode;
|
||||
private String remark;
|
||||
/** UNBILLED / BILLED */
|
||||
private String arBillStatus;
|
||||
/** 0=system 1=manual */
|
||||
private String manualFlag;
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.ruoyi.price.domain.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
/** Update manual receivable fee line on billing detail. */
|
||||
@Data
|
||||
public class UpdateIncomeBillFeeRequest implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "feeId required")
|
||||
private String feeId;
|
||||
|
||||
/** Optional: change surcharge config. */
|
||||
private Long scId;
|
||||
|
||||
/** Dict bms_fee_type. */
|
||||
private String feeType;
|
||||
|
||||
private BigDecimal amount;
|
||||
|
||||
private String currency;
|
||||
|
||||
private String remark;
|
||||
}
|
||||
+13
@@ -11,9 +11,22 @@ public interface PrcIncomeBillFeeMapper
|
||||
|
||||
public List<PrcIncomeBillFee> selectByBillIds(@Param("billIds") List<String> billIds);
|
||||
|
||||
public PrcIncomeBillFee selectByFeeId(String feeId);
|
||||
|
||||
/** Count fees of a bill under given ar_bill_status and fee_type (e.g. BILLED + BASIC_FREIGHT). */
|
||||
public int countByBillIdAndArBillStatusAndFeeType(@Param("billId") String billId,
|
||||
@Param("arBillStatus") String arBillStatus, @Param("feeType") String feeType);
|
||||
|
||||
public int insertPrcIncomeBillFee(PrcIncomeBillFee fee);
|
||||
|
||||
public int insertBatch(@Param("list") List<PrcIncomeBillFee> fees);
|
||||
|
||||
public int updatePrcIncomeBillFee(PrcIncomeBillFee fee);
|
||||
|
||||
public int updateArBillStatusByBillIds(@Param("billIds") List<String> billIds,
|
||||
@Param("fromStatus") String fromStatus, @Param("toStatus") String toStatus);
|
||||
|
||||
public int deleteByFeeId(String feeId);
|
||||
|
||||
public int deleteByBillId(String billId);
|
||||
}
|
||||
|
||||
+5
-2
@@ -44,8 +44,11 @@ public class OracleOrderBillMapper
|
||||
String countryName = firstStr(row, "COUNTRY_NAME", "COUNTRY_EINGTH_NAME", "COUNTRY");
|
||||
req.setCountryName(countryName);
|
||||
req.setDestPostcode(firstStr(row, "CONSIGNEE_POSTCODE"));
|
||||
// Prefer positive weight: Oracle WEIGHT is often 0 while ACTUAL_WEIGHT/PUSH_WEIGHT holds real value.
|
||||
BigDecimal weight = firstPositiveDecimal(row, "ACTUAL_WEIGHT", "PUSH_WEIGHT", "WEIGHT", "VOLUME_WEIGHT");
|
||||
// TAB_ORDER.DIMENSIONAL_WEIGHT_FACTOR = billing weight (计费重) -> quote weight / prc_order_bill.charge_weight
|
||||
BigDecimal chargeWeight = firstPositiveDecimal(row, "DIMENSIONAL_WEIGHT_FACTOR");
|
||||
BigDecimal weight = chargeWeight != null
|
||||
? chargeWeight
|
||||
: firstPositiveDecimal(row, "ACTUAL_WEIGHT", "PUSH_WEIGHT", "WEIGHT", "VOLUME_WEIGHT");
|
||||
req.setWeight(weight);
|
||||
req.setActualWeight(firstPositiveDecimal(row, "ACTUAL_WEIGHT", "PUSH_WEIGHT", "WEIGHT"));
|
||||
req.setPieces(firstInt(row, "ORDER_PIECE", "PACKAGE_NUM", "PRODUCT_SUM"));
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.ruoyi.price.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.price.domain.PrcOrderBill;
|
||||
import com.ruoyi.price.domain.vo.AddIncomeBillFeeRequest;
|
||||
import com.ruoyi.price.domain.vo.UpdateIncomeBillFeeRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
|
||||
import com.ruoyi.price.domain.vo.TmsOrderBillStatusResult;
|
||||
@@ -26,6 +28,15 @@ public interface IPrcOrderBillService
|
||||
|
||||
TmsPriceQuoteResult getBillByOrderNo(String orderNo);
|
||||
|
||||
/** Manual add income fee from surcharge config; refresh receivable totals. */
|
||||
TmsPriceQuoteResult addIncomeFee(AddIncomeBillFeeRequest request);
|
||||
|
||||
/** Update manual UNBILLED income fee; refresh receivable totals. */
|
||||
TmsPriceQuoteResult updateIncomeFee(UpdateIncomeBillFeeRequest request);
|
||||
|
||||
/** Delete manual UNBILLED income fee; refresh receivable totals. */
|
||||
TmsPriceQuoteResult deleteIncomeFee(String feeId);
|
||||
|
||||
/** 提供给 TMS 判断订单是否可以继续处理的客户状态查询。 */
|
||||
TmsOrderBillStatusResult queryTmsOrderBillStatus(String identifier);
|
||||
}
|
||||
|
||||
+252
-3
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.price.service.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
@@ -26,6 +27,9 @@ import com.ruoyi.price.domain.PrcOrderBill;
|
||||
import com.ruoyi.price.domain.PrcOrderBillExt;
|
||||
import com.ruoyi.price.domain.PrcPayBill;
|
||||
import com.ruoyi.price.domain.PrcPayBillFee;
|
||||
import com.ruoyi.price.domain.PrcSurcharge;
|
||||
import com.ruoyi.price.domain.vo.AddIncomeBillFeeRequest;
|
||||
import com.ruoyi.price.domain.vo.UpdateIncomeBillFeeRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceBillRequest;
|
||||
import com.ruoyi.price.domain.vo.TmsOrderBillStatusResult;
|
||||
import com.ruoyi.price.domain.vo.TmsPriceQuoteFeeLine;
|
||||
@@ -48,6 +52,7 @@ import com.ruoyi.customer.customermoney.api.CustomerFundBizType;
|
||||
import com.ruoyi.customer.customermoney.api.ICustomerAccountApi;
|
||||
import com.ruoyi.customer.customermoney.vo.CustomerAccountStatusVO;
|
||||
import com.ruoyi.price.service.IPrcOrderBillService;
|
||||
import com.ruoyi.price.service.IPrcSurchargeService;
|
||||
import com.ruoyi.price.service.ITmsPriceQuoteService;
|
||||
|
||||
@Service
|
||||
@@ -80,6 +85,9 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
@Autowired
|
||||
private ICustomerAccountApi customerAccountApi;
|
||||
|
||||
@Autowired
|
||||
private IPrcSurchargeService surchargeService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult bill(TmsPriceBillRequest request)
|
||||
@@ -201,6 +209,237 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
payBillFeeMapper.selectByBillId(bill.getBillId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult addIncomeFee(AddIncomeBillFeeRequest request)
|
||||
{
|
||||
if (request == null || StringUtils.isEmpty(request.getBillId()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.billIdRequired"));
|
||||
}
|
||||
if (request.getScId() == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.fee.scIdRequired"));
|
||||
}
|
||||
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(request.getBillId().trim());
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.notFound"));
|
||||
}
|
||||
|
||||
PrcSurcharge surcharge = requireActiveSurcharge(request.getScId());
|
||||
PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId());
|
||||
String feeType = StringUtils.isNotEmpty(request.getFeeType()) ? request.getFeeType().trim()
|
||||
: (StringUtils.isEmpty(surcharge.getFeeType()) ? "SURCHARGE" : surcharge.getFeeType());
|
||||
String currency = normalizeFeeCurrency(request.getCurrency());
|
||||
BigDecimal amount = request.getAmount();
|
||||
if (amount == null)
|
||||
{
|
||||
amount = calcManualSurchargeAmount(surcharge, bill.getChargeWeight(), bill.getFreightAmount());
|
||||
}
|
||||
if (amount == null)
|
||||
{
|
||||
amount = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
PrcIncomeBillFee fee = new PrcIncomeBillFee();
|
||||
fee.setFeeId(UUID.randomUUID().toString());
|
||||
fee.setBillId(bill.getBillId());
|
||||
fee.setIncomeBillId(incomeBill == null ? null : incomeBill.getIncomeBillId());
|
||||
fee.setFeeType(feeType);
|
||||
fee.setFeeName(surcharge.getScName());
|
||||
fee.setScId(surcharge.getScId());
|
||||
fee.setAmount(amount);
|
||||
fee.setCurrency(currency);
|
||||
fee.setRoundMode(StringUtils.isEmpty(surcharge.getRoundMode()) ? bill.getRoundMode() : surcharge.getRoundMode());
|
||||
fee.setRemark(request.getRemark());
|
||||
fee.setArBillStatus(ArOrderBillStatus.UNBILLED.getCode());
|
||||
fee.setManualFlag("1");
|
||||
incomeBillFeeMapper.insertPrcIncomeBillFee(fee);
|
||||
|
||||
return refreshBillIncomeTotals(bill);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult updateIncomeFee(UpdateIncomeBillFeeRequest request)
|
||||
{
|
||||
if (request == null || StringUtils.isEmpty(request.getFeeId()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.fee.idRequired"));
|
||||
}
|
||||
PrcIncomeBillFee fee = requireEditableManualFee(request.getFeeId().trim());
|
||||
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(fee.getBillId());
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.notFound"));
|
||||
}
|
||||
|
||||
if (request.getScId() != null)
|
||||
{
|
||||
PrcSurcharge surcharge = requireActiveSurcharge(request.getScId());
|
||||
fee.setScId(surcharge.getScId());
|
||||
fee.setFeeName(surcharge.getScName());
|
||||
if (StringUtils.isEmpty(request.getFeeType()))
|
||||
{
|
||||
fee.setFeeType(StringUtils.isEmpty(surcharge.getFeeType()) ? "SURCHARGE" : surcharge.getFeeType());
|
||||
}
|
||||
if (StringUtils.isEmpty(fee.getRoundMode()))
|
||||
{
|
||||
fee.setRoundMode(StringUtils.isEmpty(surcharge.getRoundMode()) ? bill.getRoundMode() : surcharge.getRoundMode());
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(request.getFeeType()))
|
||||
{
|
||||
fee.setFeeType(request.getFeeType().trim());
|
||||
}
|
||||
if (request.getAmount() != null)
|
||||
{
|
||||
fee.setAmount(request.getAmount());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(request.getCurrency()))
|
||||
{
|
||||
fee.setCurrency(normalizeFeeCurrency(request.getCurrency()));
|
||||
}
|
||||
if (request.getRemark() != null)
|
||||
{
|
||||
fee.setRemark(request.getRemark());
|
||||
}
|
||||
incomeBillFeeMapper.updatePrcIncomeBillFee(fee);
|
||||
return refreshBillIncomeTotals(bill);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public TmsPriceQuoteResult deleteIncomeFee(String feeId)
|
||||
{
|
||||
if (StringUtils.isEmpty(feeId))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.fee.idRequired"));
|
||||
}
|
||||
PrcIncomeBillFee fee = requireEditableManualFee(feeId.trim());
|
||||
PrcOrderBill bill = orderBillMapper.selectPrcOrderBillById(fee.getBillId());
|
||||
if (bill == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.notFound"));
|
||||
}
|
||||
incomeBillFeeMapper.deleteByFeeId(fee.getFeeId());
|
||||
return refreshBillIncomeTotals(bill);
|
||||
}
|
||||
|
||||
private PrcSurcharge requireActiveSurcharge(Long scId)
|
||||
{
|
||||
PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId);
|
||||
String ssCode = StringUtils.nvl(surcharge == null ? null : surcharge.getSsCode(), "on");
|
||||
if (surcharge == null || "of".equalsIgnoreCase(ssCode) || "vd".equalsIgnoreCase(ssCode))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.fee.surchargeNotFound"));
|
||||
}
|
||||
return surcharge;
|
||||
}
|
||||
|
||||
private PrcIncomeBillFee requireEditableManualFee(String feeId)
|
||||
{
|
||||
PrcIncomeBillFee fee = incomeBillFeeMapper.selectByFeeId(feeId);
|
||||
if (fee == null)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.fee.notFound"));
|
||||
}
|
||||
if (!"1".equals(fee.getManualFlag()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.fee.notManual"));
|
||||
}
|
||||
if (ArOrderBillStatus.BILLED.getCode().equals(fee.getArBillStatus()))
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.fee.alreadyBilled"));
|
||||
}
|
||||
return fee;
|
||||
}
|
||||
|
||||
private String normalizeFeeCurrency(String currency)
|
||||
{
|
||||
String ccy = StringUtils.isNotEmpty(currency) ? currency.trim() : "CNY";
|
||||
if ("RMB".equalsIgnoreCase(ccy))
|
||||
{
|
||||
return "CNY";
|
||||
}
|
||||
return ccy;
|
||||
}
|
||||
|
||||
/** Recalc surcharge/income totals on order + income bill, sync credit, return detail. */
|
||||
private TmsPriceQuoteResult refreshBillIncomeTotals(PrcOrderBill bill)
|
||||
{
|
||||
List<PrcIncomeBillFee> fees = incomeBillFeeMapper.selectByBillId(bill.getBillId());
|
||||
BigDecimal previousIncome = nz(bill.getIncomeTotal());
|
||||
BigDecimal surchargeTotal = BigDecimal.ZERO;
|
||||
BigDecimal incomeTotal = BigDecimal.ZERO;
|
||||
for (PrcIncomeBillFee row : fees)
|
||||
{
|
||||
BigDecimal amt = nz(row.getAmount());
|
||||
incomeTotal = incomeTotal.add(amt);
|
||||
if (!"BASIC_FREIGHT".equalsIgnoreCase(row.getFeeType())
|
||||
&& !"REGISTRATION_FEE".equalsIgnoreCase(row.getFeeType()))
|
||||
{
|
||||
surchargeTotal = surchargeTotal.add(amt);
|
||||
}
|
||||
}
|
||||
bill.setSurchargeAmount(surchargeTotal);
|
||||
bill.setIncomeTotal(incomeTotal);
|
||||
|
||||
PrcIncomeBill incomeBill = incomeBillMapper.selectByBillId(bill.getBillId());
|
||||
if (incomeBill != null)
|
||||
{
|
||||
incomeBill.setSalesAmount(incomeTotal);
|
||||
incomeBill.setIncomeTotal(incomeTotal);
|
||||
incomeBill.setCurrency(StringUtils.isEmpty(bill.getCurrency()) ? "CNY" : bill.getCurrency());
|
||||
incomeBillMapper.updatePrcIncomeBill(incomeBill);
|
||||
}
|
||||
|
||||
String creditArrearsStatus = syncOrderCredit(bill, previousIncome,
|
||||
StringUtils.isEmpty(bill.getCustomerActiveStatus())
|
||||
? BillCustomerActiveStatus.ACTIVE.getCode() : bill.getCustomerActiveStatus());
|
||||
bill.setCreditArrearsStatus(creditArrearsStatus);
|
||||
orderBillMapper.updatePrcOrderBill(bill);
|
||||
return toResult(bill, fees, payBillFeeMapper.selectByBillId(bill.getBillId()));
|
||||
}
|
||||
|
||||
private BigDecimal calcManualSurchargeAmount(PrcSurcharge surcharge, BigDecimal weight, BigDecimal freightAmount)
|
||||
{
|
||||
if (surcharge == null || surcharge.getChargeValue() == null)
|
||||
{
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
RoundingMode rm = resolveFeeRoundingMode(surcharge.getRoundMode());
|
||||
String chargeType = surcharge.getChargeType() == null ? "FIXED" : surcharge.getChargeType().toUpperCase();
|
||||
BigDecimal amount;
|
||||
if ("WEIGHT".equals(chargeType))
|
||||
{
|
||||
amount = surcharge.getChargeValue().multiply(nz(weight));
|
||||
}
|
||||
else if ("RATE".equals(chargeType))
|
||||
{
|
||||
amount = nz(freightAmount).multiply(surcharge.getChargeValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = surcharge.getChargeValue();
|
||||
}
|
||||
return amount.setScale(4, rm);
|
||||
}
|
||||
|
||||
private RoundingMode resolveFeeRoundingMode(String roundMode)
|
||||
{
|
||||
if ("UP".equalsIgnoreCase(roundMode))
|
||||
{
|
||||
return RoundingMode.UP;
|
||||
}
|
||||
if ("DOWN".equalsIgnoreCase(roundMode))
|
||||
{
|
||||
return RoundingMode.DOWN;
|
||||
}
|
||||
return RoundingMode.HALF_UP;
|
||||
}
|
||||
|
||||
/** 查询 TMS 后续流程所需的客户状态,只返回订单数据和状态事实,不代替上游做业务决策。 */
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
@@ -551,7 +790,10 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
billSearchStore.save(EsLogType.CREDIT, bill.getOrderNo(), req.toJSONString(), resp.toJSONString(), status, err);
|
||||
}
|
||||
|
||||
/** Block re-bill when order already claimed by an AR bill (BILLED). Void restores UNBILLED. */
|
||||
/**
|
||||
* Block re-bill when BASIC_FREIGHT fee is already in an AR bill (BILLED).
|
||||
* Source of truth is prc_income_bill_fee.ar_bill_status (not prc_order_bill.ar_bill_status).
|
||||
*/
|
||||
private void assertOrderBillEditable(String orderNo)
|
||||
{
|
||||
if (StringUtils.isEmpty(orderNo))
|
||||
@@ -563,11 +805,13 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
|
||||
private void assertOrderBillEditable(PrcOrderBill existed)
|
||||
{
|
||||
if (existed == null)
|
||||
if (existed == null || StringUtils.isEmpty(existed.getBillId()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ArOrderBillStatus.BILLED.getCode().equals(existed.getArBillStatus()))
|
||||
int billedFreightCount = incomeBillFeeMapper.countByBillIdAndArBillStatusAndFeeType(
|
||||
existed.getBillId(), ArOrderBillStatus.BILLED.getCode(), "BASIC_FREIGHT");
|
||||
if (billedFreightCount > 0)
|
||||
{
|
||||
throw new ServiceException(MessageUtils.message("price.bill.alreadyInArBill", existed.getOrderNo()));
|
||||
}
|
||||
@@ -598,6 +842,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
fee.setCurrency(line.getCurrency());
|
||||
fee.setRoundMode(line.getRoundMode());
|
||||
fee.setRemark(line.getRemark());
|
||||
fee.setArBillStatus(ArOrderBillStatus.UNBILLED.getCode());
|
||||
fee.setManualFlag("0");
|
||||
fees.add(fee);
|
||||
}
|
||||
return fees;
|
||||
@@ -777,6 +1023,7 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
for (PrcIncomeBillFee fee : fees)
|
||||
{
|
||||
TmsPriceQuoteFeeLine line = new TmsPriceQuoteFeeLine();
|
||||
line.setFeeId(fee.getFeeId());
|
||||
line.setFeeType(fee.getFeeType());
|
||||
line.setFeeName(fee.getFeeName());
|
||||
line.setScId(fee.getScId());
|
||||
@@ -784,6 +1031,8 @@ public class PrcOrderBillServiceImpl implements IPrcOrderBillService
|
||||
line.setCurrency(fee.getCurrency());
|
||||
line.setRoundMode(fee.getRoundMode());
|
||||
line.setRemark(fee.getRemark());
|
||||
line.setArBillStatus(fee.getArBillStatus());
|
||||
line.setManualFlag(fee.getManualFlag());
|
||||
lines.add(line);
|
||||
}
|
||||
return lines;
|
||||
|
||||
@@ -15,18 +15,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="currency" column="currency" />
|
||||
<result property="roundMode" column="round_mode" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="arBillStatus" column="ar_bill_status" />
|
||||
<result property="manualFlag" column="manual_flag" />
|
||||
<result property="createTime" column="create_time" />
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByBillId" parameterType="String" resultMap="PrcIncomeBillFeeResult">
|
||||
select fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
select fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
ar_bill_status, manual_flag, create_time
|
||||
from prc_income_bill_fee
|
||||
where bill_id = #{billId}
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
</select>
|
||||
|
||||
<select id="selectByBillIds" resultMap="PrcIncomeBillFeeResult">
|
||||
select fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
select fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
ar_bill_status, manual_flag, create_time
|
||||
from prc_income_bill_fee
|
||||
where bill_id in
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
@@ -35,24 +39,69 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
order by bill_id, field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE'), fee_id
|
||||
</select>
|
||||
|
||||
<select id="selectByFeeId" parameterType="String" resultMap="PrcIncomeBillFeeResult">
|
||||
select fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
ar_bill_status, manual_flag, create_time
|
||||
from prc_income_bill_fee
|
||||
where fee_id = #{feeId}
|
||||
</select>
|
||||
|
||||
<select id="countByBillIdAndArBillStatusAndFeeType" resultType="int">
|
||||
select count(1)
|
||||
from prc_income_bill_fee
|
||||
where bill_id = #{billId}
|
||||
and ar_bill_status = #{arBillStatus}
|
||||
and fee_type = #{feeType}
|
||||
</select>
|
||||
|
||||
<insert id="insertPrcIncomeBillFee" parameterType="PrcIncomeBillFee">
|
||||
insert into prc_income_bill_fee (
|
||||
fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
ar_bill_status, manual_flag, create_time
|
||||
) values (
|
||||
#{feeId}, #{billId}, #{incomeBillId}, #{feeType}, #{feeName}, #{scId}, #{amount}, #{currency}, #{roundMode}, #{remark}, sysdate()
|
||||
#{feeId}, #{billId}, #{incomeBillId}, #{feeType}, #{feeName}, #{scId}, #{amount}, #{currency}, #{roundMode}, #{remark},
|
||||
#{arBillStatus}, #{manualFlag}, sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertBatch" parameterType="java.util.List">
|
||||
insert into prc_income_bill_fee (
|
||||
fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
fee_id, bill_id, income_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
ar_bill_status, manual_flag, create_time
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.feeId}, #{item.billId}, #{item.incomeBillId}, #{item.feeType}, #{item.feeName}, #{item.scId}, #{item.amount},
|
||||
#{item.currency}, #{item.roundMode}, #{item.remark}, sysdate())
|
||||
#{item.currency}, #{item.roundMode}, #{item.remark},
|
||||
#{item.arBillStatus}, #{item.manualFlag}, sysdate())
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<update id="updatePrcIncomeBillFee" parameterType="PrcIncomeBillFee">
|
||||
update prc_income_bill_fee
|
||||
set fee_type = #{feeType},
|
||||
fee_name = #{feeName},
|
||||
sc_id = #{scId},
|
||||
amount = #{amount},
|
||||
currency = #{currency},
|
||||
round_mode = #{roundMode},
|
||||
remark = #{remark}
|
||||
where fee_id = #{feeId}
|
||||
</update>
|
||||
|
||||
<update id="updateArBillStatusByBillIds">
|
||||
update prc_income_bill_fee
|
||||
set ar_bill_status = #{toStatus}
|
||||
where ar_bill_status = #{fromStatus}
|
||||
and bill_id in
|
||||
<foreach collection="billIds" item="billId" open="(" separator="," close=")">
|
||||
#{billId}
|
||||
</foreach>
|
||||
</update>
|
||||
|
||||
<delete id="deleteByFeeId" parameterType="String">
|
||||
delete from prc_income_bill_fee where fee_id = #{feeId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByBillId" parameterType="String">
|
||||
delete from prc_income_bill_fee where bill_id = #{billId}
|
||||
</delete>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
-- Billing list: manual fee button permissions (parent = price:billing:bill)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
SET @parent_id := (SELECT menu_id FROM sys_menu WHERE perms = 'price:billing:bill' AND menu_type = 'C' LIMIT 1);
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT N'增加费用', @parent_id, 10, '', NULL, 1, 0, 'F', '0', '0', 'price:billing:feeAdd', '#', 'admin', sysdate(), N'计费详情手动增加费用'
|
||||
FROM DUAL
|
||||
WHERE @parent_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:billing:feeAdd');
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT N'编辑费用', @parent_id, 11, '', NULL, 1, 0, 'F', '0', '0', 'price:billing:feeEdit', '#', 'admin', sysdate(), N'编辑手动增加且未生成账单的费用'
|
||||
FROM DUAL
|
||||
WHERE @parent_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:billing:feeEdit');
|
||||
|
||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
||||
SELECT N'删除费用', @parent_id, 12, '', NULL, 1, 0, 'F', '0', '0', 'price:billing:feeRemove', '#', 'admin', sysdate(), N'删除手动增加且未生成账单的费用'
|
||||
FROM DUAL
|
||||
WHERE @parent_id IS NOT NULL
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'price:billing:feeRemove');
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, menu_id FROM sys_menu WHERE perms IN ('price:billing:feeAdd', 'price:billing:feeEdit', 'price:billing:feeRemove')
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_role_menu rm WHERE rm.role_id = 1 AND rm.menu_id = sys_menu.menu_id);
|
||||
@@ -0,0 +1,6 @@
|
||||
-- prc_income_bill_fee: ar_bill_status (UNBILLED/BILLED)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE prc_income_bill_fee
|
||||
ADD COLUMN ar_bill_status VARCHAR(20) NOT NULL DEFAULT 'UNBILLED' COMMENT '是否生成账单状态:UNBILLED未生成/BILLED已生成' AFTER remark,
|
||||
ADD KEY idx_income_fee_ar_status (ar_bill_status);
|
||||
@@ -0,0 +1,5 @@
|
||||
-- prc_income_bill_fee: manual_flag
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE prc_income_bill_fee
|
||||
ADD COLUMN manual_flag CHAR(1) NOT NULL DEFAULT '0' COMMENT '是否手动增加:0否 1是' AFTER ar_bill_status;
|
||||
@@ -78,3 +78,29 @@ export function listTransferNoOptions(keyword) {
|
||||
params: { keyword }
|
||||
})
|
||||
}
|
||||
|
||||
/** 计费详情:增加应收费用明细 */
|
||||
export function addIncomeBillFee(data) {
|
||||
return request({
|
||||
url: '/price/billing/bill/fee',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 计费详情:编辑手动应收费用 */
|
||||
export function updateIncomeBillFee(data) {
|
||||
return request({
|
||||
url: '/price/billing/bill/fee',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 计费详情:删除手动应收费用 */
|
||||
export function deleteIncomeBillFee(feeId) {
|
||||
return request({
|
||||
url: '/price/billing/bill/fee/' + feeId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</template>
|
||||
</basic-table>
|
||||
|
||||
<bill-detail v-model="open" :title="title" :loading="loading" :detail="detail" />
|
||||
<bill-detail v-model="open" :title="title" :loading="loading" :detail="detail" @updated="onDetailUpdated" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -156,6 +156,11 @@ function handleView(row) {
|
||||
})
|
||||
}
|
||||
|
||||
function onDetailUpdated(data) {
|
||||
detail.value = data
|
||||
tableRef.value?.refresh?.()
|
||||
}
|
||||
|
||||
function formatMoney(val) {
|
||||
if (val === null || val === undefined || val === '') return '-'
|
||||
const num = Number(val)
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
:title="dlgTitle"
|
||||
width="520px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="96px">
|
||||
<el-form-item :label="lblFeeName" prop="scId">
|
||||
<el-select
|
||||
v-model="form.scId"
|
||||
filterable
|
||||
clearable
|
||||
:placeholder="phSurcharge"
|
||||
style="width: 100%;"
|
||||
@change="onSurchargeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="s in surchargeOptions"
|
||||
:key="s.scId"
|
||||
:label="s.scName"
|
||||
:value="s.scId"
|
||||
>
|
||||
<span>{{ s.scName }}</span>
|
||||
<span style="float: right; color: var(--el-text-color-secondary); font-size: 12px;">{{ s.chargeValue }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="lblFeeType" prop="feeType">
|
||||
<el-select v-model="form.feeType" :placeholder="phFeeType" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="d in (bms_fee_type || [])"
|
||||
:key="d.value"
|
||||
:label="d.label"
|
||||
:value="d.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="lblAmount" prop="amount">
|
||||
<el-input-number
|
||||
v-model="form.amount"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
:step="0.1"
|
||||
controls-position="right"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="lblCurrency" prop="currency">
|
||||
<el-select v-model="form.currency" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="d in (bms_currency || [])"
|
||||
:key="d.value"
|
||||
:label="d.label"
|
||||
:value="d.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="lblRemark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" :placeholder="phRemark" maxlength="200" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="$emit('update:modelValue', false)">{{ btnCancel }}</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="submit">{{ btnOk }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { listSurchargeOptions } from '@/api/price/surcharge'
|
||||
import { addIncomeBillFee, updateIncomeBillFee } from '@/api/price/billing'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
billId: { type: String, default: '' },
|
||||
chargeWeight: { type: [Number, String], default: null },
|
||||
freightAmount: { type: [Number, String], default: null },
|
||||
/** edit row when present */
|
||||
editRow: { type: Object, default: null }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'success'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_fee_type, bms_currency } = useDict('bms_fee_type', 'bms_currency')
|
||||
|
||||
const isEdit = computed(() => !!(props.editRow && props.editRow.feeId))
|
||||
const dlgTitle = computed(() => (isEdit.value ? '编辑费用' : '增加费用'))
|
||||
const lblFeeName = '费用名称'
|
||||
const lblFeeType = '费用类型'
|
||||
const lblAmount = '金额'
|
||||
const lblCurrency = '币种'
|
||||
const lblRemark = '说明'
|
||||
const phSurcharge = '请选择附加费'
|
||||
const phFeeType = '请选择费用类型'
|
||||
const phRemark = '选填'
|
||||
const btnCancel = '取消'
|
||||
const btnOk = '保存'
|
||||
|
||||
const formRef = ref()
|
||||
const saving = ref(false)
|
||||
const surchargeOptions = ref([])
|
||||
const form = ref(emptyForm())
|
||||
|
||||
const rules = {
|
||||
scId: [{ required: true, message: '请选择附加费', trigger: 'change' }],
|
||||
feeType: [{ required: true, message: '请选择费用类型', trigger: 'change' }],
|
||||
amount: [{ required: true, message: '请输入金额', trigger: 'blur' }],
|
||||
currency: [{ required: true, message: '请选择币种', trigger: 'change' }]
|
||||
}
|
||||
|
||||
function emptyForm() {
|
||||
return {
|
||||
feeId: undefined,
|
||||
scId: undefined,
|
||||
feeType: 'SURCHARGE',
|
||||
amount: undefined,
|
||||
currency: 'CNY',
|
||||
remark: ''
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, (open) => {
|
||||
if (!open) return
|
||||
loadSurcharges().then(() => {
|
||||
if (isEdit.value) {
|
||||
const row = props.editRow
|
||||
form.value = {
|
||||
feeId: row.feeId,
|
||||
scId: row.scId || undefined,
|
||||
feeType: row.feeType || 'SURCHARGE',
|
||||
amount: row.amount != null ? Number(row.amount) : undefined,
|
||||
currency: displayCurrency(row.currency),
|
||||
remark: row.remark || ''
|
||||
}
|
||||
} else {
|
||||
form.value = emptyForm()
|
||||
}
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
})
|
||||
})
|
||||
|
||||
function displayCurrency(v) {
|
||||
if (!v || v === 'RMB' || v === 'rmb') return 'CNY'
|
||||
return v
|
||||
}
|
||||
|
||||
function loadSurcharges() {
|
||||
return listSurchargeOptions({ ssCode: 'on' }).then(res => {
|
||||
surchargeOptions.value = res.data || []
|
||||
})
|
||||
}
|
||||
|
||||
function calcAmount(s) {
|
||||
if (!s || s.chargeValue == null) return 0
|
||||
const cv = Number(s.chargeValue)
|
||||
const weight = Number(props.chargeWeight || 0)
|
||||
const freight = Number(props.freightAmount || 0)
|
||||
const type = String(s.chargeType || 'FIXED').toUpperCase()
|
||||
let amount = cv
|
||||
if (type === 'WEIGHT') amount = cv * weight
|
||||
else if (type === 'RATE') amount = freight * cv
|
||||
return Number(amount.toFixed(4))
|
||||
}
|
||||
|
||||
function onSurchargeChange(scId) {
|
||||
const s = (surchargeOptions.value || []).find(x => String(x.scId) === String(scId))
|
||||
if (!s) return
|
||||
form.value.feeType = s.feeType || 'SURCHARGE'
|
||||
form.value.currency = 'CNY'
|
||||
form.value.amount = calcAmount(s)
|
||||
if (s.scNote && !form.value.remark) {
|
||||
form.value.remark = s.scNote
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
formRef.value?.validate(valid => {
|
||||
if (!valid) return
|
||||
if (!isEdit.value && !props.billId) {
|
||||
proxy.$modal.msgError('计费ID为空')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
const payload = {
|
||||
scId: form.value.scId,
|
||||
feeType: form.value.feeType,
|
||||
amount: form.value.amount,
|
||||
currency: form.value.currency || 'CNY',
|
||||
remark: form.value.remark
|
||||
}
|
||||
const req = isEdit.value
|
||||
? updateIncomeBillFee({ ...payload, feeId: form.value.feeId })
|
||||
: addIncomeBillFee({ ...payload, billId: props.billId })
|
||||
req.then(res => {
|
||||
proxy.$modal.msgSuccess('保存成功')
|
||||
emit('update:modelValue', false)
|
||||
emit('success', res.data || null)
|
||||
}).finally(() => {
|
||||
saving.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -90,6 +90,9 @@
|
||||
<div class="sec-title">费用明细</div>
|
||||
<el-tabs v-model="feeTab" class="fee-tabs">
|
||||
<el-tab-pane label="应收" name="income">
|
||||
<div class="fee-toolbar">
|
||||
<el-button type="primary" plain size="small" icon="Plus" v-hasPermi="['price:billing:feeAdd']" @click="openAddFee">增加费用</el-button>
|
||||
</div>
|
||||
<el-table v-if="incomeFees.length" :data="incomeFees" border size="small" class="fee-table">
|
||||
<el-table-column label="费用名称" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ displayFeeName(row) }}</template>
|
||||
@@ -114,6 +117,15 @@
|
||||
<el-table-column label="说明" min-width="160" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.remark || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<template v-if="canEditManualFee(row)">
|
||||
<el-button link type="primary" v-hasPermi="['price:billing:feeEdit']" @click="openEditFee(row)">编辑</el-button>
|
||||
<el-button link type="danger" v-hasPermi="['price:billing:feeRemove']" @click="handleDeleteFee(row)">删除</el-button>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无应收明细" :image-size="64" />
|
||||
</el-tab-pane>
|
||||
@@ -149,10 +161,23 @@
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<add-income-fee-dialog
|
||||
v-model="addFeeOpen"
|
||||
:bill-id="detail?.billId || ''"
|
||||
:charge-weight="detail?.chargeWeight ?? detail?.weight"
|
||||
:freight-amount="detail?.freightAmount"
|
||||
:edit-row="editFeeRow"
|
||||
@success="onFeeAdded"
|
||||
/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AddIncomeFeeDialog from './AddIncomeFeeDialog.vue'
|
||||
import { deleteIncomeBillFee } from '@/api/price/billing'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Boolean, default: false },
|
||||
title: { type: String, default: '计费详情' },
|
||||
@@ -160,9 +185,11 @@ const props = defineProps({
|
||||
detail: { type: Object, default: null }
|
||||
})
|
||||
|
||||
defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue', 'updated'])
|
||||
|
||||
const feeTab = ref('income')
|
||||
const addFeeOpen = ref(false)
|
||||
const editFeeRow = ref(null)
|
||||
const { bms_fee_type, bms_currency } = useDict('bms_fee_type', 'bms_currency')
|
||||
|
||||
const incomeFees = computed(() => {
|
||||
@@ -181,6 +208,37 @@ watch(() => props.detail, () => {
|
||||
feeTab.value = 'income'
|
||||
})
|
||||
|
||||
function canEditManualFee(row) {
|
||||
return row && String(row.manualFlag) === '1' && row.arBillStatus !== 'BILLED'
|
||||
}
|
||||
|
||||
function openAddFee() {
|
||||
if (!props.detail?.billId) return
|
||||
editFeeRow.value = null
|
||||
addFeeOpen.value = true
|
||||
}
|
||||
|
||||
function openEditFee(row) {
|
||||
editFeeRow.value = row
|
||||
addFeeOpen.value = true
|
||||
}
|
||||
|
||||
function handleDeleteFee(row) {
|
||||
if (!canEditManualFee(row)) return
|
||||
proxy.$modal.confirm('确认删除该费用吗?').then(() => {
|
||||
return deleteIncomeBillFee(row.feeId)
|
||||
}).then(res => {
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
emit('updated', res.data || null)
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function onFeeAdded(data) {
|
||||
editFeeRow.value = null
|
||||
if (data) emit('updated', data)
|
||||
}
|
||||
|
||||
|
||||
function customerActiveLabel(status) {
|
||||
const map = { ACTIVE: '正常', MISSING: '不存在', DISABLED: '停用' }
|
||||
return map[status] || status || '-'
|
||||
@@ -378,6 +436,11 @@ function sourceTagType(source) {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 500;
|
||||
}
|
||||
.fee-toolbar {
|
||||
display: flex;
|
||||
/* justify-content: flex-end; */
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.fee-tabs {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user