提单费用调整
This commit is contained in:
+2
-1
@@ -73,7 +73,8 @@ public class SurchargeController extends BaseController
|
||||
@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:billing:bill')"
|
||||
+ " or @ss.hasPermi('price:pay:query') or @ss.hasPermi('price:pay:edit') or @ss.hasPermi('price:pay:import')")
|
||||
+ " or @ss.hasPermi('price:pay:query') or @ss.hasPermi('price:pay:edit') or @ss.hasPermi('price:pay:import')"
|
||||
+ " or @ss.hasPermi('price:airBill:fee')")
|
||||
@GetMapping("/options")
|
||||
public AjaxResult listOptions(PrcSurcharge surcharge)
|
||||
{
|
||||
|
||||
@@ -15,9 +15,24 @@ public class SrvBillAirFee
|
||||
|
||||
private String billCode;
|
||||
|
||||
/** 字典 bms_fee_type */
|
||||
/** 附加费编号 prc_surcharge.sc_id */
|
||||
private String feeType;
|
||||
|
||||
/** 附加费名称,仅展示,不入库 */
|
||||
private String feeName;
|
||||
|
||||
/** 费用阶段,字典 bms_fee_level */
|
||||
private String feeLevel;
|
||||
|
||||
/** 提单费用状态,字典 bill_fee_status。只随分摊、作废操作变化 */
|
||||
private String billFeeStatus;
|
||||
|
||||
/** 是否分摊,字典 bill_fee_alloc:1是 0否 */
|
||||
private String allocFlag;
|
||||
|
||||
/** 分摊维度(票),字典 bill_fee_alloc_dim:1订单 2重量。是否分摊为否时为空 */
|
||||
private String allocDim;
|
||||
|
||||
private BigDecimal price;
|
||||
|
||||
/** 字典 bms_currency */
|
||||
|
||||
@@ -11,7 +11,13 @@ public class PayPriceCompare
|
||||
|
||||
private PayProduct newer;
|
||||
|
||||
/** 有效期、来源文件、币种 */
|
||||
private List<PayPriceDiff> versionDiffs;
|
||||
|
||||
private List<PayPriceDiff> weightDiffs;
|
||||
|
||||
private List<PayPriceDiff> surchargeDiffs;
|
||||
|
||||
/** 邮编等不能归进重量段和附加费的原文 */
|
||||
private List<PayPriceDiff> extraDiffs;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import lombok.Data;
|
||||
@Data
|
||||
public class PayPriceDiff
|
||||
{
|
||||
/** 版本、重量段、附加费、其他配置 */
|
||||
private String kind;
|
||||
|
||||
private String name;
|
||||
|
||||
private String zoneName;
|
||||
|
||||
@@ -26,6 +26,12 @@ public class PrcPayBillFee implements Serializable
|
||||
|
||||
/** CALC 计算,LADING 提单分摊,MANUAL 人工 */
|
||||
private String feeSource;
|
||||
|
||||
/** 来源数据id */
|
||||
private String sourceId;
|
||||
|
||||
/** 是否删除:0未删除 1已删除 */
|
||||
private String isDeleted;
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ public final class PayCostCalculator
|
||||
{
|
||||
private static final ZoneId SHANGHAI = ZoneId.of("GMT+8");
|
||||
|
||||
// 定义常量集合,建议放在类级别作为 static final 常量
|
||||
private static final Set<Integer> REFUND_STATUS_SET = Set.of(9, 10);
|
||||
|
||||
private PayCostCalculator()
|
||||
{
|
||||
}
|
||||
@@ -242,6 +245,23 @@ public final class PayCostCalculator
|
||||
return kept;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加相关附加费,根据计费规则去判断
|
||||
* @param product
|
||||
* @param weight
|
||||
* @param length
|
||||
* @param width
|
||||
* @param height
|
||||
* @param postcode
|
||||
* @param zoneName
|
||||
* @param bizDate
|
||||
* @param orderStatus
|
||||
* @param freight
|
||||
* @param currency
|
||||
* @param freightSection
|
||||
* @param remoteHits
|
||||
* @param result
|
||||
*/
|
||||
private static void addSurcharges(PayProduct product, BigDecimal weight, BigDecimal length, BigDecimal width,
|
||||
BigDecimal height, String postcode, String zoneName, Date bizDate, Integer orderStatus, BigDecimal freight, String currency,
|
||||
String freightSection, List<PayRemoteBook.Hit> remoteHits, PayOrderCostResult result)
|
||||
@@ -317,6 +337,7 @@ public final class PayCostCalculator
|
||||
addFlat(result.getFees(), fee, currency, "计费重超过 " + fee.getCalcParam() + "kg");
|
||||
}
|
||||
}
|
||||
// 超出尺寸费用
|
||||
else if (PaySurchargeRules.SIZE.equals(rule))
|
||||
{
|
||||
if (length == null || width == null || height == null)
|
||||
@@ -332,13 +353,15 @@ public final class PayCostCalculator
|
||||
}
|
||||
}
|
||||
}
|
||||
// 退件费用
|
||||
else if (PaySurchargeRules.RETURN.equals(rule))
|
||||
{
|
||||
if (orderStatus != null && orderStatus == 9)
|
||||
if (orderStatus != null && REFUND_STATUS_SET.contains(orderStatus))
|
||||
{
|
||||
addFlat(result.getFees(), fee, currency, "订单已退件");
|
||||
}
|
||||
}
|
||||
// 超重费
|
||||
else if (PaySurchargeRules.EXTRA_KG.equals(rule))
|
||||
{
|
||||
if (postcodeDriven)
|
||||
|
||||
+275
-85
@@ -3,6 +3,7 @@ package com.bms.price.service.impl;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -15,6 +16,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -164,6 +166,7 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
}
|
||||
else
|
||||
{
|
||||
product.setProductType(PAY_TYPE_AP);
|
||||
Long existsId = isBlank(product.getDictValue()) ? null : payProductMapper.selectTemplateId(product.getDictValue());
|
||||
product.setDuplicated(existsId != null);
|
||||
product.setExistsId(existsId);
|
||||
@@ -266,7 +269,7 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
product.setProductType(requireProductType(product.getProductType()));
|
||||
product.setProductType(PAY_TYPE_AP);
|
||||
product.setFilePath(resolveSource(product));
|
||||
prepareCounts(product);
|
||||
fitColumns(product);
|
||||
@@ -1694,8 +1697,10 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
PayPriceCompare compared = new PayPriceCompare();
|
||||
compared.setOlder(older);
|
||||
compared.setNewer(newer);
|
||||
compared.setVersionDiffs(diffVersion(older, newer));
|
||||
compared.setWeightDiffs(diffWeights(older, newer));
|
||||
compared.setSurchargeDiffs(diffFees(older, newer));
|
||||
compared.setExtraDiffs(diffExtras(older, newer));
|
||||
return compared;
|
||||
}
|
||||
|
||||
@@ -1753,15 +1758,6 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
return stableId(link.getProductId());
|
||||
}
|
||||
|
||||
private String requireProductType(String productType)
|
||||
{
|
||||
if (isBlank(productType))
|
||||
{
|
||||
throw new ServiceException("请选择支付类型");
|
||||
}
|
||||
return payTypeOf(productType);
|
||||
}
|
||||
|
||||
private String payTypeOf(String payType)
|
||||
{
|
||||
if (isBlank(payType))
|
||||
@@ -1861,111 +1857,305 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
return left <= otherRight && otherLeft <= right;
|
||||
}
|
||||
|
||||
/** 有效期和来源文件记在版本上,价格没变时也要能看出来选的是哪两版。 */
|
||||
private List<PayPriceDiff> diffVersion(PayProduct older, PayProduct newer)
|
||||
{
|
||||
PayProductTerm oldTerm = payProductTermMapper.selectByProductId(older.getProductId());
|
||||
PayProductTerm newTerm = payProductTermMapper.selectByProductId(newer.getProductId());
|
||||
List<PayPriceDiff> diffs = new ArrayList<>();
|
||||
diffs.add(metaDiff("生效开始", termStart(oldTerm, older), termStart(newTerm, newer)));
|
||||
diffs.add(metaDiff("生效结束", termEnd(oldTerm), termEnd(newTerm)));
|
||||
diffs.add(metaDiff("来源文件", blankTo(older.getSourceFile(), "-"), blankTo(newer.getSourceFile(), "-")));
|
||||
diffs.add(metaDiff("币种", blankTo(older.getCurrency(), "-"), blankTo(newer.getCurrency(), "-")));
|
||||
return diffs;
|
||||
}
|
||||
|
||||
private List<PayPriceDiff> diffWeights(PayProduct older, PayProduct newer)
|
||||
{
|
||||
Map<String, PayWeightBand> oldRows = new LinkedHashMap<>();
|
||||
Map<String, PayWeightBand> newRows = new LinkedHashMap<>();
|
||||
if (older.getWeightBands() != null)
|
||||
{
|
||||
for (PayWeightBand band : older.getWeightBands())
|
||||
return pairDiffs(groupBy(older.getWeightBands(), PayProductServiceImpl::bandKey),
|
||||
groupBy(newer.getWeightBands(), PayProductServiceImpl::bandKey), (oldRow, newRow) ->
|
||||
{
|
||||
oldRows.put(bandKey(band), band);
|
||||
}
|
||||
}
|
||||
if (newer.getWeightBands() != null)
|
||||
{
|
||||
for (PayWeightBand band : newer.getWeightBands())
|
||||
{
|
||||
newRows.put(bandKey(band), band);
|
||||
}
|
||||
}
|
||||
List<PayPriceDiff> diffs = new ArrayList<>();
|
||||
for (Map.Entry<String, PayWeightBand> entry : oldRows.entrySet())
|
||||
{
|
||||
PayWeightBand current = newRows.remove(entry.getKey());
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setName(blankTo(entry.getValue().getWeightLabel(), ""));
|
||||
diff.setZoneName(blankTo(entry.getValue().getZoneName(), ""));
|
||||
diff.setOldValue(priceText(entry.getValue()));
|
||||
diff.setNewValue(current == null ? "" : priceText(current));
|
||||
diff.setChange(current == null ? "删除" : (diff.getOldValue().equals(diff.getNewValue()) ? "相同" : "变更"));
|
||||
diffs.add(diff);
|
||||
}
|
||||
for (PayWeightBand band : newRows.values())
|
||||
{
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setName(blankTo(band.getWeightLabel(), ""));
|
||||
diff.setZoneName(blankTo(band.getZoneName(), ""));
|
||||
diff.setOldValue("");
|
||||
diff.setNewValue(priceText(band));
|
||||
diff.setChange("新增");
|
||||
diffs.add(diff);
|
||||
}
|
||||
return diffs;
|
||||
PayWeightBand sample = oldRow != null ? oldRow : newRow;
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setKind("重量段");
|
||||
diff.setName(bandName(sample));
|
||||
diff.setZoneName(blankTo(sample.getZoneName(), ""));
|
||||
diff.setOldValue(oldRow == null ? "" : priceText(oldRow));
|
||||
diff.setNewValue(newRow == null ? "" : priceText(newRow));
|
||||
return diff;
|
||||
});
|
||||
}
|
||||
|
||||
private List<PayPriceDiff> diffFees(PayProduct older, PayProduct newer)
|
||||
{
|
||||
Map<String, PaySurcharge> oldRows = new LinkedHashMap<>();
|
||||
Map<String, PaySurcharge> newRows = new LinkedHashMap<>();
|
||||
if (older.getSurcharges() != null)
|
||||
{
|
||||
for (PaySurcharge fee : older.getSurcharges())
|
||||
return pairDiffs(groupBy(older.getSurcharges(), PayProductServiceImpl::feeKey),
|
||||
groupBy(newer.getSurcharges(), PayProductServiceImpl::feeKey), (oldRow, newRow) ->
|
||||
{
|
||||
oldRows.put(feeKey(fee), fee);
|
||||
}
|
||||
}
|
||||
if (newer.getSurcharges() != null)
|
||||
{
|
||||
for (PaySurcharge fee : newer.getSurcharges())
|
||||
PaySurcharge sample = oldRow != null ? oldRow : newRow;
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setKind("附加费");
|
||||
diff.setName(joinText(sample.getSectionName(), sample.getFeeName()));
|
||||
diff.setZoneName(blankTo(sample.getZoneName(), ""));
|
||||
diff.setOldValue(oldRow == null ? "" : feeText(oldRow));
|
||||
diff.setNewValue(newRow == null ? "" : feeText(newRow));
|
||||
return diff;
|
||||
});
|
||||
}
|
||||
|
||||
private List<PayPriceDiff> diffExtras(PayProduct older, PayProduct newer)
|
||||
{
|
||||
return pairDiffs(groupBy(older.getExtraLines(), PayProductServiceImpl::extraKey),
|
||||
groupBy(newer.getExtraLines(), PayProductServiceImpl::extraKey), (oldRow, newRow) ->
|
||||
{
|
||||
newRows.put(feeKey(fee), fee);
|
||||
}
|
||||
}
|
||||
PayExtraLine sample = oldRow != null ? oldRow : newRow;
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setKind("其他配置");
|
||||
diff.setName(blankTo(sample.getLineType(), "配置"));
|
||||
diff.setZoneName("");
|
||||
diff.setOldValue(oldRow == null ? "" : blankTo(oldRow.getLineText(), ""));
|
||||
diff.setNewValue(newRow == null ? "" : blankTo(newRow.getLineText(), ""));
|
||||
return diff;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 同一行按顺序对齐。相同费用有多条邮编或重量档时不能并成一条,否则后面的改动会被盖掉。
|
||||
*/
|
||||
private <T> List<PayPriceDiff> pairDiffs(Map<String, List<T>> oldRows, Map<String, List<T>> newRows, RowDiff<T> factory)
|
||||
{
|
||||
Set<String> keys = new LinkedHashSet<>();
|
||||
keys.addAll(oldRows.keySet());
|
||||
keys.addAll(newRows.keySet());
|
||||
List<PayPriceDiff> diffs = new ArrayList<>();
|
||||
for (Map.Entry<String, PaySurcharge> entry : oldRows.entrySet())
|
||||
for (String key : keys)
|
||||
{
|
||||
PaySurcharge current = newRows.remove(entry.getKey());
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setName(blankTo(entry.getValue().getFeeName(), ""));
|
||||
diff.setZoneName(blankTo(entry.getValue().getZoneName(), ""));
|
||||
diff.setOldValue(blankTo(entry.getValue().getChargeText(), ""));
|
||||
diff.setNewValue(current == null ? "" : blankTo(current.getChargeText(), ""));
|
||||
diff.setChange(current == null ? "删除" : (diff.getOldValue().equals(diff.getNewValue()) ? "相同" : "变更"));
|
||||
diffs.add(diff);
|
||||
}
|
||||
for (PaySurcharge fee : newRows.values())
|
||||
{
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setName(blankTo(fee.getFeeName(), ""));
|
||||
diff.setZoneName(blankTo(fee.getZoneName(), ""));
|
||||
diff.setOldValue("");
|
||||
diff.setNewValue(blankTo(fee.getChargeText(), ""));
|
||||
diff.setChange("新增");
|
||||
diffs.add(diff);
|
||||
List<T> older = oldRows.getOrDefault(key, Collections.emptyList());
|
||||
List<T> newer = newRows.getOrDefault(key, Collections.emptyList());
|
||||
int count = Math.max(older.size(), newer.size());
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T oldRow = i < older.size() ? older.get(i) : null;
|
||||
T newRow = i < newer.size() ? newer.get(i) : null;
|
||||
PayPriceDiff diff = factory.build(oldRow, newRow);
|
||||
diff.setChange(oldRow == null ? "新增" : (newRow == null ? "删除"
|
||||
: (diff.getOldValue().equals(diff.getNewValue()) ? "相同" : "变更")));
|
||||
diffs.add(diff);
|
||||
}
|
||||
}
|
||||
return diffs;
|
||||
}
|
||||
|
||||
private static String bandKey(PayWeightBand band)
|
||||
private <T> Map<String, List<T>> groupBy(List<T> rows, Function<T, String> keyFn)
|
||||
{
|
||||
return blankTo(band.getZoneName(), "") + "|" + blankTo(band.getWeightLabel(), "") + "|" + blankTo(band.getChargeName(), "");
|
||||
Map<String, List<T>> grouped = new LinkedHashMap<>();
|
||||
if (rows == null)
|
||||
{
|
||||
return grouped;
|
||||
}
|
||||
for (T row : rows)
|
||||
{
|
||||
grouped.computeIfAbsent(keyFn.apply(row), key -> new ArrayList<>()).add(row);
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
private static PayPriceDiff metaDiff(String name, String oldValue, String newValue)
|
||||
{
|
||||
PayPriceDiff diff = new PayPriceDiff();
|
||||
diff.setKind("版本");
|
||||
diff.setName(name);
|
||||
diff.setZoneName("");
|
||||
diff.setOldValue(oldValue);
|
||||
diff.setNewValue(newValue);
|
||||
diff.setChange(oldValue.equals(newValue) ? "相同" : "变更");
|
||||
return diff;
|
||||
}
|
||||
|
||||
private static String termStart(PayProductTerm term, PayProduct product)
|
||||
{
|
||||
if (term != null)
|
||||
{
|
||||
return dayOrOpen(term.getEffectiveStart());
|
||||
}
|
||||
return dayOrOpen(product.getEffectiveDate());
|
||||
}
|
||||
|
||||
private static String termEnd(PayProductTerm term)
|
||||
{
|
||||
return term == null ? "不限" : dayOrOpen(term.getEffectiveEnd());
|
||||
}
|
||||
|
||||
private static String dayOrOpen(Date date)
|
||||
{
|
||||
return date == null ? "不限" : formatDay(date);
|
||||
}
|
||||
|
||||
/** 分区、重量段和费用名确定一行单价,价格本身不参与对齐。 */
|
||||
private static String bandKey(PayWeightBand band)
|
||||
{
|
||||
return joinKey(band.getSectionName(), band.getZoneName(), band.getCountryCode(), band.getChargeName(),
|
||||
band.getWeightLabel(), numKey(band.getWeightMin()), numKey(band.getWeightMax()));
|
||||
}
|
||||
|
||||
/** 同一费用、同一分区、同一重量档里的多条邮编按导入顺序逐条比对。 */
|
||||
private static String feeKey(PaySurcharge fee)
|
||||
{
|
||||
return blankTo(fee.getFeeName(), "") + "|" + blankTo(fee.getZoneName(), "") + "|" + blankTo(fee.getCalcRule(), "");
|
||||
return joinKey(fee.getSectionName(), fee.getFeeName(), fee.getZoneName(), numKey(fee.getWeightMin()), numKey(fee.getWeightMax()));
|
||||
}
|
||||
|
||||
private static String extraKey(PayExtraLine line)
|
||||
{
|
||||
return blankTo(line.getLineType(), "");
|
||||
}
|
||||
|
||||
private static String bandName(PayWeightBand band)
|
||||
{
|
||||
String label = blankTo(band.getWeightLabel(), rangeText(band.getWeightMin(), band.getWeightMax()));
|
||||
return joinText(band.getSectionName(), label, band.getChargeName());
|
||||
}
|
||||
|
||||
private static String priceText(PayWeightBand band)
|
||||
{
|
||||
String price = band.getPrice() == null ? "" : band.getPrice().stripTrailingZeros().toPlainString();
|
||||
String price = money(band.getPrice());
|
||||
String unit = blankTo(band.getPriceUnit(), "");
|
||||
String volume = blankTo(band.getVolumeText(), "");
|
||||
String text = (price + " " + unit).trim();
|
||||
return volume.isEmpty() ? text : (text + " " + volume).trim();
|
||||
}
|
||||
|
||||
/** 金额、收费原文、计费规则和参数都算差异。只比原文会把改过的邮编和续重看成没变。 */
|
||||
private static String feeText(PaySurcharge fee)
|
||||
{
|
||||
StringBuilder text = new StringBuilder();
|
||||
String amount = money(fee.getAmount());
|
||||
String unit = blankTo(fee.getPriceUnit(), "");
|
||||
String priced = (amount + " " + unit).trim();
|
||||
String charge = blankTo(fee.getChargeText(), "");
|
||||
appendPart(text, priced);
|
||||
if (!charge.isEmpty() && !charge.equals(priced) && !charge.equals(amount))
|
||||
{
|
||||
appendPart(text, charge);
|
||||
}
|
||||
String rule = ruleText(fee.getCalcRule());
|
||||
String param = blankTo(fee.getCalcParam(), "");
|
||||
String postcode = blankTo(fee.getPostcodeParam(), "");
|
||||
if (!postcode.isEmpty() && !postcode.equals(param))
|
||||
{
|
||||
param = param.isEmpty() ? postcode : param + " " + postcode;
|
||||
}
|
||||
if (!rule.isEmpty() || !param.isEmpty())
|
||||
{
|
||||
appendPart(text, (rule + " " + param).trim());
|
||||
}
|
||||
appendPart(text, rangeText(fee.getWeightMin(), fee.getWeightMax()));
|
||||
String period = blankTo(fee.getPeriodText(), "");
|
||||
if (period.isEmpty() && (fee.getEffectiveStart() != null || fee.getEffectiveEnd() != null))
|
||||
{
|
||||
period = dayOrOpen(fee.getEffectiveStart()) + " 至 " + dayOrOpen(fee.getEffectiveEnd());
|
||||
}
|
||||
appendPart(text, period);
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
private static String ruleText(String rule)
|
||||
{
|
||||
if (isBlank(rule) || "MANUAL".equalsIgnoreCase(rule))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
switch (rule.toUpperCase(Locale.ROOT))
|
||||
{
|
||||
case "POSTCODE":
|
||||
return "邮编";
|
||||
case "EXTRA_KG":
|
||||
return "续重";
|
||||
case "ALWAYS":
|
||||
return "固定";
|
||||
case "ZONE":
|
||||
return "分区";
|
||||
case "WEIGHT":
|
||||
return "重量";
|
||||
case "SIZE":
|
||||
return "尺寸";
|
||||
case "RETURN":
|
||||
return "退件";
|
||||
default:
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
||||
private static String rangeText(BigDecimal min, BigDecimal max)
|
||||
{
|
||||
String left = money(min);
|
||||
String right = money(max);
|
||||
if (left.isEmpty() && right.isEmpty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return (left.isEmpty() ? "0" : left) + "-" + (right.isEmpty() ? "不限" : right) + "kg";
|
||||
}
|
||||
|
||||
private static String money(BigDecimal value)
|
||||
{
|
||||
return value == null ? "" : value.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private static String numKey(BigDecimal value)
|
||||
{
|
||||
return money(value);
|
||||
}
|
||||
|
||||
private static String joinKey(String... parts)
|
||||
{
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (String part : parts)
|
||||
{
|
||||
if (text.length() > 0)
|
||||
{
|
||||
text.append('|');
|
||||
}
|
||||
text.append(blankTo(part, ""));
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
private static String joinText(String... parts)
|
||||
{
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (String part : parts)
|
||||
{
|
||||
if (isBlank(part))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (text.length() > 0)
|
||||
{
|
||||
text.append(' ');
|
||||
}
|
||||
text.append(part.trim());
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
private static void appendPart(StringBuilder text, String part)
|
||||
{
|
||||
if (isBlank(part))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (text.length() > 0)
|
||||
{
|
||||
text.append(';');
|
||||
}
|
||||
text.append(part.trim());
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface RowDiff<T>
|
||||
{
|
||||
PayPriceDiff build(T older, T newer);
|
||||
}
|
||||
|
||||
private static Date today()
|
||||
{
|
||||
return toDate(java.time.LocalDate.now(java.time.ZoneId.of("GMT+8")));
|
||||
|
||||
+132
-17
@@ -21,8 +21,10 @@ import com.bms.common.utils.SecurityUtils;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFee;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeLog;
|
||||
import com.bms.price.airbill.domain.SrvBillAirFeeSave;
|
||||
import com.bms.price.domain.PrcSurcharge;
|
||||
import com.bms.price.mapper.SrvBillAirFeeMapper;
|
||||
import com.bms.price.mapper.SrvBillAirMapper;
|
||||
import com.bms.price.service.IPrcSurchargeService;
|
||||
import com.bms.price.service.ISrvBillAirFeeService;
|
||||
import com.bms.system.service.ISysDictTypeService;
|
||||
|
||||
@@ -32,13 +34,22 @@ import com.bms.system.service.ISysDictTypeService;
|
||||
@Service
|
||||
public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
private static final String FEE_DICT = "bms_fee_type";
|
||||
private static final String LEVEL_DICT = "bms_fee_level";
|
||||
|
||||
private static final String ALLOC_DICT = "bill_fee_alloc";
|
||||
|
||||
private static final String ALLOC_DIM_DICT = "bill_fee_alloc_dim";
|
||||
|
||||
private static final String STATUS_DICT = "bill_fee_status";
|
||||
|
||||
private static final String CURRENCY_DICT = "bms_currency";
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirFeeMapper feeMapper;
|
||||
|
||||
@Autowired
|
||||
private IPrcSurchargeService surchargeService;
|
||||
|
||||
@Autowired
|
||||
private SrvBillAirMapper billMapper;
|
||||
|
||||
@@ -84,7 +95,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
throw new ServiceException("没有需要保存的变更");
|
||||
}
|
||||
Map<String, String> feeLabels = dictLabels(FEE_DICT, "费用类型");
|
||||
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, "币种");
|
||||
Operator operator = operator();
|
||||
Date now = DateUtils.getNowDate();
|
||||
@@ -110,7 +124,7 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
int rows = feeMapper.deleteFee(current.getId(), billCode, operator.account, now);
|
||||
if (rows > 0)
|
||||
{
|
||||
writeLog(billCode, id, SrvBillAirFeeLog.DELETE, current, null, feeLabels, currencyLabels, operator, now);
|
||||
writeLog(billCode, id, SrvBillAirFeeLog.DELETE, current, null, feeNames(current, null), levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
@@ -129,8 +143,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
{
|
||||
throw new ServiceException("费用已删除或不属于该提单");
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, feeLabels, currencyLabels);
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(current, next, feeLabels, currencyLabels);
|
||||
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
|
||||
next.setBillFeeStatus(current.getBillFeeStatus() == null || current.getBillFeeStatus().isBlank() ? "0" : current.getBillFeeStatus());
|
||||
Map<String, String> feeNames = feeNames(current, next);
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(current, next, feeNames, levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels);
|
||||
if (changes.isEmpty())
|
||||
{
|
||||
continue;
|
||||
@@ -141,17 +157,19 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
next.setUpdateTime(now);
|
||||
if (feeMapper.updateFee(next) > 0)
|
||||
{
|
||||
writeLog(billCode, current.getId(), SrvBillAirFeeLog.UPDATE, current, next, feeLabels, currencyLabels, operator, now);
|
||||
writeLog(billCode, current.getId(), SrvBillAirFeeLog.UPDATE, current, next, feeNames, levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
for (SrvBillAirFee incoming : inserts)
|
||||
{
|
||||
if (incoming == null || blank(incoming.getFeeType()) && incoming.getPrice() == null && blank(incoming.getCurrency()) && blank(incoming.getRemark()))
|
||||
if (incoming == null || blank(incoming.getFeeType()) && blank(incoming.getFeeLevel()) && incoming.getPrice() == null
|
||||
&& blank(incoming.getCurrency()) && blank(incoming.getRemark()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
SrvBillAirFee next = normalize(incoming, feeLabels, currencyLabels);
|
||||
SrvBillAirFee next = normalize(incoming, levelLabels, allocLabels, dimLabels, currencyLabels);
|
||||
next.setBillFeeStatus("0");
|
||||
next.setId(null);
|
||||
next.setBillCode(billCode);
|
||||
next.setCreateBy(operator.account);
|
||||
@@ -159,7 +177,7 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
next.setUpdateBy(operator.account);
|
||||
next.setUpdateTime(now);
|
||||
feeMapper.insertFee(next);
|
||||
writeLog(billCode, next.getId(), SrvBillAirFeeLog.INSERT, null, next, feeLabels, currencyLabels, operator, now);
|
||||
writeLog(billCode, next.getId(), SrvBillAirFeeLog.INSERT, null, next, feeNames(null, next), levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels, operator, now);
|
||||
inserted++;
|
||||
}
|
||||
if (inserted + updated + removed == 0)
|
||||
@@ -174,18 +192,48 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
return feeMapper.selectLive(id, billCode);
|
||||
}
|
||||
|
||||
private SrvBillAirFee normalize(SrvBillAirFee raw, Map<String, String> feeLabels, Map<String, String> currencyLabels)
|
||||
private SrvBillAirFee normalize(SrvBillAirFee raw, Map<String, String> levelLabels,
|
||||
Map<String, String> allocLabels, Map<String, String> dimLabels, Map<String, String> currencyLabels)
|
||||
{
|
||||
String feeType = trim(raw.getFeeType());
|
||||
String feeLevel = trim(raw.getFeeLevel());
|
||||
String allocFlag = trim(raw.getAllocFlag());
|
||||
String allocDim = trim(raw.getAllocDim());
|
||||
String currency = trim(raw.getCurrency());
|
||||
String remark = trim(raw.getRemark());
|
||||
if (feeType.isEmpty())
|
||||
Long scId = parseScId(feeType);
|
||||
if (scId == null)
|
||||
{
|
||||
throw new ServiceException("请选择费用项");
|
||||
}
|
||||
if (feeType.length() > 32 || !feeLabels.containsKey(feeType))
|
||||
PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId);
|
||||
if (surcharge == null)
|
||||
{
|
||||
throw new ServiceException("费用项不在费用类型字典中");
|
||||
throw new ServiceException("费用项不存在");
|
||||
}
|
||||
feeType = String.valueOf(scId);
|
||||
if (allocFlag.isEmpty() || !allocLabels.containsKey(allocFlag))
|
||||
{
|
||||
throw new ServiceException("请选择是否分摊");
|
||||
}
|
||||
if (!feeLevel.isEmpty() && (feeLevel.length() > 32 || !levelLabels.containsKey(feeLevel)))
|
||||
{
|
||||
throw new ServiceException("费用阶段不在字典中");
|
||||
}
|
||||
if ("1".equals(allocFlag) && feeLevel.isEmpty())
|
||||
{
|
||||
throw new ServiceException("如需分摊费用至订单,请选择费用所属阶段");
|
||||
}
|
||||
if ("1".equals(allocFlag))
|
||||
{
|
||||
if (allocDim.isEmpty() || !dimLabels.containsKey(allocDim))
|
||||
{
|
||||
throw new ServiceException("如需分摊费用至订单,请选择分摊维度");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
allocDim = "";
|
||||
}
|
||||
if (raw.getPrice() == null)
|
||||
{
|
||||
@@ -213,6 +261,9 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
}
|
||||
SrvBillAirFee fee = new SrvBillAirFee();
|
||||
fee.setFeeType(feeType);
|
||||
fee.setFeeLevel(feeLevel);
|
||||
fee.setAllocFlag(allocFlag);
|
||||
fee.setAllocDim(allocDim);
|
||||
fee.setPrice(raw.getPrice());
|
||||
fee.setCurrency(currency);
|
||||
fee.setRemark(remark);
|
||||
@@ -220,9 +271,10 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
}
|
||||
|
||||
private void writeLog(String billCode, Long feeId, String operation, SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> currencyLabels, Operator operator, Date now)
|
||||
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)
|
||||
{
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(before, after, feeLabels, currencyLabels);
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = diff(before, after, feeLabels, levelLabels, allocLabels, dimLabels, statusLabels, currencyLabels);
|
||||
SrvBillAirFeeLog log = new SrvBillAirFeeLog();
|
||||
log.setBillCode(billCode);
|
||||
log.setFeeId(feeId);
|
||||
@@ -236,11 +288,20 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
}
|
||||
|
||||
private static List<SrvBillAirFeeLog.FieldChange> diff(SrvBillAirFee before, SrvBillAirFee after,
|
||||
Map<String, String> feeLabels, Map<String, String> currencyLabels)
|
||||
Map<String, String> feeLabels, Map<String, String> levelLabels, Map<String, String> allocLabels,
|
||||
Map<String, String> dimLabels, Map<String, String> statusLabels, Map<String, String> currencyLabels)
|
||||
{
|
||||
List<SrvBillAirFeeLog.FieldChange> changes = new ArrayList<>();
|
||||
addChange(changes, "feeType", "费用类型", label(feeLabels, value(before, SrvBillAirFee::getFeeType)),
|
||||
addChange(changes, "feeType", "费用项", label(feeLabels, value(before, SrvBillAirFee::getFeeType)),
|
||||
label(feeLabels, value(after, SrvBillAirFee::getFeeType)));
|
||||
addChange(changes, "feeLevel", "费用阶段", label(levelLabels, value(before, SrvBillAirFee::getFeeLevel)),
|
||||
label(levelLabels, value(after, SrvBillAirFee::getFeeLevel)));
|
||||
addChange(changes, "allocFlag", "是否分摊", label(allocLabels, value(before, SrvBillAirFee::getAllocFlag)),
|
||||
label(allocLabels, value(after, SrvBillAirFee::getAllocFlag)));
|
||||
addChange(changes, "allocDim", "分摊维度(票)", label(dimLabels, value(before, SrvBillAirFee::getAllocDim)),
|
||||
label(dimLabels, value(after, SrvBillAirFee::getAllocDim)));
|
||||
addChange(changes, "billFeeStatus", "费用状态", label(statusLabels, value(before, SrvBillAirFee::getBillFeeStatus)),
|
||||
label(statusLabels, value(after, SrvBillAirFee::getBillFeeStatus)));
|
||||
addChange(changes, "price", "价格", priceText(before), priceText(after));
|
||||
addChange(changes, "currency", "收费币种", label(currencyLabels, value(before, SrvBillAirFee::getCurrency)),
|
||||
label(currencyLabels, value(after, SrvBillAirFee::getCurrency)));
|
||||
@@ -329,6 +390,60 @@ public class SrvBillAirFeeServiceImpl implements ISrvBillAirFeeService
|
||||
return fee.getPrice().stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
private Map<String, String> feeNames(SrvBillAirFee before, SrvBillAirFee after)
|
||||
{
|
||||
Map<String, String> names = new LinkedHashMap<>();
|
||||
putFeeName(names, before);
|
||||
putFeeName(names, after);
|
||||
return names;
|
||||
}
|
||||
|
||||
private void putFeeName(Map<String, String> names, SrvBillAirFee fee)
|
||||
{
|
||||
if (fee == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
String id = trim(fee.getFeeType());
|
||||
if (id.isEmpty() || names.containsKey(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
names.put(id, surchargeName(id));
|
||||
}
|
||||
|
||||
private String surchargeName(String feeType)
|
||||
{
|
||||
Long scId = parseScId(feeType);
|
||||
if (scId == null)
|
||||
{
|
||||
return feeType;
|
||||
}
|
||||
PrcSurcharge surcharge = surchargeService.selectPrcSurchargeById(scId);
|
||||
if (surcharge == null || surcharge.getScName() == null || surcharge.getScName().isBlank())
|
||||
{
|
||||
return feeType;
|
||||
}
|
||||
return surcharge.getScName().trim();
|
||||
}
|
||||
|
||||
private static Long parseScId(String feeType)
|
||||
{
|
||||
if (feeType == null || feeType.isBlank() || !feeType.chars().allMatch(Character::isDigit))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
long id = Long.parseLong(feeType);
|
||||
return id > 0 ? id : null;
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String label(Map<String, String> labels, String value)
|
||||
{
|
||||
if (value == null || value.isBlank())
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
e.supplier_name as supplierName,
|
||||
(select group_concat(concat(ifnull(nullif(trim(f.fee_name), ''), ifnull(f.fee_type, '费用')), ' ', format(f.amount, 2))
|
||||
order by field(f.fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), f.fee_id separator ';')
|
||||
from prc_pay_bill_fee f where f.bill_id = o.bill_id) as feeText,
|
||||
from prc_pay_bill_fee f where f.bill_id = o.bill_id and f.is_deleted = '0') as feeText,
|
||||
(select b.bill_no from pay_bill_line l
|
||||
inner join pay_bill b on b.bill_id = l.bill_id
|
||||
where l.order_bill_id = o.bill_id and b.status <> 'VOID'
|
||||
@@ -81,9 +81,11 @@
|
||||
</select>
|
||||
|
||||
<select id="selectOrderFees" resultType="com.bms.price.domain.PrcPayBillFee">
|
||||
select fee_id as feeId, bill_id as billId, fee_name as feeName, amount, currency, fee_source as feeSource
|
||||
select fee_id as feeId, bill_id as billId, fee_name as feeName, amount, currency, fee_source as feeSource,
|
||||
source_id as sourceId, is_deleted as isDeleted
|
||||
from prc_pay_bill_fee
|
||||
where bill_id in
|
||||
where is_deleted = '0'
|
||||
and bill_id in
|
||||
<foreach collection="billIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
order by bill_id, fee_id
|
||||
</select>
|
||||
|
||||
@@ -15,31 +15,38 @@ 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="sourceId" column="source_id" />
|
||||
<result property="isDeleted" column="is_deleted" />
|
||||
<result property="createTime" column="create_time" />
|
||||
</resultMap>
|
||||
|
||||
<select id="selectByBillId" parameterType="String" resultMap="PrcPayBillFeeResult">
|
||||
select fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
select fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
source_id, is_deleted, create_time
|
||||
from prc_pay_bill_fee
|
||||
where bill_id = #{billId}
|
||||
and is_deleted = '0'
|
||||
order by field(fee_type, 'BASIC_FREIGHT', 'REGISTRATION_FEE', 'SURCHARGE', 'REFUND'), fee_id
|
||||
</select>
|
||||
|
||||
<insert id="insertPrcPayBillFee" parameterType="PrcPayBillFee">
|
||||
insert into prc_pay_bill_fee (
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
source_id, is_deleted, create_time
|
||||
) values (
|
||||
#{feeId}, #{billId}, #{payBillId}, #{feeType}, #{feeName}, #{scId}, #{amount}, #{currency}, #{roundMode}, #{remark}, sysdate()
|
||||
#{feeId}, #{billId}, #{payBillId}, #{feeType}, #{feeName}, #{scId}, #{amount}, #{currency}, #{roundMode}, #{remark},
|
||||
#{sourceId}, ifnull(#{isDeleted}, '0'), sysdate()
|
||||
)
|
||||
</insert>
|
||||
|
||||
<insert id="insertBatch" parameterType="java.util.List">
|
||||
insert into prc_pay_bill_fee (
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark, create_time
|
||||
fee_id, bill_id, pay_bill_id, fee_type, fee_name, sc_id, amount, currency, round_mode, remark,
|
||||
source_id, is_deleted, create_time
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.feeId}, #{item.billId}, #{item.payBillId}, #{item.feeType}, #{item.feeName}, #{item.scId}, #{item.amount},
|
||||
#{item.currency}, #{item.roundMode}, #{item.remark}, sysdate())
|
||||
#{item.currency}, #{item.roundMode}, #{item.remark}, #{item.sourceId}, ifnull(#{item.isDeleted}, '0'), sysdate())
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
|
||||
@@ -3,31 +3,37 @@
|
||||
<mapper namespace="com.bms.price.mapper.SrvBillAirFeeMapper">
|
||||
|
||||
<select id="selectByBillCode" resultType="com.bms.price.airbill.domain.SrvBillAirFee">
|
||||
select id, bill_code as billCode, fee_type as feeType, price, currency, remark,
|
||||
create_by as createBy, create_time as createTime, update_by as updateBy,
|
||||
update_time as updateTime, del_flag as delFlag
|
||||
from srv_bill_air_fee
|
||||
where bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
order by id
|
||||
select f.id, f.bill_code as billCode, f.fee_type as feeType, sc.sc_name as feeName,
|
||||
f.fee_level as feeLevel, f.bill_fee_status as billFeeStatus,
|
||||
f.alloc_flag as allocFlag, f.alloc_dim as allocDim, f.price, f.currency, f.remark,
|
||||
f.create_by as createBy, f.create_time as createTime, f.update_by as updateBy,
|
||||
f.update_time as updateTime, f.del_flag as delFlag
|
||||
from srv_bill_air_fee f
|
||||
left join prc_surcharge sc on sc.sc_id = f.fee_type and ifnull(sc.is_del, '0') = '0'
|
||||
where f.bill_code = #{billCode}
|
||||
and f.del_flag = '0'
|
||||
order by f.id
|
||||
</select>
|
||||
|
||||
<select id="selectLive" resultType="com.bms.price.airbill.domain.SrvBillAirFee">
|
||||
select id, bill_code as billCode, fee_type as feeType, price, currency, remark,
|
||||
create_by as createBy, create_time as createTime, update_by as updateBy,
|
||||
update_time as updateTime, del_flag as delFlag
|
||||
from srv_bill_air_fee
|
||||
where id = #{id}
|
||||
and bill_code = #{billCode}
|
||||
and del_flag = '0'
|
||||
select f.id, f.bill_code as billCode, f.fee_type as feeType, sc.sc_name as feeName,
|
||||
f.fee_level as feeLevel, f.bill_fee_status as billFeeStatus,
|
||||
f.alloc_flag as allocFlag, f.alloc_dim as allocDim, f.price, f.currency, f.remark,
|
||||
f.create_by as createBy, f.create_time as createTime, f.update_by as updateBy,
|
||||
f.update_time as updateTime, f.del_flag as delFlag
|
||||
from srv_bill_air_fee f
|
||||
left join prc_surcharge sc on sc.sc_id = f.fee_type and ifnull(sc.is_del, '0') = '0'
|
||||
where f.id = #{id}
|
||||
and f.bill_code = #{billCode}
|
||||
and f.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertFee" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into srv_bill_air_fee (
|
||||
bill_code, fee_type, price, currency, remark,
|
||||
bill_code, fee_type, fee_level, bill_fee_status, alloc_flag, alloc_dim, price, currency, remark,
|
||||
create_by, create_time, update_by, update_time, del_flag
|
||||
) values (
|
||||
#{billCode}, #{feeType}, #{price}, #{currency}, #{remark},
|
||||
#{billCode}, #{feeType}, #{feeLevel}, '0', #{allocFlag}, #{allocDim}, #{price}, #{currency}, #{remark},
|
||||
#{createBy}, #{createTime}, #{updateBy}, #{updateTime}, '0'
|
||||
)
|
||||
</insert>
|
||||
@@ -35,6 +41,9 @@
|
||||
<update id="updateFee">
|
||||
update srv_bill_air_fee
|
||||
set fee_type = #{feeType},
|
||||
fee_level = #{feeLevel},
|
||||
alloc_flag = #{allocFlag},
|
||||
alloc_dim = #{allocDim},
|
||||
price = #{price},
|
||||
currency = #{currency},
|
||||
remark = #{remark},
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
-- 提单费用阶段、提单费用状态、是否分摊、分摊维度。可重复执行。
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
SELECT N'费用阶段', 'bms_fee_level', '0', 'admin', sysdate(), N'srv_bill_air_fee.fee_level:1 a段头程仓储,2 b段提单运输,3 c段清关,4 d段尾程'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'bms_fee_level');
|
||||
|
||||
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 v.dict_sort, v.dict_label, v.dict_value, 'bms_fee_level', '', v.list_class, 'N', '0', 'admin', sysdate(), v.remark
|
||||
FROM (
|
||||
SELECT 1 AS dict_sort, N'a段(头程仓储)' AS dict_label, '1' AS dict_value, 'primary' AS list_class, N'fee_level=1' AS remark
|
||||
UNION ALL SELECT 2, N'b段(提单运输)', '2', 'success', N'fee_level=2'
|
||||
UNION ALL SELECT 3, N'c段(清关)', '3', 'warning', N'fee_level=3'
|
||||
UNION ALL SELECT 4, N'd段(尾程)', '4', 'info', N'fee_level=4'
|
||||
) v
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_dict_data d WHERE d.dict_type = 'bms_fee_level' AND d.dict_value = v.dict_value
|
||||
);
|
||||
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
SELECT N'提单费用状态', 'bill_fee_status', '0', 'admin', sysdate(), N'srv_bill_air_fee.bill_fee_status:0未分摊 1已分摊 2已作废'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'bill_fee_status');
|
||||
|
||||
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 v.dict_sort, v.dict_label, v.dict_value, 'bill_fee_status', '', v.list_class, v.is_default, '0', 'admin', sysdate(), v.remark
|
||||
FROM (
|
||||
SELECT 1 AS dict_sort, N'未分摊' AS dict_label, '0' AS dict_value, 'info' AS list_class, 'Y' AS is_default, N'新建费用的初始状态' AS remark
|
||||
UNION ALL SELECT 2, N'已分摊', '1', 'success', 'N', N'分摊操作完成后'
|
||||
UNION ALL SELECT 3, N'已作废', '2', 'danger', 'N', N'作废操作完成后'
|
||||
) v
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_dict_data d WHERE d.dict_type = 'bill_fee_status' AND d.dict_value = v.dict_value
|
||||
);
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
'ALTER TABLE srv_bill_air_fee ADD COLUMN bill_fee_status char(1) NOT NULL DEFAULT ''0'' COMMENT ''提单费用状态,字典 bill_fee_status:0未分摊 1已分摊 2已作废'' AFTER fee_level',
|
||||
'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;
|
||||
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
SELECT N'是否分摊', 'bill_fee_alloc', '0', 'admin', sysdate(), N'srv_bill_air_fee.alloc_flag:1是 0否'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'bill_fee_alloc');
|
||||
|
||||
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 v.dict_sort, v.dict_label, v.dict_value, 'bill_fee_alloc', '', v.list_class, v.is_default, '0', 'admin', sysdate(), v.remark
|
||||
FROM (
|
||||
SELECT 1 AS dict_sort, N'是' AS dict_label, '1' AS dict_value, 'success' AS list_class, 'N' AS is_default, N'这条费用参与分摊,须选择费用阶段' AS remark
|
||||
UNION ALL SELECT 2, N'否', '0', 'info', 'Y', N'这条费用不分摊,默认'
|
||||
) v
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_dict_data d WHERE d.dict_type = 'bill_fee_alloc' AND d.dict_value = v.dict_value
|
||||
);
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
'ALTER TABLE srv_bill_air_fee ADD COLUMN alloc_flag char(1) NOT NULL DEFAULT ''0'' COMMENT ''是否分摊,字典 bill_fee_alloc:1是 0否。为是时必须选择费用阶段'' AFTER bill_fee_status',
|
||||
'SELECT 1'
|
||||
)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'srv_bill_air_fee' AND COLUMN_NAME = 'alloc_flag'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
UPDATE sys_dict_data SET is_default = 'N', remark = N'这条费用参与分摊,须选择费用阶段'
|
||||
WHERE dict_type = 'bill_fee_alloc' AND dict_value = '1';
|
||||
UPDATE sys_dict_data SET is_default = 'Y', remark = N'这条费用不分摊,默认'
|
||||
WHERE dict_type = 'bill_fee_alloc' AND dict_value = '0';
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) > 0,
|
||||
'ALTER TABLE srv_bill_air_fee MODIFY COLUMN alloc_flag char(1) NOT NULL DEFAULT ''0'' COMMENT ''是否分摊,字典 bill_fee_alloc:1是 0否。为是时必须选择费用阶段''',
|
||||
'SELECT 1'
|
||||
)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'srv_bill_air_fee' AND COLUMN_NAME = 'alloc_flag'
|
||||
AND (COLUMN_DEFAULT IS NULL OR COLUMN_DEFAULT <> '0')
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
|
||||
SELECT N'分摊维度(票)', 'bill_fee_alloc_dim', '0', 'admin', sysdate(), N'srv_bill_air_fee.alloc_dim:1订单 2重量。是否分摊为是时必填'
|
||||
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'bill_fee_alloc_dim');
|
||||
|
||||
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 v.dict_sort, v.dict_label, v.dict_value, 'bill_fee_alloc_dim', '', v.list_class, 'N', '0', 'admin', sysdate(), v.remark
|
||||
FROM (
|
||||
SELECT 1 AS dict_sort, N'订单' AS dict_label, '1' AS dict_value, 'primary' AS list_class, N'按订单票数分摊' AS remark
|
||||
UNION ALL SELECT 2, N'重量', '2', 'success', N'按重量占比分摊'
|
||||
) v
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM sys_dict_data d WHERE d.dict_type = 'bill_fee_alloc_dim' AND d.dict_value = v.dict_value
|
||||
);
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
'ALTER TABLE srv_bill_air_fee ADD COLUMN alloc_dim char(1) NOT NULL DEFAULT '''' COMMENT ''分摊维度(票),字典 bill_fee_alloc_dim:1订单 2重量。是否分摊为是时必填'' AFTER alloc_flag',
|
||||
'SELECT 1'
|
||||
)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'srv_bill_air_fee' AND COLUMN_NAME = 'alloc_dim'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@@ -12,6 +12,32 @@ PREPARE stmt FROM @fee_source;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql = (
|
||||
SELECT IF(
|
||||
COUNT(*) = 0,
|
||||
'ALTER TABLE prc_pay_bill_fee ADD COLUMN source_id varchar(64) DEFAULT NULL COMMENT ''来源数据id'' AFTER fee_source',
|
||||
'SELECT 1'
|
||||
)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'prc_pay_bill_fee' AND COLUMN_NAME = 'source_id'
|
||||
);
|
||||
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已删除'' AFTER source_id',
|
||||
'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;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `pay_lading` (
|
||||
`lading_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '提单ID',
|
||||
`lading_no` varchar(64) NOT NULL COMMENT '提单号或航空主单号',
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="导入应付价卡" width="1080px" append-to-body destroy-on-close @closed="reset">
|
||||
<el-form label-width="88px" class="import-form">
|
||||
<el-form-item label="支付类型" required>
|
||||
<el-select v-model="productType" filterable placeholder="请选择支付类型" style="width: 420px">
|
||||
<el-option v-for="item in bms_pay_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品">
|
||||
<el-select v-model="dictValue" clearable filterable placeholder="不选则按工作表名称匹配全部字典产品" style="width: 420px">
|
||||
<el-option v-for="item in payment_product_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
@@ -115,14 +110,13 @@ import PayConfigDetail from './PayConfigDetail.vue'
|
||||
|
||||
const emit = defineEmits(['saved'])
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { payment_product_type, bms_pay_type } = useDict('payment_product_type', 'bms_pay_type')
|
||||
const { payment_product_type } = useDict('payment_product_type')
|
||||
|
||||
const visible = ref(false)
|
||||
const recognizing = ref(false)
|
||||
const saving = ref(false)
|
||||
const overwrite = ref(false)
|
||||
const dictValue = ref('')
|
||||
const productType = ref('AP')
|
||||
const preview = ref(null)
|
||||
const selected = ref([])
|
||||
const tableRef = ref()
|
||||
@@ -150,7 +144,6 @@ function reset() {
|
||||
selected.value = []
|
||||
overwrite.value = false
|
||||
dictValue.value = ''
|
||||
productType.value = 'AP'
|
||||
activeKey.value = ''
|
||||
recognizing.value = false
|
||||
saving.value = false
|
||||
@@ -265,10 +258,6 @@ function onSelect(rows) {
|
||||
}
|
||||
|
||||
async function save() {
|
||||
if (!productType.value) {
|
||||
proxy.$modal.msgError('请选择支付类型')
|
||||
return
|
||||
}
|
||||
if (!selected.value.length) {
|
||||
proxy.$modal.msgError('请勾选要保存的产品')
|
||||
return
|
||||
@@ -284,8 +273,7 @@ async function save() {
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const products = selected.value.map(row => ({ ...row, productType: productType.value }))
|
||||
const res = await savePay({ overwrite: overwrite.value, products })
|
||||
const res = await savePay({ overwrite: overwrite.value, products: selected.value })
|
||||
const message = res.msg || '保存成功'
|
||||
reset()
|
||||
visible.value = false
|
||||
|
||||
@@ -61,12 +61,12 @@
|
||||
|
||||
<pay-import-dialog ref="importRef" @saved="tableRef.refresh()" />
|
||||
|
||||
<el-drawer v-model="historyVisible" :title="historyTitle" size="980px" destroy-on-close>
|
||||
<el-drawer v-model="historyVisible" :title="historyTitle" size="980px" append-to-body destroy-on-close>
|
||||
<div class="history-bar">
|
||||
<el-button type="primary" plain :disabled="historyPicked.length !== 2" @click="openCompare">比对所选版本</el-button>
|
||||
<span>勾选两个版本后比对。历史订单按生效时间取价,不看当前开关。</span>
|
||||
</div>
|
||||
<el-table v-loading="historyLoading" :data="historyRows" border size="small" empty-text="还没有价格版本" @selection-change="onHistoryPick">
|
||||
<el-table v-loading="historyLoading" :data="historyRows" row-key="termId" border size="small" empty-text="还没有价格版本" @selection-change="onHistoryPick">
|
||||
<el-table-column type="selection" width="42" align="center" />
|
||||
<el-table-column prop="versionNo" label="版本" width="70" align="center" />
|
||||
<el-table-column label="来源文件" min-width="160" show-overflow-tooltip>
|
||||
@@ -123,15 +123,42 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-drawer v-model="compareVisible" :title="compareTitle" size="88%" append-to-body destroy-on-close>
|
||||
<el-table v-loading="compareLoading" :data="compareRows" border size="small" max-height="640" empty-text="没有差异">
|
||||
<el-table-column prop="kind" label="类型" width="90" align="center" />
|
||||
<el-table-column prop="change" label="变化" width="80" align="center" />
|
||||
<el-table-column prop="name" label="项目" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="zoneName" label="地区" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="oldValue" label="旧版" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="newValue" label="新版" min-width="160" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-drawer v-model="compareVisible" :title="compareTitle" size="88%" append-to-body :z-index="4000" destroy-on-close>
|
||||
<div class="compare-panel">
|
||||
<div v-if="compareReady && !compareLoading" class="history-bar">
|
||||
<el-tag type="primary" effect="plain">变更 {{ compareStat.changed }}</el-tag>
|
||||
<el-tag type="success" effect="plain">新增 {{ compareStat.added }}</el-tag>
|
||||
<el-tag type="danger" effect="plain">删除 {{ compareStat.removed }}</el-tag>
|
||||
<el-tag type="info" effect="plain">相同 {{ compareStat.same }}</el-tag>
|
||||
</div>
|
||||
<el-alert
|
||||
v-if="compareReady && !compareLoading && !compareRows.length"
|
||||
title="这两版的有效期、重量段和附加费一致"
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<el-table v-show="compareLoading || compareRows.length" v-loading="compareLoading" :data="compareRows" :row-class-name="compareRowClass" border size="small" max-height="640" empty-text="没有差异">
|
||||
<el-table-column prop="kind" label="类型" width="100" align="center" />
|
||||
<el-table-column label="变化" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="changeTagType(row.change)" effect="light">{{ row.change }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="项目" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="zoneName" label="地区" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="旧版" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span :class="changeTextClass(row.change, 'old')">{{ row.oldValue || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新版" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span :class="changeTextClass(row.change, 'new')">{{ row.newValue || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer
|
||||
@@ -187,6 +214,12 @@ const compareVisible = ref(false)
|
||||
const compareLoading = ref(false)
|
||||
const compareTitle = ref('版本比对')
|
||||
const compareRows = ref([])
|
||||
const compareAll = ref([])
|
||||
const compareReady = ref(false)
|
||||
const compareStat = computed(() => {
|
||||
const count = (name) => compareAll.value.filter(row => row.change === name).length
|
||||
return { changed: count('变更'), added: count('新增'), removed: count('删除'), same: count('相同') }
|
||||
})
|
||||
const detailTitle = computed(() => {
|
||||
if (!detail.value) return '应付配置'
|
||||
const no = detail.value.versionNo ? `第 ${detail.value.versionNo} 版` : ''
|
||||
@@ -356,23 +389,60 @@ function onHistoryPick(rows) {
|
||||
|
||||
function openCompare() {
|
||||
if (historyPicked.value.length !== 2) return
|
||||
const [first, second] = historyPicked.value
|
||||
const [first, second] = [...historyPicked.value].sort((a, b) => (Number(a.versionNo) || 0) - (Number(b.versionNo) || 0))
|
||||
if (!first.productId || !second.productId) {
|
||||
proxy.$modal.msgError('选中的版本没有价格数据')
|
||||
return
|
||||
}
|
||||
compareTitle.value = `第 ${first.versionNo || '-'} 版 对比 第 ${second.versionNo || '-'} 版`
|
||||
compareVisible.value = true
|
||||
compareLoading.value = true
|
||||
compareRows.value = []
|
||||
compareAll.value = []
|
||||
compareReady.value = false
|
||||
comparePay(first.productId, second.productId).then(res => {
|
||||
const data = res.data || {}
|
||||
const older = data.older?.versionNo || '-'
|
||||
const newer = data.newer?.versionNo || '-'
|
||||
compareTitle.value = `第 ${older} 版 对比 第 ${newer} 版`
|
||||
const weights = (data.weightDiffs || []).map(row => ({ ...row, kind: '重量段' }))
|
||||
const fees = (data.surchargeDiffs || []).map(row => ({ ...row, kind: '附加费' }))
|
||||
compareRows.value = weights.concat(fees).filter(row => row.change !== '相同')
|
||||
const older = data.older?.versionNo
|
||||
const newer = data.newer?.versionNo
|
||||
if (older && newer) compareTitle.value = `第 ${older} 版 对比 第 ${newer} 版`
|
||||
const rows = []
|
||||
.concat(markKind(data.versionDiffs, '版本'))
|
||||
.concat(markKind(data.weightDiffs, '重量段'))
|
||||
.concat(markKind(data.surchargeDiffs, '附加费'))
|
||||
.concat(markKind(data.extraDiffs, '其他配置'))
|
||||
compareAll.value = rows
|
||||
compareRows.value = rows.filter(row => row.change !== '相同')
|
||||
compareReady.value = true
|
||||
}).finally(() => {
|
||||
compareLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function markKind(rows, kind) {
|
||||
return (rows || []).map(row => ({ ...row, kind: row.kind || kind }))
|
||||
}
|
||||
|
||||
function changeTagType(change) {
|
||||
if (change === '变更') return 'primary'
|
||||
if (change === '新增') return 'success'
|
||||
if (change === '删除') return 'danger'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function compareRowClass({ row }) {
|
||||
if (row.change === '变更') return 'compare-changed'
|
||||
if (row.change === '新增') return 'compare-added'
|
||||
if (row.change === '删除') return 'compare-removed'
|
||||
return ''
|
||||
}
|
||||
|
||||
function changeTextClass(change, side) {
|
||||
if (change === '变更') return 'compare-text-changed'
|
||||
if (change === '删除' && side === 'old') return 'compare-text-removed'
|
||||
if (change === '新增' && side === 'new') return 'compare-text-added'
|
||||
return ''
|
||||
}
|
||||
|
||||
function loadHistory() {
|
||||
if (!historyProductId.value) return
|
||||
historyLoading.value = true
|
||||
@@ -446,4 +516,34 @@ function statusType(status) {
|
||||
margin-bottom: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.compare-text-changed {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
.compare-text-added {
|
||||
color: var(--el-color-success);
|
||||
font-weight: 600;
|
||||
}
|
||||
.compare-text-removed {
|
||||
color: var(--el-color-danger);
|
||||
font-weight: 600;
|
||||
}
|
||||
.compare-panel :deep(tr.compare-changed > td.el-table__cell) {
|
||||
background: var(--el-color-primary-light-9);
|
||||
}
|
||||
.compare-panel :deep(tr.compare-added > td.el-table__cell) {
|
||||
background: var(--el-color-success-light-9);
|
||||
}
|
||||
.compare-panel :deep(tr.compare-removed > td.el-table__cell) {
|
||||
background: var(--el-color-danger-light-9);
|
||||
}
|
||||
.compare-panel :deep(tr.compare-changed:hover > td.el-table__cell) {
|
||||
background: var(--el-color-primary-light-8);
|
||||
}
|
||||
.compare-panel :deep(tr.compare-added:hover > td.el-table__cell) {
|
||||
background: var(--el-color-success-light-8);
|
||||
}
|
||||
.compare-panel :deep(tr.compare-removed:hover > td.el-table__cell) {
|
||||
background: var(--el-color-danger-light-8);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,57 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" :title="title" width="980px" 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 class="fee-toolbar">
|
||||
<el-button type="primary" plain icon="Plus" @click="addRow">新增</el-button>
|
||||
<span class="fee-hint">只保存本次新增、修改和删除的费用,未改动的行不会重写。</span>
|
||||
</div>
|
||||
<el-table :data="rows" border size="small" max-height="320" row-key="rowKey" empty-text="暂无费用,点击新增">
|
||||
<el-table-column label="费用项" min-width="160">
|
||||
<el-table-column min-width="180">
|
||||
<template #header><span class="col-label"><i v-if="allocRequired" class="req-star">*</i>费用阶段</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeType" filterable clearable placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="item in bms_fee_type" :key="item.value" :label="item.label" :value="item.value" />
|
||||
<el-select v-model="row.feeLevel" filterable clearable placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="item in bms_fee_level" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" width="150">
|
||||
<el-table-column width="120">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>是否分摊</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.allocFlag" placeholder="请选择" style="width: 100%" @change="changeAlloc(row)">
|
||||
<el-option v-for="item in bill_fee_alloc" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="allocRequired" min-width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>分摊维度(票)</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-if="String(row.allocFlag) === '1'" v-model="row.allocDim" placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="item in bill_fee_alloc_dim" :key="item.value" :label="item.label" :value="String(item.value)" />
|
||||
</el-select>
|
||||
</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 min-width="160">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>费用项</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.feeType" filterable remote clearable reserve-keyword placeholder="输入费用名称"
|
||||
:remote-method="searchSurcharge" :loading="surchargeLoading" style="width: 100%" @change="name => onFeeChange(row, name)">
|
||||
<el-option v-for="item in surchargeOptions" :key="item.scId" :label="item.scName" :value="item.scId" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="150">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>价格</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-model="row.price" :min="0" :precision="4" :controls="false" :value-on-clear="null" placeholder="价格" style="width: 100%" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收费币种" width="140">
|
||||
<el-table-column width="140">
|
||||
<template #header><span class="col-label"><i class="req-star">*</i>收费币种</span></template>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.currency" filterable clearable placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="item in bms_currency" :key="item.value" :label="item.label" :value="item.value" />
|
||||
@@ -91,10 +124,11 @@
|
||||
|
||||
<script setup>
|
||||
import { listAirBillFee, listAirBillFeeLog, saveAirBillFee } from '@/api/price/airBill'
|
||||
import { listSurchargeOptions } from '@/api/price/surcharge'
|
||||
import { parseTime } from '@/utils/bms'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const { bms_fee_type, bms_currency } = useDict('bms_fee_type', 'bms_currency')
|
||||
const { bms_fee_level, bms_currency, bill_fee_status, bill_fee_alloc, bill_fee_alloc_dim } = useDict('bms_fee_level', 'bms_currency', 'bill_fee_status', 'bill_fee_alloc', 'bill_fee_alloc_dim')
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -107,10 +141,14 @@ const logs = ref([])
|
||||
const logPageNum = ref(1)
|
||||
const logPageSize = ref(10)
|
||||
const logTotal = ref(0)
|
||||
const surchargeOptions = ref([])
|
||||
const surchargeLoading = ref(false)
|
||||
let rowSeed = 0
|
||||
let logSeq = 0
|
||||
let surchargeSeq = 0
|
||||
|
||||
const title = computed(() => billCode.value ? `提单费用 - ${billCode.value}` : '提单费用')
|
||||
const allocRequired = computed(() => rows.value.some(row => String(row.allocFlag) === '1'))
|
||||
const dirty = computed(() => deletedIds.value.length > 0 || rows.value.some(row => rowChanged(row)))
|
||||
|
||||
function open(row) {
|
||||
@@ -130,6 +168,9 @@ function reset() {
|
||||
logPageSize.value = 10
|
||||
logTotal.value = 0
|
||||
logSeq += 1
|
||||
surchargeSeq += 1
|
||||
surchargeOptions.value = []
|
||||
surchargeLoading.value = false
|
||||
saving.value = false
|
||||
loading.value = false
|
||||
logLoading.value = false
|
||||
@@ -139,6 +180,7 @@ function load() {
|
||||
logPageNum.value = 1
|
||||
loadFees()
|
||||
loadLogs()
|
||||
searchSurcharge('')
|
||||
}
|
||||
|
||||
function loadFees() {
|
||||
@@ -151,16 +193,58 @@ function loadFees() {
|
||||
rowKey: `fee-${item.id}`,
|
||||
id: item.id,
|
||||
feeType: item.feeType == null ? '' : String(item.feeType),
|
||||
feeName: item.feeName || '',
|
||||
feeLevel: item.feeLevel == null ? '' : String(item.feeLevel),
|
||||
allocFlag: item.allocFlag == null || item.allocFlag === '' ? '0' : String(item.allocFlag),
|
||||
allocDim: item.allocDim == null ? '' : String(item.allocDim),
|
||||
billFeeStatus: item.billFeeStatus == null || item.billFeeStatus === '' ? '0' : String(item.billFeeStatus),
|
||||
price: item.price === null || item.price === undefined ? null : Number(item.price),
|
||||
currency: item.currency || '',
|
||||
remark: item.remark || '',
|
||||
origin: snapshot(item)
|
||||
}))
|
||||
pinSelectedFees()
|
||||
}).finally(() => {
|
||||
if (code === billCode.value) loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function pinSelectedFees() {
|
||||
const known = new Map(surchargeOptions.value.map(item => [String(item.scId), item]))
|
||||
for (const row of rows.value) {
|
||||
if (!row.feeType) continue
|
||||
if (!known.has(String(row.feeType))) {
|
||||
known.set(String(row.feeType), { scId: String(row.feeType), scName: row.feeName || String(row.feeType) })
|
||||
}
|
||||
}
|
||||
surchargeOptions.value = [...known.values()]
|
||||
}
|
||||
|
||||
function searchSurcharge(keyword) {
|
||||
const seq = ++surchargeSeq
|
||||
surchargeLoading.value = true
|
||||
const params = { ssCode: 'on' }
|
||||
const name = String(keyword || '').trim()
|
||||
if (name) params.scName = name
|
||||
listSurchargeOptions(params).then(res => {
|
||||
if (seq !== surchargeSeq) return
|
||||
surchargeOptions.value = (res.data || []).map(item => ({
|
||||
scId: item.scId == null ? '' : String(item.scId),
|
||||
scName: item.scName || ''
|
||||
})).filter(item => item.scId)
|
||||
pinSelectedFees()
|
||||
}).catch(() => {
|
||||
if (seq === surchargeSeq) pinSelectedFees()
|
||||
}).finally(() => {
|
||||
if (seq === surchargeSeq) surchargeLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function onFeeChange(row, id) {
|
||||
const found = surchargeOptions.value.find(item => String(item.scId) === String(id || ''))
|
||||
row.feeName = found ? found.scName : ''
|
||||
}
|
||||
|
||||
function unwrapLogs(res) {
|
||||
const rows = res?.rows
|
||||
if (Array.isArray(rows)) {
|
||||
@@ -207,6 +291,11 @@ function addRow() {
|
||||
rowKey: `new-${rowSeed}`,
|
||||
id: null,
|
||||
feeType: '',
|
||||
feeName: '',
|
||||
feeLevel: '',
|
||||
allocFlag: '0',
|
||||
allocDim: '',
|
||||
billFeeStatus: '0',
|
||||
price: null,
|
||||
currency: '',
|
||||
remark: '',
|
||||
@@ -214,6 +303,10 @@ function addRow() {
|
||||
})
|
||||
}
|
||||
|
||||
function changeAlloc(row) {
|
||||
if (String(row.allocFlag) !== '1') row.allocDim = ''
|
||||
}
|
||||
|
||||
function removeRow(row) {
|
||||
if (row.id != null && !deletedIds.value.some(id => String(id) === String(row.id))) {
|
||||
deletedIds.value.push(row.id)
|
||||
@@ -224,6 +317,9 @@ function removeRow(row) {
|
||||
function snapshot(item) {
|
||||
return {
|
||||
feeType: item.feeType || '',
|
||||
feeLevel: item.feeLevel || '',
|
||||
allocFlag: item.allocFlag == null || item.allocFlag === '' ? '0' : String(item.allocFlag),
|
||||
allocDim: item.allocDim || '',
|
||||
price: normPrice(item.price),
|
||||
currency: item.currency || '',
|
||||
remark: (item.remark || '').trim()
|
||||
@@ -233,6 +329,9 @@ function snapshot(item) {
|
||||
function currentOf(row) {
|
||||
return {
|
||||
feeType: row.feeType || '',
|
||||
feeLevel: row.feeLevel || '',
|
||||
allocFlag: row.allocFlag || '',
|
||||
allocDim: String(row.allocFlag) === '1' ? (row.allocDim || '') : '',
|
||||
price: normPrice(row.price),
|
||||
currency: row.currency || '',
|
||||
remark: (row.remark || '').trim()
|
||||
@@ -248,20 +347,40 @@ function normPrice(value) {
|
||||
function rowChanged(row) {
|
||||
if (!row.origin) {
|
||||
const now = currentOf(row)
|
||||
return !!(now.feeType || now.price || now.currency || now.remark)
|
||||
return !!(now.feeType || now.feeLevel || now.allocFlag === '1' || now.allocDim || now.price || now.currency || now.remark)
|
||||
}
|
||||
const now = currentOf(row)
|
||||
return now.feeType !== row.origin.feeType || now.price !== row.origin.price
|
||||
return now.feeType !== row.origin.feeType || now.feeLevel !== row.origin.feeLevel || now.allocFlag !== row.origin.allocFlag
|
||||
|| now.allocDim !== row.origin.allocDim || now.price !== row.origin.price
|
||||
|| now.currency !== row.origin.currency || now.remark !== row.origin.remark
|
||||
}
|
||||
|
||||
function rowError(row) {
|
||||
if (String(row.allocFlag) !== '0' && String(row.allocFlag) !== '1') return '请选择是否分摊'
|
||||
if (String(row.allocFlag) === '1' && !row.feeLevel) return '如需分摊费用至订单,请选择费用所属阶段'
|
||||
if (String(row.allocFlag) === '1' && !row.allocDim) return '如需分摊费用至订单,请选择分摊维度'
|
||||
if (!row.feeType) return '请选择费用项'
|
||||
if (row.price === null || row.price === undefined || row.price === '') return '请填写价格'
|
||||
if (Number(row.price) < 0) return '价格不能小于0'
|
||||
if (!row.currency) return '请选择收费币种'
|
||||
return ''
|
||||
}
|
||||
|
||||
function save() {
|
||||
const invalid = rows.value.find(row => rowChanged(row) && rowError(row))
|
||||
if (invalid) {
|
||||
proxy.$modal.msgError(rowError(invalid))
|
||||
return
|
||||
}
|
||||
const inserts = []
|
||||
const updates = []
|
||||
rows.value.forEach(row => {
|
||||
if (!rowChanged(row)) return
|
||||
const payload = {
|
||||
feeType: row.feeType || '',
|
||||
feeLevel: row.feeLevel || '',
|
||||
allocFlag: row.allocFlag || '',
|
||||
allocDim: String(row.allocFlag) === '1' ? (row.allocDim || '') : '',
|
||||
price: row.price,
|
||||
currency: row.currency || '',
|
||||
remark: (row.remark || '').trim()
|
||||
@@ -323,6 +442,18 @@ defineExpose({ open })
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
.col-label {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.req-star {
|
||||
color: var(--el-color-danger);
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
margin-right: 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
.log-title {
|
||||
margin: 16px 0 8px;
|
||||
font-weight: 600;
|
||||
|
||||
Reference in New Issue
Block a user