From 6e301ecee72f173c524497c4d152cb01c9110fbf Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Wed, 30 Sep 2026 16:15:44 +0800 Subject: [PATCH] Enhance price set import functionality with preview options and update handling. Added new endpoints for previewing product library pulls and Excel imports, along with support for update-only imports. Updated internationalization messages for import success and error scenarios. --- .../controller/price/PriceSetController.java | 38 +- .../main/resources/i18n/messages.properties | 3 + .../bms/price/domain/PrcPriceSetVersion.java | 5 + .../price/mapper/PrcPriceSetDetailMapper.java | 8 + .../mapper/PrcPriceSetProductMapper.java | 3 + .../price/service/IPrcPriceSetService.java | 23 +- .../service/impl/PrcPriceSetServiceImpl.java | 674 ++++++++++++++++-- .../task/PrcPriceSetSaveAsyncRunner.java | 11 +- .../mapper/price/PrcPriceSetDetailMapper.xml | 80 +++ .../mapper/price/PrcPriceSetProductMapper.xml | 25 + .../mapper/price/PrcPriceSetVersionMapper.xml | 6 +- bms-back/sql/bms_init.sql | 2 + ...ice_set_version_price_build_mode_alter.sql | 3 + bms-front/src/api/price/set.js | 40 +- .../set/components/create/PriceEditPanel.vue | 4 +- .../set/components/create/StepBasicInfo.vue | 70 +- .../set/components/create/StepDetails.vue | 329 +++++++-- bms-front/src/views/price/set/create.vue | 181 +++-- .../src/views/price/set/utils/priceSet.js | 15 + 19 files changed, 1260 insertions(+), 260 deletions(-) create mode 100644 bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql 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 cf4a3080..173c72f0 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 @@ -24,6 +24,7 @@ import com.bms.common.core.domain.AjaxResult; import com.bms.common.core.page.TableDataInfo; import com.bms.common.enums.BusinessType; import com.bms.common.utils.MessageUtils; +import com.bms.common.utils.StringUtils; import com.bms.price.domain.PrcCustomerPriceSet; import com.bms.price.domain.PrcPriceSet; import com.bms.price.domain.PrcPriceSetDetail; @@ -233,14 +234,47 @@ public class PriceSetController extends BaseController @PostMapping("/detail/importPrices") public AjaxResult importDetailPrices(MultipartFile file, @RequestParam("priceSetId") Long priceSetId, - @RequestParam(value = "psvId", required = false) Long psvId) throws Exception + @RequestParam(value = "psvId", required = false) Long psvId, + @RequestParam(value = "updateOnly", required = false, defaultValue = "false") boolean updateOnly) throws Exception { - String taskId = priceSetService.submitImportDetailPricesAsync(priceSetId, psvId, file, getUsername()); + String taskId = priceSetService.submitImportDetailPricesAsync(priceSetId, psvId, file, getUsername(), updateOnly); Map payload = new HashMap(); payload.put("taskId", taskId); return success(payload); } + /** + * Preview pull product library (no DB write). For create wizard before finish. + */ + @PreAuthorize("@ss.hasPermi('price:set:edit') or @ss.hasPermi('price:set:forceEdit') or @ss.hasPermi('price:set:add')") + @PostMapping("/detail/previewPullFromLibrary") + public AjaxResult previewPullFromLibrary() + { + return success(priceSetService.previewPullFromLibrary()); + } + + /** + * Preview Excel import (no DB write). Optional detailsJson for updateOnly merge. + */ + @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 = "detailsJson", required = false) String detailsJson) throws Exception + { + if (file == null || file.isEmpty()) + { + return error("file required"); + } + List existing = null; + if (StringUtils.isNotEmpty(detailsJson)) + { + existing = com.alibaba.fastjson2.JSON.parseArray(detailsJson, PrcPriceSetDetail.class); + } + return success(priceSetService.previewImportDetailPrices( + file.getBytes(), file.getOriginalFilename(), updateOnly, existing)); + } + /** * Async pull product library into version details. Returns taskId; poll /save/task/{taskId}. */ diff --git a/bms-back/bms-admin/src/main/resources/i18n/messages.properties b/bms-back/bms-admin/src/main/resources/i18n/messages.properties index ab0b574e..25136454 100644 --- a/bms-back/bms-admin/src/main/resources/i18n/messages.properties +++ b/bms-back/bms-admin/src/main/resources/i18n/messages.properties @@ -79,7 +79,10 @@ price.set.import.noDetails=\u5957\u9910\u6682\u65e0\u4ef7\u683c\u660e\u7ec6\uff0 price.set.import.noPriceRows=\u4ef7\u8868\u672a\u89e3\u6790\u5230\u6709\u6548\u4ef7\u683c\u884c price.set.import.success=\u5bfc\u5165\u5b8c\u6210\uff1a\u66f4\u65b0 {0} \u6761\uff0c\u672a\u5339\u914d {1} \u6761\uff0c\u5957\u9910\u65e0\u5bf9\u5e94\u4ea7\u54c1 {2} \u4e2a price.set.import.noDetailWritten=\u672a\u5199\u5165\u4efb\u4f55\u5957\u9910\u660e\u7ec6\uff08\u4ef7\u8868\u65e0\u53ef\u7528\u91cd\u91cf\u6bb5\u6216\u5747\u4e3a\u7a7a\u8fd0\u8d39/\u6302\u53f7\u8d39\u4e14\u65e0\u53ef\u66f4\u65b0\u884c\uff09 +price.set.import.updateSuccess=\u5bfc\u5165\u4fee\u6539\u5b8c\u6210\uff1a\u66f4\u65b0 {0} \u6761\uff0c\u672a\u5339\u914d {1} \u6761\uff0c\u8df3\u8fc7\u7a7a\u8fd0\u8d39/\u6302\u53f7\u8d39 {2} \u6761 price.set.import.upsertSuccess=\u5bfc\u5165\u5b8c\u6210\uff1a\u65b0\u589e {0} \u6761\uff0c\u66f4\u65b0 {1} \u6761\uff0c\u8df3\u8fc7\u7a7a\u8fd0\u8d39/\u6302\u53f7\u8d39 {2} \u6761 +price.set.import.weightBandMismatch=\u5bfc\u5165\u5931\u8d25\uff1a\u4ee5\u4e0b\u4ea7\u54c1+\u56fd\u5bb6\u7684\u91cd\u91cf\u6bb5\u4e0e\u5957\u9910\u4e0d\u4e00\u81f4\uff1a{0} +price.set.import.weightBandMismatch.item=\u4ea7\u54c1 {0} / \u56fd\u5bb6 {1}\uff08\u5957\u9910\u91cd\u91cf\u6bb5\uff1a{2}\uff1bExcel\uff1a{3}\uff09 price.set.pull.success=\u62c9\u53d6\u5b8c\u6210\uff1a\u5df2\u5199\u5165 {0} \u6761\u5957\u9910\u660e\u7ec6 price.set.customer.notExists=客户绑定不存在 price.set.customer.unbindAudit=审核中的套餐不允许解绑客户 diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java index ec4ee3ca..4eb440e3 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/PrcPriceSetVersion.java @@ -26,6 +26,11 @@ public class PrcPriceSetVersion extends BaseEntity private Date expireTime; /** 0 draft / 3 pending / 1 active / 2 expired */ private String status; + /** + * Create-price mode for this version: PULL = from product library, IMPORT = Excel. + * Column: price_build_mode + */ + private String priceBuildMode; private List details; private List products; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java index 015a4edb..97f45ec8 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetDetailMapper.java @@ -22,6 +22,9 @@ public interface PrcPriceSetDetailMapper public int insertPrcPriceSetDetail(PrcPriceSetDetail detail); + /** Batch insert details; fills generated psdId on each item. */ + public int insertPrcPriceSetDetailBatch(@Param("list") List list); + public int updatePrcPriceSetDetail(PrcPriceSetDetail detail); public int deletePrcPriceSetDetailByIds(Long[] psdIds); @@ -49,6 +52,11 @@ public interface PrcPriceSetDetailMapper @Param("asOf") Date asOf, @Param("openOnly") boolean openOnly); + /** Preview: FIXED details from product country library (no insert). */ + public List selectFixedDetailsFromLibrary( + @Param("asOf") Date asOf, + @Param("openOnly") boolean openOnly); + /** DISCOUNT: one detail row per effective product */ public int insertDiscountDetailsFromLibrary( @Param("priceSetId") Long priceSetId, diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java index 90cd6013..10efa5ba 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/PrcPriceSetProductMapper.java @@ -26,6 +26,9 @@ public interface PrcPriceSetProductMapper /** Copy active library products into this version (ignore duplicates). */ public int insertFromLibrary(@Param("priceSetId") Long priceSetId, @Param("psvId") Long psvId); + /** Preview: active library products without writing set-product rows. */ + public List selectFromLibrary(); + /** Copy set products from one version to another. */ public int copyFromVersion(@Param("srcPsvId") Long srcPsvId, @Param("newPsvId") Long newPsvId); 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 813ca921..a1482912 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 @@ -109,15 +109,24 @@ public interface IPrcPriceSetService */ public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName) throws Exception; + /** + * @param updateOnly true = only update matched existing details (no insert / no new product) + */ + public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName, + boolean updateOnly) throws Exception; + /** * Sync worker: parse bytes and upsert FIXED detail prices matched by - * product + country + zone(if any) + weight band (insert when missing). + * product + country + zone(if any) + weight band (insert when missing unless updateOnly). */ public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName) throws Exception; /** Sync worker with optional taskId for progress reporting. */ public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, String taskId) throws Exception; + public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, + String taskId, boolean updateOnly) throws Exception; + /** * Submit async pull of product library into version details. * @return taskId for polling getSaveTask @@ -126,4 +135,16 @@ public interface IPrcPriceSetService /** Sync worker: replace (or skip) version details from product / product_country. */ public String pullFromLibrary(Long priceSetId, Long psvId, boolean force, String operName, String taskId); + + /** + * Preview pull: return products + FIXED details from library without writing DB. + */ + public Map previewPullFromLibrary(); + + /** + * Preview import: parse Excel into products + details without writing DB. + * When updateOnly, merge prices into existingDetails (in-memory match). + */ + public Map previewImportDetailPrices(byte[] bytes, String filename, boolean updateOnly, + List existingDetails) throws Exception; } 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 1c5c3bd3..4d91b8c1 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 @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Locale; import java.util.Set; +import java.util.TreeSet; import java.util.zip.CRC32; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -69,6 +70,14 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService /** Pending: effective_time set but not yet reached */ private static final String VER_PENDING = "3"; private static final String VER_EXPIRED = "2"; + /** Bulk detail insert chunk size for create/finish async save. */ + private static final int DETAIL_INSERT_BATCH = 200; + /** Request-scoped cache: productCode -> open product_country rows (bulk upsert). */ + private final ThreadLocal>> countryCacheTl = + new ThreadLocal>>(); + /** Request-scoped cache: productCode -> psp_id (bulk upsert). */ + private final ThreadLocal> setProductCacheTl = + new ThreadLocal>(); @Autowired private PrcPriceSetMapper setMapper; @@ -406,6 +415,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService nv.setStatus(VER_DRAFT); nv.setEffectiveTime(null); nv.setExpireTime(null); + nv.setPriceBuildMode(src.getPriceBuildMode()); nv.setCreateBy(createBy); versionMapper.insertPrcPriceSetVersion(nv); @@ -836,6 +846,13 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService @Override public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName) throws Exception + { + return submitImportDetailPricesAsync(priceSetId, psvId, file, operName, false); + } + + @Override + public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName, + boolean updateOnly) throws Exception { PrcPriceSet db = requireSet(priceSetId); assertChildEditable(db); @@ -860,7 +877,7 @@ 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); + saveAsyncRunner.runImportPrices(taskId, priceSetId, psvId, bytes, filename, operName, updateOnly); return taskId; } @@ -915,16 +932,241 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return MessageUtils.message("price.set.pull.success", Integer.valueOf(count)); } + @Override + public Map previewPullFromLibrary() + { + List products = setProductMapper.selectFromLibrary(); + if (products == null || products.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.set.library.empty")); + } + List details = detailMapper.selectFixedDetailsFromLibrary(null, true); + if (details == null || details.isEmpty()) + { + details = detailMapper.selectFixedDetailsFromLibrary(new Date(), false); + } + if (details == null || details.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.set.library.empty")); + } + Map result = new HashMap(); + result.put("products", products); + result.put("details", details); + result.put("message", MessageUtils.message("price.set.pull.success", Integer.valueOf(details.size()))); + return result; + } + + @Override + public Map previewImportDetailPrices(byte[] bytes, String filename, boolean updateOnly, + List existingDetails) throws Exception + { + Map> byProduct = productService.parseQuoteExcelByProduct(bytes, filename); + if (byProduct == null || byProduct.isEmpty()) + { + throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows")); + } + + List details = new ArrayList(); + if (existingDetails != null) + { + for (PrcPriceSetDetail d : existingDetails) + { + if (d != null) + { + details.add(d); + } + } + } + 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); + } + + if (updateOnly) + { + assertUpdateOnlyWeightBandsMatch(byProduct, detailIndex); + } + + Map productMap = new LinkedHashMap(); + int updated = 0; + int inserted = 0; + int skippedFee = 0; + int skippedUnmatched = 0; + Set touchedLocal = new HashSet(); + + for (Map.Entry> e : byProduct.entrySet()) + { + String productCode = e.getKey(); + List candidates = detailIndex.get(productCode); + if (candidates == null) + { + candidates = new ArrayList(); + detailIndex.put(productCode, candidates); + } + if (updateOnly && candidates.isEmpty()) + { + skippedUnmatched += e.getValue() == null ? 0 : e.getValue().size(); + continue; + } + String productName = resolveImportProductName(null, productCode, candidates); + if (!updateOnly) + { + ensurePreviewProduct(productMap, productCode, productName); + } + for (BmsProductCountry row : e.getValue()) + { + if (row == null) + { + continue; + } + BigDecimal freight = row.getFreightPrice(); + BigDecimal registration = row.getRegistrationFee(); + List hits = matchImportDetails(candidates, row); + if (!hits.isEmpty()) + { + if (freight == null && registration == null) + { + skippedFee++; + continue; + } + for (PrcPriceSetDetail hit : hits) + { + int identity = System.identityHashCode(hit); + if (touchedLocal.contains(Integer.valueOf(identity))) + { + continue; + } + if (freight != null) + { + hit.setFvValue(freight); + } + if (registration != null) + { + hit.setFvRegistrationFee(registration); + } + touchedLocal.add(Integer.valueOf(identity)); + updated++; + } + continue; + } + if (updateOnly) + { + skippedUnmatched++; + continue; + } + if (freight == null && registration == null) + { + skippedFee++; + continue; + } + PrcPriceSetDetail created = buildDetailFromImportRow( + null, null, productCode, productName, row, freight, registration); + candidates.add(created); + details.add(created); + inserted++; + } + } + + if (updateOnly) + { + if (updated == 0) + { + throw new ServiceException(MessageUtils.message("price.set.import.noDetailWritten")); + } + // Keep product list from existing details codes + for (PrcPriceSetDetail d : details) + { + if (d != null && StringUtils.isNotEmpty(d.getProductCode())) + { + ensurePreviewProduct(productMap, d.getProductCode(), d.getProductName()); + } + } + } + else if (inserted == 0 && updated == 0) + { + throw new ServiceException(MessageUtils.message("price.set.import.noDetailWritten")); + } + + Map result = new HashMap(); + result.put("products", new ArrayList(productMap.values())); + result.put("details", details); + if (updateOnly) + { + result.put("message", MessageUtils.message("price.set.import.updateSuccess", + Integer.valueOf(updated), Integer.valueOf(skippedUnmatched), Integer.valueOf(skippedFee))); + } + else + { + result.put("message", MessageUtils.message("price.set.import.upsertSuccess", + Integer.valueOf(inserted), Integer.valueOf(updated), Integer.valueOf(skippedFee))); + } + return result; + } + + private void ensurePreviewProduct(Map productMap, String productCode, String productName) + { + if (productMap == null || StringUtils.isEmpty(productCode)) + { + return; + } + String code = productCode.trim(); + String key = code.toUpperCase(Locale.ROOT); + if (productMap.containsKey(key)) + { + return; + } + PrcPriceSetProduct sp = new PrcPriceSetProduct(); + sp.setProductCode(code); + BmsProduct lib = productMapper.selectBmsProductByCode(code); + if (lib != null) + { + sp.setProductName(lib.getProductName()); + sp.setServiceLevel(lib.getServiceLevel()); + sp.setCargoAttr(lib.getCargoAttr()); + sp.setDestRegion(lib.getDestRegion()); + sp.setUpdateNote(lib.getUpdateNote()); + sp.setTaxMode(StringUtils.isNotEmpty(lib.getTaxMode()) ? lib.getTaxMode() : "TAX_INCLUDED"); + sp.setSourceProductId(lib.getProductId()); + sp.setSourceSheet(lib.getSourceSheet()); + } + else + { + sp.setProductName(StringUtils.isNotEmpty(productName) ? productName : code); + sp.setTaxMode("TAX_INCLUDED"); + } + productMap.put(key, sp); + } + @Override @Transactional(rollbackFor = Exception.class) public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName) throws Exception { - return importDetailPrices(priceSetId, psvId, bytes, filename, operName, null); + return importDetailPrices(priceSetId, psvId, bytes, filename, operName, null, false); } @Override @Transactional(rollbackFor = Exception.class) public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, String taskId) throws Exception + { + return importDetailPrices(priceSetId, psvId, bytes, filename, operName, taskId, false); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, + String taskId, boolean updateOnly) throws Exception { PrcPriceSet db = requireSet(priceSetId); assertChildEditable(db); @@ -972,9 +1214,15 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService list.add(d); } + if (updateOnly) + { + assertUpdateOnlyWeightBandsMatch(byProduct, detailIndex); + } + int updated = 0; int inserted = 0; int skippedFee = 0; + int skippedUnmatched = 0; Set touched = new HashSet(); int productTotal = byProduct.size(); int productIndex = 0; @@ -991,9 +1239,17 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService candidates = new ArrayList(); detailIndex.put(productCode, candidates); } + if (updateOnly && candidates.isEmpty()) + { + skippedUnmatched += e.getValue() == null ? 0 : e.getValue().size(); + continue; + } String productName = resolveImportProductName(psvId, productCode, candidates); - // Excel import is one of the only two paths that create set-product rows - Long pspId = ensureSetProduct(priceSetId, psvId, productCode, productName); + Long pspId = null; + if (!updateOnly) + { + pspId = ensureSetProduct(priceSetId, psvId, productCode, productName); + } for (BmsProductCountry row : e.getValue()) { if (row == null) @@ -1005,7 +1261,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService List hits = matchImportDetails(candidates, row); if (!hits.isEmpty()) { - // Existing band: only skip when Excel left both fees blank (keep old prices). if (freight == null && registration == null) { skippedFee++; @@ -1031,7 +1286,11 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } continue; } - // New band: skip when Excel has no usable fee (blank / slash = no service). + if (updateOnly) + { + skippedUnmatched++; + continue; + } if (freight == null && registration == null) { skippedFee++; @@ -1049,12 +1308,20 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService inserted++; } } - setProductMapper.bindDetailPspByPsvId(psvId); + if (!updateOnly) + { + setProductMapper.bindDetailPspByPsvId(psvId); + } reportImportProgress(taskId, priceSetId, 95, "finishing"); if (inserted == 0 && updated == 0) { throw new ServiceException(MessageUtils.message("price.set.import.noDetailWritten")); } + if (updateOnly) + { + return MessageUtils.message("price.set.import.updateSuccess", + Integer.valueOf(updated), Integer.valueOf(skippedUnmatched), Integer.valueOf(skippedFee)); + } return MessageUtils.message("price.set.import.upsertSuccess", Integer.valueOf(inserted), Integer.valueOf(updated), Integer.valueOf(skippedFee)); } @@ -1183,6 +1450,158 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return pick == null ? null : pick.getPsvId(); } + private void assertUpdateOnlyWeightBandsMatch(Map> byProduct, + Map> detailIndex) + { + if (byProduct == null || byProduct.isEmpty() || detailIndex == null || detailIndex.isEmpty()) + { + return; + } + List mismatches = new ArrayList(); + for (Map.Entry> e : byProduct.entrySet()) + { + String productCode = e.getKey(); + if (StringUtils.isEmpty(productCode) || e.getValue() == null || e.getValue().isEmpty()) + { + continue; + } + String pcKey = productCode.trim().toUpperCase(Locale.ROOT); + List candidates = detailIndex.get(pcKey); + if (candidates == null) + { + candidates = detailIndex.get(productCode); + } + if (candidates == null || candidates.isEmpty()) + { + continue; + } + // Group Excel rows by country (code preferred, else name) + Map> excelGroups = new LinkedHashMap>(); + for (BmsProductCountry row : e.getValue()) + { + if (row == null) + { + continue; + } + String ck = countryKeyOfImport(row); + if (StringUtils.isEmpty(ck)) + { + continue; + } + List g = excelGroups.get(ck); + if (g == null) + { + g = new ArrayList(); + excelGroups.put(ck, g); + } + g.add(row); + } + for (Map.Entry> ge : excelGroups.entrySet()) + { + List excelRows = ge.getValue(); + BmsProductCountry sample = excelRows.get(0); + List matchedDetails = new ArrayList(); + for (PrcPriceSetDetail d : candidates) + { + if (d != null && sameCountry(d, sample)) + { + matchedDetails.add(d); + } + } + if (matchedDetails.isEmpty()) + { + continue; + } + Set excelBands = new TreeSet(); + for (BmsProductCountry row : excelRows) + { + excelBands.add(weightBandLabel(row.getZoneCode(), row.getWeightMin(), row.getWeightMax())); + } + Set pkgBands = new TreeSet(); + for (PrcPriceSetDetail d : matchedDetails) + { + pkgBands.add(weightBandLabel(d.getZoneCode(), d.getWeightMin(), d.getWeightMax())); + } + if (pkgBands.equals(excelBands)) + { + continue; + } + mismatches.add(MessageUtils.message("price.set.import.weightBandMismatch.item", + productCode, + displayCountryOfImport(sample), + joinBands(pkgBands), + joinBands(excelBands))); + } + } + if (!mismatches.isEmpty()) + { + String joined = String.join(";", mismatches); + if (joined.length() > 800) + { + joined = joined.substring(0, 800) + "..."; + } + throw new ServiceException(MessageUtils.message("price.set.import.weightBandMismatch", joined)); + } + } + + private static String joinBands(Set bands) + { + if (bands == null || bands.isEmpty()) + { + return "-"; + } + return String.join("、", bands); + } + + private static String countryKeyOfImport(BmsProductCountry row) + { + if (row == null) + { + return ""; + } + if (StringUtils.isNotEmpty(row.getCountryCode())) + { + return "C:" + row.getCountryCode().trim().toUpperCase(Locale.ROOT); + } + if (StringUtils.isNotEmpty(row.getCountryName())) + { + return "N:" + row.getCountryName().trim(); + } + return ""; + } + + private static String displayCountryOfImport(BmsProductCountry row) + { + if (row == null) + { + return "-"; + } + if (StringUtils.isNotEmpty(row.getCountryName())) + { + return row.getCountryName().trim(); + } + if (StringUtils.isNotEmpty(row.getCountryCode())) + { + return row.getCountryCode().trim().toUpperCase(Locale.ROOT); + } + return "-"; + } + + private static String weightBandLabel(String zoneCode, BigDecimal min, BigDecimal max) + { + String w = formatWeightLabel(min, max); + if (StringUtils.isEmpty(w)) + { + w = "-"; + } + String z = zoneCode == null ? "" : zoneCode.trim(); + if (StringUtils.isNotEmpty(z)) + { + return z + ":" + w; + } + return w; + } + private List matchImportDetails(List candidates, BmsProductCountry row) { List hits = new ArrayList(); @@ -1698,7 +2117,11 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { if (detail.getProductCountryId() != null) { - syncZoneFromProductCountry(detail); + // Preview/pull already stamped weight bounds — skip per-row product_country reload. + if (detail.getWeightMin() == null || detail.getWeightMax() == null) + { + syncZoneFromProductCountry(detail); + } return; } if (StringUtils.isEmpty(detail.getProductCode()) || StringUtils.isEmpty(detail.getWeightLabel())) @@ -1732,9 +2155,14 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService { return; } - BmsProductCountry query = new BmsProductCountry(); - query.setProductCode(detail.getProductCode()); - List rows = productCountryMapper.selectBmsProductCountryList(query); + List rows = listOpenProductCountries(detail.getProductCode()); + if (rows == null || rows.isEmpty()) + { + // Fallback: include expired rows when open-only cache misses + BmsProductCountry query = new BmsProductCountry(); + query.setProductCode(detail.getProductCode()); + rows = productCountryMapper.selectBmsProductCountryList(query); + } if (rows == null) { return; @@ -1750,6 +2178,28 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } private List listOpenProductCountries(String productCode) + { + if (StringUtils.isEmpty(productCode)) + { + return java.util.Collections.emptyList(); + } + Map> cache = countryCacheTl.get(); + if (cache != null) + { + String key = productCode.trim().toUpperCase(Locale.ROOT); + List hit = cache.get(key); + if (hit != null) + { + return hit; + } + List loaded = loadOpenProductCountries(productCode); + cache.put(key, loaded); + return loaded; + } + return loadOpenProductCountries(productCode); + } + + private List loadOpenProductCountries(String productCode) { if (StringUtils.isEmpty(productCode)) { @@ -2315,6 +2765,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService /** * Upsert details by psdId. Keeps existing rows when id matches; inserts new; deletes orphans * not present in the payload. Avoids full delete+reinsert. + * Create/finish path: caches set-product + product_country lookups, batch-inserts new rows. */ private void upsertVersionDetails(PrcPriceSet set, PrcPriceSetVersion version, List details) { @@ -2332,93 +2783,156 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService } } } - Set keepIds = new HashSet(); - int total = details == null ? 0 : details.size(); - int index = 0; + + Map pspCache = new HashMap(); + PrcPriceSetProduct pq0 = new PrcPriceSetProduct(); + pq0.setPsvId(version.getPsvId()); + List existingSp = setProductMapper.selectPrcPriceSetProductList(pq0); + if (existingSp != null) + { + for (PrcPriceSetProduct sp : existingSp) + { + if (sp != null && StringUtils.isNotEmpty(sp.getProductCode()) && sp.getPspId() != null) + { + pspCache.put(sp.getProductCode().trim().toUpperCase(Locale.ROOT), sp.getPspId()); + } + } + } + Map> countryCache = new HashMap>(); if (details != null) { + Set codes = new HashSet(); for (PrcPriceSetDetail d : details) { - validateDetail(set, d); - d.setPriceSetId(set.getPriceSetId()); - d.setPsvId(version.getPsvId()); - Long pspId = lookupSetProductId(version.getPsvId(), d.getProductCode()); - if (pspId != null) + if (d != null && StringUtils.isNotEmpty(d.getProductCode())) { - d.setPspId(pspId); + codes.add(d.getProductCode().trim()); } - if (d.getPsdId() != null && byId.containsKey(d.getPsdId())) + } + for (String code : codes) + { + countryCache.put(code.toUpperCase(Locale.ROOT), loadOpenProductCountries(code)); + } + } + setProductCacheTl.set(pspCache); + countryCacheTl.set(countryCache); + try + { + Set keepIds = new HashSet(); + int total = details == null ? 0 : details.size(); + int done = 0; + List toInsert = new ArrayList(); + if (details != null) + { + for (PrcPriceSetDetail d : details) { - detailMapper.updatePrcPriceSetDetail(d); - keepIds.add(d.getPsdId()); + validateDetail(set, d); + d.setPriceSetId(set.getPriceSetId()); + d.setPsvId(version.getPsvId()); + Long pspId = ensureSetProduct(set.getPriceSetId(), version.getPsvId(), + d.getProductCode(), d.getProductName()); + if (pspId != null) + { + d.setPspId(pspId); + } + if (d.getPsdId() != null && byId.containsKey(d.getPsdId())) + { + detailMapper.updatePrcPriceSetDetail(d); + keepIds.add(d.getPsdId()); + done++; + reportDetailProgress(set, done, total); + } + else + { + d.setPsdId(null); + toInsert.add(d); + } } - else + } + for (int i = 0; i < toInsert.size(); i += DETAIL_INSERT_BATCH) + { + int end = Math.min(i + DETAIL_INSERT_BATCH, toInsert.size()); + List chunk = new ArrayList(toInsert.subList(i, end)); + detailMapper.insertPrcPriceSetDetailBatch(chunk); + for (PrcPriceSetDetail d : chunk) { - d.setPsdId(null); - detailMapper.insertPrcPriceSetDetail(d); if (d.getPsdId() != null) { keepIds.add(d.getPsdId()); } } - index++; - if (total > 0 && (index == total || index % 50 == 0 || index == 1)) + done += chunk.size(); + reportDetailProgress(set, done, total); + } + + List orphanIds = new ArrayList(); + for (Long id : byId.keySet()) + { + if (!keepIds.contains(id)) { - int pct = 25 + (int) Math.floor(index * 55.0 / total); - if (pct > 80) + orphanIds.add(id); + } + } + if (!orphanIds.isEmpty()) + { + for (Long id : orphanIds) + { + versionSurchargeMapper.deleteByPsdId(id); + } + detailMapper.deletePrcPriceSetDetailByIds(orphanIds.toArray(new Long[0])); + } + Set keepCodes = new HashSet(); + if (details != null) + { + for (PrcPriceSetDetail d : details) + { + if (d != null && StringUtils.isNotEmpty(d.getProductCode())) { - pct = 80; + keepCodes.add(d.getProductCode().trim()); } - reportSaveProgress(set, pct, "saving details " + index + "/" + total); } } - } - List orphanIds = new ArrayList(); - for (Long id : byId.keySet()) - { - if (!keepIds.contains(id)) + PrcPriceSetProduct pq = new PrcPriceSetProduct(); + pq.setPsvId(version.getPsvId()); + List setProducts = setProductMapper.selectPrcPriceSetProductList(pq); + if (setProducts != null) { - orphanIds.add(id); - } - } - if (!orphanIds.isEmpty()) - { - for (Long id : orphanIds) - { - versionSurchargeMapper.deleteByPsdId(id); - } - detailMapper.deletePrcPriceSetDetailByIds(orphanIds.toArray(new Long[0])); - } - // drop set-product rows whose codes no longer have any detail - Set keepCodes = new HashSet(); - if (details != null) - { - for (PrcPriceSetDetail d : details) - { - if (d != null && StringUtils.isNotEmpty(d.getProductCode())) + for (PrcPriceSetProduct sp : setProducts) { - keepCodes.add(d.getProductCode().trim()); + if (sp == null || StringUtils.isEmpty(sp.getProductCode())) + { + continue; + } + if (!keepCodes.contains(sp.getProductCode())) + { + setProductMapper.deleteByPsvIdAndCode(version.getPsvId(), sp.getProductCode()); + } } } + version.setDetails(details); } - PrcPriceSetProduct pq = new PrcPriceSetProduct(); - pq.setPsvId(version.getPsvId()); - List setProducts = setProductMapper.selectPrcPriceSetProductList(pq); - if (setProducts != null) + finally { - for (PrcPriceSetProduct sp : setProducts) - { - if (sp == null || StringUtils.isEmpty(sp.getProductCode())) - { - continue; - } - if (!keepCodes.contains(sp.getProductCode())) - { - setProductMapper.deleteByPsvIdAndCode(version.getPsvId(), sp.getProductCode()); - } - } + setProductCacheTl.remove(); + countryCacheTl.remove(); + } + } + + private void reportDetailProgress(PrcPriceSet set, int done, int total) + { + if (total <= 0) + { + return; + } + if (done == total || done == 1 || done % 200 == 0) + { + int pct = 25 + (int) Math.floor(done * 55.0 / total); + if (pct > 80) + { + pct = 80; + } + reportSaveProgress(set, pct, "saving details " + done + "/" + total); } - version.setDetails(details); } /** @@ -2437,7 +2951,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService /** * Ensure package-side product master row exists for this version+code; return psp_id. - * Only called from pull-from-library / Excel import. + * Uses request-scoped cache during bulk upsert to avoid N selects on same code. */ private Long ensureSetProduct(Long priceSetId, Long psvId, String productCode, String productNameHint) { @@ -2446,6 +2960,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService return null; } String code = productCode.trim(); + String key = code.toUpperCase(Locale.ROOT); + Map cache = setProductCacheTl.get(); + if (cache != null && cache.containsKey(key)) + { + return cache.get(key); + } PrcPriceSetProduct existing = setProductMapper.selectByPsvIdAndCode(psvId, code); if (existing != null) { @@ -2455,6 +2975,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService existing.setProductName(productNameHint); setProductMapper.updatePrcPriceSetProduct(existing); } + if (cache != null) + { + cache.put(key, existing.getPspId()); + } return existing.getPspId(); } PrcPriceSetProduct row = new PrcPriceSetProduct(); @@ -2487,6 +3011,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService row.setTaxMode("TAX_INCLUDED"); } setProductMapper.insertPrcPriceSetProduct(row); + if (cache != null) + { + cache.put(key, row.getPspId()); + } return row.getPspId(); } 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 192d8d86..4d6cd8f9 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 @@ -80,13 +80,20 @@ public class PrcPriceSetSaveAsyncRunner /** Background import of quote Excel into package detail prices. */ @Async("threadPoolTaskExecutor") public void runImportPrices(String taskId, Long priceSetId, Long psvId, byte[] bytes, String filename, String operName) + { + runImportPrices(taskId, priceSetId, psvId, bytes, filename, operName, false); + } + + @Async("threadPoolTaskExecutor") + public void runImportPrices(String taskId, Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, + boolean updateOnly) { try { put(taskId, PrcPriceSetSaveTask.STATUS_RUNNING, 10, "parsing excel", priceSetId, null); - String message = priceSetService.importDetailPrices(priceSetId, psvId, bytes, filename, operName, taskId); + String message = priceSetService.importDetailPrices(priceSetId, psvId, bytes, filename, operName, taskId, updateOnly); put(taskId, PrcPriceSetSaveTask.STATUS_SUCCESS, 100, message, priceSetId, null); - log.info("async import prices done, taskId={}, priceSetId={}", taskId, priceSetId); + log.info("async import prices done, taskId={}, priceSetId={}, updateOnly={}", taskId, priceSetId, updateOnly); } catch (Exception e) { diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml index 9cc331ab..95d5f48a 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetDetailMapper.xml @@ -185,6 +185,66 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by p.product_code asc, c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc + + insert ignore into prc_price_set_detail ( price_set_id, psv_id, product_code, product_name, round_mode, create_time @@ -261,6 +321,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ) + + insert into prc_price_set_detail ( + price_set_id, psv_id, psp_id, product_code, product_name, + country_code, country_name, zone_id, zone_code, zone_name, + zd_id, wsd_id, weight_label, weight_min, weight_max, product_country_id, + fv_value, fv_registration_fee, discount_value, round_mode, pr_id, + effective_date, expire_date, create_time + ) values + + ( + #{item.priceSetId}, #{item.psvId}, #{item.pspId}, #{item.productCode}, #{item.productName}, + #{item.countryCode}, #{item.countryName}, #{item.zoneId}, #{item.zoneCode}, #{item.zoneName}, + #{item.zdId}, #{item.wsdId}, #{item.weightLabel}, #{item.weightMin}, #{item.weightMax}, #{item.productCountryId}, + #{item.fvValue}, #{item.fvRegistrationFee}, #{item.discountValue}, + ifnull(#{item.roundMode}, 'HALF_UP'), #{item.prId}, + #{item.effectiveDate}, #{item.expireDate}, sysdate() + ) + + + update prc_price_set_detail diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml index 35eab469..382778e1 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetProductMapper.xml @@ -125,6 +125,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by p.product_code asc + + update prc_price_set_detail d inner join prc_price_set_product sp diff --git a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml index d4c0da0f..a7ddb0aa 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/PrcPriceSetVersionMapper.xml @@ -12,6 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -20,7 +21,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select psv_id, price_set_id, version_no, version_seq, effective_time, expire_time, status, - created_by, create_time, updated_by, update_time + price_build_mode, created_by, create_time, updated_by, update_time from prc_price_set_version @@ -57,6 +58,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" effective_time, expire_time, status, + price_build_mode, created_by, create_time ) values ( @@ -66,6 +68,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{effectiveTime}, #{expireTime}, #{status}, + #{priceBuildMode}, #{createBy}, sysdate() ) @@ -79,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" effective_time = #{effectiveTime}, expire_time = #{expireTime}, status = #{status}, + price_build_mode = #{priceBuildMode}, updated_by = #{updateBy}, update_time = sysdate() diff --git a/bms-back/sql/bms_init.sql b/bms-back/sql/bms_init.sql index d020ac36..b7e88d4a 100644 --- a/bms-back/sql/bms_init.sql +++ b/bms-back/sql/bms_init.sql @@ -1305,6 +1305,8 @@ CREATE TABLE `prc_price_set_version` ( `effective_time` datetime DEFAULT NULL COMMENT '生效时间', `expire_time` datetime DEFAULT NULL COMMENT '失效时间(空=长期;下一版本启动时写入该启动时间)', `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0 draft 3 pending 1 effect 2 expired', + `price_build_mode` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 PULL=拉取产品库 IMPORT=Excel导入', + `created_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_by` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新人', diff --git a/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql b/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql new file mode 100644 index 00000000..4f3751a7 --- /dev/null +++ b/bms-back/sql/price/prc_price_set_version_price_build_mode_alter.sql @@ -0,0 +1,3 @@ +-- Add create-price mode on package version: PULL / IMPORT +ALTER TABLE prc_price_set_version + ADD COLUMN price_build_mode varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建价格方式 PULL=拉取产品库 IMPORT=Excel导入' AFTER status; diff --git a/bms-front/src/api/price/set.js b/bms-front/src/api/price/set.js index 62301de0..7d0a89e3 100644 --- a/bms-front/src/api/price/set.js +++ b/bms-front/src/api/price/set.js @@ -56,7 +56,7 @@ export async function savePriceSetAsync(data, onProgress) { err.task = task throw err } - await new Promise(resolve => setTimeout(resolve, 800)) + await new Promise(resolve => setTimeout(resolve, 400)) } throw new Error('save timeout') } @@ -202,7 +202,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 }} query + * @param {{ priceSetId: string|number, psvId?: string|number, updateOnly?: boolean }} query * @param {(task: object) => void} [onProgress] * @returns {Promise<{ task: object }>} */ @@ -212,6 +212,7 @@ export async function importSetDetailPricesAsync(file, query, onProgress) { const params = new URLSearchParams() 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') const submitRes = await request({ url: '/price/set/detail/importPrices?' + params.toString(), method: 'post', @@ -225,6 +226,37 @@ export async function importSetDetailPricesAsync(file, query, onProgress) { return pollPriceSetSaveTask(taskId, onProgress) } +/** + * Preview pull product library (no DB write). Returns { products, details, message }. + */ +export function previewPullFromLibrary() { + return request({ + url: '/price/set/detail/previewPullFromLibrary', + method: 'post' + }) +} + +/** + * Preview Excel import (no DB write). + * @param {File|Blob} file + * @param {{ updateOnly?: boolean, 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)) { + formData.append('detailsJson', JSON.stringify(opts.details)) + } + return request({ + url: '/price/set/detail/previewImportPrices', + method: 'post', + data: formData, + headers: { 'Content-Type': 'multipart/form-data' }, + timeout: 10 * 60 * 1000 + }) +} + /** * Async pull product library into package version details; poll until done. * @param {{ priceSetId: string|number, psvId?: string|number, force?: boolean }} query @@ -263,7 +295,7 @@ async function pollPriceSetSaveTask(taskId, onProgress) { err.task = task throw err } - await new Promise(resolve => setTimeout(resolve, 800)) + await new Promise(resolve => setTimeout(resolve, 400)) } throw new Error('task timeout') } @@ -306,7 +338,7 @@ export async function updateSetDetailSurchargeAsync(details, onProgress) { err.task = task throw err } - await new Promise(resolve => setTimeout(resolve, 800)) + await new Promise(resolve => setTimeout(resolve, 400)) } throw new Error('surcharge save timeout') } diff --git a/bms-front/src/views/price/set/components/create/PriceEditPanel.vue b/bms-front/src/views/price/set/components/create/PriceEditPanel.vue index 740d7080..c35f46e2 100644 --- a/bms-front/src/views/price/set/components/create/PriceEditPanel.vue +++ b/bms-front/src/views/price/set/components/create/PriceEditPanel.vue @@ -24,6 +24,7 @@ :psv-id="version.psvId" @update:psv-id="v => { if (v != null && v !== '') version.psvId = v }" :set-type="setType" + :price-build-mode="version.priceBuildMode" :effective-time="version.effectiveTime" :version-no="version.versionNo" :ref-data="refData" @@ -52,7 +53,7 @@ const refData = usePriceSetRefData() const loading = ref(false) const loaded = ref(false) const setType = ref('FIXED') -const version = reactive({ psvId: undefined, versionNo: 'V1', effectiveTime: undefined }) +const version = reactive({ psvId: undefined, versionNo: 'V1', effectiveTime: undefined, priceBuildMode: undefined }) const details = ref([]) const products = ref([]) @@ -77,6 +78,7 @@ function load() { version.psvId = current.psvId version.versionNo = current.versionNo || 'V1' version.effectiveTime = current.effectiveTime + version.priceBuildMode = current.priceBuildMode details.value = [] products.value = (current.products || []).map(p => ({ ...p })) diff --git a/bms-front/src/views/price/set/components/create/StepBasicInfo.vue b/bms-front/src/views/price/set/components/create/StepBasicInfo.vue index 0392fe6e..4026e0af 100644 --- a/bms-front/src/views/price/set/components/create/StepBasicInfo.vue +++ b/bms-front/src/views/price/set/components/create/StepBasicInfo.vue @@ -7,40 +7,6 @@
显示给客户的套餐对外名称
- -
-
-
FIXED 固定价
-
可不填明细(走产品默认价);有明细则按产品×国家×重量段覆盖运费+挂号费
-
-
-
DISCOUNT 折扣
-
按默认等级价统一乘折扣,可选产品范围
-
-
-
- - - - % - 仅 DISCOUNT 模式生效 -
范围 1~99,88 表示默认价 × 0.88;FIXED 模式此值忽略
-
- @@ -49,20 +15,15 @@