# Conflicts:
#	bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPayBillFee.java
#	bms-back/bms-system/src/main/resources/mapper/price/PrcPayBillFeeMapper.xml
This commit is contained in:
wuweihua
2026-11-14 00:32:55 +08:00
29 changed files with 1755 additions and 252 deletions
@@ -11,7 +11,7 @@ public class SrvBillAirAllocFee
private String billCode;
/** 提单主键 srv_bill_air.guid,写入订单费用的 source_id */
/** 提单主键 srv_bill_air.guid */
private String ladingId;
/** 费用阶段,字典 bms_fee_level */
@@ -59,6 +59,15 @@ public class PayPostcodeGroup extends BaseEntity
/** 查询条件:邮编 */
private String postcode;
/** 国家下的分区名,例如 mainland。列表按国家归拢后,分区在抽屉里维护 */
private String zoneName;
/** 修改分区时的原名称 */
private String oldZoneName;
/** 这个国家下的分区个数 */
private Integer zoneCount;
/** 导入时挂上的明细,列表查询不填充 */
private List<PayPostcodeLine> lines = new ArrayList<>();
}
@@ -10,6 +10,38 @@ public interface PayPostcodeMapper
{
List<PayPostcodeGroup> selectGroupList(PayPostcodeGroup query);
List<PayPostcodeGroup> selectCountryList(PayPostcodeGroup query);
List<PayPostcodeGroup> selectZoneList(PayPostcodeGroup query);
PayPostcodeGroup selectSampleGroup(PayPostcodeGroup query);
int countZoneName(PayPostcodeGroup query);
int updateCountryProfile(PayPostcodeGroup country);
List<Long> selectGroupIdsByCountry(PayPostcodeGroup query);
int renameZoneLines(PayPostcodeGroup zone);
int updateExclusiveZone(PayPostcodeGroup zone);
int insertLine(PayPostcodeLine line);
int updateLine(PayPostcodeLine line);
PayPostcodeLine selectLineById(Long lineId);
List<Long> selectGroupIdsByLineIds(@Param("lineIds") List<Long> lineIds);
int deleteLineByIds(@Param("lineIds") List<Long> lineIds);
int deleteLinesByZone(PayPostcodeGroup zone);
int refreshLineCount(Long groupId);
Long selectZoneGroupId(PayPostcodeGroup query);
/** 按国家去重区块标题 */
List<String> selectBlockTitlesByCountry(@Param("countryCode") String countryCode,
@Param("countryName") String countryName);
@@ -31,6 +63,10 @@ public interface PayPostcodeMapper
/** 报价表、销售产品或工作表与产品名称完全一致的邮编配置 */
List<PayPostcodeGroup> selectGroupsForProduct(@Param("names") List<String> names);
/** 目的国家下标记为偏远的邮编组。产品自己没有偏远名单时,用这一份做偏远附加费。 */
List<PayPostcodeGroup> selectRemoteGroupsByCountry(@Param("countryCode") String countryCode,
@Param("countryName") String countryName);
List<PayPostcodeLine> selectRegionKeys(@Param("groupIds") List<Long> groupIds);
List<PayPostcodeLine> selectLinesByGroups(@Param("groupIds") List<Long> groupIds);
@@ -31,6 +31,9 @@ public interface PrcPayBillFeeMapper
*/
public int insertBatch(@Param("list") List<PrcPayBillFee> fees);
/** 把还没标明来源的费用,关联到本行主键 id。 */
public int fillSourceIdByFeeIds(@Param("feeIds") List<String> feeIds);
/**
* 删除一票订单上来源为计算的费用。提单分摊和人工费用保留。
*
@@ -311,7 +311,7 @@ public final class LabelArCostCalculator
{
result.getSkipped().add(name + ":订单没有邮编");
}
else if (bookApplies(remoteHits, fee, rule))
else if (bookApplies(remoteHits, fee, rule, postcode))
{
String remark = "邮编 " + postcode.trim();
if (PaySurchargeRules.EXTRA_KG.equals(rule))
@@ -551,10 +551,11 @@ public final class LabelArCostCalculator
return name.contains("偏远") || name.contains("岛屿") || name.contains("remote") || name.contains("island");
}
/** 邮编配置命中后,金额相同的那一档才收;分区写明时还要对上地区。 */
private static boolean bookApplies(List<PayRemoteBook.Hit> hits, PaySurcharge fee, String rule)
/** 邮编配置命中后,金额相同的那一档才收。命中写了地区时要对上分区;没写地区时用价卡上的邮编前缀选档。 */
private static boolean bookApplies(List<PayRemoteBook.Hit> hits, PaySurcharge fee, String rule, String postcode)
{
boolean extra = PaySurchargeRules.EXTRA_KG.equals(rule);
boolean tier = extra || PaySurchargeRules.POSTCODE.equals(rule);
boolean zoned = !blank(fee.getZoneName()).isEmpty();
for (PayRemoteBook.Hit hit : hits)
{
@@ -572,11 +573,15 @@ public final class LabelArCostCalculator
{
continue;
}
if (extra && zoned && !sameArea(hit, fee))
if ((zoned && !areaKnown || !zoned && tier) && !feePostcodeMatches(fee, rule, postcode))
{
continue;
}
if (extra && amountKnown && hit.getAmount().compareTo(fee.getAmount()) != 0 && !sameArea(hit, fee))
if (extra && zoned && areaKnown && !sameArea(hit, fee))
{
continue;
}
if (extra && amountKnown && hit.getAmount().compareTo(fee.getAmount()) != 0 && areaKnown && !sameArea(hit, fee))
{
continue;
}
@@ -585,6 +590,21 @@ public final class LabelArCostCalculator
return false;
}
/** 价卡这一档写了邮编时,订单邮编必须落在里面。续重的重量门槛不算邮编。 */
private static boolean feePostcodeMatches(PaySurcharge fee, String rule, String postcode)
{
String param = blank(fee.getPostcodeParam());
if (param.isEmpty() && !PaySurchargeRules.EXTRA_KG.equals(rule))
{
param = blank(fee.getCalcParam());
}
if (param.isEmpty())
{
return false;
}
return PaySurchargeRules.matchesPostcode(param, postcode);
}
private static boolean sameFeeName(String bookName, String feeName)
{
String book = feeKey(bookName);
@@ -603,8 +623,11 @@ public final class LabelArCostCalculator
private static boolean sameArea(PayRemoteBook.Hit hit, PaySurcharge fee)
{
return PayZoneMatcher.sameZone(fee.getZoneName(), hit.getZoneName())
|| PayZoneMatcher.sameZone(fee.getZoneName(), hit.getRegionName());
if (!blank(hit.getZoneName()).isEmpty() && PayZoneMatcher.sameZone(fee.getZoneName(), hit.getZoneName()))
{
return true;
}
return !blank(hit.getRegionName()).isEmpty() && PayZoneMatcher.sameZone(fee.getZoneName(), hit.getRegionName());
}
private static boolean perKg(PaySurcharge fee)
@@ -297,6 +297,7 @@ public final class PayCostCalculator
return;
}
List<PayCostLine> sized = new ArrayList<>();
List<PayCostLine> stepped = new ArrayList<>();
for (PaySurcharge fee : product.getSurcharges())
{
if (fee == null || !inForce(fee, bizDate))
@@ -314,7 +315,7 @@ public final class PayCostCalculator
{
result.getSkipped().add(name + ":订单没有邮编");
}
else if (bookApplies(remoteHits, fee, rule))
else if (bookApplies(remoteHits, fee, rule, postcode))
{
String remark = "邮编 " + postcode.trim();
if (PaySurchargeRules.EXTRA_KG.equals(rule))
@@ -413,10 +414,17 @@ public final class PayCostCalculator
}
else if (sizeHit(product, fee, length, width, height, weight))
{
PayCostLine matched = matchedFee(fee, freight, currency, "尺寸命中");
PayCostLine matched = sizedFee(product, fee, length, width, height, freight, currency);
if (matched != null)
{
sized.add(matched);
if (stepPriced(fee))
{
stepped.add(matched);
}
else
{
sized.add(matched);
}
}
}
}
@@ -465,9 +473,10 @@ public final class PayCostCalculator
}
}
result.getFees().addAll(keepHighestByName(sized));
result.getFees().addAll(stepped);
}
/** 同一费用名的尺寸档同时命中时,只收较高的一档,例如 70% 覆盖 35%。 */
/** 同一费用名的按票尺寸档同时命中时,只收较高的一档,例如 70% 覆盖 35%。按超出单位计的不并进这一档。 */
private static List<PayCostLine> keepHighestByName(List<PayCostLine> lines)
{
List<PayCostLine> kept = new ArrayList<>();
@@ -660,10 +669,11 @@ public final class PayCostCalculator
return name.contains("偏远") || name.contains("岛屿") || name.contains("remote") || name.contains("island");
}
/** 邮编配置命中后,金额相同的那一档才收;分区写明时还要对上地区。 */
private static boolean bookApplies(List<PayRemoteBook.Hit> hits, PaySurcharge fee, String rule)
/** 邮编配置命中后,金额相同的那一档才收。命中写了地区时要对上分区;没写地区时用价卡上的邮编前缀选档。 */
private static boolean bookApplies(List<PayRemoteBook.Hit> hits, PaySurcharge fee, String rule, String postcode)
{
boolean extra = PaySurchargeRules.EXTRA_KG.equals(rule);
boolean tier = extra || PaySurchargeRules.POSTCODE.equals(rule);
boolean zoned = !blank(fee.getZoneName()).isEmpty();
for (PayRemoteBook.Hit hit : hits)
{
@@ -677,15 +687,19 @@ public final class PayCostCalculator
continue;
}
boolean areaKnown = !blank(hit.getZoneName()).isEmpty() || !blank(hit.getRegionName()).isEmpty();
if (zoned && (!areaKnown || !sameArea(hit, fee)))
if (zoned && areaKnown && !sameArea(hit, fee))
{
continue;
}
if (extra && zoned && !sameArea(hit, fee))
if ((zoned && !areaKnown || !zoned && tier) && !feePostcodeMatches(fee, rule, postcode))
{
continue;
}
if (extra && amountKnown && hit.getAmount().compareTo(fee.getAmount()) != 0 && !sameArea(hit, fee))
if (extra && zoned && areaKnown && !sameArea(hit, fee))
{
continue;
}
if (extra && amountKnown && hit.getAmount().compareTo(fee.getAmount()) != 0 && areaKnown && !sameArea(hit, fee))
{
continue;
}
@@ -694,6 +708,21 @@ public final class PayCostCalculator
return false;
}
/** 价卡这一档写了邮编时,订单邮编必须落在里面。续重的重量门槛不算邮编。 */
private static boolean feePostcodeMatches(PaySurcharge fee, String rule, String postcode)
{
String param = blank(fee.getPostcodeParam());
if (param.isEmpty() && !PaySurchargeRules.EXTRA_KG.equals(rule))
{
param = blank(fee.getCalcParam());
}
if (param.isEmpty())
{
return false;
}
return PaySurchargeRules.matchesPostcode(param, postcode);
}
private static boolean sameFeeName(String bookName, String feeName)
{
String book = feeKey(bookName);
@@ -712,8 +741,11 @@ public final class PayCostCalculator
private static boolean sameArea(PayRemoteBook.Hit hit, PaySurcharge fee)
{
return PayZoneMatcher.sameZone(fee.getZoneName(), hit.getZoneName())
|| PayZoneMatcher.sameZone(fee.getZoneName(), hit.getRegionName());
if (!blank(hit.getZoneName()).isEmpty() && PayZoneMatcher.sameZone(fee.getZoneName(), hit.getZoneName()))
{
return true;
}
return !blank(hit.getRegionName()).isEmpty() && PayZoneMatcher.sameZone(fee.getZoneName(), hit.getRegionName());
}
private static boolean perKg(PaySurcharge fee)
@@ -747,6 +779,107 @@ public final class PayCostCalculator
}
}
/**
* 收费标准写了每增加若干厘米时,按三边之和超出下限的档数乘单价。
* 没有步长配置的尺寸费仍按价卡金额收一次。
*/
private static PayCostLine sizedFee(PayProduct product, PaySurcharge fee, BigDecimal length, BigDecimal width,
BigDecimal height, BigDecimal freight, String currency)
{
if (stepPriced(fee))
{
PayCostLine stepped = stepBySum(product, fee, length, width, height, currency);
if (stepped != null || hasSumStep(product))
{
return stepped;
}
}
return matchedFee(fee, freight, currency, "尺寸命中");
}
/** 价卡写成每增加、每超出时,金额是一个步长的单价。 */
private static boolean stepPriced(PaySurcharge fee)
{
String charge = fee.getChargeText() == null ? "" : fee.getChargeText();
return charge.contains("增加") || charge.contains("每超出");
}
private static boolean hasSumStep(PayProduct product)
{
for (Map<String, Object> detail : ruleDetails(product, PaySurchargeRules.SIZE))
{
BigDecimal step = decimal(detail.get("exceedStepCm"));
BigDecimal threshold = decimal(detail.get("sumMin"));
if (step != null && step.compareTo(BigDecimal.ZERO) > 0 && threshold != null)
{
return true;
}
}
return false;
}
/** 超出厘米数按步长向上取整。刚超过不足一个步长也收一档。 */
private static PayCostLine stepBySum(PayProduct product, PaySurcharge fee, BigDecimal length, BigDecimal width,
BigDecimal height, String currency)
{
if (fee.getAmount() == null)
{
return null;
}
BigDecimal sum = length.add(width).add(height);
for (Map<String, Object> detail : ruleDetails(product, PaySurchargeRules.SIZE))
{
BigDecimal step = decimal(detail.get("exceedStepCm"));
BigDecimal threshold = decimal(detail.get("sumMin"));
if (step == null || step.compareTo(BigDecimal.ZERO) <= 0 || threshold == null || sum.compareTo(threshold) <= 0)
{
continue;
}
String feeCurrency = normalize(fee.getCurrency());
if (!feeCurrency.isEmpty() && !feeCurrency.equals(currency))
{
return null;
}
BigDecimal excess = sum.subtract(threshold);
BigDecimal steps = excess.divide(step, 0, RoundingMode.CEILING);
if (steps.compareTo(BigDecimal.ZERO) <= 0)
{
continue;
}
BigDecimal amount = fee.getAmount().multiply(steps);
String remark = "三边之和 " + plain(sum) + "cm,超出 " + plain(excess) + "cm,每 " + plain(step) + "cm "
+ plain(fee.getAmount()) + "," + plain(steps) + " 档";
long scId = fee.getSurchargeId() == null ? 0L : fee.getSurchargeId();
return line("SURCHARGE", blank(fee.getFeeName()), scId, amount, currency, remark);
}
return null;
}
private static BigDecimal decimal(Object value)
{
if (value == null)
{
return null;
}
if (value instanceof BigDecimal decimal)
{
return decimal;
}
String text = String.valueOf(value).trim();
if (text.isEmpty())
{
return null;
}
try
{
return new BigDecimal(text);
}
catch (NumberFormatException ex)
{
return null;
}
}
private static PayCostLine matchedFee(PaySurcharge fee, BigDecimal freight, String currency, String remark)
{
if (fee.getAmount() == null)
@@ -374,6 +374,7 @@ public final class PaySurchargeRules
return false;
}
java.time.LocalDate day = bizDate == null ? null : bizDate.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
boolean open = false;
for (java.util.Map<String, Object> detail : details)
{
if (detail == null)
@@ -381,6 +382,14 @@ public final class PaySurchargeRules
continue;
}
String ruleName = detail.get("feeName") == null ? "" : String.valueOf(detail.get("feeName")).trim();
if (ruleName.isEmpty())
{
if (!hasPeriod(detail))
{
open = true;
}
continue;
}
if (!ruleName.equals(name))
{
continue;
@@ -389,12 +398,9 @@ public final class PaySurchargeRules
{
return true;
}
if (day != null && inPeriod(day, detail))
{
return true;
}
return day != null && inPeriod(day, detail);
}
return false;
return open;
}
private static void addBound(List<Boolean> checks, BigDecimal actual, Object min, Object max)
@@ -10,6 +10,29 @@ public interface IPayPostcodeService
{
List<PayPostcodeGroup> selectGroupList(PayPostcodeGroup query);
/** 每个国家一行:邮编属性、服务类型、销售产品、渠道、分区数、邮编总数、导入时间 */
List<PayPostcodeGroup> selectCountryList(PayPostcodeGroup query);
/** 一个国家下面的分区,同一分区名只出现一行 */
List<PayPostcodeGroup> selectZoneList(PayPostcodeGroup query);
int addZone(PayPostcodeGroup zone, String username);
int updateZone(PayPostcodeGroup zone);
int deleteZone(PayPostcodeGroup zone);
/** 保存后写回这个国家下的全部分组,报价表和工作表保持不动,计费仍能对上产品 */
int updateCountry(PayPostcodeGroup country);
int deleteCountry(PayPostcodeGroup country);
int addLine(PayPostcodeLine line);
int updateLine(PayPostcodeLine line);
int deleteLines(Long[] lineIds);
/** 附加费分区下拉:按国家去重区块标题 */
List<String> selectBlockTitlesByCountry(String countryCode, String countryName);
@@ -398,7 +398,7 @@ public class PayBillServiceImpl implements IPayBillService
throw PaymentMesEnum.BMS_PAY_PENDING.exception();
}
String operator = SecurityUtils.getUsername();
if (operator.equals(bill.getCreateBy()) || (bill.getSubmitBy() != null && operator.equals(bill.getSubmitBy())))
if (!SecurityUtils.isAdmin() && (operator.equals(bill.getCreateBy()) || (bill.getSubmitBy() != null && operator.equals(bill.getSubmitBy()))))
{
throw PaymentMesEnum.BMS_PAY_SAME_USER.exception();
}
@@ -582,11 +582,15 @@ public class PayBillServiceImpl implements IPayBillService
adjust.setAmount(delta);
adjust.setCurrency(line.getCurrency());
adjust.setFeeSource("MANUAL");
adjust.setSourceId(String.valueOf(open.getDiffId()));
adjust.setSourceId(source != null && source.getId() != null ? String.valueOf(source.getId()) : null);
adjust.setRemark(open.getReason());
adjust.setSettleSupplierId(bill.getSupplierId());
adjust.setSettleSupplierName(bill.getSupplierName());
payBillFeeMapper.insertPrcPayBillFee(adjust);
if (adjust.getSourceId() == null || adjust.getSourceId().isBlank())
{
payBillFeeMapper.fillSourceIdByFeeIds(List.of(adjust.getFeeId()));
}
payFinanceMapper.stampFee(adjust.getFeeId(), bill.getSupplierId(), bill.getSupplierName());
if (line.getOrderBillId() != null)
{
@@ -115,13 +115,9 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
boolean postcodeOn = ruleOn(billingRules, PaySurchargeRules.POSTCODE);
boolean zoneOn = ruleOn(billingRules, PaySurchargeRules.ZONE);
List<PayRemoteBook.Hit> remoteHits = null;
if (postcodeOn && !hasPrefixRules(billingRules, PaySurchargeRules.POSTCODE))
if (!hasPrefixRules(billingRules, PaySurchargeRules.POSTCODE))
{
remoteHits = remoteHits(linked, bill.getDestPostcode(), bill.getCountryCode(), bill.getCountryName());
if (remoteHits == null)
{
remoteHits = new ArrayList<>();
}
}
PayOrderCostResult result = PayCostCalculator.calculate(product, weight, bill.getPkgLength(), bill.getPkgWidth(),
bill.getPkgHeight(), bill.getDestPostcode(), zoneName, bizDate, orderStatus, remoteHits, postcodeOn, zoneOn,
@@ -196,7 +192,7 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
priced.setBillingRules(rules == null ? new ArrayList<>() : rules);
}
/** 配置页已经写了邮编前缀时,命中邮编不再去应付邮编配置里查偏远。 */
/** 附加费规则里已经写了邮编前缀时,偏远不再去应付邮编配置里查。 */
private static boolean hasPrefixRules(List<PaySurchargeRule> rules, String calcRule)
{
if (rules == null)
@@ -265,19 +261,49 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
}
/**
* 用产品自己的偏远邮编判断这票是否偏远。没有偏远配置时返回 null,计费改看价卡上的邮编条件。
* 判断这票是否偏远。产品报价表已经挂了偏远名单时只用那一份,没命中就不再回价卡邮编。
* 没有这份产品名单时,改查目的国家的偏远邮编;国家名单没命中则返回 null,价卡上的邮编条件仍可用。
*
* @param linked 已对上的邮编组
* @param postcode 收件邮编
* @param countryCode 目的国二字码
* @param countryName 目的国名称
* @return 命中的偏远分区;有配置但没命中时返回空列表
* @return 命中的偏远分区;产品有名单但没命中时返回空列表
*/
private List<PayRemoteBook.Hit> remoteHits(List<PayPostcodeGroup> linked, String postcode, String countryCode, String countryName)
{
boolean productBook = false;
for (PayPostcodeGroup group : linked)
{
if (group != null && PayRemoteBook.remoteGroup(group))
{
productBook = true;
break;
}
}
if (productBook)
{
return hitsOf(linked, postcode, countryCode, countryName);
}
if (blank(countryCode) && blank(countryName))
{
return null;
}
List<PayPostcodeGroup> countryGroups = payPostcodeMapper.selectRemoteGroupsByCountry(countryCode, countryName);
if (countryGroups == null || countryGroups.isEmpty())
{
return null;
}
List<PayRemoteBook.Hit> hits = hitsOf(countryGroups, postcode, countryCode, countryName);
return hits == null || hits.isEmpty() ? null : hits;
}
/** 在给定邮编组里做偏远匹配。没有偏远组时返回 null,有组但邮编没命中时返回空列表。 */
private List<PayRemoteBook.Hit> hitsOf(List<PayPostcodeGroup> groups, String postcode, String countryCode, String countryName)
{
List<Long> remoteIds = new ArrayList<>();
boolean anyRemote = false;
for (PayPostcodeGroup group : linked)
for (PayPostcodeGroup group : groups)
{
if (group == null || group.getGroupId() == null || !PayRemoteBook.remoteGroup(group))
{
@@ -299,7 +325,7 @@ public class PayOrderCostServiceImpl implements IPayOrderCostService
}
List<PayPostcodeLine> lines = postcode == null || postcode.isBlank()
? new ArrayList<>() : payPostcodeMapper.selectLinesByGroups(remoteIds);
return PayRemoteBook.hits(linked, lines, postcode, countryCode, countryName);
return PayRemoteBook.hits(groups, lines, postcode, countryCode, countryName);
}
/**
@@ -131,7 +131,7 @@ public class PayPaymentServiceImpl implements IPayPaymentService
payment.setAllocs(payPaymentMapper.selectAllocs(paymentId));
checkAllocs(payment);
String operator = SecurityUtils.getUsername();
if (operator.equals(payment.getCreateBy()))
if (!SecurityUtils.isAdmin() && operator.equals(payment.getCreateBy()))
{
throw PaymentMesEnum.BMS_PAY_SAME_PAY.exception();
}
@@ -170,7 +170,7 @@ public class PayPaymentServiceImpl implements IPayPaymentService
throw PaymentMesEnum.BMS_PAY_NO_REVERSE.exception();
}
String operator = SecurityUtils.getUsername();
if (operator.equals(payment.getConfirmBy()))
if (!SecurityUtils.isAdmin() && operator.equals(payment.getConfirmBy()))
{
throw PaymentMesEnum.BMS_PAY_SAME_PAY.exception();
}
@@ -192,7 +192,7 @@ public class PayPaymentServiceImpl implements IPayPaymentService
throw PaymentMesEnum.BMS_PAY_NO_REVERSE.exception();
}
String operator = SecurityUtils.getUsername();
if (operator.equals(payment.getConfirmBy()))
if (!SecurityUtils.isAdmin() && operator.equals(payment.getConfirmBy()))
{
throw PaymentMesEnum.BMS_PAY_SAME_PAY.exception();
}
@@ -4,6 +4,8 @@ import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.bms.common.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -34,6 +36,227 @@ public class PayPostcodeServiceImpl implements IPayPostcodeService
return payPostcodeMapper.selectGroupList(query);
}
@Override
public List<PayPostcodeGroup> selectCountryList(PayPostcodeGroup query)
{
return payPostcodeMapper.selectCountryList(query);
}
@Override
public List<PayPostcodeGroup> selectZoneList(PayPostcodeGroup query)
{
requireCountry(query);
return payPostcodeMapper.selectZoneList(query);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int addZone(PayPostcodeGroup zone, String username)
{
requireCountry(zone);
String zoneName = requireZoneName(zone.getZoneName());
zone.setZoneName(zoneName);
if (payPostcodeMapper.countZoneName(zone) > 0)
{
throw new ServiceException("分区「" + zoneName + "」已存在");
}
PayPostcodeGroup sample = payPostcodeMapper.selectSampleGroup(zone);
PayPostcodeGroup row = new PayPostcodeGroup();
row.setSourceFile("手工新增");
row.setFileMd5(UUID.randomUUID().toString().replace("-", ""));
row.setBlockTitle(zoneName);
row.setConfigKind("ZONE");
row.setCoverage(normalizeCoverage(zone.getCoverage()));
row.setCountryName(firstText(zone.getCountryName(), sample == null ? "" : sample.getCountryName()));
row.setCountryCode(firstText(zone.getCountryCode(), sample == null ? "" : sample.getCountryCode()));
row.setServiceType(sample == null ? "" : trimToEmpty(sample.getServiceType()));
row.setCatalogCountry(sample == null ? "" : trimToEmpty(sample.getCatalogCountry()));
if ("REMOTE".equals(row.getCoverage()))
{
row.setSheetName("手工偏远");
row.setChannelName("");
row.setProductName("");
row.setQuoteSheet("");
row.setFeeName("偏远");
}
else
{
row.setSheetName(sample != null && !trimToEmpty(sample.getSheetName()).isEmpty() ? sample.getSheetName() : zoneName);
row.setChannelName(sample == null ? "" : trimToEmpty(sample.getChannelName()));
row.setProductName(sample == null ? "" : trimToEmpty(sample.getProductName()));
row.setQuoteSheet(sample == null ? "" : trimToEmpty(sample.getQuoteSheet()));
row.setFeeName("");
}
row.setFeeText("");
row.setLineCount(0);
row.setCreateBy(username);
return payPostcodeMapper.insertGroup(row);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int updateZone(PayPostcodeGroup zone)
{
requireCountry(zone);
String zoneName = requireZoneName(zone.getZoneName());
String oldName = requireZoneName(zone.getOldZoneName());
zone.setZoneName(zoneName);
zone.setOldZoneName(oldName);
zone.setCoverage(normalizeCoverage(zone.getCoverage()));
if (!oldName.equals(zoneName) && payPostcodeMapper.countZoneName(zone) > 0)
{
throw new ServiceException("分区「" + zoneName + "」已存在");
}
payPostcodeMapper.renameZoneLines(zone);
return payPostcodeMapper.updateExclusiveZone(zone);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteZone(PayPostcodeGroup zone)
{
requireCountry(zone);
zone.setZoneName(requireZoneName(zone.getZoneName()));
payPostcodeMapper.deleteLinesByZone(zone);
List<Long> groupIds = payPostcodeMapper.selectGroupIdsByCountry(zone);
int removed = 0;
if (groupIds != null)
{
for (Long groupId : groupIds)
{
payPostcodeMapper.refreshLineCount(groupId);
PayPostcodeGroup group = payPostcodeMapper.selectGroupById(groupId);
if (group != null && "手工新增".equals(group.getSourceFile()) && zone.getZoneName().equals(trimToEmpty(group.getBlockTitle()))
&& (group.getLineCount() == null || group.getLineCount() == 0))
{
payPostcodeMapper.deleteGroups(List.of(groupId));
removed++;
}
}
}
return removed + 1;
}
@Override
public int updateCountry(PayPostcodeGroup country)
{
requireCountry(country);
country.setServiceType(trimToEmpty(country.getServiceType()));
country.setProductName(trimToEmpty(country.getProductName()));
country.setChannelName(trimToEmpty(country.getChannelName()));
return payPostcodeMapper.updateCountryProfile(country);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteCountry(PayPostcodeGroup country)
{
requireCountry(country);
List<Long> ids = payPostcodeMapper.selectGroupIdsByCountry(country);
if (ids == null || ids.isEmpty())
{
return 0;
}
payPostcodeMapper.deleteLines(ids);
return payPostcodeMapper.deleteGroups(ids);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int addLine(PayPostcodeLine line)
{
if (line == null)
{
throw new ServiceException("请填写邮编");
}
PayPostcodeGroup zone = countryOf(line);
zone.setZoneName(requireZoneName(line.getZoneName()));
Long groupId = payPostcodeMapper.selectZoneGroupId(zone);
if (groupId == null)
{
addZone(zone, "system");
groupId = payPostcodeMapper.selectZoneGroupId(zone);
}
if (groupId == null)
{
throw new ServiceException("分区不存在");
}
PayPostcodeGroup group = payPostcodeMapper.selectGroupById(groupId);
line.setGroupId(groupId);
line.setZoneName(zone.getZoneName());
line.setCountryName(group == null ? zone.getCountryName() : group.getCountryName());
line.setCountryCode(group == null ? zone.getCountryCode() : group.getCountryCode());
line.setCityName(trimToEmpty(line.getCityName()));
line.setPostcode(cleanPostcode(line.getPostcode()));
line.setPostcodeRaw(trimToEmpty(line.getPostcodeRaw()).isEmpty() ? line.getPostcode() : trimToEmpty(line.getPostcodeRaw()));
line.setPostcodeEnd(cleanOptionalPostcode(line.getPostcodeEnd()));
if (line.getPostcode().isEmpty())
{
throw new ServiceException("请填写邮编");
}
int rows = payPostcodeMapper.insertLine(line);
payPostcodeMapper.refreshLineCount(groupId);
return rows;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int updateLine(PayPostcodeLine line)
{
if (line == null || line.getLineId() == null)
{
throw new ServiceException("请选择邮编");
}
PayPostcodeLine existed = payPostcodeMapper.selectLineById(line.getLineId());
if (existed == null)
{
throw new ServiceException("邮编不存在或已删除");
}
line.setCityName(trimToEmpty(line.getCityName()));
line.setZoneName(trimToEmpty(line.getZoneName()).isEmpty() ? existed.getZoneName() : trimToEmpty(line.getZoneName()));
line.setPostcode(cleanPostcode(line.getPostcode()));
line.setPostcodeRaw(line.getPostcode());
line.setPostcodeEnd(cleanOptionalPostcode(line.getPostcodeEnd()));
if (line.getPostcode().isEmpty())
{
throw new ServiceException("请填写邮编");
}
int rows = payPostcodeMapper.updateLine(line);
payPostcodeMapper.refreshLineCount(existed.getGroupId());
return rows;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteLines(Long[] lineIds)
{
List<Long> ids = new ArrayList<>();
if (lineIds != null)
{
for (Long id : lineIds)
{
if (id != null)
{
ids.add(id);
}
}
}
if (ids.isEmpty())
{
return 0;
}
List<Long> groupIds = payPostcodeMapper.selectGroupIdsByLineIds(ids);
int rows = payPostcodeMapper.deleteLineByIds(ids);
if (groupIds != null)
{
for (Long groupId : groupIds)
{
payPostcodeMapper.refreshLineCount(groupId);
}
}
return rows;
}
@Override
public List<String> selectBlockTitlesByCountry(String countryCode, String countryName)
{
@@ -180,4 +403,53 @@ public class PayPostcodeServiceImpl implements IPayPostcodeService
}
throw PaymentMesEnum.BMS_PAY_ZIP_ATTR.exception();
}
private static void requireCountry(PayPostcodeGroup country)
{
if (country == null || (trimToEmpty(country.getCountryCode()).isEmpty() && trimToEmpty(country.getCountryName()).isEmpty()))
{
throw new ServiceException("请选择国家");
}
}
private static String requireZoneName(String zoneName)
{
String name = trimToEmpty(zoneName);
if (name.isEmpty())
{
throw new ServiceException("请填写分区");
}
return name;
}
private static PayPostcodeGroup countryOf(PayPostcodeLine line)
{
PayPostcodeGroup country = new PayPostcodeGroup();
country.setCountryCode(line.getCountryCode());
country.setCountryName(line.getCountryName());
requireCountry(country);
return country;
}
private static String firstText(String preferred, String fallback)
{
String value = trimToEmpty(preferred);
return value.isEmpty() ? trimToEmpty(fallback) : value;
}
private static String cleanPostcode(String postcode)
{
String value = trimToEmpty(postcode);
while (value.endsWith("*"))
{
value = value.substring(0, value.length() - 1).trim();
}
return value;
}
private static String cleanOptionalPostcode(String postcode)
{
String value = cleanPostcode(postcode);
return value.isEmpty() ? null : value;
}
}
@@ -175,7 +175,7 @@ public class PaySurchargeRuleServiceImpl implements IPaySurchargeRuleService
String feeName = text(row.getDetail().get("feeName"));
if (feeName != null && !alwaysFees.add(feeName))
{
throw new ServiceException("每票都收的费用项不能重复:" + feeName);
throw new ServiceException("存在两笔同类型的附加费:" + feeName);
}
}
return rules;
@@ -43,6 +43,19 @@ public class PrcPayBillFeeServiceImpl implements IPrcPayBillFeeService
{
List<PrcPayBillFee> chunk = fees.subList(from, Math.min(from + BATCH_SIZE, fees.size()));
payBillFeeMapper.insertBatch(chunk);
List<String> ownIds = new ArrayList<>();
for (PrcPayBillFee fee : chunk)
{
if (fee != null && fee.getFeeId() != null && !fee.getFeeId().isBlank()
&& (fee.getSourceId() == null || fee.getSourceId().isBlank()))
{
ownIds.add(fee.getFeeId());
}
}
if (!ownIds.isEmpty())
{
payBillFeeMapper.fillSourceIdByFeeIds(ownIds);
}
for (PrcPayBillFee fee : chunk)
{
if (fee != null && fee.getBillId() != null)
@@ -245,7 +245,6 @@ public class SrvBillAirAllocServiceImpl implements ISrvBillAirAllocService
row.setRoundMode("HALF_UP");
row.setRemark(remark(fee));
row.setFeeSource(FEE_SOURCE);
row.setSourceId(fee.getLadingId().trim());
row.setIsDeleted("0");
return row;
}
@@ -50,18 +50,292 @@
order by block_title asc
</select>
<select id="selectLineList" parameterType="com.bms.price.domain.PayPostcodeLine" resultType="com.bms.price.domain.PayPostcodeLine">
select line_id as lineId, group_id as groupId, country_name as countryName, country_code as countryCode,
region_name as regionName, region_name_cn as regionNameCn, city_name as cityName, region_code as regionCode,
zone_name as zoneName, postcode, postcode_raw as postcodeRaw, postcode_end as postcodeEnd,
extra_text as extraText, sort_no as sortNo
from pay_postcode_line
<sql id="countryMatch">
<choose>
<when test="countryCode != null and countryCode != ''">
and upper(trim(g.country_code)) = upper(trim(#{countryCode}))
</when>
<otherwise>
and (g.country_code is null or trim(g.country_code) = '')
and g.country_name = #{countryName}
</otherwise>
</choose>
</sql>
<sql id="zoneExpr">
case
when nullif(trim(l.zone_name), '') is not null then trim(l.zone_name)
when nullif(trim(l.region_name), '') is not null then trim(l.region_name)
when nullif(trim(g.block_title), '') is not null then trim(g.block_title)
else '未分区'
end
</sql>
<select id="selectCountryList" parameterType="com.bms.price.domain.PayPostcodeGroup" resultType="com.bms.price.domain.PayPostcodeGroup">
select min(g.group_id) as groupId,
max(g.country_name) as countryName,
max(g.country_code) as countryCode,
group_concat(distinct nullif(trim(g.coverage), '') order by g.coverage separator '、') as coverage,
group_concat(distinct nullif(trim(g.service_type), '') order by g.service_type separator '、') as serviceType,
group_concat(distinct nullif(trim(g.product_name), '') order by g.product_name separator '、') as productName,
group_concat(distinct nullif(trim(g.channel_name), '') order by g.channel_name separator '、') as channelName,
count(distinct
case
when l.line_id is not null then
case
when nullif(trim(l.zone_name), '') is not null then trim(l.zone_name)
when nullif(trim(l.region_name), '') is not null then trim(l.region_name)
when nullif(trim(g.block_title), '') is not null then trim(g.block_title)
else '未分区'
end
when nullif(trim(g.block_title), '') is not null then trim(g.block_title)
else null
end
) as zoneCount,
count(l.line_id) as lineCount,
max(g.create_time) as createTime
from pay_postcode_group g
left join pay_postcode_line l on l.group_id = g.group_id
<where>
<if test="groupId != null">and group_id = #{groupId}</if>
<if test="postcode != null and postcode != ''">and postcode like concat('%', #{postcode}, '%')</if>
<if test="regionName != null and regionName != ''">and (region_name like concat('%', #{regionName}, '%') or region_name_cn like concat('%', #{regionName}, '%') or zone_name like concat('%', #{regionName}, '%'))</if>
<if test="countryName != null and countryName != ''">
and (g.country_name like concat('%', #{countryName}, '%') or g.country_code like concat('%', #{countryName}, '%') or g.catalog_country like concat('%', #{countryName}, '%'))
</if>
<if test="serviceType != null and serviceType != ''">and g.service_type like concat('%', #{serviceType}, '%')</if>
<if test="productName != null and productName != ''">and g.product_name like concat('%', #{productName}, '%')</if>
<if test="channelName != null and channelName != ''">and g.channel_name like concat('%', #{channelName}, '%')</if>
<if test="coverage != null and coverage != ''">and g.coverage = #{coverage}</if>
<if test="postcode != null and postcode != ''">
and exists (
select 1 from pay_postcode_line lp
where lp.group_id = g.group_id and lp.postcode like concat('%', #{postcode}, '%')
)
</if>
</where>
order by sort_no asc, line_id asc
group by case
when nullif(trim(g.country_code), '') is not null then upper(trim(g.country_code))
when nullif(trim(g.country_name), '') is not null then trim(g.country_name)
else '未填写国家'
end
order by max(g.create_time) desc
</select>
<select id="selectZoneList" parameterType="com.bms.price.domain.PayPostcodeGroup" resultType="com.bms.price.domain.PayPostcodeGroup">
select t.zone_name as zoneName,
min(t.group_id) as groupId,
group_concat(distinct nullif(trim(t.coverage), '') order by t.coverage separator '、') as coverage,
count(t.line_id) as lineCount,
max(t.country_name) as countryName,
max(t.country_code) as countryCode
from (
select g.group_id, g.coverage, g.country_name, g.country_code, l.line_id,
case
when l.line_id is null then nullif(trim(g.block_title), '')
else
case
when nullif(trim(l.zone_name), '') is not null then trim(l.zone_name)
when nullif(trim(l.region_name), '') is not null then trim(l.region_name)
when nullif(trim(g.block_title), '') is not null then trim(g.block_title)
else '未分区'
end
end as zone_name
from pay_postcode_group g
left join pay_postcode_line l on l.group_id = g.group_id
<where>
<include refid="countryMatch"/>
</where>
) t
where t.zone_name is not null and t.zone_name &lt;&gt; ''
group by t.zone_name
order by t.zone_name asc
</select>
<select id="selectSampleGroup" parameterType="com.bms.price.domain.PayPostcodeGroup" resultType="com.bms.price.domain.PayPostcodeGroup">
select group_id as groupId, source_file as sourceFile, file_md5 as fileMd5, sheet_name as sheetName,
block_title as blockTitle, config_kind as configKind, coverage, channel_name as channelName,
country_name as countryName, country_code as countryCode,
service_type as serviceType, catalog_country as catalogCountry, product_name as productName, quote_sheet as quoteSheet
from pay_postcode_group g
<where>
<include refid="countryMatch"/>
</where>
order by g.group_id asc
limit 1
</select>
<select id="countZoneName" parameterType="com.bms.price.domain.PayPostcodeGroup" resultType="int">
select count(*) from (
select 1
from pay_postcode_group g
left join pay_postcode_line l on l.group_id = g.group_id
<where>
<include refid="countryMatch"/>
and (
(l.line_id is null and trim(g.block_title) = #{zoneName})
or (l.line_id is not null and <include refid="zoneExpr"/> = #{zoneName})
)
</where>
limit 1
) hit
</select>
<select id="selectZoneGroupId" parameterType="com.bms.price.domain.PayPostcodeGroup" resultType="long">
select g.group_id
from pay_postcode_group g
left join pay_postcode_line l on l.group_id = g.group_id
<where>
<include refid="countryMatch"/>
and (
(g.source_file = '手工新增' and trim(g.block_title) = #{zoneName})
or (l.line_id is not null and <include refid="zoneExpr"/> = #{zoneName})
)
</where>
order by case when g.source_file = '手工新增' and trim(g.block_title) = #{zoneName} then 0 else 1 end, g.group_id asc
limit 1
</select>
<update id="updateCountryProfile" parameterType="com.bms.price.domain.PayPostcodeGroup">
update pay_postcode_group g
set g.service_type = #{serviceType},
g.product_name = #{productName},
g.channel_name = #{channelName}
<where>
<include refid="countryMatch"/>
</where>
</update>
<select id="selectGroupIdsByCountry" parameterType="com.bms.price.domain.PayPostcodeGroup" resultType="long">
select g.group_id from pay_postcode_group g
<where>
<include refid="countryMatch"/>
</where>
</select>
<update id="renameZoneLines" parameterType="com.bms.price.domain.PayPostcodeGroup">
update pay_postcode_line l
inner join pay_postcode_group g on g.group_id = l.group_id
set l.zone_name = #{zoneName}
<where>
<include refid="countryMatch"/>
and <include refid="zoneExpr"/> = #{oldZoneName}
</where>
</update>
<update id="updateExclusiveZone" parameterType="com.bms.price.domain.PayPostcodeGroup">
update pay_postcode_group g
set g.block_title = case
when trim(g.block_title) = #{oldZoneName} or g.source_file = '手工新增' then #{zoneName}
else g.block_title
end,
g.coverage = #{coverage},
g.fee_name = case
when #{coverage} = 'REMOTE' and (g.fee_name is null or trim(g.fee_name) = '') then '偏远'
else g.fee_name
end
<where>
<include refid="countryMatch"/>
and (
trim(g.block_title) = #{oldZoneName}
or (
exists (
select 1 from pay_postcode_line l
where l.group_id = g.group_id and <include refid="zoneExpr"/> = #{zoneName}
)
and not exists (
select 1 from pay_postcode_line l
where l.group_id = g.group_id and <include refid="zoneExpr"/> &lt;&gt; #{zoneName}
)
)
)
</where>
</update>
<insert id="insertLine" parameterType="com.bms.price.domain.PayPostcodeLine" useGeneratedKeys="true" keyProperty="lineId">
insert into pay_postcode_line (
group_id, country_name, country_code, region_name, region_name_cn, city_name, region_code,
zone_name, postcode, postcode_raw, postcode_end, extra_text, sort_no
) values (
#{groupId}, #{countryName}, #{countryCode}, #{regionName}, #{regionNameCn}, #{cityName}, #{regionCode},
#{zoneName}, #{postcode}, #{postcodeRaw}, #{postcodeEnd}, #{extraText}, ifnull(#{sortNo}, 0)
)
</insert>
<update id="updateLine" parameterType="com.bms.price.domain.PayPostcodeLine">
update pay_postcode_line
set city_name = #{cityName},
zone_name = #{zoneName},
postcode = #{postcode},
postcode_raw = #{postcodeRaw},
postcode_end = #{postcodeEnd}
where line_id = #{lineId}
</update>
<select id="selectLineById" resultType="com.bms.price.domain.PayPostcodeLine">
select line_id as lineId, group_id as groupId, country_name as countryName, country_code as countryCode,
city_name as cityName, zone_name as zoneName, postcode, postcode_raw as postcodeRaw, postcode_end as postcodeEnd
from pay_postcode_line
where line_id = #{lineId}
</select>
<select id="selectGroupIdsByLineIds" resultType="long">
select distinct group_id from pay_postcode_line
where line_id in
<foreach collection="lineIds" item="id" open="(" separator="," close=")">#{id}</foreach>
</select>
<delete id="deleteLineByIds">
delete from pay_postcode_line where line_id in
<foreach collection="lineIds" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
<delete id="deleteLinesByZone" parameterType="com.bms.price.domain.PayPostcodeGroup">
delete l from pay_postcode_line l
inner join pay_postcode_group g on g.group_id = l.group_id
<where>
<include refid="countryMatch"/>
and <include refid="zoneExpr"/> = #{zoneName}
</where>
</delete>
<update id="refreshLineCount">
update pay_postcode_group g
set g.line_count = (select count(*) from pay_postcode_line l where l.group_id = g.group_id)
where g.group_id = #{groupId}
</update>
<select id="selectLineList" parameterType="com.bms.price.domain.PayPostcodeLine" resultType="com.bms.price.domain.PayPostcodeLine">
select l.line_id as lineId, l.group_id as groupId, l.country_name as countryName, l.country_code as countryCode,
l.region_name as regionName, l.region_name_cn as regionNameCn, l.city_name as cityName, l.region_code as regionCode,
l.zone_name as zoneName, l.postcode, l.postcode_raw as postcodeRaw, l.postcode_end as postcodeEnd,
l.extra_text as extraText, l.sort_no as sortNo
from pay_postcode_line l
<where>
<if test="groupId != null">and l.group_id = #{groupId}</if>
<if test="postcode != null and postcode != ''">and l.postcode like concat('%', #{postcode}, '%')</if>
<if test="regionName != null and regionName != ''">and (l.region_name like concat('%', #{regionName}, '%') or l.region_name_cn like concat('%', #{regionName}, '%') or l.zone_name like concat('%', #{regionName}, '%') or l.city_name like concat('%', #{regionName}, '%'))</if>
<if test="zoneName != null and zoneName != ''">
and (
trim(l.zone_name) = #{zoneName}
or ((l.zone_name is null or trim(l.zone_name) = '') and trim(l.region_name) = #{zoneName})
or (
(l.zone_name is null or trim(l.zone_name) = '')
and (l.region_name is null or trim(l.region_name) = '')
and l.group_id in (select g.group_id from pay_postcode_group g where trim(g.block_title) = #{zoneName})
)
)
</if>
<if test="countryCode != null and countryCode != ''">
and l.group_id in (
select g.group_id from pay_postcode_group g where upper(trim(g.country_code)) = upper(trim(#{countryCode}))
)
</if>
<if test="(countryCode == null or countryCode == '') and countryName != null and countryName != ''">
and l.group_id in (
select g.group_id from pay_postcode_group g
where (g.country_code is null or trim(g.country_code) = '') and g.country_name = #{countryName}
)
</if>
</where>
order by l.sort_no asc, l.line_id asc
</select>
<insert id="insertGroup" parameterType="com.bms.price.domain.PayPostcodeGroup" useGeneratedKeys="true" keyProperty="groupId">
@@ -134,6 +408,30 @@
</foreach>
</select>
<select id="selectRemoteGroupsByCountry" resultType="com.bms.price.domain.PayPostcodeGroup">
select group_id as groupId, block_title as blockTitle, channel_name as channelName,
product_name as productName, quote_sheet as quoteSheet, sheet_name as sheetName,
config_kind as configKind, coverage, fee_name as feeName, fee_text as feeText,
country_name as countryName, country_code as countryCode
from pay_postcode_group
where (
upper(ifnull(coverage, '')) = 'REMOTE'
or ifnull(fee_name, '') like '%偏远%'
or lower(ifnull(fee_name, '')) like '%remote%'
)
<choose>
<when test="countryCode != null and countryCode != ''">
and upper(trim(country_code)) = upper(trim(#{countryCode}))
</when>
<when test="countryName != null and countryName != ''">
and trim(country_name) = trim(#{countryName})
</when>
<otherwise>
and 1 = 0
</otherwise>
</choose>
</select>
<select id="selectGroupsByTokens" resultType="com.bms.price.domain.PayPostcodeGroup">
select group_id as groupId, block_title as blockTitle, channel_name as channelName,
product_name as productName, quote_sheet as quoteSheet, sheet_name as sheetName,
@@ -91,6 +91,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and ifnull(is_deleted, '0') = '0'
</update>
<update id="fillSourceIdByFeeIds">
update prc_pay_bill_fee
set source_id = id
where (source_id is null or source_id = '')
and fee_id in
<foreach collection="feeIds" item="feeId" open="(" separator="," close=")">#{feeId}</foreach>
</update>
<delete id="deleteByBillId" parameterType="Long">
delete from prc_pay_bill_fee
where bill_id = #{billId}