From 32ab61b51c756d570ca2dcf7cc9478d6a1554312 Mon Sep 17 00:00:00 2001 From: wuweihua <88888888> Date: Mon, 28 Sep 2026 15:45:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=9F=BA=E7=A1=80=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=E7=89=88=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/price/ProductController.java | 9 + .../main/resources/i18n/messages.properties | 4 +- .../bms/price/domain/BmsProductCountry.java | 4 + .../price/domain/BmsProductPriceVersion.java | 32 +++ .../com/bms/price/domain/PrcPriceSet.java | 5 + .../price/mapper/BmsProductCountryMapper.java | 29 +++ .../mapper/BmsProductPriceVersionMapper.java | 38 +++ .../price/mapper/PrcPriceSetDetailMapper.java | 13 +- .../bms/price/service/IBmsProductService.java | 4 + .../service/impl/BmsProductServiceImpl.java | 237 ++++++++++++++++-- .../service/impl/PrcPriceSetServiceImpl.java | 15 +- .../mapper/price/BmsProductCountryMapper.xml | 142 ++++++++--- .../price/BmsProductPriceVersionMapper.xml | 123 +++++++++ .../mapper/price/PrcPriceSetDetailMapper.xml | 71 ++++-- bms-back/sql/bms_init.sql | 26 ++ .../sql/price/product_price_version_alter.sql | 98 ++++++++ bms-front/src/api/price/product.js | 8 + .../components/ProductCountryDrawer.vue | 1 + .../src/views/price/basic/product/index.vue | 5 +- .../set/components/create/StepBasicInfo.vue | 97 ++++--- bms-front/src/views/price/set/create.vue | 1 + .../src/views/price/set/utils/priceSet.js | 1 + 22 files changed, 831 insertions(+), 132 deletions(-) create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductPriceVersion.java create mode 100644 bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductPriceVersionMapper.java create mode 100644 bms-back/bms-system/src/main/resources/mapper/price/BmsProductPriceVersionMapper.xml create mode 100644 bms-back/sql/price/product_price_version_alter.sql diff --git a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java index 13430b95..da267dd8 100644 --- a/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java +++ b/bms-back/bms-admin/src/main/java/com/bms/web/controller/price/ProductController.java @@ -22,6 +22,7 @@ import com.bms.common.core.page.TableDataInfo; import com.bms.common.enums.BusinessType; import com.bms.price.domain.BmsProduct; import com.bms.price.domain.BmsProductCountry; +import com.bms.price.domain.BmsProductPriceVersion; import com.bms.price.service.IBmsProductService; /** @@ -45,6 +46,14 @@ public class ProductController extends BaseController return getDataTable(list); } + @PreAuthorize("@ss.hasPermi('price:product:query') or @ss.hasPermi('price:product:list') or @ss.hasPermi('price:set:add') or @ss.hasPermi('price:set:query')") + @GetMapping("/priceVersions") + public AjaxResult priceVersions() + { + List list = productService.selectPriceVersionOptions(); + return success(list); + } + @PreAuthorize("@ss.hasPermi('price:product:query')") @GetMapping("/{productCode}/countries") public AjaxResult countries(@PathVariable String productCode, 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 4b1a3385..0b3e3693 100644 --- a/bms-back/bms-admin/src/main/resources/i18n/messages.properties +++ b/bms-back/bms-admin/src/main/resources/i18n/messages.properties @@ -53,6 +53,7 @@ price.set.name.empty=套餐名称不能为空 price.set.name.exists=套餐名称已存在 price.set.effectiveDate.empty=生效日期不能为空 price.set.date.invalid=生效日期不能晚于失效日期 +price.set.baseVersionNo.empty=请选择基础价格版本 price.set.discount.range=折扣模式默认折扣需在 1~99 price.set.delete.statusNotAllowed=仅待启用或驳回状态的套餐允许删除:{0} price.set.delete.boundCustomer=套餐【{0}】已绑定客户,请先解绑后再删除 @@ -110,10 +111,11 @@ price.product.import.regionEmpty=bms_region 无可用国家数据,请先同步 price.product.import.partialFail=导入完成但存在失败项:新增 {0},跳过 {1};失败明细:{2} price.product.import.effTooEarly=生效日期 {0} 早于当前开放版 {1},请使用更晚的生效日期导入 price.product.import.needFeeAlter=请先执行 SQL:bms-back/sql/price/product_country_fee_alter.sql(缺少运费/挂号费字段) +price.product.import.needVersionAlter=请先执行 SQL:bms-back/sql/price/product_price_version_alter.sql(缺少产品基础价格版本表/字段) price.product.import.needExpireAlter=请先执行 SQL:bms-back/sql/price/product_country_expire_alter.sql(缺少失效日期/版本唯一键) price.product.import.needCodeEffUnique=无法新增「同代码、不同生效日」的产品。请先执行 SQL:bms-back/sql/price/product_code_eff_unique_alter.sql(去掉 product_code 单列唯一,改为代码+生效日唯一) price.product.import.success=导入成功:新增 {0} 个产品,跳过 {1} 个(同代码同生效日不覆盖);写入国家/重量段明细 {2} 行。 -price.product.import.success.detail1=产品主数据按「产品代码+生效日期」新增,不覆盖已有记录。国家明细:新生效日晚于上次则保留历史,上次失效日=本次生效日。 +price.product.import.success.detail1=产品主数据按「产品代码+生效日期」新增,不覆盖已有记录。国家明细:新生效日晚于上次则新建版本并复制上一版,上次失效日=本次生效日;导入行匹配到则更新,未匹配则插入。 price.product.import.success.detail2=范围:产品目录 + 重量段 + 运费/挂号费 + 尺寸/尾程/时效;国家码来自 bms_region。 price.product.import.noSheet=未找到产品明细sheet(需与产品名称一致):{0} price.product.import.noCountry=产品明细sheet无有效国家/重量段数据:{0} 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 b21931b3..2a31c2a9 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 @@ -15,6 +15,10 @@ public class BmsProductCountry implements Serializable private Long detailId; /** FK to bms_product.product_id (one country version belongs to one product row) */ private Long productId; + /** FK to bms_product_price_version.ppv_id */ + private Long ppvId; + /** Display only: joined from bms_product_price_version.version_no */ + private String versionNo; private String productCode; private String countryName; private String countryCode; diff --git a/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductPriceVersion.java b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductPriceVersion.java new file mode 100644 index 00000000..9c37f731 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/domain/BmsProductPriceVersion.java @@ -0,0 +1,32 @@ +package com.bms.price.domain; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.bms.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** Product base price version bms_product_price_version */ +@Data +@EqualsAndHashCode(callSuper = true) +public class BmsProductPriceVersion extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + private Long ppvId; + private String productCode; + /** Linked bms_product.product_id for this version row */ + private Long productId; + /** V1 / V2 */ + private String versionNo; + private Integer versionSeq; + @JsonFormat(pattern = "yyyy-MM-dd") + private Date effectiveDate; + /** null = open; set to next version effective_date when that version starts */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date expireDate; + /** 0 pending / 1 active / 2 expired */ + private String status; + /** query only: product count for this effective_date option */ + private Integer productCount; +} 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 e5d4bd43..6ed87327 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 @@ -24,6 +24,11 @@ public class PrcPriceSet extends BaseEntity private BigDecimal defaultDiscount; private String defaultType; private String calculateSign; + /** + * Pull-only: product base price version_no (V1/V2 from bms_product_price_version). + * Not a DB column — used when pullFromLibrary=Y to choose library rows. + */ + private String baseVersionNo; @JsonFormat(pattern = "yyyy-MM-dd") private Date effectiveDate; @JsonFormat(pattern = "yyyy-MM-dd") diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java index ecc07d95..db2502a7 100644 --- a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductCountryMapper.java @@ -20,6 +20,12 @@ public interface BmsProductCountryMapper /** 按明细行主键更新可编辑字段(重量段/运费/挂号费/尺寸/尾程/时效/生效失效日期) */ public int updateBmsProductCountry(BmsProductCountry country); + /** Import overlay: update price/zone/attrs on matched country row */ + public int updateBmsProductCountryImport(BmsProductCountry country); + + /** Bind legacy open row to a version */ + public int updatePpvId(@Param("detailId") Long detailId, @Param("ppvId") Long ppvId); + public int deleteByProductId(Long productId); public int deleteByProductCode(String productCode); @@ -44,6 +50,29 @@ public interface BmsProductCountryMapper @Param("productCode") String productCode, @Param("effectiveDate") Date effectiveDate); + /** Open country rows (expire_date is null) for product */ + public List selectOpenByProductCode(@Param("productCode") String productCode); + + /** Country rows under a price version */ + public List selectByPpvId(@Param("ppvId") Long ppvId); + + /** Copy open/source version rows into a new version (new ppv_id / product_id / effective_date) */ + public int copyCountriesFromVersion( + @Param("srcPpvId") Long srcPpvId, + @Param("newPpvId") Long newPpvId, + @Param("productId") Long productId, + @Param("productCode") String productCode, + @Param("effectiveDate") Date effectiveDate, + @Param("status") String status); + + /** Copy currently open rows (legacy / no ppv_id) into a new version */ + public int copyOpenCountriesToVersion( + @Param("productCode") String productCode, + @Param("newPpvId") Long newPpvId, + @Param("productId") Long productId, + @Param("effectiveDate") Date effectiveDate, + @Param("status") String status); + /** * Match by product + country + weight + bizDate (+ optional zoneCode). * When zoneCode is empty, prefer rows without zone. diff --git a/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductPriceVersionMapper.java b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductPriceVersionMapper.java new file mode 100644 index 00000000..398cff60 --- /dev/null +++ b/bms-back/bms-system/src/main/java/com/bms/price/mapper/BmsProductPriceVersionMapper.java @@ -0,0 +1,38 @@ +package com.bms.price.mapper; + +import java.util.Date; +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.bms.price.domain.BmsProductPriceVersion; + +/** + * Product base price version + */ +public interface BmsProductPriceVersionMapper +{ + public BmsProductPriceVersion selectBmsProductPriceVersionById(Long ppvId); + + /** Open version (expire_date is null), prefer latest effective_date */ + public BmsProductPriceVersion selectOpenByProductCode(@Param("productCode") String productCode); + + public BmsProductPriceVersion selectByProductCodeAndEffectiveDate( + @Param("productCode") String productCode, + @Param("effectiveDate") Date effectiveDate); + + public Integer selectMaxVersionSeq(@Param("productCode") String productCode); + + /** Distinct effective_date options for package create dropdown (desc) */ + public List selectVersionOptions(); + + public int insertBmsProductPriceVersion(BmsProductPriceVersion version); + + public int updateBmsProductPriceVersion(BmsProductPriceVersion version); + + /** Close open versions: set expire_date = newEffectiveDate where effective_date < newEffectiveDate */ + public int expireOpenByProductCode( + @Param("productCode") String productCode, + @Param("newEffectiveDate") Date newEffectiveDate); + + /** Recompute status from effective_date / expire_date vs today */ + public int refreshStatusByDates(); +} 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 aa7df259..42817419 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 @@ -42,19 +42,22 @@ public interface PrcPriceSetDetailMapper public int copyDetailsFromVersion(@Param("srcPsvId") Long srcPsvId, @Param("newPsvId") Long newPsvId); /** - * FIXED: insert details from active product + country library (asOf window or openOnly fallback). - * @return inserted row count + * FIXED: insert details from product country library. + * When baseVersionNo is set, pull that base-price version only; + * otherwise asOf window or openOnly fallback. */ public int insertFixedDetailsFromLibrary( @Param("priceSetId") Long priceSetId, @Param("psvId") Long psvId, @Param("asOf") Date asOf, - @Param("openOnly") boolean openOnly); + @Param("openOnly") boolean openOnly, + @Param("baseVersionNo") String baseVersionNo); - /** DISCOUNT: one detail row per active product */ + /** DISCOUNT: one detail row per product (optionally scoped to base version_no) */ public int insertDiscountDetailsFromLibrary( @Param("priceSetId") Long priceSetId, - @Param("psvId") Long psvId); + @Param("psvId") Long psvId, + @Param("baseVersionNo") String baseVersionNo); /** FIXED: product + country + zone + productCountryId / weight; optional psvId scopes to version */ public PrcPriceSetDetail selectFixedMatch( 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 2eafcf21..4c7218a6 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 @@ -5,6 +5,7 @@ import java.util.List; import org.springframework.web.multipart.MultipartFile; import com.bms.price.domain.BmsProduct; import com.bms.price.domain.BmsProductCountry; +import com.bms.price.domain.BmsProductPriceVersion; /** * 产品列表 服务层 @@ -54,4 +55,7 @@ public interface IBmsProductService * @return HTML result message */ public String importCountryProductExcel(MultipartFile file, String countryCode, String operName) throws Exception; + + /** Distinct base-price version options (by effective_date desc) for package create */ + public List selectPriceVersionOptions(); } 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 c74382db..eeb9cab2 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 @@ -34,9 +34,11 @@ import com.bms.common.utils.MessageUtils; import com.bms.common.utils.StringUtils; import com.bms.price.domain.BmsProduct; import com.bms.price.domain.BmsProductCountry; +import com.bms.price.domain.BmsProductPriceVersion; import com.bms.price.domain.PrcZone; import com.bms.price.mapper.BmsProductCountryMapper; import com.bms.price.mapper.BmsProductMapper; +import com.bms.price.mapper.BmsProductPriceVersionMapper; import com.bms.price.mapper.PrcZoneMapper; import com.bms.price.service.IBmsProductService; import com.bms.price.service.IPrcSurchargeRelService; @@ -74,6 +76,9 @@ public class BmsProductServiceImpl implements IBmsProductService @Autowired private BmsProductCountryMapper countryMapper; + @Autowired + private BmsProductPriceVersionMapper priceVersionMapper; + @Autowired private BmsRegionMapper regionMapper; @@ -97,6 +102,7 @@ public class BmsProductServiceImpl implements IBmsProductService { productMapper.refreshStatusByDates(); countryMapper.refreshStatusByDates(); + priceVersionMapper.refreshStatusByDates(); if (product != null) { product.setStatus(normalizeStatusCode(product.getStatus())); @@ -415,6 +421,13 @@ public class BmsProductServiceImpl implements IBmsProductService } } + @Override + public List selectPriceVersionOptions() + { + List list = priceVersionMapper.selectVersionOptions(); + return list == null ? new ArrayList<>() : list; + } + /** @return int[]{added, skipped, countryRows} */ private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName, Map regionByNameCn, List euRegions, @@ -476,8 +489,10 @@ public class BmsProductServiceImpl implements IBmsProductService /** * Versioned country save: - * - newEff > last open: close old (expire_date = newEff), insert new (keep history) - * - newEff equals last open: replace open rows + * - require effective_date + * - newEff > last open: expire previous version (expire_date = newEff), create new version, + * copy previous country rows, then match import (hit=update, miss=insert) + * - newEff equals last open: reuse open version, match update/insert (no full replace) * - newEff < last open: reject */ private int saveCountriesVersioned(BmsProduct product, List countries) @@ -488,40 +503,125 @@ public class BmsProductServiceImpl implements IBmsProductService } String productCode = product.getProductCode(); Long productId = product.getProductId(); - Date newEff = resolveImportEffectiveDate(countries); - Date lastOpenEff = countryMapper.selectMaxOpenEffectiveDate(productCode); + Date newEff = toDateOnly(resolveImportEffectiveDate(countries)); + if (newEff == null) + { + newEff = toDateOnly(product.getEffectiveDate()); + } + if (newEff == null) + { + throw new ServiceException(MessageUtils.message("price.product.import.effEmpty")); + } - if (lastOpenEff != null && newEff != null && newEff.before(lastOpenEff)) + BmsProductPriceVersion openVer = priceVersionMapper.selectOpenByProductCode(productCode); + Date lastOpenEff = openVer != null ? toDateOnly(openVer.getEffectiveDate()) + : toDateOnly(countryMapper.selectMaxOpenEffectiveDate(productCode)); + + if (lastOpenEff != null && newEff.before(lastOpenEff)) { throw new ServiceException(MessageUtils.message("price.product.import.effTooEarly", formatDate(newEff), formatDate(lastOpenEff))); } - if (lastOpenEff != null && newEff != null && newEff.after(lastOpenEff)) + BmsProductPriceVersion targetVer; + boolean newVersionDay = lastOpenEff == null || newEff.after(lastOpenEff); + + if (newVersionDay) { - // Previous open version expire_date = this effective_date; keep old rows - countryMapper.expireOpenByProductCode(productCode, newEff); - countryMapper.refreshStatusByDates(); + List prevSnapshot = null; + Long srcPpvId = openVer != null ? openVer.getPpvId() : null; + if (lastOpenEff != null) + { + if (srcPpvId != null) + { + prevSnapshot = null; // copy via SQL by ppv_id after expire + } + else + { + prevSnapshot = countryMapper.selectOpenByProductCode(productCode); + } + priceVersionMapper.expireOpenByProductCode(productCode, newEff); + countryMapper.expireOpenByProductCode(productCode, newEff); + } + targetVer = createPriceVersion(product, newEff); + String newStatus = resolveStoredStatus(newEff, null); + if (srcPpvId != null) + { + countryMapper.copyCountriesFromVersion(srcPpvId, targetVer.getPpvId(), productId, + productCode, newEff, newStatus); + } + else if (prevSnapshot != null && !prevSnapshot.isEmpty()) + { + insertCountryCopies(prevSnapshot, targetVer.getPpvId(), productId, productCode, newEff, newStatus); + } } - else if (lastOpenEff != null) + else { - // Same effective day: replace open rows so re-import can fill/fix details - countryMapper.deleteOpenByProductCodeAndEffectiveDate(productCode, newEff); + targetVer = priceVersionMapper.selectByProductCodeAndEffectiveDate(productCode, newEff); + if (targetVer == null) + { + targetVer = openVer != null ? openVer : createPriceVersion(product, newEff); + } + else if (targetVer.getProductId() == null && productId != null) + { + targetVer.setProductId(productId); + targetVer.setUpdateBy(product.getUpdateBy()); + priceVersionMapper.updateBmsProductPriceVersion(targetVer); + } + assignOpenRowsToVersion(productCode, targetVer.getPpvId()); } - for (BmsProductCountry c : countries) + List baseRows = countryMapper.selectByPpvId(targetVer.getPpvId()); + if (baseRows == null) { - c.setProductId(productId); - c.setProductCode(productCode); - c.setExpireDate(null); - if (c.getEffectiveDate() == null) - { - c.setEffectiveDate(newEff); - } - c.setStatus(resolveStoredStatus(c.getEffectiveDate(), c.getExpireDate())); + baseRows = new ArrayList<>(); + } + Map byKey = new HashMap<>(); + for (BmsProductCountry row : baseRows) + { + byKey.put(countryMatchKey(row), row); + } + + int touched = 0; + for (BmsProductCountry incoming : countries) + { + incoming.setProductId(productId); + incoming.setProductCode(productCode); + incoming.setPpvId(targetVer.getPpvId()); + incoming.setEffectiveDate(newEff); + incoming.setExpireDate(null); + incoming.setStatus(resolveStoredStatus(newEff, null)); + + String key = countryMatchKey(incoming); + BmsProductCountry existing = byKey.get(key); try { - countryMapper.insertBmsProductCountry(c); + if (existing != null) + { + existing.setProductId(productId); + existing.setPpvId(targetVer.getPpvId()); + existing.setCountryCode(incoming.getCountryCode()); + existing.setZoneId(incoming.getZoneId()); + existing.setZoneCode(incoming.getZoneCode()); + existing.setZoneName(incoming.getZoneName()); + existing.setWeightMin(incoming.getWeightMin()); + existing.setWeightMax(incoming.getWeightMax()); + existing.setFreightPrice(incoming.getFreightPrice()); + existing.setRegistrationFee(incoming.getRegistrationFee()); + existing.setSizeLimit(incoming.getSizeLimit()); + existing.setLastMile(incoming.getLastMile()); + existing.setLeadTime(incoming.getLeadTime()); + existing.setEffectiveDate(newEff); + existing.setExpireDate(null); + existing.setStatus(resolveStoredStatus(newEff, null)); + countryMapper.updateBmsProductCountryImport(existing); + } + else + { + countryMapper.insertBmsProductCountry(incoming); + byKey.put(key, incoming); + } + touched++; } catch (Exception e) { @@ -530,6 +630,10 @@ public class BmsProductServiceImpl implements IBmsProductService { throw new ServiceException(MessageUtils.message("price.product.import.needFeeAlter")); } + if (tip.contains("ppv_id") || tip.contains("bms_product_price_version")) + { + throw new ServiceException(MessageUtils.message("price.product.import.needVersionAlter")); + } if (tip.contains("expire_date") || tip.contains("uk_product_country_weight") || tip.contains("uk_product_country_zone_weight")) { @@ -538,7 +642,94 @@ public class BmsProductServiceImpl implements IBmsProductService throw e; } } - return countries.size(); + countryMapper.refreshStatusByDates(); + priceVersionMapper.refreshStatusByDates(); + return touched; + } + + private BmsProductPriceVersion createPriceVersion(BmsProduct product, Date effectiveDate) + { + Integer maxSeq = priceVersionMapper.selectMaxVersionSeq(product.getProductCode()); + int seq = maxSeq == null ? 1 : maxSeq.intValue() + 1; + BmsProductPriceVersion ver = new BmsProductPriceVersion(); + ver.setProductCode(product.getProductCode()); + ver.setProductId(product.getProductId()); + ver.setVersionSeq(Integer.valueOf(seq)); + ver.setVersionNo("V" + seq); + ver.setEffectiveDate(effectiveDate); + ver.setExpireDate(null); + ver.setStatus(resolveStoredStatus(effectiveDate, null)); + ver.setCreateBy(product.getCreateBy() != null ? product.getCreateBy() : product.getUpdateBy()); + priceVersionMapper.insertBmsProductPriceVersion(ver); + return ver; + } + + /** After expire, insert in-memory previous open rows into the new version */ + private void insertCountryCopies(List prevSnapshot, Long newPpvId, Long productId, + String productCode, Date newEff, String status) + { + for (BmsProductCountry src : prevSnapshot) + { + BmsProductCountry copy = new BmsProductCountry(); + copy.setProductId(productId); + copy.setPpvId(newPpvId); + copy.setProductCode(productCode); + copy.setCountryName(src.getCountryName()); + copy.setCountryCode(src.getCountryCode()); + copy.setZoneId(src.getZoneId()); + copy.setZoneCode(src.getZoneCode()); + copy.setZoneName(src.getZoneName()); + copy.setWeightMin(src.getWeightMin()); + copy.setWeightMax(src.getWeightMax()); + copy.setFreightPrice(src.getFreightPrice()); + copy.setRegistrationFee(src.getRegistrationFee()); + copy.setSizeLimit(src.getSizeLimit()); + copy.setLastMile(src.getLastMile()); + copy.setLeadTime(src.getLeadTime()); + copy.setEffectiveDate(newEff); + copy.setExpireDate(null); + copy.setStatus(status); + countryMapper.insertBmsProductCountry(copy); + } + } + + private void assignOpenRowsToVersion(String productCode, Long ppvId) + { + if (ppvId == null) + { + return; + } + List openRows = countryMapper.selectOpenByProductCode(productCode); + if (openRows == null) + { + return; + } + for (BmsProductCountry row : openRows) + { + if (row.getPpvId() == null) + { + countryMapper.updatePpvId(row.getDetailId(), ppvId); + } + } + } + + /** Match key: country + zone + weight band */ + private String countryMatchKey(BmsProductCountry row) + { + String country = row.getCountryName() == null ? "" : row.getCountryName().trim(); + String zone = row.getZoneCode() == null ? "" : row.getZoneCode().trim().toUpperCase(Locale.ROOT); + String wMin = normalizeDecimal(row.getWeightMin()); + String wMax = normalizeDecimal(row.getWeightMax()); + return country + "|" + zone + "|" + wMin + "|" + wMax; + } + + private String normalizeDecimal(BigDecimal v) + { + if (v == null) + { + return ""; + } + return v.stripTrailingZeros().toPlainString(); } private Date resolveImportEffectiveDate(List countries) 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 8b025ea5..6238f125 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 @@ -1830,20 +1830,21 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService detailMapper.deletePrcPriceSetDetailByPsvId(version.getPsvId()); // clean legacy rows without psv_id under this set (would look like duplicates in UI) detailMapper.deleteOrphanDetailsByPriceSetId(set.getPriceSetId()); + String baseVersionNo = set.getBaseVersionNo(); + if (StringUtils.isEmpty(baseVersionNo)) + { + throw new ServiceException(MessageUtils.message("price.set.baseVersionNo.empty")); + } int inserted; if (TYPE_DISCOUNT.equals(set.getSetType())) { - inserted = detailMapper.insertDiscountDetailsFromLibrary(set.getPriceSetId(), version.getPsvId()); + inserted = detailMapper.insertDiscountDetailsFromLibrary( + set.getPriceSetId(), version.getPsvId(), baseVersionNo); } else { inserted = detailMapper.insertFixedDetailsFromLibrary( - set.getPriceSetId(), version.getPsvId(), version.getEffectiveTime(), false); - if (inserted <= 0) - { - inserted = detailMapper.insertFixedDetailsFromLibrary( - set.getPriceSetId(), version.getPsvId(), version.getEffectiveTime(), true); - } + set.getPriceSetId(), version.getPsvId(), null, false, baseVersionNo); } if (inserted <= 0) { diff --git a/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml index 3f641671..0b5797bb 100644 --- a/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml +++ b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductCountryMapper.xml @@ -7,6 +7,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -29,64 +31,69 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select detail_id, product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, - weight_min, weight_max, - freight_price, registration_fee, size_limit, last_mile, lead_time, - effective_date, expire_date, status, create_time, update_time - from bms_product_country + select c.detail_id, c.product_id, c.ppv_id, v.version_no, c.product_code, c.country_name, c.country_code, + c.zone_id, c.zone_code, c.zone_name, + c.weight_min, c.weight_max, + c.freight_price, c.registration_fee, c.size_limit, c.last_mile, c.lead_time, + c.effective_date, c.expire_date, c.status, c.create_time, c.update_time + from bms_product_country c + left join bms_product_price_version v on v.ppv_id = c.ppv_id insert into bms_product_country ( - product_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, + product_id, ppv_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, effective_date, expire_date, status, create_time ) values ( - #{productId}, #{productCode}, #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName}, + #{productId}, #{ppvId}, #{productCode}, #{countryName}, #{countryCode}, #{zoneId}, #{zoneCode}, #{zoneName}, #{weightMin}, #{weightMax}, #{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime}, #{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), sysdate() @@ -110,6 +117,35 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where detail_id = #{detailId} + + update bms_product_country + set product_id = #{productId}, + ppv_id = #{ppvId}, + country_code = #{countryCode}, + zone_id = #{zoneId}, + zone_code = #{zoneCode}, + zone_name = #{zoneName}, + weight_min = #{weightMin}, + weight_max = #{weightMax}, + freight_price = #{freightPrice}, + registration_fee = #{registrationFee}, + size_limit = #{sizeLimit}, + last_mile = #{lastMile}, + lead_time = #{leadTime}, + effective_date = #{effectiveDate}, + expire_date = #{expireDate}, + status = IFNULL(#{status}, status), + update_time = sysdate() + where detail_id = #{detailId} + + + + update bms_product_country + set ppv_id = #{ppvId}, + update_time = sysdate() + where detail_id = #{detailId} + + delete from bms_product_country where product_id = #{productId} @@ -170,15 +206,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" @@ -228,4 +264,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + + insert into bms_product_country ( + product_id, ppv_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, + effective_date, expire_date, status, create_time + ) + select + #{productId}, #{newPpvId}, #{productCode}, country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, + #{effectiveDate}, null, IFNULL(#{status}, '0'), sysdate() + from bms_product_country + where ppv_id = #{srcPpvId} + order by detail_id asc + + + + insert into bms_product_country ( + product_id, ppv_id, product_code, country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, + effective_date, expire_date, status, create_time + ) + select + #{productId}, #{newPpvId}, product_code, country_name, country_code, zone_id, zone_code, zone_name, + weight_min, weight_max, freight_price, registration_fee, size_limit, last_mile, lead_time, + #{effectiveDate}, null, IFNULL(#{status}, '0'), sysdate() + from bms_product_country + where product_code = #{productCode} + and expire_date is null + order by detail_id asc + + diff --git a/bms-back/bms-system/src/main/resources/mapper/price/BmsProductPriceVersionMapper.xml b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductPriceVersionMapper.xml new file mode 100644 index 00000000..e0fa01fa --- /dev/null +++ b/bms-back/bms-system/src/main/resources/mapper/price/BmsProductPriceVersionMapper.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + select ppv_id, product_code, product_id, version_no, version_seq, + effective_date, expire_date, status, + created_by, create_time, updated_by, update_time + from bms_product_price_version + + + + + + + + + + + + + + insert into bms_product_price_version ( + product_code, product_id, version_no, version_seq, + effective_date, expire_date, status, created_by, create_time + ) values ( + #{productCode}, #{productId}, #{versionNo}, #{versionSeq}, + #{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), #{createBy}, sysdate() + ) + + + + update bms_product_price_version + + product_id = #{productId}, + version_no = #{versionNo}, + version_seq = #{versionSeq}, + effective_date = #{effectiveDate}, + expire_date = #{expireDate}, + status = #{status}, + updated_by = #{updateBy}, + update_time = sysdate() + + where ppv_id = #{ppvId} + + + + update bms_product_price_version + set expire_date = #{newEffectiveDate}, + update_time = sysdate() + where product_code = #{productCode} + and expire_date is null + and (effective_date is null or effective_date < #{newEffectiveDate}) + + + + update bms_product_price_version + set status = case + when expire_date is not null and expire_date <= curdate() then '2' + when effective_date is null or effective_date > curdate() then '0' + else '1' + end + + + 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 e205448f..53868a93 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 @@ -151,23 +151,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" c.effective_date, c.expire_date, sysdate() - from bms_product p - inner join bms_product_country c on c.product_id = p.product_id - where p.status = '1' - and p.product_id = ( - select max(p2.product_id) from bms_product p2 - where p2.product_code = p.product_code and p2.status = '1' - ) - and ifnull(c.status, '1') <> '2' - - - and (c.expire_date is null or date(c.expire_date) > curdate()) - - - and date(c.effective_date) <= date(#{asOf}) - and (c.expire_date is null or date(c.expire_date) > date(#{asOf})) - - + from bms_product_country c + inner join bms_product p on p.product_id = c.product_id + + + + and exists ( + select 1 from bms_product_price_version v + where v.ppv_id = c.ppv_id + and v.version_no = #{baseVersionNo} + ) + + + and p.status = '1' + and p.product_id = ( + select max(p2.product_id) from bms_product p2 + where p2.product_code = p.product_code and p2.status = '1' + ) + and ifnull(c.status, '1') <> '2' + + + and (c.expire_date is null or date(c.expire_date) > curdate()) + + + and date(c.effective_date) <= date(#{asOf}) + and (c.expire_date is null or date(c.expire_date) > date(#{asOf})) + + + + + order by p.product_code asc, c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc @@ -183,11 +196,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 'HALF_UP', sysdate() from bms_product p - where p.status = '1' - and p.product_id = ( - select max(p2.product_id) from bms_product p2 - where p2.product_code = p.product_code and p2.status = '1' - ) + + + + and exists ( + select 1 from bms_product_country c + inner join bms_product_price_version v on v.ppv_id = c.ppv_id + where c.product_id = p.product_id + and v.version_no = #{baseVersionNo} + ) + + + and p.status = '1' + and p.product_id = ( + select max(p2.product_id) from bms_product p2 + where p2.product_code = p.product_code and p2.status = '1' + ) + + + order by p.product_code asc diff --git a/bms-back/sql/bms_init.sql b/bms-back/sql/bms_init.sql index dc5fa3aa..75532a3b 100644 --- a/bms-back/sql/bms_init.sql +++ b/bms-back/sql/bms_init.sql @@ -306,12 +306,37 @@ CREATE TABLE `bms_product` ( KEY `idx_product_code` (`product_code`) ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='BMS产品主表-来自产品目录'; + +-- ---------------------------- +-- 表结构:`bms_product_price_version` +-- ---------------------------- +CREATE TABLE `bms_product_price_version` ( + `ppv_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '产品基础价格版本ID', + `product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品代码', + `product_id` bigint(20) DEFAULT NULL COMMENT '对应 bms_product.product_id(该版本产品行)', + `version_no` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '版本号 V1/V2', + `version_seq` int(11) NOT NULL COMMENT '版本序号,用于自动+1', + `effective_date` date NOT NULL COMMENT '生效日期', + `expire_date` date DEFAULT NULL COMMENT '失效日期(空=长期;下一版生效日=本版失效日)', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0待生效 1已生效 2已失效', + `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 '更新人', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`ppv_id`), + UNIQUE KEY `uk_ppv_code_seq` (`product_code`,`version_seq`), + UNIQUE KEY `uk_ppv_code_eff` (`product_code`,`effective_date`), + KEY `idx_ppv_code_status` (`product_code`,`status`), + KEY `idx_ppv_product_id` (`product_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品基础价格版本'; + -- ---------------------------- -- 表结构:`bms_product_country` -- ---------------------------- CREATE TABLE `bms_product_country` ( `detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '明细ID', `product_id` bigint(20) DEFAULT NULL COMMENT '产品ID(bms_product.product_id,绑定具体产品版本行)', + `ppv_id` bigint(20) DEFAULT NULL COMMENT '产品基础价格版本ID bms_product_price_version.ppv_id', `product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品代码', `country_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '国家中文名', `country_code` char(2) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'ISO2,可空待补齐', @@ -336,6 +361,7 @@ CREATE TABLE `bms_product_country` ( KEY `idx_weight_match` (`product_code`,`country_code`,`weight_min`,`weight_max`) USING BTREE, KEY `idx_product_eff` (`product_code`,`effective_date`,`expire_date`), KEY `idx_product_country_pid` (`product_id`), + KEY `idx_product_country_ppv` (`ppv_id`), KEY `idx_product_country_zone` (`zone_id`), KEY `idx_product_country_zone_code` (`country_code`,`zone_code`), CONSTRAINT `fk_product_country_pid` FOREIGN KEY (`product_id`) REFERENCES `bms_product` (`product_id`) ON DELETE CASCADE diff --git a/bms-back/sql/price/product_price_version_alter.sql b/bms-back/sql/price/product_price_version_alter.sql new file mode 100644 index 00000000..f4c92ba4 --- /dev/null +++ b/bms-back/sql/price/product_price_version_alter.sql @@ -0,0 +1,98 @@ +-- Product base price version: table + bms_product_country.ppv_id +-- Run once on existing DB. New installs: see bms_init.sql + +CREATE TABLE IF NOT EXISTS `bms_product_price_version` ( + `ppv_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '产品基础价格版本ID', + `product_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '产品代码', + `product_id` bigint(20) DEFAULT NULL COMMENT '对应 bms_product.product_id(该版本产品行)', + `version_no` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '版本号 V1/V2', + `version_seq` int(11) NOT NULL COMMENT '版本序号,用于自动+1', + `effective_date` date NOT NULL COMMENT '生效日期', + `expire_date` date DEFAULT NULL COMMENT '失效日期(空=长期;下一版生效日=本版失效日)', + `status` char(1) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '0待生效 1已生效 2已失效', + `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 '更新人', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`ppv_id`), + UNIQUE KEY `uk_ppv_code_seq` (`product_code`,`version_seq`), + UNIQUE KEY `uk_ppv_code_eff` (`product_code`,`effective_date`), + KEY `idx_ppv_code_status` (`product_code`,`status`), + KEY `idx_ppv_product_id` (`product_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品基础价格版本'; + +SET @col_exists := ( + SELECT COUNT(1) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'bms_product_country' + AND COLUMN_NAME = 'ppv_id' +); +SET @sql := IF(@col_exists = 0, + 'ALTER TABLE `bms_product_country` ADD COLUMN `ppv_id` bigint(20) DEFAULT NULL COMMENT ''产品基础价格版本ID bms_product_price_version.ppv_id'' AFTER `product_id`, ADD KEY `idx_product_country_ppv` (`ppv_id`)', + 'SELECT 1'); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +INSERT INTO `bms_product_price_version` ( + product_code, product_id, version_no, version_seq, effective_date, expire_date, status, created_by, create_time +) +SELECT + t.product_code, + ( + SELECT p.product_id FROM bms_product p + WHERE p.product_code = t.product_code + AND p.effective_date = t.effective_date + ORDER BY p.product_id ASC + LIMIT 1 + ) AS product_id, + CONCAT('V', t.version_seq) AS version_no, + t.version_seq, + t.effective_date, + t.expire_date, + CASE + WHEN t.expire_date IS NOT NULL AND t.expire_date <= CURDATE() THEN '2' + WHEN t.effective_date > CURDATE() THEN '0' + ELSE '1' + END AS status, + 'migrate' AS created_by, + NOW() AS create_time +FROM ( + SELECT + g.product_code, + g.effective_date, + g.expire_date, + ( + SELECT COUNT(1) + FROM ( + SELECT product_code, effective_date + FROM bms_product_country + WHERE effective_date IS NOT NULL + GROUP BY product_code, effective_date + ) earlier + WHERE earlier.product_code = g.product_code + AND earlier.effective_date <= g.effective_date + ) AS version_seq + FROM ( + SELECT + product_code, + effective_date, + MAX(expire_date) AS expire_date + FROM bms_product_country + WHERE effective_date IS NOT NULL + GROUP BY product_code, effective_date + ) g +) t +WHERE NOT EXISTS ( + SELECT 1 FROM bms_product_price_version v + WHERE v.product_code = t.product_code + AND v.effective_date = t.effective_date +); + +UPDATE bms_product_country c +INNER JOIN bms_product_price_version v + ON v.product_code = c.product_code + AND v.effective_date = c.effective_date +SET c.ppv_id = v.ppv_id +WHERE c.ppv_id IS NULL + AND c.effective_date IS NOT NULL; diff --git a/bms-front/src/api/price/product.js b/bms-front/src/api/price/product.js index d475032b..07101e0f 100644 --- a/bms-front/src/api/price/product.js +++ b/bms-front/src/api/price/product.js @@ -63,3 +63,11 @@ export function delProduct(productIds) { method: 'delete' }) } + +/** List base-price version options (by effective_date desc) for package create */ +export function listProductPriceVersions() { + return request({ + url: '/price/product/priceVersions', + method: 'get' + }) +} diff --git a/bms-front/src/views/price/basic/product/components/ProductCountryDrawer.vue b/bms-front/src/views/price/basic/product/components/ProductCountryDrawer.vue index db24b387..23e8e25e 100644 --- a/bms-front/src/views/price/basic/product/components/ProductCountryDrawer.vue +++ b/bms-front/src/views/price/basic/product/components/ProductCountryDrawer.vue @@ -95,6 +95,7 @@ import { formatWeightLabel } from '@/views/price/set/utils/priceSet' const { proxy } = getCurrentInstance() const columns = [ + { prop: 'versionNo', label: '版本', width: 70, align: 'center', formatter: (row) => row.versionNo || '-' }, { prop: 'countryName', label: '国家', width: 120, showOverflowTooltip: true }, // { prop: 'countryCode', label: 'ISO2', width: 60, align: 'center' }, { prop: 'zoneName', label: '分区名称', width: 140, showOverflowTooltip: true, formatter: (row) => row.zoneName || '-' }, diff --git a/bms-front/src/views/price/basic/product/index.vue b/bms-front/src/views/price/basic/product/index.vue index e58b9bb1..9f847f5b 100644 --- a/bms-front/src/views/price/basic/product/index.vue +++ b/bms-front/src/views/price/basic/product/index.vue @@ -116,12 +116,13 @@ const regionOptions = ref([]) const countryImportQuery = computed(() => ({ countryCode: importCountryCode.value })) const allImportDesc = [ '适用多国家价表(B2C 小包价表等),按目录 sheet 匹配国家导入。', + '生效日期必填。新版导入时上一版失效日=本次生效日,复制上一版后按国家/分区/重量段匹配:命中更新,未命中插入。', '请在弹窗底部下载模板,按模板格式整理后上传。相同产品代码+生效日期不覆盖主数据。' ] const countryImportDesc = [ '选择目的国家后上传专线成本价表,目录中的产品都会记到该国家。', - '邮编、赔偿标准等 sheet 不导入。相同产品代码+生效日期不覆盖主数据,重量段按生效日写入。', - '可在弹窗底部下载模板,按模板格式整理后上传。' + '生效日期必填。新版会复制上一版全部国家明细再与导入行匹配更新/插入;其他国家行会转结到新版。', + '邮编、赔偿标准等 sheet 不导入。可在弹窗底部下载模板。' ] const columns = [ 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..8bcd8ffc 100644 --- a/bms-front/src/views/price/set/components/create/StepBasicInfo.vue +++ b/bms-front/src/views/price/set/components/create/StepBasicInfo.vue @@ -7,6 +7,25 @@
显示给客户的套餐对外名称
+ + + + +
创建套餐价格时按此版本从产品库拉取明细;默认最新版本
+
+
/** - * 第 1 步 · 基本信息 - * 直接读写父级传入的 basic 对象(同一引用),校验规则由本组件维护。 + * Step1 basic info. baseVersionNo is pull-only (not persisted). */ +import { listProductPriceVersions } from '@/api/price/product' + const basic = defineModel({ type: Object, required: true }) const formRef = ref() +const versionLoading = ref(false) +const versionOptions = ref([]) const rules = { priceSetName: [{ required: true, message: '套餐名称不能为空', trigger: 'blur' }], + baseVersionNo: [{ required: true, message: '请选择基础价格版本', trigger: 'change' }], setType: [{ required: true, message: '请选择计价类型', trigger: 'change' }], } -/** 供父级在下一步 / 保存前调用 */ +function versionLabel(item, isLatest) { + const no = item.versionNo || '-' + return no +} + +async function loadVersions() { + versionLoading.value = true + try { + const res = await listProductPriceVersions() + const list = res.data || [] + versionOptions.value = list + if (!basic.value.baseVersionNo && list.length) { + basic.value.baseVersionNo = list[0].versionNo + } + } finally { + versionLoading.value = false + } +} + function validate() { return new Promise(resolve => { if (!formRef.value) return resolve(false) @@ -70,46 +111,20 @@ function validate() { }) } +onMounted(() => { + loadVersions() +}) + defineExpose({ validate }) diff --git a/bms-front/src/views/price/set/create.vue b/bms-front/src/views/price/set/create.vue index 1548be5f..55c6a25d 100644 --- a/bms-front/src/views/price/set/create.vue +++ b/bms-front/src/views/price/set/create.vue @@ -220,6 +220,7 @@ function buildPayload() { setType: basic.setType, defaultDiscount: basic.setType === 'DISCOUNT' ? basic.defaultDiscount : undefined, priceSetNote: basic.priceSetNote, + baseVersionNo: basic.baseVersionNo || undefined, version: { psvId: copyMode.value ? undefined : version.psvId, versionNo: version.versionNo diff --git a/bms-front/src/views/price/set/utils/priceSet.js b/bms-front/src/views/price/set/utils/priceSet.js index 24aa25ba..b4bd53e9 100644 --- a/bms-front/src/views/price/set/utils/priceSet.js +++ b/bms-front/src/views/price/set/utils/priceSet.js @@ -361,6 +361,7 @@ export function createEmptyBasicInfo() { defaultDiscount: 88, effectiveDate: undefined, expireDate: undefined, + baseVersionNo: undefined, priceSetNote: '' } }