This commit is contained in:
wuweihua
2026-11-10 16:21:16 +08:00
16 changed files with 911 additions and 170 deletions
@@ -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)
@@ -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);
@@ -1876,8 +1879,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;
}
@@ -1935,15 +1940,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))
@@ -2043,111 +2039,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")));
@@ -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 &lt;&gt; '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},
+119
View File
@@ -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;
+26
View File
@@ -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 '提单号或航空主单号',