提交价格功能绑定

This commit is contained in:
wuweihua
2026-09-07 10:41:21 +08:00
parent 7204497a65
commit 10352fca6d
12 changed files with 578 additions and 105 deletions
@@ -24,6 +24,10 @@ public class BmsProductCountry implements Serializable
private BigDecimal weightMin;
/** 重量段最大值 KG */
private BigDecimal weightMax;
/** 运费 RMB/KG */
private BigDecimal freightPrice;
/** 挂号费 RMB/票 */
private BigDecimal registrationFee;
private String sizeLimit;
private String lastMile;
private String leadTime;
@@ -46,6 +50,10 @@ public class BmsProductCountry implements Serializable
public void setWeightMin(BigDecimal weightMin) { this.weightMin = weightMin; }
public BigDecimal getWeightMax() { return weightMax; }
public void setWeightMax(BigDecimal weightMax) { this.weightMax = weightMax; }
public BigDecimal getFreightPrice() { return freightPrice; }
public void setFreightPrice(BigDecimal freightPrice) { this.freightPrice = freightPrice; }
public BigDecimal getRegistrationFee() { return registrationFee; }
public void setRegistrationFee(BigDecimal registrationFee) { this.registrationFee = registrationFee; }
public String getSizeLimit() { return sizeLimit; }
public void setSizeLimit(String sizeLimit) { this.sizeLimit = sizeLimit; }
public String getLastMile() { return lastMile; }
@@ -69,6 +77,8 @@ public class BmsProductCountry implements Serializable
.append("countryCode", getCountryCode())
.append("weightMin", getWeightMin())
.append("weightMax", getWeightMax())
.append("freightPrice", getFreightPrice())
.append("registrationFee", getRegistrationFee())
.toString();
}
}
@@ -25,7 +25,7 @@ public interface IBmsProductService
public List<BmsProductCountry> selectCountryListByProductCode(String productCode);
/**
* 导入威狮B2C小包价表:产品目录 + 各产品 sheet 国家服务属性(不含运费/挂号费)
* 导入威狮B2C小包价表:产品目录 + 各产品 sheet 国家×重量段(含运费/挂号费/尺寸/尾程/时效)
*
* @param file 上传的 xlsx/xls
* @param updateSupport 是否更新已存在产品
@@ -198,6 +198,14 @@ public class BmsProductServiceImpl implements IBmsProductService
updateNum++;
countryNum += countries.size();
}
else if (!countries.isEmpty())
{
// Product already exists and "update" unchecked: still refresh country/fee rows from Excel.
countryMapper.deleteByProductCode(product.getProductCode());
saveCountries(product.getProductCode(), countries);
updateNum++;
countryNum += countries.size();
}
else
{
skipNum++;
@@ -216,7 +224,7 @@ public class BmsProductServiceImpl implements IBmsProductService
}
String msg = "\u5bfc\u5165\u6210\u529f\uff1a\u65b0\u589e " + successNum + " \u4e2a\u4ea7\u54c1\uff0c\u66f4\u65b0 " + updateNum + " \u4e2a\uff0c\u8df3\u8fc7 "
+ skipNum + " \u4e2a\uff1b\u5199\u5165\u56fd\u5bb6/\u91cd\u91cf\u6bb5\u660e\u7ec6 " + countryNum + " \u884c\u3002"
+ "<br/>\u8303\u56f4\uff1a\u4ea7\u54c1\u76ee\u5f55 + \u91cd\u91cf\u6bb5min/max + \u5c3a\u5bf8/\u5c3e\u7a0b/\u65f6\u6548\uff1b\u56fd\u5bb6\u7801\u6765\u81ea bms_region.country_name_cn / country_code_2\u3002";
+ "<br/>\u8303\u56f4\uff1a\u4ea7\u54c1\u76ee\u5f55 + \u91cd\u91cf\u6bb5 + \u8fd0\u8d39/\u6302\u53f7\u8d39 + \u5c3a\u5bf8/\u5c3e\u7a0b/\u65f6\u6548\uff1b\u56fd\u5bb6\u7801\u6765\u81ea bms_region\u3002";
if (!unmatchedCountries.isEmpty())
{
msg += "<br/>\u672a\u5339\u914d\u56fd\u5bb6\u8868\uff08country_code \u4e3a\u7a7a\uff09\uff1a" + String.join("\u3001", unmatchedCountries);
@@ -230,7 +238,20 @@ public class BmsProductServiceImpl implements IBmsProductService
for (BmsProductCountry c : countries)
{
c.setProductCode(productCode);
countryMapper.insertBmsProductCountry(c);
try
{
countryMapper.insertBmsProductCountry(c);
}
catch (Exception e)
{
String tip = e.getMessage() == null ? "" : e.getMessage();
if (tip.contains("freight_price") || tip.contains("registration_fee") || tip.contains("Unknown column"))
{
throw new ServiceException(
"\u8bf7\u5148\u6267\u884c SQL\uff1abms-back/sql/price/product_country_fee_alter.sql\uff08\u7f3a\u5c11\u8fd0\u8d39/\u6302\u53f7\u8d39\u5b57\u6bb5\uff09");
}
throw e;
}
}
}
@@ -308,24 +329,60 @@ public class BmsProductServiceImpl implements IBmsProductService
Date sheetEffective = parseSheetEffectiveDate(sheet);
List<BmsProductCountry> list = new ArrayList<>();
// Product sheets also often start at column B.
int[] header = findHeaderCell(sheet, "\u56fd\u5bb6");
int baseCol = header != null ? header[1] : 0;
int startRow = header != null ? header[0] + 1 : 0;
int[] countryHeader = findHeaderCell(sheet, "\u56fd\u5bb6");
if (countryHeader == null)
{
return list;
}
int headerRowIdx = countryHeader[0];
Row headerRow = sheet.getRow(headerRowIdx);
int colCountry = countryHeader[1];
int colWeight = findColByHeader(headerRow, "\u91cd\u91cf", colCountry + 1);
int colFreight = findColByHeader(headerRow, "\u8fd0\u8d39", colCountry + 1);
int colRegister = findColByHeader(headerRow, "\u6302\u53f7", colCountry + 1);
int colSize = findColByHeader(headerRow, "\u5c3a\u5bf8", colCountry + 1);
int colLastMile = findColByHeader(headerRow, "\u5c3e\u7a0b", colCountry + 1);
int colLeadTime = findColByHeader(headerRow, "\u65f6\u6548", colCountry + 1);
// Fallback offsets when header text missing (Weishi layout: B=country ... H=lead time)
if (colWeight < 0)
{
colWeight = colCountry + 1;
}
if (colFreight < 0)
{
colFreight = colCountry + 2;
}
if (colRegister < 0)
{
colRegister = colCountry + 3;
}
if (colSize < 0)
{
colSize = colCountry + 4;
}
if (colLastMile < 0)
{
colLastMile = colCountry + 5;
}
if (colLeadTime < 0)
{
colLeadTime = colCountry + 6;
}
String currentCountry = null;
String currentSize = null;
String currentLastMile = null;
String currentLeadTime = null;
int lastRow = sheet.getLastRowNum();
for (int i = startRow; i <= lastRow; i++)
for (int i = headerRowIdx + 1; i <= lastRow; i++)
{
Row row = sheet.getRow(i);
if (row == null)
{
continue;
}
String countryCell = cellString(row.getCell(baseCol));
String weightLabel = cellString(row.getCell(baseCol + 1));
String countryCell = cellString(row.getCell(colCountry));
String weightLabel = cellString(row.getCell(colWeight));
if ("\u56fd\u5bb6".equals(countryCell) || weightLabel.contains("\u91cd\u91cf\u6bb5"))
{
continue;
@@ -333,9 +390,9 @@ public class BmsProductServiceImpl implements IBmsProductService
if (StringUtils.isNotEmpty(countryCell))
{
currentCountry = countryCell.trim();
currentSize = emptyToNull(cellString(row.getCell(baseCol + 4)));
currentLastMile = emptyToNull(cellString(row.getCell(baseCol + 5)));
currentLeadTime = emptyToNull(cellString(row.getCell(baseCol + 6)));
currentSize = emptyToNull(cellString(row.getCell(colSize)));
currentLastMile = emptyToNull(cellString(row.getCell(colLastMile)));
currentLeadTime = emptyToNull(cellString(row.getCell(colLeadTime)));
}
if (StringUtils.isEmpty(currentCountry))
{
@@ -361,6 +418,8 @@ public class BmsProductServiceImpl implements IBmsProductService
}
c.setWeightMin(range[0]);
c.setWeightMax(range[1]);
c.setFreightPrice(cellDecimal(row.getCell(colFreight)));
c.setRegistrationFee(cellDecimal(row.getCell(colRegister)));
c.setSizeLimit(currentSize);
c.setLastMile(currentLastMile);
c.setLeadTime(currentLeadTime);
@@ -371,7 +430,7 @@ public class BmsProductServiceImpl implements IBmsProductService
}
/**
* ???? bms_region??country_name_cn -> ????? country_code_2??
* Load bms_region map: country_name_cn -> region (for country_code_2).
*/
private Map<String, BmsRegion> loadRegionByNameCn()
{
@@ -395,7 +454,7 @@ public class BmsProductServiceImpl implements IBmsProductService
}
/**
* ????? country_name_cn ?????????????????????????? ??? / ?????????
* Exact country_name_cn match first; single contains match as fallback (e.g. Czech / Czech Republic).
*/
private BmsRegion resolveRegion(String excelName, Map<String, BmsRegion> regionByNameCn)
{
@@ -507,6 +566,28 @@ public class BmsProductServiceImpl implements IBmsProductService
return "TAX_INCLUDED";
}
/**
* Find column index on header row whose text contains keyword; -1 if not found.
*/
private static int findColByHeader(Row headerRow, String keyword, int fromCol)
{
if (headerRow == null || StringUtils.isEmpty(keyword))
{
return -1;
}
int last = Math.max(headerRow.getLastCellNum(), fromCol + 12);
last = Math.min(last, 30);
for (int c = Math.max(fromCol, 0); c < last; c++)
{
String v = cellString(headerRow.getCell(c));
if (StringUtils.isNotEmpty(v) && v.contains(keyword))
{
return c;
}
}
return -1;
}
private static String emptyToNull(String s)
{
return StringUtils.isEmpty(s) ? null : s;
@@ -595,6 +676,57 @@ public class BmsProductServiceImpl implements IBmsProductService
}
}
private static BigDecimal cellDecimal(Cell cell)
{
if (cell == null)
{
return null;
}
CellType type = cell.getCellType();
if (type == CellType.FORMULA)
{
type = cell.getCachedFormulaResultType();
}
try
{
if (type == CellType.NUMERIC)
{
return BigDecimal.valueOf(cell.getNumericCellValue());
}
String s = cellString(cell);
if (StringUtils.isEmpty(s))
{
return null;
}
// strip currency / unit suffixes: 56、56元、56 RMB
s = s.replace(",", "").replace("\uffe5", "").replace("\u5143", "")
.replace("RMB", "").replace("rmb", "").trim();
if (s.isEmpty())
{
return null;
}
int cut = -1;
for (int i = 0; i < s.length(); i++)
{
char ch = s.charAt(i);
if (!(ch == '.' || ch == '-' || (ch >= '0' && ch <= '9')))
{
cut = i;
break;
}
}
if (cut > 0)
{
s = s.substring(0, cut).trim();
}
return new BigDecimal(s);
}
catch (Exception ignored)
{
return null;
}
}
private static Date cellDate(Cell cell)
{
if (cell == null)
@@ -332,7 +332,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
int incomplete = 0;
for (PrcPriceSetDetail d : details)
{
if (StringUtils.isEmpty(d.getProductCode()) || StringUtils.isEmpty(d.getCountryCode())
boolean noCountry = StringUtils.isEmpty(d.getCountryCode()) && StringUtils.isEmpty(d.getCountryName());
if (StringUtils.isEmpty(d.getProductCode()) || noCountry
|| (d.getWsdId() == null && StringUtils.isEmpty(d.getWeightLabel()))
|| d.getFvValue() == null || d.getFvRegistrationFee() == null)
{
@@ -341,15 +342,16 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
if (details.isEmpty())
{
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "FAIL", "固定价模式至少需要 1 条明细"));
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "PASS",
"\u65e0\u5957\u9910\u660e\u7ec6\uff1a\u8ba1\u8d39\u53d6 bms_product / bms_product_country \u9ed8\u8ba4\u8fd0\u8d39\u4e0e\u6302\u53f7\u8d39"));
}
else if (incomplete > 0)
{
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "FAIL", incomplete + " 条明细缺少产品/国家/重量段/运费/挂号费"));
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "FAIL", incomplete + " \u6761\u660e\u7ec6\u7f3a\u5c11\u4ea7\u54c1/\u56fd\u5bb6/\u91cd\u91cf\u6bb5/\u8fd0\u8d39/\u6302\u53f7\u8d39"));
}
else
{
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "PASS", details.size() + " 条明细,运费与挂号费均填写"));
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "PASS", details.size() + " \u6761\u8986\u76d6\u660e\u7ec6\uff08\u672a\u8986\u76d6\u7684\u4ea7\u54c1\u56fd\u5bb6\u91cd\u91cf\u6bb5\u4ecd\u8d70\u9ed8\u8ba4\u4ef7\uff09"));
}
}
else
@@ -418,11 +420,15 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
if (TYPE_FIXED.equals(setType) && !details.isEmpty())
{
items.add(new PrcPriceSetCheckItem("运费单价 vs 默认等级价偏差", "INFO", "默认等级价对照将在计费试算上线后自动计算"));
items.add(new PrcPriceSetCheckItem("\u8fd0\u8d39\u5355\u4ef7 vs \u9ed8\u8ba4\u7b49\u7ea7\u4ef7\u504f\u5dee", "INFO", "\u6709\u8986\u76d6\u660e\u7ec6\u65f6\u4f18\u5148\u53d6\u5957\u9910\u56fa\u5b9a\u4ef7\uff1b\u672a\u547d\u4e2d\u5219\u56de\u9000 bms_product_country"));
}
else if (TYPE_FIXED.equals(setType))
{
items.add(new PrcPriceSetCheckItem("\u8fd0\u8d39\u5355\u4ef7 vs \u9ed8\u8ba4\u7b49\u7ea7\u4ef7\u504f\u5dee", "INFO", "\u65e0\u5957\u9910\u660e\u7ec6\uff0c\u5168\u90e8\u8d70 bms_product_country \u9ed8\u8ba4\u8fd0\u8d39/\u6302\u53f7\u8d39"));
}
else if (TYPE_DISCOUNT.equals(setType))
{
items.add(new PrcPriceSetCheckItem("运费单价 vs 默认等级价偏差", "INFO", "折扣模式按下单时默认等级价 × 折扣计费"));
items.add(new PrcPriceSetCheckItem("\u8fd0\u8d39\u5355\u4ef7 vs \u9ed8\u8ba4\u7b49\u7ea7\u4ef7\u504f\u5dee", "INFO", "\u6298\u6263\u6a21\u5f0f\u6309\u4e0b\u5355\u65f6\u9ed8\u8ba4\u7b49\u7ea7\u4ef7 \u00d7 \u6298\u6263\u8ba1\u8d39"));
}
return items;
@@ -599,9 +605,9 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
if (TYPE_FIXED.equals(set.getSetType()))
{
if (StringUtils.isEmpty(detail.getCountryCode()))
if (StringUtils.isEmpty(detail.getCountryCode()) && StringUtils.isEmpty(detail.getCountryName()))
{
throw new ServiceException("固定价明细必须填写国家");
throw new ServiceException("\u56fa\u5b9a\u4ef7\u660e\u7ec6\u5fc5\u987b\u586b\u5199\u56fd\u5bb6");
}
if (detail.getWsdId() == null && StringUtils.isEmpty(detail.getWeightLabel()))
{