修改基础价格版本功能

This commit is contained in:
wuweihua
2026-09-28 15:45:07 +08:00
parent 1083c2876f
commit 32ab61b51c
22 changed files with 831 additions and 132 deletions
@@ -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<BmsProductPriceVersion> list = productService.selectPriceVersionOptions();
return success(list);
}
@PreAuthorize("@ss.hasPermi('price:product:query')")
@GetMapping("/{productCode}/countries")
public AjaxResult countries(@PathVariable String productCode,
@@ -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}
@@ -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;
@@ -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;
}
@@ -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")
@@ -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<BmsProductCountry> selectOpenByProductCode(@Param("productCode") String productCode);
/** Country rows under a price version */
public List<BmsProductCountry> 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.
@@ -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<BmsProductPriceVersion> selectVersionOptions();
public int insertBmsProductPriceVersion(BmsProductPriceVersion version);
public int updateBmsProductPriceVersion(BmsProductPriceVersion version);
/** Close open versions: set expire_date = newEffectiveDate where effective_date &lt; newEffectiveDate */
public int expireOpenByProductCode(
@Param("productCode") String productCode,
@Param("newEffectiveDate") Date newEffectiveDate);
/** Recompute status from effective_date / expire_date vs today */
public int refreshStatusByDates();
}
@@ -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(
@@ -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<BmsProductPriceVersion> selectPriceVersionOptions();
}
@@ -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<BmsProductPriceVersion> selectPriceVersionOptions()
{
List<BmsProductPriceVersion> list = priceVersionMapper.selectVersionOptions();
return list == null ? new ArrayList<>() : list;
}
/** @return int[]{added, skipped, countryRows} */
private int[] importOneProduct(Workbook workbook, BmsProduct product, String operName,
Map<String, BmsRegion> regionByNameCn, List<BmsRegion> euRegions,
@@ -476,8 +489,10 @@ public class BmsProductServiceImpl implements IBmsProductService
/**
* Versioned country save:
* - newEff &gt; last open: close old (expire_date = newEff), insert new (keep history)
* - newEff equals last open: replace open rows
* - require effective_date
* - newEff &gt; 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 &lt; last open: reject
*/
private int saveCountriesVersioned(BmsProduct product, List<BmsProductCountry> 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<BmsProductCountry> 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 if (lastOpenEff != null)
else
{
// Same effective day: replace open rows so re-import can fill/fix details
countryMapper.deleteOpenByProductCodeAndEffectiveDate(productCode, newEff);
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
{
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<BmsProductCountry> baseRows = countryMapper.selectByPpvId(targetVer.getPpvId());
if (baseRows == null)
{
c.setProductId(productId);
c.setProductCode(productCode);
c.setExpireDate(null);
if (c.getEffectiveDate() == null)
{
c.setEffectiveDate(newEff);
baseRows = new ArrayList<>();
}
c.setStatus(resolveStoredStatus(c.getEffectiveDate(), c.getExpireDate()));
Map<String, BmsProductCountry> 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<BmsProductCountry> 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<BmsProductCountry> 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<BmsProductCountry> countries)
@@ -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)
{
@@ -7,6 +7,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="BmsProductCountry" id="BmsProductCountryResult">
<id property="detailId" column="detail_id" />
<result property="productId" column="product_id" />
<result property="ppvId" column="ppv_id" />
<result property="versionNo" column="version_no" />
<result property="productCode" column="product_code" />
<result property="countryName" column="country_name" />
<result property="countryCode" column="country_code" />
@@ -29,64 +31,69 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectVo">
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
</sql>
<select id="selectBmsProductCountryList" parameterType="BmsProductCountry" resultMap="BmsProductCountryResult">
<include refid="selectVo"/>
<where>
<if test="productId != null">
AND product_id = #{productId}
AND c.product_id = #{productId}
</if>
<if test="ppvId != null">
AND c.ppv_id = #{ppvId}
</if>
<if test="productCode != null and productCode != ''">
AND product_code = #{productCode}
AND c.product_code = #{productCode}
</if>
<if test="countryName != null and countryName != ''">
AND country_name like concat('%', #{countryName}, '%')
AND c.country_name like concat('%', #{countryName}, '%')
</if>
<if test="countryCode != null and countryCode != ''">
AND country_code = #{countryCode}
AND c.country_code = #{countryCode}
</if>
<if test="zoneId != null">
AND zone_id = #{zoneId}
AND c.zone_id = #{zoneId}
</if>
<if test="zoneCode != null and zoneCode != ''">
AND zone_code = #{zoneCode}
AND c.zone_code = #{zoneCode}
</if>
<if test="weightMin != null">
AND weight_min = #{weightMin}
AND c.weight_min = #{weightMin}
</if>
<if test="weightMax != null">
AND weight_max = #{weightMax}
AND c.weight_max = #{weightMax}
</if>
<if test="effectiveDate != null">
AND effective_date = #{effectiveDate}
AND c.effective_date = #{effectiveDate}
</if>
<if test="openOnly != null and openOnly">
AND expire_date is null
AND c.expire_date is null
</if>
<if test="status != null and status != ''">
AND status = #{status}
AND c.status = #{status}
</if>
<if test="effectStatus != null and effectStatus != ''">
AND status = #{effectStatus}
AND c.status = #{effectStatus}
</if>
</where>
order by effective_date desc, country_name asc, zone_code asc, weight_min asc, detail_id asc
order by c.effective_date desc, c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc
</select>
<insert id="insertBmsProductCountry" parameterType="BmsProductCountry" useGeneratedKeys="true" keyProperty="detailId">
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>
<update id="updateBmsProductCountryImport" parameterType="BmsProductCountry">
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>
<update id="updatePpvId">
update bms_product_country
set ppv_id = #{ppvId},
update_time = sysdate()
where detail_id = #{detailId}
</update>
<delete id="deleteByProductId" parameterType="Long">
delete from bms_product_country where product_id = #{productId}
</delete>
@@ -170,15 +206,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectMatchByProductCountryWeight" resultMap="BmsProductCountryResult">
<include refid="selectVo"/>
where product_code = #{productCode}
and weight_min &lt;= #{weight}
and weight_max &gt;= #{weight}
where c.product_code = #{productCode}
and c.weight_min &lt;= #{weight}
and c.weight_max &gt;= #{weight}
<choose>
<when test="countryCode != null and countryCode != ''">
and upper(country_code) = upper(#{countryCode})
and upper(c.country_code) = upper(#{countryCode})
</when>
<when test="countryName != null and countryName != ''">
and country_name = #{countryName}
and c.country_name = #{countryName}
</when>
<otherwise>
and 1 = 0
@@ -186,20 +222,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</choose>
<choose>
<when test="zoneCode != null and zoneCode != ''">
and upper(ifnull(zone_code,'')) = upper(#{zoneCode})
and upper(ifnull(c.zone_code,'')) = upper(#{zoneCode})
</when>
<otherwise>
and (zone_code is null or zone_code = '')
and (c.zone_code is null or c.zone_code = '')
</otherwise>
</choose>
<if test="bizDate != null">
and (effective_date is null or effective_date &lt;= #{bizDate})
and (expire_date is null or expire_date &gt; #{bizDate})
and (c.effective_date is null or c.effective_date &lt;= #{bizDate})
and (c.expire_date is null or c.expire_date &gt; #{bizDate})
</if>
<if test="bizDate == null">
and expire_date is null
and c.expire_date is null
</if>
order by effective_date desc, weight_min asc, detail_id asc
order by c.effective_date desc, c.weight_min asc, c.detail_id asc
limit 1
</select>
@@ -228,4 +264,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</if>
</select>
<select id="selectOpenByProductCode" resultMap="BmsProductCountryResult">
<include refid="selectVo"/>
where c.product_code = #{productCode}
and c.expire_date is null
order by c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc
</select>
<select id="selectByPpvId" resultMap="BmsProductCountryResult">
<include refid="selectVo"/>
where c.ppv_id = #{ppvId}
order by c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc
</select>
<insert id="copyCountriesFromVersion">
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>
<insert id="copyOpenCountriesToVersion">
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
</insert>
</mapper>
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bms.price.mapper.BmsProductPriceVersionMapper">
<resultMap type="BmsProductPriceVersion" id="BmsProductPriceVersionResult">
<id property="ppvId" column="ppv_id" />
<result property="productCode" column="product_code" />
<result property="productId" column="product_id" />
<result property="versionNo" column="version_no" />
<result property="versionSeq" column="version_seq" />
<result property="effectiveDate" column="effective_date" />
<result property="expireDate" column="expire_date" />
<result property="status" column="status" />
<result property="productCount" column="product_count" />
<result property="createBy" column="created_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="updated_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectVo">
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
</sql>
<select id="selectBmsProductPriceVersionById" parameterType="Long" resultMap="BmsProductPriceVersionResult">
<include refid="selectVo"/>
where ppv_id = #{ppvId}
</select>
<select id="selectOpenByProductCode" resultMap="BmsProductPriceVersionResult">
<include refid="selectVo"/>
where product_code = #{productCode}
and expire_date is null
order by effective_date desc, version_seq desc, ppv_id desc
limit 1
</select>
<select id="selectByProductCodeAndEffectiveDate" resultMap="BmsProductPriceVersionResult">
<include refid="selectVo"/>
where product_code = #{productCode}
and effective_date = #{effectiveDate}
limit 1
</select>
<select id="selectMaxVersionSeq" resultType="java.lang.Integer">
select max(version_seq) from bms_product_price_version where product_code = #{productCode}
</select>
<select id="selectVersionOptions" resultMap="BmsProductPriceVersionResult">
select
min(ppv_id) as ppv_id,
null as product_code,
null as product_id,
version_no,
max(version_seq) as version_seq,
max(effective_date) as effective_date,
max(expire_date) as expire_date,
case
when sum(case when expire_date is null then 1 else 0 end) &gt; 0
and max(effective_date) &lt;= curdate() then '1'
when max(effective_date) &gt; curdate() then '0'
else '2'
end as status,
count(distinct product_code) as product_count,
null as created_by,
null as create_time,
null as updated_by,
null as update_time
from bms_product_price_version
where version_no is not null and version_no &lt;&gt; ''
group by version_no
order by max(version_seq) desc, version_no desc
</select>
<insert id="insertBmsProductPriceVersion" parameterType="BmsProductPriceVersion" useGeneratedKeys="true" keyProperty="ppvId">
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()
)
</insert>
<update id="updateBmsProductPriceVersion" parameterType="BmsProductPriceVersion">
update bms_product_price_version
<set>
<if test="productId != null">product_id = #{productId},</if>
<if test="versionNo != null and versionNo != ''">version_no = #{versionNo},</if>
<if test="versionSeq != null">version_seq = #{versionSeq},</if>
<if test="effectiveDate != null">effective_date = #{effectiveDate},</if>
expire_date = #{expireDate},
<if test="status != null and status != ''">status = #{status},</if>
<if test="updateBy != null and updateBy != ''">updated_by = #{updateBy},</if>
update_time = sysdate()
</set>
where ppv_id = #{ppvId}
</update>
<update id="expireOpenByProductCode">
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 &lt; #{newEffectiveDate})
</update>
<update id="refreshStatusByDates">
update bms_product_price_version
set status = case
when expire_date is not null and expire_date &lt;= curdate() then '2'
when effective_date is null or effective_date &gt; curdate() then '0'
else '1'
end
</update>
</mapper>
@@ -151,9 +151,19 @@ 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'
from bms_product_country c
inner join bms_product p on p.product_id = c.product_id
<where>
<choose>
<when test="baseVersionNo != null and baseVersionNo != ''">
and exists (
select 1 from bms_product_price_version v
where v.ppv_id = c.ppv_id
and v.version_no = #{baseVersionNo}
)
</when>
<otherwise>
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'
@@ -168,6 +178,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and (c.expire_date is null or date(c.expire_date) &gt; date(#{asOf}))
</otherwise>
</choose>
</otherwise>
</choose>
</where>
order by p.product_code asc, c.country_name asc, c.zone_code asc, c.weight_min asc, c.detail_id asc
</insert>
@@ -183,11 +196,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
'HALF_UP',
sysdate()
from bms_product p
where p.status = '1'
<where>
<choose>
<when test="baseVersionNo != null and baseVersionNo != ''">
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}
)
</when>
<otherwise>
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'
)
</otherwise>
</choose>
</where>
order by p.product_code asc
</insert>
+26
View File
@@ -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
@@ -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;
+8
View File
@@ -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'
})
}
@@ -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 || '-' },
@@ -116,12 +116,13 @@ const regionOptions = ref([])
const countryImportQuery = computed(() => ({ countryCode: importCountryCode.value }))
const allImportDesc = [
'适用多国家价表(B2C 小包价表等),按目录 sheet 匹配国家导入。',
'生效日期必填。新版导入时上一版失效日=本次生效日,复制上一版后按国家/分区/重量段匹配:命中更新,未命中插入。',
'请在弹窗底部下载模板,按模板格式整理后上传。相同产品代码+生效日期不覆盖主数据。'
]
const countryImportDesc = [
'选择目的国家后上传专线成本价表,目录中的产品都会记到该国家。',
'邮编、赔偿标准等 sheet 不导入。相同产品代码+生效日期不覆盖主数据,重量段按生效日写入。',
'可在弹窗底部下载模板,按模板格式整理后上传。'
'生效日期必填。新版会复制上一版全部国家明细再与导入行匹配更新/插入;其他国家行会转结到新版。',
'邮编、赔偿标准等 sheet 不导入。可在弹窗底部下载模板。'
]
const columns = [
@@ -7,6 +7,25 @@
<div class="form-hint">显示给客户的套餐对外名称</div>
</el-form-item>
<el-form-item label="基础价格版本" prop="baseVersionNo">
<el-select
v-model="basic.baseVersionNo"
filterable
clearable
placeholder="请选择基础价格版本"
style="width: 100%"
:loading="versionLoading"
>
<el-option
v-for="(item, idx) in versionOptions"
:key="item.versionNo"
:label="versionLabel(item, idx === 0)"
:value="item.versionNo"
/>
</el-select>
<div class="form-hint">创建套餐价格时按此版本从产品库拉取明细;默认最新版本</div>
</el-form-item>
<el-form-item label="计价类型" prop="setType">
<div class="type-row">
<div
@@ -50,19 +69,41 @@
<script setup name="StepBasicInfo">
/**
* 第 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 })
</script>
<style scoped>
.max-form {
max-width: 760px;
}
.form-hint {
font-size: 12px;
color: #909399;
line-height: 1.4;
margin-top: 4px;
}
.unit {
margin-left: 8px;
color: #909399;
}
.type-row {
display: flex;
gap: 12px;
width: 100%;
}
.type-card {
flex: 1;
border: 1px solid #d9d9d9;
border-radius: 6px;
padding: 14px 16px;
cursor: pointer;
}
.type-card.selected {
border-color: #1890ff;
background: #e6f7ff;
}
.t-title {
font-weight: 600;
margin-bottom: 4px;
}
.t-desc {
font-size: 12px;
color: #909399;
line-height: 1.5;
}
.max-form { max-width: 760px; }
.form-hint { font-size: 12px; color: #909399; line-height: 1.4; margin-top: 4px; }
.unit { margin-left: 8px; color: #909399; }
.type-row { display: flex; gap: 12px; width: 100%; }
.type-card { flex: 1; border: 1px solid #d9d9d9; border-radius: 6px; padding: 14px 16px; cursor: pointer; }
.type-card.selected { border-color: #1890ff; background: #e6f7ff; }
.t-title { font-weight: 600; margin-bottom: 4px; }
.t-desc { font-size: 12px; color: #909399; line-height: 1.5; }
</style>
+1
View File
@@ -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
@@ -361,6 +361,7 @@ export function createEmptyBasicInfo() {
defaultDiscount: 88,
effectiveDate: undefined,
expireDate: undefined,
baseVersionNo: undefined,
priceSetNote: ''
}
}