修改价格套餐版本

This commit is contained in:
wuweihua
2026-09-17 10:41:19 +08:00
parent f76c90643e
commit 5fe2351990
21 changed files with 668 additions and 116 deletions
@@ -53,4 +53,10 @@ public class PrcPriceSetDetail implements Serializable
private Date updateTime;
/** query only: product/country/zone/weight keyword */
private String keyword;
/** query only: product code/name like */
private String productKeyword;
/** query only: country code/name like */
private String countryKeyword;
/** query only: zone code/name like */
private String zoneKeyword;
}
@@ -25,6 +25,12 @@ public class TmsPriceQuoteResult implements Serializable
private String setType;
private Integer bindPriority;
private Long customerPricesetId;
/** Hit package version id (prc_price_set_version.psv_id) */
private Long psvId;
/** Hit package version no (e.g. V1) */
private String versionNo;
/** Hit package detail id when package version detail matched */
private Long psdId;
private String productCode;
private String productName;
@@ -40,7 +40,7 @@ public interface PrcCustomerPriceSetMapper
@Param("endDate") java.util.Date endDate);
/**
* 客户在业务日有效、且套餐状态为生效(E) 的绑定,按优先级升序
* Customer binds valid on bizDate; package not stopped. Version match is done in quote service.
*/
public java.util.List<PrcCustomerPriceSet> selectEffectiveByCustomer(
@Param("customerId") Long customerId,
@@ -53,9 +53,10 @@ public interface PrcPriceSetDetailMapper
@Param("priceSetId") Long priceSetId,
@Param("psvId") Long psvId);
/** FIXED: product + country + zone + productCountryId / weight */
/** FIXED: product + country + zone + productCountryId / weight; optional psvId scopes to version */
public PrcPriceSetDetail selectFixedMatch(
@Param("priceSetId") Long priceSetId,
@Param("psvId") Long psvId,
@Param("productCode") String productCode,
@Param("countryCode") String countryCode,
@Param("countryName") String countryName,
@@ -63,8 +64,9 @@ public interface PrcPriceSetDetailMapper
@Param("productCountryId") Long productCountryId,
@Param("weightLabel") String weightLabel);
/** DISCOUNT:按产品命中明细折扣(可空) */
/** DISCOUNT by product; optional psvId scopes to version */
public PrcPriceSetDetail selectDiscountMatch(
@Param("priceSetId") Long priceSetId,
@Param("psvId") Long psvId,
@Param("productCode") String productCode);
}
@@ -1,5 +1,6 @@
package com.ruoyi.price.mapper;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.price.domain.PrcPriceSetVersion;
@@ -24,4 +25,12 @@ public interface PrcPriceSetVersionMapper
public int deletePrcPriceSetVersionByPriceSetId(Long priceSetId);
public List<Long> selectPriceSetIds();
/**
* Version effective at asOf: effective_time &lt;= asOf and (expire_time null or &gt; asOf).
* Prefer latest effective_time.
*/
public PrcPriceSetVersion selectActiveByPriceSetAt(
@Param("priceSetId") Long priceSetId,
@Param("asOf") Date asOf);
}
@@ -6,11 +6,14 @@ import com.ruoyi.price.domain.vo.TmsPriceQuoteResult;
import java.util.List;
/**
* TMS freight quote service.
* TMS freight quote / bill calc service (shared by console trial and upstream bill).
*
* Match order: effective customer package (bind_priority ASC) FIXED override
* then DISCOUNT, else bms_product_country default freight/registration.
* After calc, persist fee lines by bms_fee_type; same trial params return stored result.
* Match order:
* 1) Customer binds (not stopped), bind_priority ASC
* 2) Active package version at bill-time end-of-day (effective_time / expire_time)
* 3) FIXED detail override, else DISCOUNT on that version
* 4) Else bms_product_country default
* Package hit uses version surcharge snapshot when present; else product library.
*
* @author ruoyi
*/
@@ -824,37 +824,101 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
reportSaveProgress(set, 90, "saving customers");
if (set.getCustomers() != null)
{
customerMapper.deletePrcCustomerPriceSetByPriceSetId(set.getPriceSetId());
Set<String> seenCustomers = new HashSet<String>();
for (PrcCustomerPriceSet c : filterCustomers(set.getCustomers()))
upsertCustomers(set);
}
reportSaveProgress(set, 96, "finishing");
}
/** Upsert customer binds by customerPricesetId / customerCode; delete removed ones. */
private void upsertCustomers(PrcPriceSet set)
{
PrcCustomerPriceSet cquery = new PrcCustomerPriceSet();
cquery.setPriceSetId(set.getPriceSetId());
List<PrcCustomerPriceSet> existing = customerMapper.selectPrcCustomerPriceSetList(cquery);
Map<Long, PrcCustomerPriceSet> byId = new HashMap<Long, PrcCustomerPriceSet>();
Map<String, PrcCustomerPriceSet> byCode = new HashMap<String, PrcCustomerPriceSet>();
if (existing != null)
{
for (PrcCustomerPriceSet e : existing)
{
if (StringUtils.isNotEmpty(c.getCustomerCode()) && !seenCustomers.add(c.getCustomerCode()))
if (e == null)
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
continue;
}
validateCustomer(c, set);
c.setCustomerPricesetId(null);
c.setPriceSetId(set.getPriceSetId());
c.setCreateBy(set.getCreateBy());
if (c.getBindPriority() == null)
if (e.getCustomerPricesetId() != null)
{
c.setBindPriority(1);
byId.put(e.getCustomerPricesetId(), e);
}
try
if (StringUtils.isNotEmpty(e.getCustomerCode()))
{
customerMapper.insertPrcCustomerPriceSet(c);
}
catch (Exception e)
{
if (isCustomerUniqueViolation(e))
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
}
throw e;
byCode.put(e.getCustomerCode(), e);
}
}
}
reportSaveProgress(set, 96, "finishing");
Set<Long> keepIds = new HashSet<Long>();
Set<String> seenCustomers = new HashSet<String>();
for (PrcCustomerPriceSet c : filterCustomers(set.getCustomers()))
{
if (StringUtils.isNotEmpty(c.getCustomerCode()) && !seenCustomers.add(c.getCustomerCode()))
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
}
validateCustomer(c, set);
c.setPriceSetId(set.getPriceSetId());
if (c.getBindPriority() == null)
{
c.setBindPriority(1);
}
PrcCustomerPriceSet hit = null;
if (c.getCustomerPricesetId() != null)
{
hit = byId.get(c.getCustomerPricesetId());
}
if (hit == null && StringUtils.isNotEmpty(c.getCustomerCode()))
{
hit = byCode.get(c.getCustomerCode());
}
try
{
if (hit != null)
{
c.setCustomerPricesetId(hit.getCustomerPricesetId());
c.setUpdateBy(set.getUpdateBy());
customerMapper.updatePrcCustomerPriceSet(c);
keepIds.add(hit.getCustomerPricesetId());
}
else
{
c.setCustomerPricesetId(null);
c.setCreateBy(set.getCreateBy());
customerMapper.insertPrcCustomerPriceSet(c);
if (c.getCustomerPricesetId() != null)
{
keepIds.add(c.getCustomerPricesetId());
}
}
}
catch (Exception e)
{
if (isCustomerUniqueViolation(e))
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
}
throw e;
}
}
List<Long> orphanIds = new ArrayList<Long>();
for (Long id : byId.keySet())
{
if (!keepIds.contains(id))
{
orphanIds.add(id);
}
}
if (!orphanIds.isEmpty())
{
customerMapper.deletePrcCustomerPriceSetByIds(orphanIds.toArray(new Long[0]));
}
}
private void reportSaveProgress(PrcPriceSet set, int percent, String message)
@@ -1466,7 +1530,13 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
PrcPriceSetVersion version = resolveSaveVersion(set);
boolean pullLibrary = "Y".equalsIgnoreCase(set.getPullFromLibrary());
// null details = leave existing rows alone; empty list = clear / replace with nothing
boolean detailsProvided = set.getDetails() != null;
List<PrcPriceSetDetail> details = filterDetails(set.getDetails());
if (version == null && !detailsProvided && !pullLibrary)
{
return;
}
if (version == null && details.isEmpty() && !pullLibrary)
{
return;
@@ -1476,7 +1546,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
version = new PrcPriceSetVersion();
version.setEffectiveTime(set.getEffectiveDate());
}
// Draft versions may omit effective_time; set on activate (启动)
// Draft versions may omit effective_time; set on activate
version.setPriceSetId(set.getPriceSetId());
version.setCreateBy(set.getCreateBy());
version.setUpdateBy(set.getUpdateBy());
@@ -1522,11 +1592,15 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
replaceVersionDetailsFromLibrary(set, version);
}
else if (detailsProvided)
{
upsertVersionDetails(set, version, details);
reportSaveProgress(set, 85, "saving surcharges");
resnapshotSurchargesIfNeeded(version, details);
}
else
{
replaceVersionDetails(set, version, details);
reportSaveProgress(set, 85, "saving surcharges");
snapshotVersionSurcharges(version, details);
reportSaveProgress(set, 80, "details unchanged");
}
refreshVersionStatus(set.getPriceSetId());
}
@@ -1615,33 +1689,103 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
fillVersionChildren(version);
}
private void replaceVersionDetails(PrcPriceSet set, PrcPriceSetVersion version, List<PrcPriceSetDetail> details)
/**
* Upsert details by psdId. Keeps existing rows when id matches; inserts new; deletes orphans
* not present in the payload. Avoids full delete+reinsert.
*/
private void upsertVersionDetails(PrcPriceSet set, PrcPriceSetVersion version, List<PrcPriceSetDetail> details)
{
versionSurchargeMapper.deleteByPsvId(version.getPsvId());
detailMapper.deletePrcPriceSetDetailByPsvId(version.getPsvId());
int total = details == null ? 0 : details.size();
int index = 0;
for (PrcPriceSetDetail d : details)
PrcPriceSetDetail query = new PrcPriceSetDetail();
query.setPsvId(version.getPsvId());
List<PrcPriceSetDetail> existing = detailMapper.selectPrcPriceSetDetailList(query);
Map<Long, PrcPriceSetDetail> byId = new HashMap<Long, PrcPriceSetDetail>();
if (existing != null)
{
validateDetail(set, d);
d.setPsdId(null);
d.setPriceSetId(set.getPriceSetId());
d.setPsvId(version.getPsvId());
detailMapper.insertPrcPriceSetDetail(d);
index++;
if (total > 0 && (index == total || index % 50 == 0 || index == 1))
for (PrcPriceSetDetail e : existing)
{
int pct = 25 + (int) Math.floor(index * 55.0 / total);
if (pct > 80)
if (e != null && e.getPsdId() != null)
{
pct = 80;
byId.put(e.getPsdId(), e);
}
reportSaveProgress(set, pct, "saving details " + index + "/" + total);
}
}
Set<Long> keepIds = new HashSet<Long>();
int total = details == null ? 0 : details.size();
int index = 0;
if (details != null)
{
for (PrcPriceSetDetail d : details)
{
validateDetail(set, d);
d.setPriceSetId(set.getPriceSetId());
d.setPsvId(version.getPsvId());
if (d.getPsdId() != null && byId.containsKey(d.getPsdId()))
{
detailMapper.updatePrcPriceSetDetail(d);
keepIds.add(d.getPsdId());
}
else
{
d.setPsdId(null);
detailMapper.insertPrcPriceSetDetail(d);
if (d.getPsdId() != null)
{
keepIds.add(d.getPsdId());
}
}
index++;
if (total > 0 && (index == total || index % 50 == 0 || index == 1))
{
int pct = 25 + (int) Math.floor(index * 55.0 / total);
if (pct > 80)
{
pct = 80;
}
reportSaveProgress(set, pct, "saving details " + index + "/" + total);
}
}
}
List<Long> orphanIds = new ArrayList<Long>();
for (Long id : byId.keySet())
{
if (!keepIds.contains(id))
{
orphanIds.add(id);
}
}
if (!orphanIds.isEmpty())
{
for (Long id : orphanIds)
{
versionSurchargeMapper.deleteByPsdId(id);
}
detailMapper.deletePrcPriceSetDetailByIds(orphanIds.toArray(new Long[0]));
}
version.setDetails(details);
}
/** Only rewrite surcharge snapshots for rows that explicitly sent surcharges. */
private void resnapshotSurchargesIfNeeded(PrcPriceSetVersion version, List<PrcPriceSetDetail> details)
{
if (details == null || details.isEmpty())
{
return;
}
List<PrcPriceSetDetail> withSc = new ArrayList<PrcPriceSetDetail>();
for (PrcPriceSetDetail d : details)
{
if (d != null && d.getPsdId() != null && d.getSurcharges() != null)
{
versionSurchargeMapper.deleteByPsdId(d.getPsdId());
withSc.add(d);
}
}
if (!withSc.isEmpty())
{
snapshotVersionSurcharges(version, withSc);
}
}
private void snapshotVersionSurcharges(PrcPriceSetVersion version, List<PrcPriceSetDetail> details)
{
if (details == null || details.isEmpty())
@@ -5,6 +5,7 @@ import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
@@ -25,6 +26,8 @@ import com.ruoyi.price.domain.BmsProductCountry;
import com.ruoyi.price.domain.PrcCustomerPriceSet;
import com.ruoyi.price.domain.PrcPriceSet;
import com.ruoyi.price.domain.PrcPriceSetDetail;
import com.ruoyi.price.domain.PrcPriceSetVersion;
import com.ruoyi.price.domain.PrcPriceSetVersionSurcharge;
import com.ruoyi.price.domain.PrcQuote;
import com.ruoyi.price.domain.PrcQuoteFee;
import com.ruoyi.price.domain.PrcSurchargeRel;
@@ -38,6 +41,8 @@ import com.ruoyi.price.mapper.BmsProductMapper;
import com.ruoyi.price.mapper.PrcCustomerPriceSetMapper;
import com.ruoyi.price.mapper.PrcPriceSetDetailMapper;
import com.ruoyi.price.mapper.PrcPriceSetMapper;
import com.ruoyi.price.mapper.PrcPriceSetVersionMapper;
import com.ruoyi.price.mapper.PrcPriceSetVersionSurchargeMapper;
import com.ruoyi.price.mapper.PrcQuoteFeeMapper;
import com.ruoyi.price.mapper.PrcQuoteMapper;
import com.ruoyi.price.mapper.PrcSurchargeRelMapper;
@@ -47,8 +52,8 @@ import com.ruoyi.price.search.IBillSearchStore;
import com.ruoyi.price.service.ITmsPriceQuoteService;
/**
* TMS freight quote: customer package first, then product default.
* After calc: persist fee lines by bms_fee_type; same trial params return stored rows.
* Shared quote/bill calc: bound set + active version by bizDate, else product default.
* Quote persists prc_quote; bill path calls calculate() then persists prc_order_bill.
*
* @author ruoyi
*/
@@ -71,6 +76,12 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
@Autowired
private PrcPriceSetMapper priceSetMapper;
@Autowired
private PrcPriceSetVersionMapper versionMapper;
@Autowired
private PrcPriceSetVersionSurchargeMapper versionSurchargeMapper;
@Autowired
private PrcPriceSetDetailMapper detailMapper;
@@ -249,17 +260,24 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
if (binds != null)
{
Date asOf = endOfDay(bizDate);
for (PrcCustomerPriceSet bind : binds)
{
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(bind.getPriceSetId());
if (set == null || !"E".equals(set.getStatus()))
if (set == null || "S".equals(set.getStatus()))
{
continue;
}
PrcPriceSetVersion version = versionMapper.selectActiveByPriceSetAt(set.getPriceSetId(), asOf);
if (version == null || version.getPsvId() == null)
{
// Bound package but no version effective at bill time -> try next bind / default
continue;
}
String setType = StringUtils.isEmpty(set.getSetType()) ? TYPE_FIXED : set.getSetType();
if (TYPE_FIXED.equals(setType))
{
TmsPriceQuoteResult hit = tryFixed(bind, set, base, productCode, productName, weight);
TmsPriceQuoteResult hit = tryFixed(bind, set, version, base, productCode, productName, weight);
if (hit != null)
{
stampRequestMeta(request, hit);
@@ -268,7 +286,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
}
else if (TYPE_DISCOUNT.equals(setType))
{
TmsPriceQuoteResult hit = tryDiscount(bind, set, base, productCode, productName, weight);
TmsPriceQuoteResult hit = tryDiscount(bind, set, version, base, productCode, productName, weight);
if (hit != null)
{
stampRequestMeta(request, hit);
@@ -334,23 +352,25 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
return n;
}
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set,
private TmsPriceQuoteResult tryFixed(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
{
int detailCount = detailMapper.countByPriceSetId(set.getPriceSetId());
Long psvId = version.getPsvId();
int detailCount = detailMapper.countByPsvId(psvId);
if (detailCount == 0)
{
TmsPriceQuoteResult r = buildFromRates(SRC_DEFAULT, base.getFreightPrice(), null, null,
base.getRegistrationFee(), base, productCode, productName, weight, null);
fillPackage(r, bind, set);
// 套餐固定价格没有明细,使用产品默认价格
stampVersion(r, version, null);
// Package fixed has no version details; use product default rates
r.setMatchMessage(MessageUtils.message("price.quote.fixedPackageNoDetails"));
r.setCustomerId(bind.getCustomerId());
return r;
}
PrcPriceSetDetail detail = detailMapper.selectFixedMatch(
set.getPriceSetId(), productCode,
set.getPriceSetId(), psvId, productCode,
base.getCountryCode(), base.getCountryName(), base.getZoneCode(),
base.getDetailId(),
formatWeightLabel(base.getWeightMin(), base.getWeightMax()));
@@ -365,6 +385,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
TmsPriceQuoteResult r = buildFromRates(SRC_FIXED, detail.getFvValue(), null, null,
detail.getFvRegistrationFee(), base, productCode, productName, weight, detail.getRoundMode());
fillPackage(r, bind, set);
stampVersion(r, version, detail.getPsdId());
if (StringUtils.isNotEmpty(detail.getProductName()))
{
r.setProductName(detail.getProductName());
@@ -377,25 +398,28 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{
r.setProductCountryId(detail.getProductCountryId());
}
// 命中客户套餐固定价格明细
r.setMatchMessage(MessageUtils.message("price.quote.hitCustomerFixedPackageDetail"));
r.setMatchMessage(MessageUtils.message("price.quote.hitCustomerFixedPackageDetail")
+ " [" + (version.getVersionNo() == null ? ("psv=" + psvId) : version.getVersionNo()) + "]");
r.setCustomerId(bind.getCustomerId());
return r;
}
private TmsPriceQuoteResult tryDiscount(PrcCustomerPriceSet bind, PrcPriceSet set,
private TmsPriceQuoteResult tryDiscount(PrcCustomerPriceSet bind, PrcPriceSet set, PrcPriceSetVersion version,
BmsProductCountry base, String productCode, String productName, BigDecimal weight)
{
int detailCount = detailMapper.countByPriceSetId(set.getPriceSetId());
Long psvId = version.getPsvId();
int detailCount = detailMapper.countByPsvId(psvId);
BigDecimal discount = set.getDefaultDiscount();
String roundMode = null;
Long psdId = null;
if (detailCount > 0)
{
PrcPriceSetDetail detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), productCode);
PrcPriceSetDetail detail = detailMapper.selectDiscountMatch(set.getPriceSetId(), psvId, productCode);
if (detail == null)
{
return null;
}
psdId = detail.getPsdId();
if (detail.getDiscountValue() != null)
{
discount = detail.getDiscountValue();
@@ -419,11 +443,25 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
TmsPriceQuoteResult r = buildFromRates(SRC_DISCOUNT, rate, baseRate, discount,
base.getRegistrationFee(), base, productCode, productName, weight, roundMode);
fillPackage(r, bind, set);
r.setMatchMessage("hit customer DISCOUNT package; freight = default * discount%");
stampVersion(r, version, psdId);
r.setMatchMessage("hit customer DISCOUNT package version "
+ (version.getVersionNo() == null ? String.valueOf(psvId) : version.getVersionNo())
+ "; freight = default * discount%");
r.setCustomerId(bind.getCustomerId());
return r;
}
private void stampVersion(TmsPriceQuoteResult result, PrcPriceSetVersion version, Long psdId)
{
if (result == null || version == null)
{
return;
}
result.setPsvId(version.getPsvId());
result.setVersionNo(version.getVersionNo());
result.setPsdId(psdId);
}
private TmsPriceQuoteResult buildDefault(Long customerId, BmsProductCountry base,
String productCode, String productName, BigDecimal weight, String message)
{
@@ -538,6 +576,23 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{
return matched;
}
if (result.getPsdId() != null)
{
List<PrcPriceSetVersionSurcharge> snaps =
versionSurchargeMapper.selectByPsdIds(Collections.singletonList(result.getPsdId()));
if (snaps != null && !snaps.isEmpty())
{
for (PrcPriceSetVersionSurcharge snap : snaps)
{
PrcSurchargeRel rel = toRelFromVersionSnap(snap);
if (surchargeApplies(rel, result))
{
matched.add(rel);
}
}
return matched;
}
}
PrcSurchargeRel query = new PrcSurchargeRel();
query.setProductCode(result.getProductCode());
List<PrcSurchargeRel> rels = surchargeRelMapper.selectPrcSurchargeRelList(query);
@@ -555,6 +610,27 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
return matched;
}
private PrcSurchargeRel toRelFromVersionSnap(PrcPriceSetVersionSurcharge snap)
{
PrcSurchargeRel rel = new PrcSurchargeRel();
if (snap == null)
{
return rel;
}
rel.setScId(snap.getScId());
rel.setProductCode(snap.getProductCode());
rel.setScName(snap.getScName());
rel.setFeeType(snap.getFeeType());
rel.setChargeType(snap.getChargeType());
rel.setChargeValue(snap.getChargeValue());
rel.setCurrency(snap.getCurrency());
rel.setRoundMode(snap.getRoundMode());
rel.setCountryCode(snap.getCountryCode());
rel.setScNote(snap.getScNote());
rel.setSsCode("on");
return rel;
}
private boolean surchargeApplies(PrcSurchargeRel rel, TmsPriceQuoteResult result)
{
if (rel == null)
@@ -961,6 +1037,24 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
}
}
/** Use end of bill day so same-day version activate still matches that day's quote. */
private static Date endOfDay(Date day)
{
if (day == null)
{
return new Date();
}
try
{
String d = new SimpleDateFormat("yyyy-MM-dd").format(day);
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(d + " 23:59:59");
}
catch (ParseException e)
{
return day;
}
}
private String resolveProductName(String productCode, Date bizDate)
{
BmsProduct p = productMapper.selectEffectiveBmsProductByCode(productCode, bizDate);