修改基础价格版本功能
This commit is contained in:
@@ -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.
|
||||
|
||||
+38
@@ -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 < 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();
|
||||
}
|
||||
|
||||
+213
-22
@@ -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 > 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<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)
|
||||
|
||||
+8
-7
@@ -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 <= #{weight}
|
||||
and weight_max >= #{weight}
|
||||
where c.product_code = #{productCode}
|
||||
and c.weight_min <= #{weight}
|
||||
and c.weight_max >= #{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 <= #{bizDate})
|
||||
and (expire_date is null or expire_date > #{bizDate})
|
||||
and (c.effective_date is null or c.effective_date <= #{bizDate})
|
||||
and (c.expire_date is null or c.expire_date > #{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) > 0
|
||||
and max(effective_date) <= curdate() then '1'
|
||||
when max(effective_date) > 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 <> ''
|
||||
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 < #{newEffectiveDate})
|
||||
</update>
|
||||
|
||||
<update id="refreshStatusByDates">
|
||||
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
|
||||
</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) > 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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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: ''
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user