diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java index 9bad75c1..dab60160 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/PriceSetController.java @@ -102,6 +102,16 @@ public class PriceSetController extends BaseController return toAjax(priceSetService.updatePrcPriceSet(set)); } + /** Update package name and note only. */ + @PreAuthorize("@ss.hasPermi('price:set:edit')") + @Log(title = "价格套餐基本信息", businessType = BusinessType.UPDATE) + @PutMapping("/basic") + public AjaxResult editBasic(@RequestBody PrcPriceSet set) + { + set.setUpdateBy(getUsername()); + return toAjax(priceSetService.updateBasicInfo(set)); + } + @PreAuthorize("@ss.hasPermi('price:set:remove')") @Log(title = "价格套餐", businessType = BusinessType.DELETE) @DeleteMapping("/{priceSetIds}") diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java index 8f260747..32ec60c5 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSet.java @@ -34,8 +34,12 @@ public class PrcPriceSet extends BaseEntity private Integer productCount; private Integer customerCount; + /** List display: bound customer code/name summary (not a DB column). */ + private String boundCustomers; private Boolean expired; private String keyword; + /** Query only: filter packages by bound customer id. */ + private Long customerId; private String submitFlag; /** current version on create/edit save */ private PrcPriceSetVersion version; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetMapper.java index 86f20570..5174b0f7 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetMapper.java @@ -21,6 +21,9 @@ public interface PrcPriceSetMapper public int updatePrcPriceSet(PrcPriceSet set); + /** Update name / note only (does not touch set_type / discount / status). */ + public int updateBasicInfo(PrcPriceSet set); + public int updatePrcPriceSetStatus(PrcPriceSet set); public int deletePrcPriceSetByIds(Long[] priceSetIds); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java index 4b10014c..e2d9dd2f 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IPrcPriceSetService.java @@ -26,6 +26,9 @@ public interface IPrcPriceSetService public int updatePrcPriceSet(PrcPriceSet set); + /** Update package name and note only; allowed for effect / draft / stopped. */ + public int updateBasicInfo(PrcPriceSet set); + public int deletePrcPriceSetByIds(Long[] priceSetIds); public int submit(Long priceSetId, String updateBy); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java index dfc53670..b44c7018 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsProductServiceImpl.java @@ -383,12 +383,16 @@ public class BmsProductServiceImpl implements IBmsProductService try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) { + assertNotSingleCountryQuoteForMultiImport(workbook); + if (workbookHasInlineProductColumns(workbook)) + { + return importProductExcelByInline(workbook, bytes, filename, operName, taskId, forcedEff); + } Sheet catalog = workbook.getSheet(CATALOG_SHEET); if (catalog == null) { throw new ServiceException(MessageUtils.message("price.product.import.sheetNotFound")); } - assertNotSingleCountryQuoteForMultiImport(workbook); List products = parseCatalog(catalog); if (products.isEmpty()) @@ -433,7 +437,7 @@ public class BmsProductServiceImpl implements IBmsProductService try { int[] nums = importOneProductWithDeadlockRetry(workbook, product, operName, regionByNameCn, - euRegions, zoneByCountryAndCode, unmatchedCountries, forcedEff, importPsvId); + euRegions, zoneByCountryAndCode, unmatchedCountries, forcedEff, importPsvId, null); successNum += nums[0]; skipNum += nums[1]; countryNum += nums[2]; @@ -684,12 +688,18 @@ public class BmsProductServiceImpl implements IBmsProductService } try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) { + assertNotSingleCountryQuoteForMultiImport(workbook); + // Detail sheets with inline product-code / product-name column: identify by those + // columns, not by catalog sheet-name matching (variants like RM/IP/EV share one sheet). + if (workbookHasInlineProductColumns(workbook)) + { + return readQuoteWorkbookByInlineProductCode(workbook); + } Sheet catalog = workbook.getSheet(CATALOG_SHEET); if (catalog == null) { throw new ServiceException(MessageUtils.message("price.product.import.sheetNotFound")); } - assertNotSingleCountryQuoteForMultiImport(workbook); List products = dedupeCatalogProducts(parseCatalog(catalog)); if (products.isEmpty()) { @@ -791,6 +801,543 @@ public class BmsProductServiceImpl implements IBmsProductService private Map> byCode; } + /** + * Formal product import when price sheets carry inline product-code / product-name columns. + * Identifies products from detail rows (not catalog sheet-name matching). + */ + private String importProductExcelByInline(Workbook workbook, byte[] bytes, String filename, + String operName, String taskId, Date forcedEff) throws Exception + { + QuoteWorkbook parsed = readQuoteWorkbookByInlineProductCode(workbook); + List products = enrichInlineProductsFromCatalog(workbook, parsed.products); + if (products.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.noData")); + } + reportImportProgress(taskId, 20, "parsing excel"); + assertImportProductsEffectiveNotBeforePrev(products, forcedEff); + + Map regionByNameCn = loadRegionByNameCn(); + if (regionByNameCn.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.regionEmpty")); + } + List euRegions = loadEuRegions(regionByNameCn); + Map zoneByCountryAndCode = loadZoneByCountryAndCode(); + + Long importPsvId = forcedEff == null + ? catalogVersionSupport.ensureDraftVersion(PriceType.PRODUCT, null, false).getPsvId() + : catalogVersionSupport.requirePsvId(PriceType.PRODUCT, null, forcedEff); + priceExcelFileService.saveImportExcel( + PrcPriceExcelFile.TYPE_PRODUCT, forcedEff, bytes, filename, operName, importPsvId); + + int successNum = 0; + int skipNum = 0; + int countryNum = 0; + Set unmatchedCountries = new HashSet(); + StringBuilder failureMsg = new StringBuilder(); + int total = products.size(); + int index = 0; + for (BmsProduct product : products) + { + index++; + if (product == null || StringUtils.isEmpty(product.getProductCode())) + { + continue; + } + String code = product.getProductCode().trim().toUpperCase(Locale.ROOT); + reportImportProductProgress(taskId, index, total, product.getProductCode()); + List countries = parsed.byCode.get(code); + try + { + int[] nums = importOneProductWithDeadlockRetry(workbook, product, operName, regionByNameCn, + euRegions, zoneByCountryAndCode, unmatchedCountries, forcedEff, importPsvId, countries); + successNum += nums[0]; + skipNum += nums[1]; + countryNum += nums[2]; + } + catch (Exception e) + { + String tip = e.getMessage(); + if (StringUtils.isEmpty(tip)) + { + tip = e.getClass().getSimpleName(); + } + failureMsg.append("
").append(product.getProductCode()).append(":").append(tip); + } + } + reportImportProgress(taskId, 95, "finishing"); + if (failureMsg.length() > 0) + { + return MessageUtils.message("price.product.import.partialFail", + Integer.valueOf(successNum), Integer.valueOf(skipNum), failureMsg.toString()); + } + String msg = MessageUtils.message("price.product.import.success", successNum, skipNum, countryNum) + + "
" + MessageUtils.message("price.product.import.success.detail1") + + "
" + MessageUtils.message("price.product.import.success.detail2"); + if (!unmatchedCountries.isEmpty()) + { + msg += "
" + MessageUtils.message("price.product.import.success.unmatched", + String.join("、", unmatchedCountries)); + } + return msg; + } + + /** Prefer catalog metadata when code matches; keep detail product name for variants. */ + private List enrichInlineProductsFromCatalog(Workbook workbook, List inlineProducts) + { + Map catalogByCode = new HashMap(); + Sheet catalog = workbook == null ? null : workbook.getSheet(CATALOG_SHEET); + if (catalog != null) + { + try + { + for (BmsProduct p : dedupeCatalogProducts(parseCatalog(catalog))) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + catalogByCode.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + catch (Exception ignored) + { + // Catalog optional for inline import. + } + } + List out = new ArrayList(); + if (inlineProducts == null) + { + return out; + } + for (BmsProduct inline : inlineProducts) + { + if (inline == null || StringUtils.isEmpty(inline.getProductCode())) + { + continue; + } + String code = inline.getProductCode().trim().toUpperCase(Locale.ROOT); + BmsProduct cat = catalogByCode.get(code); + if (cat == null) + { + out.add(inline); + continue; + } + BmsProduct merged = new BmsProduct(); + merged.setProductCode(inline.getProductCode()); + merged.setProductName(StringUtils.isNotEmpty(inline.getProductName()) + ? inline.getProductName() : cat.getProductName()); + merged.setServiceLevel(cat.getServiceLevel()); + merged.setCargoAttr(cat.getCargoAttr()); + merged.setDestRegion(cat.getDestRegion()); + merged.setUpdateNote(cat.getUpdateNote()); + merged.setEffectiveDate(cat.getEffectiveDate()); + merged.setTaxMode(StringUtils.isNotEmpty(cat.getTaxMode()) + ? cat.getTaxMode() : resolveTaxMode(merged.getProductName())); + out.add(merged); + } + return out; + } + + /** + * True when any multi-country price sheet has an inline product-code or product-name column + * (产品代码 / 产品对接代码 / 产品名称). + */ + private boolean workbookHasInlineProductColumns(Workbook workbook) + { + if (workbook == null) + { + return false; + } + for (int i = 0; i < workbook.getNumberOfSheets(); i++) + { + String sheetName = workbook.getSheetName(i); + if (shouldSkipCountryPriceSheet(sheetName)) + { + continue; + } + Sheet sheet = workbook.getSheetAt(i); + if (sheet == null) + { + continue; + } + int[] countryHeader = findHeaderCell(sheet, "国家"); + if (countryHeader == null) + { + continue; + } + Row head = sheet.getRow(countryHeader[0]); + if (head == null) + { + continue; + } + if (firstCol(head, "产品代码", "产品对接代码") >= 0 + || findColByHeader(head, "产品名称", 0) >= 0) + { + return true; + } + } + return false; + } + + /** + * Multi-country parse keyed by inline product-code column on price sheets. + * Does not use 产品目录 sheet matching. + */ + private QuoteWorkbook readQuoteWorkbookByInlineProductCode(Workbook workbook) throws Exception + { + Map catalogCodeByName = buildCatalogCodeByName(workbook); + Map regionByNameCn = loadRegionByNameCn(); + List euRegions = loadEuRegions(regionByNameCn); + Map zoneByCountryAndCode = loadZoneByCountryAndCode(); + Set unmatchedCountries = new HashSet(); + Map> out = new LinkedHashMap>(); + Map productMeta = new LinkedHashMap(); + List tips = new ArrayList(); + + for (int i = 0; i < workbook.getNumberOfSheets(); i++) + { + String sheetName = workbook.getSheetName(i); + if (shouldSkipCountryPriceSheet(sheetName)) + { + continue; + } + Sheet sheet = workbook.getSheetAt(i); + if (sheet == null || findHeaderCell(sheet, "国家") == null) + { + continue; + } + Map> sheetByCode = parseProductSheetByInlineCode( + sheet, catalogCodeByName, regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries); + if (sheetByCode == null || sheetByCode.isEmpty()) + { + continue; + } + for (Map.Entry> e : sheetByCode.entrySet()) + { + if (e == null || StringUtils.isEmpty(e.getKey()) || e.getValue() == null || e.getValue().isEmpty()) + { + continue; + } + String code = e.getKey(); + List rows = e.getValue(); + List existing = out.get(code); + if (existing == null) + { + out.put(code, new ArrayList(rows)); + } + else + { + existing.addAll(rows); + } + if (!productMeta.containsKey(code)) + { + BmsProduct p = new BmsProduct(); + p.setProductCode(code); + String name = null; + for (BmsProductCountry r : rows) + { + if (r != null && StringUtils.isNotEmpty(r.getProductName())) + { + name = r.getProductName(); + break; + } + } + p.setProductName(StringUtils.isNotEmpty(name) ? name : code); + p.setTaxMode(resolveTaxMode(p.getProductName())); + productMeta.put(code, p); + } + } + } + + for (Map.Entry> e : out.entrySet()) + { + BmsProduct p = productMeta.get(e.getKey()); + String tip = buildDuplicateCountryWeightTip(p, e.getValue()); + if (StringUtils.isNotEmpty(tip)) + { + tips.add(tip); + } + } + if (!tips.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.dupCountry", + String.join("
", tips))); + } + if (out.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); + } + QuoteWorkbook parsed = new QuoteWorkbook(); + parsed.products = new ArrayList(productMeta.values()); + parsed.byCode = out; + return parsed; + } + + /** + * Parse one multi-country price sheet grouped by inline product code / product name. + * Prefer product-code column; if only product-name exists, resolve code via catalog name map. + * Country / product-code / product-name / size / last-mile / lead-time carry forward + * when subsequent weight-band rows leave those cells blank. + */ + private Map> parseProductSheetByInlineCode(Sheet sheet, + Map catalogCodeByName, Map regionByNameCn, + List euRegions, Map zoneByCountryAndCode, + Set unmatchedCountries) + { + Map> out = new LinkedHashMap>(); + if (sheet == null) + { + return out; + } + Date sheetEffective = parseSheetEffectiveDate(sheet); + int[] countryHeader = findHeaderCell(sheet, "国家"); + if (countryHeader == null) + { + return out; + } + int headerRowIdx = countryHeader[0]; + Row headerRow = sheet.getRow(headerRowIdx); + int colCountry = countryHeader[1]; + int colProductCode = firstCol(headerRow, "产品代码", "产品对接代码"); + int colProductName = findColByHeader(headerRow, "产品名称", 0); + if (colProductCode < 0 && colProductName < 0) + { + return out; + } + int colWeight = findColByHeader(headerRow, "重量", colCountry + 1); + int colFreight = findColByHeader(headerRow, "运费", colCountry + 1); + int colRegister = findColByHeader(headerRow, "挂号", colCountry + 1); + int colSize = findColByHeader(headerRow, "尺寸", colCountry + 1); + int colLastMile = findColByHeader(headerRow, "尾程", colCountry + 1); + int colLeadTime = findColByHeader(headerRow, "时效", colCountry + 1); + if (colWeight < 0 || colFreight < 0) + { + return out; + } + + String currentCountryRaw = null; + String currentProductCode = null; + String currentProductName = null; + String currentSize = null; + String currentLastMile = null; + String currentLeadTime = null; + int lastRow = sheet.getLastRowNum(); + for (int i = headerRowIdx + 1; i <= lastRow; i++) + { + Row row = sheet.getRow(i); + if (row == null) + { + continue; + } + String countryCell = cellString(row.getCell(colCountry)); + String weightLabel = cellString(row.getCell(colWeight)); + if ("国家".equals(countryCell) + || (weightLabel != null && weightLabel.contains("重量段"))) + { + continue; + } + if (StringUtils.isNotEmpty(countryCell)) + { + currentCountryRaw = countryCell; + } + boolean productChanged = false; + if (colProductCode >= 0) + { + String codeCell = normalizeImportProductCode(cellString(row.getCell(colProductCode))); + if (StringUtils.isNotEmpty(codeCell)) + { + String newCode = codeCell.trim().toUpperCase(Locale.ROOT); + if (currentProductCode == null || !newCode.equals(currentProductCode)) + { + productChanged = true; + currentProductCode = newCode; + } + } + } + if (colProductName >= 0) + { + String nameCell = cellString(row.getCell(colProductName)); + if (StringUtils.isNotEmpty(nameCell) && !isSectionTitle(nameCell)) + { + String newName = nameCell.replace("\n", " ").replace("\r", " ").trim(); + if (currentProductName == null || !newName.equals(currentProductName)) + { + productChanged = true; + currentProductName = newName; + // Name-only sheets: resolve product code from catalog by product name. + if (colProductCode < 0) + { + String mapped = resolveCodeByProductName(currentProductName, catalogCodeByName); + if (StringUtils.isNotEmpty(mapped)) + { + currentProductCode = mapped; + } + else + { + currentProductCode = null; + } + } + } + } + } + if (productChanged) + { + currentSize = null; + currentLastMile = null; + currentLeadTime = null; + } + if (colSize >= 0 && StringUtils.isNotEmpty(cellString(row.getCell(colSize)))) + { + currentSize = emptyToNull(cellString(row.getCell(colSize))); + } + if (colLastMile >= 0 && StringUtils.isNotEmpty(cellString(row.getCell(colLastMile)))) + { + currentLastMile = emptyToNull(cellString(row.getCell(colLastMile))); + } + if (colLeadTime >= 0 && StringUtils.isNotEmpty(cellString(row.getCell(colLeadTime)))) + { + currentLeadTime = emptyToNull(cellString(row.getCell(colLeadTime))); + } + if (StringUtils.isEmpty(currentCountryRaw) || StringUtils.isEmpty(currentProductCode)) + { + continue; + } + BigDecimal[] range = parseWeightRange(weightLabel); + if (range == null) + { + continue; + } + BigDecimal freight = cellDecimal(row.getCell(colFreight)); + BigDecimal registration = colRegister >= 0 ? cellDecimal(row.getCell(colRegister)) : null; + Date eff = sheetEffective; + + ZoneCodeAbbrevs.CountryZoneParts parts = ZoneCodeAbbrevs.splitCountryZone(currentCountryRaw); + if (parts == null || StringUtils.isEmpty(parts.countryName)) + { + continue; + } + String baseCountry = parts.countryName; + String zoneLabel = parts.zoneLabel; + List bucket = out.get(currentProductCode); + if (bucket == null) + { + bucket = new ArrayList(); + out.put(currentProductCode, bucket); + } + + BmsRegion region = resolveRegion(baseCountry, regionByNameCn); + if (region != null) + { + BmsProductCountry c = buildCountryRow(region.getCountryNameCn(), region.getCountryCode2(), + zoneLabel, zoneByCountryAndCode, + range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff); + c.setProductCode(currentProductCode); + c.setProductName(currentProductName); + bucket.add(c); + continue; + } + if (isEuZoneName(baseCountry) && StringUtils.isEmpty(zoneLabel)) + { + if (euRegions == null || euRegions.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.euEmpty")); + } + for (BmsRegion eu : euRegions) + { + BmsProductCountry c = buildCountryRow(eu.getCountryNameCn(), eu.getCountryCode2(), + null, zoneByCountryAndCode, + range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff); + c.setProductCode(currentProductCode); + c.setProductName(currentProductName); + bucket.add(c); + } + continue; + } + unmatchedCountries.add(currentCountryRaw.trim().replace("\n", " ").replace("\r", " ")); + BmsProductCountry c = buildCountryRow(baseCountry, null, zoneLabel, zoneByCountryAndCode, + range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff); + c.setProductCode(currentProductCode); + c.setProductName(currentProductName); + bucket.add(c); + } + return out; + } + + /** Build productName -> productCode from 产品目录 (first row per name wins). */ + private Map buildCatalogCodeByName(Workbook workbook) + { + Map map = new LinkedHashMap(); + if (workbook == null) + { + return map; + } + Sheet catalog = workbook.getSheet(CATALOG_SHEET); + if (catalog == null) + { + return map; + } + try + { + List products = dedupeCatalogProducts(parseCatalog(catalog)); + for (BmsProduct p : products) + { + if (p == null || StringUtils.isEmpty(p.getProductCode()) || StringUtils.isEmpty(p.getProductName())) + { + continue; + } + String nameKey = compactName(p.getProductName()); + if (StringUtils.isEmpty(nameKey) || map.containsKey(nameKey)) + { + continue; + } + map.put(nameKey, p.getProductCode().trim().toUpperCase(Locale.ROOT)); + } + } + catch (Exception ignored) + { + // Catalog optional for inline identity; ignore parse errors. + } + return map; + } + + /** Resolve product code by product name; exact compact match, then base name before '(' / '('. */ + private String resolveCodeByProductName(String productName, Map catalogCodeByName) + { + if (StringUtils.isEmpty(productName) || catalogCodeByName == null || catalogCodeByName.isEmpty()) + { + return null; + } + String key = compactName(productName); + String hit = catalogCodeByName.get(key); + if (StringUtils.isNotEmpty(hit)) + { + return hit; + } + String base = productName.trim(); + int idx = base.indexOf('('); + int idx2 = base.indexOf('('); + int cut = -1; + if (idx >= 0) + { + cut = idx; + } + if (idx2 >= 0 && (cut < 0 || idx2 < cut)) + { + cut = idx2; + } + if (cut > 0) + { + hit = catalogCodeByName.get(compactName(base.substring(0, cut))); + if (StringUtils.isNotEmpty(hit)) + { + return hit; + } + } + return null; + } + /** * Multi-country import expects price sheets with a "country" column. * Single-country dedicated-line sheets use product-name + weight blocks instead. @@ -1293,7 +1840,8 @@ public class BmsProductServiceImpl implements IBmsProductService */ private int[] importOneProductWithDeadlockRetry(Workbook workbook, BmsProduct product, String operName, Map regionByNameCn, List euRegions, - Map zoneByCountryAndCode, Set unmatchedCountries, Date forcedEff, Long importPsvId) + Map zoneByCountryAndCode, Set unmatchedCountries, Date forcedEff, Long importPsvId, + List preParsedCountries) throws Exception { int max = 8; @@ -1310,8 +1858,10 @@ public class BmsProductServiceImpl implements IBmsProductService { product.setPsvId(importPsvId); } - int[] nums = importOneProduct(workbook, product, operName, regionByNameCn, euRegions, - zoneByCountryAndCode, unmatchedCountries, forcedEff); + int[] nums = preParsedCountries != null + ? importOneProductFromRows(product, preParsedCountries, operName, forcedEff) + : importOneProduct(workbook, product, operName, regionByNameCn, euRegions, + zoneByCountryAndCode, unmatchedCountries, forcedEff); transactionManager.commit(tx); return nums; } @@ -1461,6 +2011,27 @@ public class BmsProductServiceImpl implements IBmsProductService throw new ServiceException(MessageUtils.message("price.product.import.noCountry", productSheet.getSheetName())); } + return importOneProductFromRows(product, countries, operName, forcedEffectiveDate); + } + + /** Import using pre-parsed country rows (inline product-code / product-name path). */ + private int[] importOneProductFromRows(BmsProduct product, List countries, + String operName, Date forcedEffectiveDate) + { + if (product != null) + { + if (StringUtils.isEmpty(product.getSourceSheet())) + { + product.setSourceSheet(product.getProductName()); + } + product.setCreateBy(operName); + product.setUpdateBy(operName); + } + if (countries == null || countries.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.noCountry", + product == null ? "" : product.getProductCode())); + } assertNoDuplicateCountryWeightRows(product, countries); // UI-selected effective date wins over Excel; previous open product/country rows expire at this date Date newEff = forcedEffectiveDate == null && product.getEffectiveDate() == null diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java index 06a6b800..027be1ff 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/BmsRedlineProductServiceImpl.java @@ -362,6 +362,10 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) { + if (workbookHasInlineProductColumns(workbook)) + { + return importRedlineExcelByInline(workbook, bytes, filename, operName, taskId, forcedEff); + } Sheet catalog = workbook.getSheet(CATALOG_SHEET); if (catalog == null) { @@ -665,6 +669,10 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService } try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) { + if (workbookHasInlineProductColumns(workbook)) + { + return readQuoteWorkbookByInlineProductCode(workbook).byCode; + } Sheet catalog = workbook.getSheet(CATALOG_SHEET); if (catalog == null) { @@ -712,9 +720,15 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService @Override public Map previewImportCatalog(byte[] bytes, String filename) throws Exception { - Map> byCode = parseQuoteExcelByProduct(bytes, filename); try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) { + if (workbookHasInlineProductColumns(workbook)) + { + RedlineQuoteWorkbook parsed = readQuoteWorkbookByInlineProductCode(workbook); + List products = enrichInlineProductsFromCatalog(workbook, parsed.products); + return buildCatalogPreview(products, parsed.byCode); + } + Map> byCode = parseQuoteExcelByProduct(bytes, filename); Sheet catalog = workbook.getSheet(CATALOG_SHEET); List catalogProducts = dedupeCatalogProducts(parseCatalog(catalog)); return buildCatalogPreview(catalogProducts, byCode); @@ -1205,8 +1219,28 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService throw new ServiceException(MessageUtils.message("price.product.import.noCountry", productSheet.getSheetName())); } + return importOneProductFromRows(product, countries, operName, forcedEffectiveDate); + } + + /** Import using pre-parsed country rows (inline product-code / product-name path). */ + private int[] importOneProductFromRows(BmsRedlineProduct product, List countries, + String operName, Date forcedEffectiveDate) + { + if (product != null) + { + if (StringUtils.isEmpty(product.getSourceSheet())) + { + product.setSourceSheet(product.getProductName()); + } + product.setCreateBy(operName); + product.setUpdateBy(operName); + } + if (countries == null || countries.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.noCountry", + product == null ? "" : product.getProductCode())); + } assertNoDuplicateCountryWeightRows(product, countries); - // UI-selected effective date wins over Excel; previous open product/country rows expire at this date Date newEff = forcedEffectiveDate == null && product.getEffectiveDate() == null ? null : requireImportEffectiveDate(forcedEffectiveDate != null ? forcedEffectiveDate @@ -2447,6 +2481,574 @@ public class BmsRedlineProductServiceImpl implements IBmsRedlineProductService return map; } + + private static final class RedlineQuoteWorkbook + { + private List products; + private Map> byCode; + } + + private int[] importOneProductInline(BmsRedlineProduct product, List countries, + String operName, Date forcedEff, Long importPsvId) throws Exception + { + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + TransactionStatus tx = transactionManager.getTransaction(def); + try + { + product.setEffectiveDate(forcedEff); + if (importPsvId != null) + { + product.setPsvId(importPsvId); + } + int[] nums = importOneProductFromRows(product, countries, operName, forcedEff); + transactionManager.commit(tx); + return nums; + } + catch (Exception e) + { + try { transactionManager.rollback(tx); } catch (Exception ignore) { } + throw e; + } + } + + /** + * Formal redline import when price sheets carry inline product-code / product-name columns. + * Identifies products from detail rows (not catalog sheet-name matching). + */ + private String importRedlineExcelByInline(Workbook workbook, byte[] bytes, String filename, + String operName, String taskId, Date forcedEff) throws Exception + { + RedlineQuoteWorkbook parsed = readQuoteWorkbookByInlineProductCode(workbook); + List products = enrichInlineProductsFromCatalog(workbook, parsed.products); + if (products.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.noData")); + } + reportImportProgress(taskId, 20, "parsing excel"); + assertImportProductsEffectiveNotBeforePrev(products, forcedEff); + + Map regionByNameCn = loadRegionByNameCn(); + if (regionByNameCn.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.regionEmpty")); + } + List euRegions = loadEuRegions(regionByNameCn); + Map zoneByCountryAndCode = loadZoneByCountryAndCode(); + + Long importPsvId = forcedEff == null + ? catalogVersionSupport.ensureDraftVersion(PriceType.REDLINE, null, false).getPsvId() + : catalogVersionSupport.requirePsvId(PriceType.REDLINE, null, forcedEff); + priceExcelFileService.saveImportExcel( + PrcPriceExcelFile.TYPE_REDLINE, forcedEff, bytes, filename, operName, importPsvId); + + int successNum = 0; + int skipNum = 0; + int countryNum = 0; + Set unmatchedCountries = new HashSet(); + StringBuilder failureMsg = new StringBuilder(); + int total = products.size(); + int index = 0; + for (BmsRedlineProduct product : products) + { + index++; + if (product == null || StringUtils.isEmpty(product.getProductCode())) + { + continue; + } + String code = product.getProductCode().trim().toUpperCase(Locale.ROOT); + reportImportProductProgress(taskId, index, total, product.getProductCode()); + List countries = parsed.byCode.get(code); + try + { + int[] nums = importOneProductInline(product, countries, operName, forcedEff, importPsvId); + successNum += nums[0]; + skipNum += nums[1]; + countryNum += nums[2]; + } + catch (Exception e) + { + String tip = e.getMessage(); + if (StringUtils.isEmpty(tip)) + { + tip = e.getClass().getSimpleName(); + } + failureMsg.append("
").append(product.getProductCode()).append(":").append(tip); + } + } + reportImportProgress(taskId, 95, "finishing"); + if (failureMsg.length() > 0) + { + return MessageUtils.message("price.product.import.partialFail", + Integer.valueOf(successNum), Integer.valueOf(skipNum), failureMsg.toString()); + } + String msg = MessageUtils.message("price.product.import.success", successNum, skipNum, countryNum) + + "
" + MessageUtils.message("price.product.import.success.detail1") + + "
" + MessageUtils.message("price.product.import.success.detail2"); + if (!unmatchedCountries.isEmpty()) + { + msg += "
" + MessageUtils.message("price.product.import.success.unmatched", + String.join("、", unmatchedCountries)); + } + return msg; + } + + /** Prefer catalog metadata when code matches; keep detail product name for variants. */ + private List enrichInlineProductsFromCatalog(Workbook workbook, List inlineProducts) + { + Map catalogByCode = new HashMap(); + Sheet catalog = workbook == null ? null : workbook.getSheet(CATALOG_SHEET); + if (catalog != null) + { + try + { + for (BmsRedlineProduct p : dedupeCatalogProducts(parseCatalog(catalog))) + { + if (p == null || StringUtils.isEmpty(p.getProductCode())) + { + continue; + } + catalogByCode.put(p.getProductCode().trim().toUpperCase(Locale.ROOT), p); + } + } + catch (Exception ignored) + { + // Catalog optional for inline import. + } + } + List out = new ArrayList(); + if (inlineProducts == null) + { + return out; + } + for (BmsRedlineProduct inline : inlineProducts) + { + if (inline == null || StringUtils.isEmpty(inline.getProductCode())) + { + continue; + } + String code = inline.getProductCode().trim().toUpperCase(Locale.ROOT); + BmsRedlineProduct cat = catalogByCode.get(code); + if (cat == null) + { + out.add(inline); + continue; + } + BmsRedlineProduct merged = new BmsRedlineProduct(); + merged.setProductCode(inline.getProductCode()); + merged.setProductName(StringUtils.isNotEmpty(inline.getProductName()) + ? inline.getProductName() : cat.getProductName()); + merged.setServiceLevel(cat.getServiceLevel()); + merged.setCargoAttr(cat.getCargoAttr()); + merged.setDestRegion(cat.getDestRegion()); + merged.setUpdateNote(cat.getUpdateNote()); + merged.setEffectiveDate(cat.getEffectiveDate()); + merged.setTaxMode(StringUtils.isNotEmpty(cat.getTaxMode()) + ? cat.getTaxMode() : resolveTaxMode(merged.getProductName())); + out.add(merged); + } + return out; + } + + /** + * True when any multi-country price sheet has an inline product-code or product-name column + * (产品代码 / 产品对接代码 / 产品名称). + */ + private boolean workbookHasInlineProductColumns(Workbook workbook) + { + if (workbook == null) + { + return false; + } + for (int i = 0; i < workbook.getNumberOfSheets(); i++) + { + String sheetName = workbook.getSheetName(i); + if (shouldSkipCountryPriceSheet(sheetName)) + { + continue; + } + Sheet sheet = workbook.getSheetAt(i); + if (sheet == null) + { + continue; + } + int[] countryHeader = findHeaderCell(sheet, "国家"); + if (countryHeader == null) + { + continue; + } + Row head = sheet.getRow(countryHeader[0]); + if (head == null) + { + continue; + } + if (firstCol(head, "产品代码", "产品对接代码") >= 0 + || findColByHeader(head, "产品名称", 0) >= 0) + { + return true; + } + } + return false; + } + + /** + * Multi-country parse keyed by inline product-code column on price sheets. + * Does not use 产品目录 sheet matching. + */ + private RedlineQuoteWorkbook readQuoteWorkbookByInlineProductCode(Workbook workbook) throws Exception + { + Map catalogCodeByName = buildCatalogCodeByName(workbook); + Map regionByNameCn = loadRegionByNameCn(); + List euRegions = loadEuRegions(regionByNameCn); + Map zoneByCountryAndCode = loadZoneByCountryAndCode(); + Set unmatchedCountries = new HashSet(); + Map> out = new LinkedHashMap>(); + Map productMeta = new LinkedHashMap(); + List tips = new ArrayList(); + + for (int i = 0; i < workbook.getNumberOfSheets(); i++) + { + String sheetName = workbook.getSheetName(i); + if (shouldSkipCountryPriceSheet(sheetName)) + { + continue; + } + Sheet sheet = workbook.getSheetAt(i); + if (sheet == null || findHeaderCell(sheet, "国家") == null) + { + continue; + } + Map> sheetByCode = parseProductSheetByInlineCode( + sheet, catalogCodeByName, regionByNameCn, euRegions, zoneByCountryAndCode, unmatchedCountries); + if (sheetByCode == null || sheetByCode.isEmpty()) + { + continue; + } + for (Map.Entry> e : sheetByCode.entrySet()) + { + if (e == null || StringUtils.isEmpty(e.getKey()) || e.getValue() == null || e.getValue().isEmpty()) + { + continue; + } + String code = e.getKey(); + List rows = e.getValue(); + List existing = out.get(code); + if (existing == null) + { + out.put(code, new ArrayList(rows)); + } + else + { + existing.addAll(rows); + } + if (!productMeta.containsKey(code)) + { + BmsRedlineProduct p = new BmsRedlineProduct(); + p.setProductCode(code); + String name = null; + for (BmsRedlineProductCountry r : rows) + { + if (r != null && StringUtils.isNotEmpty(r.getProductName())) + { + name = r.getProductName(); + break; + } + } + p.setProductName(StringUtils.isNotEmpty(name) ? name : code); + p.setTaxMode(resolveTaxMode(p.getProductName())); + productMeta.put(code, p); + } + } + } + + for (Map.Entry> e : out.entrySet()) + { + BmsRedlineProduct p = productMeta.get(e.getKey()); + String tip = buildDuplicateCountryWeightTip(p, e.getValue()); + if (StringUtils.isNotEmpty(tip)) + { + tips.add(tip); + } + } + if (!tips.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.dupCountry", + String.join("
", tips))); + } + if (out.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); + } + RedlineQuoteWorkbook parsed = new RedlineQuoteWorkbook(); + parsed.products = new ArrayList(productMeta.values()); + parsed.byCode = out; + return parsed; + } + + /** + * Parse one multi-country price sheet grouped by inline product code / product name. + * Prefer product-code column; if only product-name exists, resolve code via catalog name map. + * Country / product-code / product-name / size / last-mile / lead-time carry forward + * when subsequent weight-band rows leave those cells blank. + */ + private Map> parseProductSheetByInlineCode(Sheet sheet, + Map catalogCodeByName, Map regionByNameCn, + List euRegions, Map zoneByCountryAndCode, + Set unmatchedCountries) + { + Map> out = new LinkedHashMap>(); + if (sheet == null) + { + return out; + } + Date sheetEffective = parseSheetEffectiveDate(sheet); + int[] countryHeader = findHeaderCell(sheet, "国家"); + if (countryHeader == null) + { + return out; + } + int headerRowIdx = countryHeader[0]; + Row headerRow = sheet.getRow(headerRowIdx); + int colCountry = countryHeader[1]; + int colProductCode = firstCol(headerRow, "产品代码", "产品对接代码"); + int colProductName = findColByHeader(headerRow, "产品名称", 0); + if (colProductCode < 0 && colProductName < 0) + { + return out; + } + int colWeight = findColByHeader(headerRow, "重量", colCountry + 1); + int colFreight = findColByHeader(headerRow, "运费", colCountry + 1); + int colRegister = findColByHeader(headerRow, "挂号", colCountry + 1); + int colSize = findColByHeader(headerRow, "尺寸", colCountry + 1); + int colLastMile = findColByHeader(headerRow, "尾程", colCountry + 1); + int colLeadTime = findColByHeader(headerRow, "时效", colCountry + 1); + if (colWeight < 0 || colFreight < 0) + { + return out; + } + + String currentCountryRaw = null; + String currentProductCode = null; + String currentProductName = null; + String currentSize = null; + String currentLastMile = null; + String currentLeadTime = null; + int lastRow = sheet.getLastRowNum(); + for (int i = headerRowIdx + 1; i <= lastRow; i++) + { + Row row = sheet.getRow(i); + if (row == null) + { + continue; + } + String countryCell = cellString(row.getCell(colCountry)); + String weightLabel = cellString(row.getCell(colWeight)); + if ("国家".equals(countryCell) + || (weightLabel != null && weightLabel.contains("重量段"))) + { + continue; + } + if (StringUtils.isNotEmpty(countryCell)) + { + currentCountryRaw = countryCell; + } + boolean productChanged = false; + if (colProductCode >= 0) + { + String codeCell = normalizeImportProductCode(cellString(row.getCell(colProductCode))); + if (StringUtils.isNotEmpty(codeCell)) + { + String newCode = codeCell.trim().toUpperCase(Locale.ROOT); + if (currentProductCode == null || !newCode.equals(currentProductCode)) + { + productChanged = true; + currentProductCode = newCode; + } + } + } + if (colProductName >= 0) + { + String nameCell = cellString(row.getCell(colProductName)); + if (StringUtils.isNotEmpty(nameCell) && !isSectionTitle(nameCell)) + { + String newName = nameCell.replace("\n", " ").replace("\r", " ").trim(); + if (currentProductName == null || !newName.equals(currentProductName)) + { + productChanged = true; + currentProductName = newName; + // Name-only sheets: resolve product code from catalog by product name. + if (colProductCode < 0) + { + String mapped = resolveCodeByProductName(currentProductName, catalogCodeByName); + if (StringUtils.isNotEmpty(mapped)) + { + currentProductCode = mapped; + } + else + { + currentProductCode = null; + } + } + } + } + } + if (productChanged) + { + currentSize = null; + currentLastMile = null; + currentLeadTime = null; + } + if (colSize >= 0 && StringUtils.isNotEmpty(cellString(row.getCell(colSize)))) + { + currentSize = emptyToNull(cellString(row.getCell(colSize))); + } + if (colLastMile >= 0 && StringUtils.isNotEmpty(cellString(row.getCell(colLastMile)))) + { + currentLastMile = emptyToNull(cellString(row.getCell(colLastMile))); + } + if (colLeadTime >= 0 && StringUtils.isNotEmpty(cellString(row.getCell(colLeadTime)))) + { + currentLeadTime = emptyToNull(cellString(row.getCell(colLeadTime))); + } + if (StringUtils.isEmpty(currentCountryRaw) || StringUtils.isEmpty(currentProductCode)) + { + continue; + } + BigDecimal[] range = parseWeightRange(weightLabel); + if (range == null) + { + continue; + } + BigDecimal freight = cellDecimal(row.getCell(colFreight)); + BigDecimal registration = colRegister >= 0 ? cellDecimal(row.getCell(colRegister)) : null; + Date eff = sheetEffective; + + ZoneCodeAbbrevs.CountryZoneParts parts = ZoneCodeAbbrevs.splitCountryZone(currentCountryRaw); + if (parts == null || StringUtils.isEmpty(parts.countryName)) + { + continue; + } + String baseCountry = parts.countryName; + String zoneLabel = parts.zoneLabel; + List bucket = out.get(currentProductCode); + if (bucket == null) + { + bucket = new ArrayList(); + out.put(currentProductCode, bucket); + } + + BmsRegion region = resolveRegion(baseCountry, regionByNameCn); + if (region != null) + { + BmsRedlineProductCountry c = buildCountryRow(region.getCountryNameCn(), region.getCountryCode2(), + zoneLabel, zoneByCountryAndCode, + range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff); + c.setProductCode(currentProductCode); + c.setProductName(currentProductName); + bucket.add(c); + continue; + } + if (isEuZoneName(baseCountry) && StringUtils.isEmpty(zoneLabel)) + { + if (euRegions == null || euRegions.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.euEmpty")); + } + for (BmsRegion eu : euRegions) + { + BmsRedlineProductCountry c = buildCountryRow(eu.getCountryNameCn(), eu.getCountryCode2(), + null, zoneByCountryAndCode, + range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff); + c.setProductCode(currentProductCode); + c.setProductName(currentProductName); + bucket.add(c); + } + continue; + } + unmatchedCountries.add(currentCountryRaw.trim().replace("\n", " ").replace("\r", " ")); + BmsRedlineProductCountry c = buildCountryRow(baseCountry, null, zoneLabel, zoneByCountryAndCode, + range, freight, registration, currentSize, currentLastMile, currentLeadTime, eff); + c.setProductCode(currentProductCode); + c.setProductName(currentProductName); + bucket.add(c); + } + return out; + } + + /** Build productName -> productCode from 产品目录 (first row per name wins). */ + private Map buildCatalogCodeByName(Workbook workbook) + { + Map map = new LinkedHashMap(); + if (workbook == null) + { + return map; + } + Sheet catalog = workbook.getSheet(CATALOG_SHEET); + if (catalog == null) + { + return map; + } + try + { + List products = dedupeCatalogProducts(parseCatalog(catalog)); + for (BmsRedlineProduct p : products) + { + if (p == null || StringUtils.isEmpty(p.getProductCode()) || StringUtils.isEmpty(p.getProductName())) + { + continue; + } + String nameKey = compactName(p.getProductName()); + if (StringUtils.isEmpty(nameKey) || map.containsKey(nameKey)) + { + continue; + } + map.put(nameKey, p.getProductCode().trim().toUpperCase(Locale.ROOT)); + } + } + catch (Exception ignored) + { + // Catalog optional for inline identity; ignore parse errors. + } + return map; + } + + /** Resolve product code by product name; exact compact match, then base name before '(' / '('. */ + private String resolveCodeByProductName(String productName, Map catalogCodeByName) + { + if (StringUtils.isEmpty(productName) || catalogCodeByName == null || catalogCodeByName.isEmpty()) + { + return null; + } + String key = compactName(productName); + String hit = catalogCodeByName.get(key); + if (StringUtils.isNotEmpty(hit)) + { + return hit; + } + String base = productName.trim(); + int idx = base.indexOf('('); + int idx2 = base.indexOf('('); + int cut = -1; + if (idx >= 0) + { + cut = idx; + } + if (idx2 >= 0 && (cut < 0 || idx2 < cut)) + { + cut = idx2; + } + if (cut > 0) + { + hit = catalogCodeByName.get(compactName(base.substring(0, cut))); + if (StringUtils.isNotEmpty(hit)) + { + return hit; + } + } + return null; + } + + private static boolean shouldSkipCountryPriceSheet(String name) { if (StringUtils.isEmpty(name)) diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java index 2562f3ca..1c060e66 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/impl/PrcPriceSetServiceImpl.java @@ -258,6 +258,30 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return setMapper.updatePrcPriceSet(set); } + @Override + public int updateBasicInfo(PrcPriceSet set) + { + if (set == null || set.getPriceSetId() == null) + { + throw new ServiceException(MessageUtils.message("price.set.notExists")); + } + PrcPriceSet db = requireSet(set.getPriceSetId()); + if (STATUS_AUDIT.equals(db.getStatus())) + { + throw new ServiceException(MessageUtils.message("price.set.edit.audit")); + } + if (StringUtils.isEmpty(set.getPriceSetName())) + { + throw new ServiceException(MessageUtils.message("price.set.name.empty")); + } + set.setPriceSetName(set.getPriceSetName().trim()); + if (setMapper.countByName(set) > 0) + { + throw new ServiceException(MessageUtils.message("price.set.name.exists")); + } + return setMapper.updateBasicInfo(set); + } + @Override @Transactional public int deletePrcPriceSetByIds(Long[] priceSetIds) diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml index 39decd38..ba21255b 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetMapper.xml @@ -21,6 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -33,7 +34,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" inner join prc_price_set_version v on v.psv_id = p.psv_id where v.price_set_id = s.price_set_id and ifnull(p.is_del, '0') = '0') as product_count, - (select count(1) from prc_customer_price_set c where c.price_set_id = s.price_set_id) as customer_count + (select count(1) from prc_customer_price_set c where c.price_set_id = s.price_set_id) as customer_count, + (select group_concat( + concat(ifnull(c.customer_code, ''), + case when ifnull(c.customer_name, '') = '' then '' else concat(' ', c.customer_name) end) + order by c.bind_priority asc, c.customer_priceset_id asc + separator '、') + from prc_customer_price_set c + where c.price_set_id = s.price_set_id) as bound_customers from prc_price_set s @@ -47,7 +55,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" s.set_type, s.default_discount, s.default_type, s.calculate_sign, s.status, s.tms_id, s.created_by, s.create_time, s.updated_by, s.update_time, - null as product_count, null as customer_count + null as product_count, null as customer_count, null as bound_customers from prc_price_set s where s.price_set_id = #{priceSetId} @@ -71,14 +79,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND ( s.price_set_code like concat('%', #{keyword}, '%') OR s.price_set_name like concat('%', #{keyword}, '%') - OR exists ( - select 1 from prc_customer_price_set c - where c.price_set_id = s.price_set_id - and ( - c.customer_name like concat('%', #{keyword}, '%') - or concat(c.customer_id, '') like concat('%', #{keyword}, '%') - ) - ) + ) + + + AND exists ( + select 1 from prc_customer_price_set c + where c.price_set_id = s.price_set_id + and c.customer_id = #{customerId} )