新建应付配置的附加费开关配置
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 一个应付产品上的附加费开关。多个产品各自一组,互不影响。
|
||||
*/
|
||||
@Data
|
||||
public class PayFeeRuleGroup
|
||||
{
|
||||
private Long productId;
|
||||
|
||||
private String productName;
|
||||
|
||||
private List<PayProductFeeRule> rules = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.bms.price.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.bms.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 应付产品附加费开关。挂在根产品上,价格仍在价卡版本的附加费行里。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PayProductFeeRule extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long ruleId;
|
||||
|
||||
/** 根产品编号 */
|
||||
private Long productId;
|
||||
|
||||
/** 附加费名称,用来到价卡版本里取价 */
|
||||
private String feeName;
|
||||
|
||||
/** 1 参与计算,0 不参与 */
|
||||
private String enabled;
|
||||
|
||||
/** SIZE 尺寸,VOLUME 体积,WEIGHT 重量,POSTCODE 邮编,ALWAYS 每票,RETURN 退件 */
|
||||
private String triggerType;
|
||||
|
||||
private BigDecimal sideMaxCm;
|
||||
|
||||
private BigDecimal sumMaxCm;
|
||||
|
||||
private BigDecimal volumeMaxCm3;
|
||||
|
||||
private BigDecimal minLengthCm;
|
||||
|
||||
private BigDecimal minWidthCm;
|
||||
|
||||
private BigDecimal minHeightCm;
|
||||
|
||||
private BigDecimal weightMaxKg;
|
||||
|
||||
/** ANY 任一条超出就收,ALL 写了的条件都超出才收 */
|
||||
private String joinMode;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.bms.price.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.price.domain.PayProductFeeRule;
|
||||
|
||||
/**
|
||||
* 应付产品附加费开关
|
||||
*/
|
||||
public interface PayProductFeeRuleMapper
|
||||
{
|
||||
List<PayProductFeeRule> selectByProductId(Long productId);
|
||||
|
||||
int deleteByProductId(Long productId);
|
||||
|
||||
int deleteByProductIds(@Param("productIds") List<Long> productIds);
|
||||
|
||||
int insertBatch(@Param("list") List<PayProductFeeRule> list);
|
||||
}
|
||||
@@ -2811,17 +2811,22 @@ public class PayWorkbookParser
|
||||
{
|
||||
continue;
|
||||
}
|
||||
PayWeightBand band = new PayWeightBand();
|
||||
band.setSectionName("分区报价");
|
||||
String country = countryRow < 0 ? "" : grid.text(countryRow, col);
|
||||
band.setCountryName(country);
|
||||
band.setCountryCode(zoneCodes(country));
|
||||
band.setZoneName(grid.text(header, col));
|
||||
band.setChargeName(charge);
|
||||
band.setPrice(price);
|
||||
band.setPriceUnit(unitOf(charge + " EUR"));
|
||||
band.setSortNo(product.getWeightBands().size() + 1);
|
||||
product.getWeightBands().add(band);
|
||||
String zone = grid.text(header, col);
|
||||
for (String piece : zoneCountryPieces(country))
|
||||
{
|
||||
PayWeightBand band = new PayWeightBand();
|
||||
band.setSectionName("分区报价");
|
||||
String code = zoneCodes(piece);
|
||||
band.setCountryName(zoneCountryName(piece, code, country));
|
||||
band.setCountryCode(code.contains(",") ? "" : code);
|
||||
band.setZoneName(zone);
|
||||
band.setChargeName(charge);
|
||||
band.setPrice(price);
|
||||
band.setPriceUnit(unitOf(charge + " EUR"));
|
||||
band.setSortNo(product.getWeightBands().size() + 1);
|
||||
product.getWeightBands().add(band);
|
||||
}
|
||||
}
|
||||
return row + 1;
|
||||
}
|
||||
@@ -3268,19 +3273,103 @@ public class PayWorkbookParser
|
||||
|
||||
private static String zoneCodes(String text)
|
||||
{
|
||||
Matcher matcher = Pattern.compile("\\b([A-Z]{2})\\b").matcher(text == null ? "" : text);
|
||||
StringBuilder codes = new StringBuilder();
|
||||
while (matcher.find())
|
||||
for (String code : zoneCodeList(text))
|
||||
{
|
||||
if (codes.length() > 0)
|
||||
{
|
||||
codes.append(',');
|
||||
}
|
||||
codes.append(matcher.group(1));
|
||||
codes.append(code);
|
||||
}
|
||||
return codes.toString();
|
||||
}
|
||||
|
||||
private static List<String> zoneCodeList(String text)
|
||||
{
|
||||
List<String> codes = new ArrayList<>();
|
||||
Matcher matcher = Pattern.compile("\\b([A-Z]{2})\\b").matcher(text == null ? "" : text);
|
||||
while (matcher.find())
|
||||
{
|
||||
codes.add(matcher.group(1));
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 德国 UPS 这类分区国家格把多个国家写在同一格,用顿号或换行接起来。
|
||||
* 纯国家码列表按出现顺序拆开;没有国家码、只用顿号连接的国名同样拆开。
|
||||
* 只有一个国家时保留原格,避免把说明文字拆碎。
|
||||
*/
|
||||
private static List<String> zoneCountryPieces(String text)
|
||||
{
|
||||
if (text == null || text.isBlank())
|
||||
{
|
||||
return List.of("");
|
||||
}
|
||||
String folded = text.replace('\r', '\n');
|
||||
List<String> codes = zoneCodeList(folded);
|
||||
if (codes.size() > 1 && isCountryCodeList(folded))
|
||||
{
|
||||
return codes;
|
||||
}
|
||||
if (!folded.contains("、"))
|
||||
{
|
||||
return List.of(text.trim());
|
||||
}
|
||||
List<String> pieces = new ArrayList<>();
|
||||
for (String part : folded.split("、"))
|
||||
{
|
||||
String token = part.replace('\n', ' ').trim();
|
||||
if (token.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
List<String> tokenCodes = zoneCodeList(token);
|
||||
if (tokenCodes.size() > 1 && isCountryCodeList(token))
|
||||
{
|
||||
pieces.addAll(tokenCodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
pieces.add(token);
|
||||
}
|
||||
}
|
||||
return pieces.size() > 1 ? pieces : List.of(text.trim());
|
||||
}
|
||||
|
||||
private static boolean isCountryCodeList(String text)
|
||||
{
|
||||
String stripped = text.replaceAll("\\b[A-Z]{2}\\b", "");
|
||||
return stripped.replaceAll("[、,,;;\\s]+", "").isEmpty();
|
||||
}
|
||||
|
||||
/** 拆开后的一格如果就是国家码,名称也用这个码;没拆开的原格保持原文。 */
|
||||
private static String zoneCountryName(String piece, String code, String original)
|
||||
{
|
||||
if (piece == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
if (original != null && piece.equals(original.trim()))
|
||||
{
|
||||
return original.trim();
|
||||
}
|
||||
String trimmed = piece.trim();
|
||||
if (code == null || code.isBlank() || code.contains(","))
|
||||
{
|
||||
return trimmed;
|
||||
}
|
||||
if (trimmed.equalsIgnoreCase(code))
|
||||
{
|
||||
return code;
|
||||
}
|
||||
String name = trimmed.replaceAll("(?i)\\b" + Pattern.quote(code) + "\\b", "")
|
||||
.replaceAll("[()()]", " ")
|
||||
.trim();
|
||||
return name.isEmpty() ? code : name;
|
||||
}
|
||||
|
||||
private static int currencyHits(String text)
|
||||
{
|
||||
if (text == null || text.isBlank())
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.bms.price.service;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Date;
|
||||
import com.bms.price.domain.PayFeeRuleGroup;
|
||||
import com.bms.price.domain.PayPreview;
|
||||
import com.bms.price.domain.PayPriceCompare;
|
||||
import com.bms.price.domain.PayProduct;
|
||||
@@ -74,4 +75,10 @@ public interface IPayProductService
|
||||
void writeSource(Long productId, OutputStream output);
|
||||
|
||||
int deletePayProducts(Long[] productIds);
|
||||
|
||||
/** 按根产品编号带出附加费开关。没有配置过的产品返回空列表 */
|
||||
List<PayFeeRuleGroup> listFeeRules(List<Long> productIds);
|
||||
|
||||
/** 按产品整组替换附加费开关。每个产品一份,互不影响 */
|
||||
void saveFeeRules(List<PayFeeRuleGroup> products);
|
||||
}
|
||||
|
||||
+181
@@ -3,11 +3,13 @@ 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;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -26,6 +28,8 @@ import com.bms.common.utils.file.OssStorage;
|
||||
import com.bms.common.utils.uuid.IdUtils;
|
||||
import com.bms.price.domain.ChannelProductMap;
|
||||
import com.bms.price.domain.PayExtraLine;
|
||||
import com.bms.price.domain.PayFeeRuleGroup;
|
||||
import com.bms.price.domain.PayProductFeeRule;
|
||||
import com.bms.price.domain.PayPreview;
|
||||
import com.bms.price.domain.PayPriceCompare;
|
||||
import com.bms.price.domain.PayPriceDiff;
|
||||
@@ -35,6 +39,7 @@ import com.bms.price.domain.PaySaveCommand;
|
||||
import com.bms.price.domain.PaySurcharge;
|
||||
import com.bms.price.domain.PayWeightBand;
|
||||
import com.bms.price.mapper.ChannelProductMapMapper;
|
||||
import com.bms.price.mapper.PayProductFeeRuleMapper;
|
||||
import com.bms.price.mapper.PayProductMapper;
|
||||
import com.bms.price.mapper.PayProductTermMapper;
|
||||
import com.bms.price.pay.PayWorkbookParser;
|
||||
@@ -64,6 +69,11 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
@Autowired
|
||||
private PayProductTermMapper payProductTermMapper;
|
||||
|
||||
@Autowired
|
||||
private PayProductFeeRuleMapper payProductFeeRuleMapper;
|
||||
|
||||
private static final Set<String> FEE_TRIGGERS = Set.of("SIZE", "VOLUME", "WEIGHT", "POSTCODE", "ALWAYS", "RETURN");
|
||||
|
||||
@Autowired
|
||||
private ISysDictTypeService dictTypeService;
|
||||
|
||||
@@ -670,6 +680,7 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
return 0;
|
||||
}
|
||||
payProductTermMapper.deleteByProductIds(ids);
|
||||
payProductFeeRuleMapper.deleteByProductIds(ids);
|
||||
payProductMapper.deleteWeightBands(ids);
|
||||
payProductMapper.deleteSurcharges(ids);
|
||||
payProductMapper.deleteExtraLines(ids);
|
||||
@@ -1216,6 +1227,176 @@ public class PayProductServiceImpl implements IPayProductService
|
||||
return rows == null ? 0 : rows.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PayFeeRuleGroup> listFeeRules(List<Long> productIds)
|
||||
{
|
||||
List<PayFeeRuleGroup> groups = new ArrayList<>();
|
||||
if (productIds == null)
|
||||
{
|
||||
return groups;
|
||||
}
|
||||
LinkedHashSet<Long> ordered = new LinkedHashSet<>();
|
||||
for (Long productId : productIds)
|
||||
{
|
||||
if (productId != null)
|
||||
{
|
||||
ordered.add(productId);
|
||||
}
|
||||
}
|
||||
for (Long productId : ordered)
|
||||
{
|
||||
PayProduct root = requireRoot(productId);
|
||||
PayFeeRuleGroup group = new PayFeeRuleGroup();
|
||||
group.setProductId(root.getProductId());
|
||||
group.setProductName(root.getProductName());
|
||||
List<PayProductFeeRule> rules = payProductFeeRuleMapper.selectByProductId(root.getProductId());
|
||||
group.setRules(rules == null ? new ArrayList<>() : rules);
|
||||
groups.add(group);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveFeeRules(List<PayFeeRuleGroup> products)
|
||||
{
|
||||
if (products == null || products.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请选择应付产品");
|
||||
}
|
||||
String operator = SecurityUtils.getUsername();
|
||||
Date now = DateUtils.getNowDate();
|
||||
for (PayFeeRuleGroup group : products)
|
||||
{
|
||||
if (group == null || group.getProductId() == null)
|
||||
{
|
||||
throw new ServiceException("请选择应付产品");
|
||||
}
|
||||
PayProduct root = requireRoot(group.getProductId());
|
||||
List<PayProductFeeRule> rules = normalizeFeeRules(root, group.getRules(), operator, now);
|
||||
payProductFeeRuleMapper.deleteByProductId(root.getProductId());
|
||||
if (!rules.isEmpty())
|
||||
{
|
||||
payProductFeeRuleMapper.insertBatch(rules);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PayProduct requireRoot(Long productId)
|
||||
{
|
||||
PayProduct product = payProductMapper.selectPayProductById(productId);
|
||||
if (product == null)
|
||||
{
|
||||
throw new ServiceException("应付产品不存在");
|
||||
}
|
||||
if (product.getOriginId() != null)
|
||||
{
|
||||
product = payProductMapper.selectPayProductById(product.getOriginId());
|
||||
if (product == null)
|
||||
{
|
||||
throw new ServiceException("应付产品不存在");
|
||||
}
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
private List<PayProductFeeRule> normalizeFeeRules(PayProduct root, List<PayProductFeeRule> source, String operator, Date now)
|
||||
{
|
||||
List<PayProductFeeRule> rules = new ArrayList<>();
|
||||
Set<String> names = new HashSet<>();
|
||||
String productName = root.getProductName() == null ? String.valueOf(root.getProductId()) : root.getProductName();
|
||||
if (source == null)
|
||||
{
|
||||
return rules;
|
||||
}
|
||||
for (PayProductFeeRule raw : source)
|
||||
{
|
||||
if (raw == null || isBlank(raw.getFeeName()))
|
||||
{
|
||||
if (raw != null && (feeRuleFilled(raw) || feeRuleChanged(raw)))
|
||||
{
|
||||
throw new ServiceException(productName + "有未选择名称的附加费");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
String feeName = raw.getFeeName().trim();
|
||||
if (feeName.length() > 100)
|
||||
{
|
||||
throw new ServiceException(productName + "的附加费名称过长");
|
||||
}
|
||||
if (!names.add(feeName))
|
||||
{
|
||||
throw new ServiceException(productName + "的附加费「" + feeName + "」重复了");
|
||||
}
|
||||
String trigger = isBlank(raw.getTriggerType()) ? "ALWAYS" : raw.getTriggerType().trim().toUpperCase(Locale.ROOT);
|
||||
if (!FEE_TRIGGERS.contains(trigger))
|
||||
{
|
||||
throw new ServiceException(productName + "的触发类型不正确");
|
||||
}
|
||||
String joinMode = isBlank(raw.getJoinMode()) ? "ANY" : raw.getJoinMode().trim().toUpperCase(Locale.ROOT);
|
||||
if (!"ANY".equals(joinMode) && !"ALL".equals(joinMode))
|
||||
{
|
||||
throw new ServiceException(productName + "的命中方式不正确");
|
||||
}
|
||||
String enabled = "1".equals(raw.getEnabled()) ? "1" : "0";
|
||||
checkFeeNumber(raw.getSideMaxCm(), productName, "最长边");
|
||||
checkFeeNumber(raw.getSumMaxCm(), productName, "长宽高之和");
|
||||
checkFeeNumber(raw.getVolumeMaxCm3(), productName, "体积");
|
||||
checkFeeNumber(raw.getMinLengthCm(), productName, "最小长度");
|
||||
checkFeeNumber(raw.getMinWidthCm(), productName, "最小宽度");
|
||||
checkFeeNumber(raw.getMinHeightCm(), productName, "最小高度");
|
||||
checkFeeNumber(raw.getWeightMaxKg(), productName, "重量上限");
|
||||
String remark = raw.getRemark() == null ? null : raw.getRemark().trim();
|
||||
if (remark != null && remark.length() > 500)
|
||||
{
|
||||
throw new ServiceException(productName + "的说明过长");
|
||||
}
|
||||
PayProductFeeRule rule = new PayProductFeeRule();
|
||||
rule.setProductId(root.getProductId());
|
||||
rule.setFeeName(feeName);
|
||||
rule.setEnabled(enabled);
|
||||
rule.setTriggerType(trigger);
|
||||
rule.setSideMaxCm(raw.getSideMaxCm());
|
||||
rule.setSumMaxCm(raw.getSumMaxCm());
|
||||
rule.setVolumeMaxCm3(raw.getVolumeMaxCm3());
|
||||
rule.setMinLengthCm(raw.getMinLengthCm());
|
||||
rule.setMinWidthCm(raw.getMinWidthCm());
|
||||
rule.setMinHeightCm(raw.getMinHeightCm());
|
||||
rule.setWeightMaxKg(raw.getWeightMaxKg());
|
||||
rule.setJoinMode(joinMode);
|
||||
rule.setRemark(isBlank(remark) ? null : remark);
|
||||
rule.setCreateBy(operator);
|
||||
rule.setCreateTime(now);
|
||||
rule.setUpdateBy(operator);
|
||||
rule.setUpdateTime(now);
|
||||
rules.add(rule);
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
private static boolean feeRuleChanged(PayProductFeeRule rule)
|
||||
{
|
||||
String trigger = rule.getTriggerType() == null ? "" : rule.getTriggerType().trim();
|
||||
String joinMode = rule.getJoinMode() == null ? "" : rule.getJoinMode().trim();
|
||||
return (!trigger.isEmpty() && !"ALWAYS".equalsIgnoreCase(trigger))
|
||||
|| (!joinMode.isEmpty() && !"ANY".equalsIgnoreCase(joinMode));
|
||||
}
|
||||
|
||||
private static boolean feeRuleFilled(PayProductFeeRule rule)
|
||||
{
|
||||
return rule.getSideMaxCm() != null || rule.getSumMaxCm() != null || rule.getVolumeMaxCm3() != null
|
||||
|| rule.getMinLengthCm() != null || rule.getMinWidthCm() != null || rule.getMinHeightCm() != null
|
||||
|| rule.getWeightMaxKg() != null || !isBlank(rule.getRemark()) || "1".equals(rule.getEnabled());
|
||||
}
|
||||
|
||||
private static void checkFeeNumber(BigDecimal value, String productName, String label)
|
||||
{
|
||||
if (value != null && value.signum() < 0)
|
||||
{
|
||||
throw new ServiceException(productName + "的" + label + "不能小于0");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isBlank(String value)
|
||||
{
|
||||
return value == null || value.trim().isEmpty();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bms.price.mapper.PayProductFeeRuleMapper">
|
||||
|
||||
<select id="selectByProductId" resultType="com.bms.price.domain.PayProductFeeRule">
|
||||
select rule_id as ruleId, product_id as productId, fee_name as feeName, enabled,
|
||||
trigger_type as triggerType, side_max_cm as sideMaxCm, sum_max_cm as sumMaxCm,
|
||||
volume_max_cm3 as volumeMaxCm3, min_length_cm as minLengthCm, min_width_cm as minWidthCm,
|
||||
min_height_cm as minHeightCm, weight_max_kg as weightMaxKg, join_mode as joinMode,
|
||||
remark, created_by as createBy, create_time as createTime, updated_by as updateBy,
|
||||
update_time as updateTime
|
||||
from pay_product_fee_rule
|
||||
where product_id = #{productId}
|
||||
order by rule_id
|
||||
</select>
|
||||
|
||||
<delete id="deleteByProductId">
|
||||
delete from pay_product_fee_rule where product_id = #{productId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByProductIds">
|
||||
delete from pay_product_fee_rule where product_id in
|
||||
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
</delete>
|
||||
|
||||
<insert id="insertBatch">
|
||||
insert into pay_product_fee_rule (
|
||||
product_id, fee_name, enabled, trigger_type, side_max_cm, sum_max_cm, volume_max_cm3,
|
||||
min_length_cm, min_width_cm, min_height_cm, weight_max_kg, join_mode, remark,
|
||||
created_by, create_time, updated_by, update_time
|
||||
) values
|
||||
<foreach collection="list" item="item" separator=",">
|
||||
(#{item.productId}, #{item.feeName}, #{item.enabled}, #{item.triggerType},
|
||||
#{item.sideMaxCm}, #{item.sumMaxCm}, #{item.volumeMaxCm3},
|
||||
#{item.minLengthCm}, #{item.minWidthCm}, #{item.minHeightCm}, #{item.weightMaxKg},
|
||||
#{item.joinMode}, #{item.remark}, #{item.createBy}, #{item.createTime},
|
||||
#{item.updateBy}, #{item.updateTime})
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user