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 44f2504b..1beec34e 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 @@ -244,9 +244,13 @@ public class PriceSetController extends BaseController public AjaxResult importDetailPrices(MultipartFile file, @RequestParam("priceSetId") Long priceSetId, @RequestParam(value = "psvId", required = false) Long psvId, - @RequestParam(value = "updateOnly", required = false, defaultValue = "false") boolean updateOnly) throws Exception + @RequestParam(value = "updateOnly", required = false, defaultValue = "false") boolean updateOnly, + @RequestParam(value = "replaceByProductCountry", required = false, defaultValue = "false") boolean replaceByProductCountry, + @RequestParam(value = "countryCode", required = false) String countryCode) + throws Exception { - String taskId = priceSetService.submitImportDetailPricesAsync(priceSetId, psvId, file, getUsername(), updateOnly); + String taskId = priceSetService.submitImportDetailPricesAsync(priceSetId, psvId, file, getUsername(), updateOnly, + replaceByProductCountry, countryCode); Map payload = new HashMap(); payload.put("taskId", taskId); return success(payload); @@ -263,12 +267,15 @@ public class PriceSetController extends BaseController } /** - * Preview Excel import (no DB write). Optional detailsJson for updateOnly merge. + * Preview Excel import (no DB write). Optional detailsJson for updateOnly / build-price merge. + * Optional countryCode = single-country dedicated-line Excel (same as product country import). */ @PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit') or @ss.hasPermi('price:set:add')") @PostMapping("/detail/previewImportPrices") public AjaxResult previewImportPrices(MultipartFile file, @RequestParam(value = "updateOnly", required = false, defaultValue = "false") boolean updateOnly, + @RequestParam(value = "replaceByProductCountry", required = false, defaultValue = "false") boolean replaceByProductCountry, + @RequestParam(value = "countryCode", required = false) String countryCode, @RequestParam(value = "detailsJson", required = false) String detailsJson) throws Exception { if (file == null || file.isEmpty()) @@ -281,7 +288,7 @@ public class PriceSetController extends BaseController existing = com.alibaba.fastjson2.JSON.parseArray(detailsJson, PrcPriceSetDetail.class); } return success(priceSetService.previewImportDetailPrices( - file.getBytes(), file.getOriginalFilename(), updateOnly, existing)); + file.getBytes(), file.getOriginalFilename(), updateOnly, existing, replaceByProductCountry, countryCode)); } /** diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java index 91f2982d..50573368 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductCountry.java @@ -16,6 +16,8 @@ public class BmsProductCountry implements Serializable /** FK to product.product_id */ private Long productId; private String productCode; + /** Parse/import helper only (not a DB column). */ + private String productName; private String countryName; private String countryCode; /** prc_zone.zone_id; null when product country has no zone */ diff --git a/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java b/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java index f91e57b8..80acfa43 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/service/IBmsProductService.java @@ -54,4 +54,11 @@ public interface IBmsProductService public java.util.Map> parseQuoteExcelByProduct(MultipartFile file) throws Exception; public java.util.Map> parseQuoteExcelByProduct(byte[] bytes, String filename) throws Exception; + + /** + * Parse single-country dedicated-line quote Excel (same format as product country import). + * All weight rows are stamped with the selected country. Keys are upper-case product codes. + */ + public java.util.Map> parseCountryQuoteExcelByProduct(byte[] bytes, String filename, + String countryCode) throws Exception; } 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 fca616e4..534a5b89 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 @@ -121,6 +121,13 @@ public interface IPrcPriceSetService public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName, boolean updateOnly) throws Exception; + /** + * @param replaceByProductCountry true = Excel build-price mode: replace only product+country scopes in this file + * @param countryCode optional; when set, parse single-country dedicated-line Excel (product country-import format) + */ + public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName, + boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception; + /** * Sync worker: parse bytes and upsert FIXED detail prices matched by * product + country + zone(if any) + weight band (insert when missing unless updateOnly). @@ -133,6 +140,13 @@ public interface IPrcPriceSetService public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, String taskId, boolean updateOnly) throws Exception; + /** + * @param replaceByProductCountry package Excel build-price only; other import modes pass false + * @param countryCode optional single-country dedicated-line import + */ + public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, + String taskId, boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception; + /** * Submit async pull of product library into version details. * @return taskId for polling getSaveTask @@ -153,4 +167,11 @@ public interface IPrcPriceSetService */ public Map previewImportDetailPrices(byte[] bytes, String filename, boolean updateOnly, List existingDetails) throws Exception; + + /** + * @param replaceByProductCountry package Excel build-price only: keep other product/country rows + * @param countryCode optional single-country dedicated-line import + */ + public Map previewImportDetailPrices(byte[] bytes, String filename, boolean updateOnly, + List existingDetails, boolean replaceByProductCountry, String countryCode) throws Exception; } 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 f0904e82..0e4289e7 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 @@ -628,6 +628,67 @@ public class BmsProductServiceImpl implements IBmsProductService } } + @Override + public Map> parseCountryQuoteExcelByProduct(byte[] bytes, String filename, + String countryCode) throws Exception + { + validateImportBytes(bytes, filename); + if (StringUtils.isEmpty(countryCode)) + { + throw new ServiceException(MessageUtils.message("price.product.import.countryRequired")); + } + BmsRegion region = regionMapper.selectRegionByCode2(countryCode.trim().toUpperCase(Locale.ROOT)); + if (region == null || "2".equals(region.getDelFlag()) + || (StringUtils.isNotEmpty(region.getStatus()) && !"0".equals(region.getStatus()))) + { + throw new ServiceException(MessageUtils.message("price.product.import.countryNotFound", countryCode)); + } + try (InputStream is = new java.io.ByteArrayInputStream(bytes); Workbook workbook = WorkbookFactory.create(is)) + { + Sheet catalog = workbook.getSheet(CATALOG_SHEET); + if (catalog == null) + { + throw new ServiceException(MessageUtils.message("price.product.import.countrySheetNotFound")); + } + List products = dedupeCatalogProducts(parseCountryCatalog(catalog)); + if (products.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.product.import.noData")); + } + Map priceBlocks = parseCountryPriceBlocks(workbook); + Map> out = new LinkedHashMap>(); + for (BmsProduct product : products) + { + if (product == null || StringUtils.isEmpty(product.getProductCode())) + { + continue; + } + PriceBlock block = findPriceBlock(product.getProductName(), priceBlocks); + if (block == null || block.rows == null || block.rows.isEmpty()) + { + continue; + } + List countries = stampSelectedCountry(block.rows, region, null); + for (BmsProductCountry c : countries) + { + if (c == null) + { + continue; + } + c.setProductCode(product.getProductCode()); + c.setProductName(product.getProductName()); + } + assertNoDuplicateCountryWeightRows(product, countries); + out.put(product.getProductCode().trim().toUpperCase(Locale.ROOT), countries); + } + if (out.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); + } + return out; + } + } + /** @return int[]{added, skipped, countryRows} */ private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName, 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 b28ee9e3..d0a45229 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 @@ -929,6 +929,13 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService @Override public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName, boolean updateOnly) throws Exception + { + return submitImportDetailPricesAsync(priceSetId, psvId, file, operName, updateOnly, false, null); + } + + @Override + public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName, + boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception { PrcPriceSet db = requireSet(priceSetId); assertChildEditable(db); @@ -953,7 +960,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } String taskId = IdUtils.fastSimpleUUID(); saveAsyncRunner.put(taskId, PrcPriceSetSaveTask.STATUS_PENDING, 0, "queued", priceSetId, null); - saveAsyncRunner.runImportPrices(taskId, priceSetId, psvId, bytes, filename, operName, updateOnly); + saveAsyncRunner.runImportPrices(taskId, priceSetId, psvId, bytes, filename, operName, updateOnly, + replaceByProductCountry, countryCode); return taskId; } @@ -1036,14 +1044,23 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService public Map previewImportDetailPrices(byte[] bytes, String filename, boolean updateOnly, List existingDetails) throws Exception { - Map> byProduct = productService.parseQuoteExcelByProduct(bytes, filename); + return previewImportDetailPrices(bytes, filename, updateOnly, existingDetails, false, null); + } + + @Override + public Map previewImportDetailPrices(byte[] bytes, String filename, boolean updateOnly, + List existingDetails, boolean replaceByProductCountry, String countryCode) throws Exception + { + boolean replaceScope = replaceByProductCountry || StringUtils.isNotEmpty(countryCode); + Map> byProduct = parseSetImportExcel(bytes, filename, countryCode); if (byProduct == null || byProduct.isEmpty()) { throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); } List details = new ArrayList(); - if (existingDetails != null) + // Keep prior draft rows only for updateOnly or Excel build-price merge. + if (existingDetails != null && (updateOnly || replaceScope)) { for (PrcPriceSetDetail d : existingDetails) { @@ -1053,27 +1070,17 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } } } - Map> detailIndex = new HashMap>(); - for (PrcPriceSetDetail d : details) - { - if (d == null || StringUtils.isEmpty(d.getProductCode())) - { - continue; - } - String pc = d.getProductCode().trim().toUpperCase(Locale.ROOT); - List list = detailIndex.get(pc); - if (list == null) - { - list = new ArrayList(); - detailIndex.put(pc, list); - } - list.add(d); - } + Map> detailIndex = buildImportDetailIndex(details); if (updateOnly) { assertUpdateOnlyWeightBandsMatch(byProduct, detailIndex); } + else if (replaceScope && !details.isEmpty()) + { + // Package Excel build-price: replace only product+country scopes in this file. + removeDetailsByReplaceScopes(details, detailIndex, collectImportReplaceScopes(byProduct)); + } String operName = null; try { @@ -1088,6 +1095,16 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService Long pefId = excelFile != null ? excelFile.getPefId() : null; Map productMap = new LinkedHashMap(); + if (replaceScope) + { + for (PrcPriceSetDetail d : details) + { + if (d != null) + { + ensurePreviewProduct(productMap, d.getProductCode(), d.getProductName()); + } + } + } int updated = 0; int inserted = 0; int skippedFee = 0; @@ -1097,21 +1114,26 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService for (Map.Entry> e : byProduct.entrySet()) { String productCode = e.getKey(); - List candidates = detailIndex.get(productCode); + String pcKey = productCode == null ? null : productCode.trim().toUpperCase(Locale.ROOT); + List candidates = detailIndex.get(pcKey); if (candidates == null) { candidates = new ArrayList(); - detailIndex.put(productCode, candidates); + if (pcKey != null) + { + detailIndex.put(pcKey, candidates); + } } if (updateOnly && candidates.isEmpty()) { skippedUnmatched += e.getValue() == null ? 0 : e.getValue().size(); continue; } - String productName = resolveImportProductName(null, productCode, candidates); + String productName = resolveImportProductNameFromRows(e.getValue(), null, productCode, candidates); if (!updateOnly) { ensurePreviewProduct(productMap, productCode, productName); + stampPreviewProductDestRegion(productMap, productCode, e.getValue()); } for (BmsProductCountry row : e.getValue()) { @@ -1189,14 +1211,29 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService if (pefId != null) { - for (PrcPriceSetProduct sp : productMap.values()) + for (String code : byProduct.keySet()) { + if (StringUtils.isEmpty(code)) + { + continue; + } + PrcPriceSetProduct sp = productMap.get(code.trim().toUpperCase(Locale.ROOT)); if (sp != null) { sp.setPefId(pefId); } } } + if (replaceScope) + { + for (PrcPriceSetDetail d : details) + { + if (d != null) + { + ensurePreviewProduct(productMap, d.getProductCode(), d.getProductName()); + } + } + } Map result = new HashMap(); result.put("products", new ArrayList(productMap.values())); result.put("details", details); @@ -1214,6 +1251,54 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return result; } + private Map> parseSetImportExcel(byte[] bytes, String filename, String countryCode) + throws Exception + { + if (StringUtils.isNotEmpty(countryCode)) + { + return productService.parseCountryQuoteExcelByProduct(bytes, filename, countryCode); + } + return productService.parseQuoteExcelByProduct(bytes, filename); + } + + private String resolveImportProductNameFromRows(List rows, Long psvId, String productCode, + List candidates) + { + if (rows != null) + { + for (BmsProductCountry row : rows) + { + if (row != null && StringUtils.isNotEmpty(row.getProductName())) + { + return row.getProductName(); + } + } + } + return resolveImportProductName(psvId, productCode, candidates); + } + + private void stampPreviewProductDestRegion(Map productMap, String productCode, + List rows) + { + if (productMap == null || StringUtils.isEmpty(productCode) || rows == null || rows.isEmpty()) + { + return; + } + PrcPriceSetProduct sp = productMap.get(productCode.trim().toUpperCase(Locale.ROOT)); + if (sp == null || StringUtils.isNotEmpty(sp.getDestRegion())) + { + return; + } + for (BmsProductCountry row : rows) + { + if (row != null && StringUtils.isNotEmpty(row.getCountryName())) + { + sp.setDestRegion(row.getCountryName()); + return; + } + } + } + private void ensurePreviewProduct(Map productMap, String productCode, String productName) { if (productMap == null || StringUtils.isEmpty(productCode)) @@ -1266,6 +1351,14 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService @Transactional(rollbackFor = Exception.class) public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, String taskId, boolean updateOnly) throws Exception + { + return importDetailPrices(priceSetId, psvId, bytes, filename, operName, taskId, updateOnly, false, null); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, + String taskId, boolean updateOnly, boolean replaceByProductCountry, String countryCode) throws Exception { PrcPriceSet db = requireSet(priceSetId); assertChildEditable(db); @@ -1282,7 +1375,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService throw new ServiceException(MessageUtils.message("price.set.version.notExists")); } reportImportProgress(taskId, priceSetId, 20, "parsing excel"); - Map> byProduct = productService.parseQuoteExcelByProduct(bytes, filename); + boolean replaceScope = replaceByProductCountry || StringUtils.isNotEmpty(countryCode); + Map> byProduct = parseSetImportExcel(bytes, filename, countryCode); if (byProduct == null || byProduct.isEmpty()) { throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); @@ -1296,27 +1390,35 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { details = new ArrayList(); } - Map> detailIndex = new HashMap>(); - for (PrcPriceSetDetail d : details) - { - if (d == null || StringUtils.isEmpty(d.getProductCode())) - { - continue; - } - String pc = d.getProductCode().trim().toUpperCase(Locale.ROOT); - List list = detailIndex.get(pc); - if (list == null) - { - list = new ArrayList(); - detailIndex.put(pc, list); - } - list.add(d); - } + Map> detailIndex = buildImportDetailIndex(details); if (updateOnly) { assertUpdateOnlyWeightBandsMatch(byProduct, detailIndex); } + else if (replaceScope && !details.isEmpty()) + { + // Package Excel build-price only. + Set replaceScopes = collectImportReplaceScopes(byProduct); + List deleteIds = new ArrayList(); + for (PrcPriceSetDetail d : details) + { + if (d == null || d.getPsdId() == null) + { + continue; + } + String scope = importCountryScopeKey(d.getProductCode(), d.getCountryCode(), d.getCountryName()); + if (scope != null && replaceScopes.contains(scope)) + { + deleteIds.add(d.getPsdId()); + } + } + if (!deleteIds.isEmpty()) + { + detailMapper.deletePrcPriceSetDetailByIds(deleteIds.toArray(new Long[0])); + } + removeDetailsByReplaceScopes(details, detailIndex, replaceScopes); + } PrcPriceSetVersion verForFile = versionMapper.selectPrcPriceSetVersionById(psvId); Date excelEff = verForFile != null && verForFile.getEffectiveTime() != null ? verForFile.getEffectiveTime() : new Date(); @@ -1338,18 +1440,22 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService reportImportProgress(taskId, priceSetId, 20 + (int) Math.floor(70.0 * productIndex / Math.max(productTotal, 1)), "importing " + productCode + " (" + productIndex + "/" + productTotal + ")"); - List candidates = detailIndex.get(productCode); + String pcKey = productCode == null ? null : productCode.trim().toUpperCase(Locale.ROOT); + List candidates = detailIndex.get(pcKey); if (candidates == null) { candidates = new ArrayList(); - detailIndex.put(productCode, candidates); + if (pcKey != null) + { + detailIndex.put(pcKey, candidates); + } } if (updateOnly && candidates.isEmpty()) { skippedUnmatched += e.getValue() == null ? 0 : e.getValue().size(); continue; } - String productName = resolveImportProductName(psvId, productCode, candidates); + String productName = resolveImportProductNameFromRows(e.getValue(), psvId, productCode, candidates); Long pspId = null; if (!updateOnly) { @@ -1712,6 +1818,108 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return w; } + /** + * Replace scope for upsert import: productCode + country (code preferred, else name). + * Rows in Excel for a scope replace all existing details of that product+country. + */ + private String importCountryScopeKey(String productCode, String countryCode, String countryName) + { + if (StringUtils.isEmpty(productCode)) + { + return null; + } + String pc = productCode.trim().toUpperCase(Locale.ROOT); + if (StringUtils.isNotEmpty(countryCode)) + { + return pc + "|C:" + countryCode.trim().toUpperCase(Locale.ROOT); + } + if (StringUtils.isNotEmpty(countryName)) + { + return pc + "|N:" + countryName.trim(); + } + return null; + } + + private Set collectImportReplaceScopes(Map> byProduct) + { + Set scopes = new HashSet(); + if (byProduct == null || byProduct.isEmpty()) + { + return scopes; + } + for (Map.Entry> e : byProduct.entrySet()) + { + if (e.getValue() == null) + { + continue; + } + for (BmsProductCountry row : e.getValue()) + { + if (row == null) + { + continue; + } + String key = importCountryScopeKey(e.getKey(), row.getCountryCode(), row.getCountryName()); + if (key != null) + { + scopes.add(key); + } + } + } + return scopes; + } + + private Map> buildImportDetailIndex(List details) + { + Map> detailIndex = new HashMap>(); + if (details == null) + { + return detailIndex; + } + for (PrcPriceSetDetail d : details) + { + if (d == null || StringUtils.isEmpty(d.getProductCode())) + { + continue; + } + String pc = d.getProductCode().trim().toUpperCase(Locale.ROOT); + List list = detailIndex.get(pc); + if (list == null) + { + list = new ArrayList(); + detailIndex.put(pc, list); + } + list.add(d); + } + return detailIndex; + } + + private void removeDetailsByReplaceScopes(List details, + Map> detailIndex, Set replaceScopes) + { + if (details == null || replaceScopes == null || replaceScopes.isEmpty()) + { + return; + } + java.util.Iterator it = details.iterator(); + while (it.hasNext()) + { + PrcPriceSetDetail d = it.next(); + if (d == null) + { + it.remove(); + continue; + } + String scope = importCountryScopeKey(d.getProductCode(), d.getCountryCode(), d.getCountryName()); + if (scope != null && replaceScopes.contains(scope)) + { + it.remove(); + } + } + detailIndex.clear(); + detailIndex.putAll(buildImportDetailIndex(details)); + } + private List matchImportDetails(List candidates, BmsProductCountry row) { List hits = new ArrayList(); diff --git a/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java index 4d6cd8f9..2f69d5f4 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/task/PrcPriceSetSaveAsyncRunner.java @@ -87,13 +87,22 @@ public class PrcPriceSetSaveAsyncRunner @Async("threadPoolTaskExecutor") public void runImportPrices(String taskId, Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, boolean updateOnly) + { + runImportPrices(taskId, priceSetId, psvId, bytes, filename, operName, updateOnly, false, null); + } + + @Async("threadPoolTaskExecutor") + public void runImportPrices(String taskId, Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, + boolean updateOnly, boolean replaceByProductCountry, String countryCode) { try { put(taskId, PrcPriceSetSaveTask.STATUS_RUNNING, 10, "parsing excel", priceSetId, null); - String message = priceSetService.importDetailPrices(priceSetId, psvId, bytes, filename, operName, taskId, updateOnly); + String message = priceSetService.importDetailPrices(priceSetId, psvId, bytes, filename, operName, taskId, + updateOnly, replaceByProductCountry, countryCode); put(taskId, PrcPriceSetSaveTask.STATUS_SUCCESS, 100, message, priceSetId, null); - log.info("async import prices done, taskId={}, priceSetId={}, updateOnly={}", taskId, priceSetId, updateOnly); + log.info("async import prices done, taskId={}, priceSetId={}, updateOnly={}, replaceByProductCountry={}, countryCode={}", + taskId, priceSetId, updateOnly, replaceByProductCountry, countryCode); } catch (Exception e) { diff --git a/bms-front/src/api/price/set.js b/bms-front/src/api/price/set.js index fb2a344f..833fe37d 100644 --- a/bms-front/src/api/price/set.js +++ b/bms-front/src/api/price/set.js @@ -210,7 +210,7 @@ export function addSetDetail(data) { /** * Async import quote Excel into FIXED package detail prices; poll until done. * @param {File|Blob} file - * @param {{ priceSetId: string|number, psvId?: string|number, updateOnly?: boolean }} query + * @param {{ priceSetId: string|number, psvId?: string|number, updateOnly?: boolean, replaceByProductCountry?: boolean, countryCode?: string }} query * @param {(task: object) => void} [onProgress] * @returns {Promise<{ task: object }>} */ @@ -221,6 +221,9 @@ export async function importSetDetailPricesAsync(file, query, onProgress) { if (query && query.priceSetId != null) params.set('priceSetId', query.priceSetId) if (query && query.psvId != null && query.psvId !== '') params.set('psvId', query.psvId) if (query && query.updateOnly) params.set('updateOnly', 'true') + // Package Excel build-price only (product+country replace scope) + if (query && query.replaceByProductCountry) params.set('replaceByProductCountry', 'true') + if (query && query.countryCode) params.set('countryCode', query.countryCode) const submitRes = await request({ url: '/price/set/detail/importPrices?' + params.toString(), method: 'post', @@ -247,20 +250,24 @@ export function previewPullFromLibrary() { /** * Preview Excel import (no DB write). * @param {File|Blob} file - * @param {{ updateOnly?: boolean, details?: Array }} [opts] + * @param {{ updateOnly?: boolean, replaceByProductCountry?: boolean, countryCode?: string, details?: Array }} [opts] */ export function previewImportDetailPrices(file, opts = {}) { const formData = new FormData() formData.append('file', file) if (opts.updateOnly) formData.append('updateOnly', 'true') - if (opts.updateOnly && Array.isArray(opts.details)) { + if (opts.replaceByProductCountry) formData.append('replaceByProductCountry', 'true') + if (opts.countryCode) formData.append('countryCode', opts.countryCode) + // updateOnly / Excel build-price merge need current draft details + if ((opts.updateOnly || opts.replaceByProductCountry || opts.countryCode) && Array.isArray(opts.details)) { formData.append('detailsJson', JSON.stringify(opts.details)) } return request({ url: '/price/set/detail/previewImportPrices', method: 'post', data: formData, - headers: { 'Content-Type': 'multipart/form-data' }, + // FormData must not go through repeat-submit JSON.stringify (collapses to "{}") + headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false }, timeout: 10 * 60 * 1000 }) } diff --git a/bms-front/src/components/ExcelImportDialog/index.vue b/bms-front/src/components/ExcelImportDialog/index.vue index 344b937e..54e1d28a 100644 --- a/bms-front/src/components/ExcelImportDialog/index.vue +++ b/bms-front/src/components/ExcelImportDialog/index.vue @@ -1,5 +1,13 @@