From 10352fca6deebed02c1a9a487f6841d915736307 Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Mon, 7 Sep 2026 10:41:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=B7=E6=A0=BC=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=BB=91=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ruoyi/price/domain/BmsProductCountry.java | 10 + .../price/service/IBmsProductService.java | 2 +- .../service/impl/BmsProductServiceImpl.java | 160 +++++++++++- .../service/impl/PrcPriceSetServiceImpl.java | 22 +- .../mapper/price/BmsProductCountryMapper.xml | 35 +-- bms-back/sql/price/product.sql | 8 +- .../sql/price/product_country_fee_alter.sql | 7 + .../price/product_country_fee_fix_comment.sql | 6 + .../components/ExcelImportDialog/index.vue | 7 +- .../src/views/price/basic/product/index.vue | 11 +- bms-front/src/views/price/set/create.vue | 242 ++++++++++++++---- bms-front/src/views/price/set/detail.vue | 173 ++++++++++++- 12 files changed, 578 insertions(+), 105 deletions(-) create mode 100644 bms-back/sql/price/product_country_fee_alter.sql create mode 100644 bms-back/sql/price/product_country_fee_fix_comment.sql diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java index 5a99c157..8938ded7 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/domain/BmsProductCountry.java @@ -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(); } } diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IBmsProductService.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IBmsProductService.java index ddf988f2..afb1fca4 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IBmsProductService.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/IBmsProductService.java @@ -25,7 +25,7 @@ public interface IBmsProductService public List selectCountryListByProductCode(String productCode); /** - * 导入威狮B2C小包价表:产品目录 + 各产品 sheet 国家服务属性(不含运费/挂号费) + * 导入威狮B2C小包价表:产品目录 + 各产品 sheet 国家×重量段(含运费/挂号费/尺寸/尾程/时效) * * @param file 上传的 xlsx/xls * @param updateSupport 是否更新已存在产品 diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java index 953bf11f..e014e462 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/BmsProductServiceImpl.java @@ -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" - + "
\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"; + + "
\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 += "
\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 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 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 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) diff --git a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java index 60840c93..78415dcd 100644 --- a/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java +++ b/bms-back/ruoyi-system/src/main/java/com/ruoyi/price/service/impl/PrcPriceSetServiceImpl.java @@ -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())) { diff --git a/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml b/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml index 1a7017d9..5ca2e97b 100644 --- a/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml +++ b/bms-back/ruoyi-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml @@ -5,23 +5,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - - - - - - - - - - + + + + + + + + + + + + + +