修改产品价格显示

This commit is contained in:
wuweihua
2026-09-09 17:06:50 +08:00
parent fddd8f0586
commit e265656060
30 changed files with 771 additions and 194 deletions
@@ -25,16 +25,18 @@ public class BmsProduct extends BaseEntity
private String taxMode;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date effectiveDate;
/** 0启用(按日期展示已生效/待生效) 1已失效 */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date expireDate;
/** 0 pending, 1 active, 2 expired */
public static final String STATUS_PENDING = "0";
public static final String STATUS_ACTIVE = "1";
public static final String STATUS_EXPIRED = "2";
private String status;
private String sourceSheet;
private Long tmsId;
private Integer countryCount;
private Integer surchargeCount;
private List<BmsProductCountry> countries;
/**
* 查询用:ACTIVE已生效 / PENDING待生效 / EXPIRED已失效
* (非表字段,由生效日期+status 推导)
*/
/** query alias of status (0/1/2) */
private String effectStatus;
}
@@ -13,6 +13,8 @@ public class BmsProductCountry implements Serializable
private static final long serialVersionUID = 1L;
private Long detailId;
/** FK to bms_product.product_id (one country version belongs to one product row) */
private Long productId;
private String productCode;
private String countryName;
private String countryCode;
@@ -28,12 +30,14 @@ public class BmsProductCountry implements Serializable
/** null=open; closed to next import effective_date */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date expireDate;
/** 0 pending, 1 active, 2 expired */
private String status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdAt;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updatedAt;
/** query only: true = expire_date is null */
private Boolean openOnly;
/** query only: ACTIVE已生效 / PENDING待生效 / EXPIRED已失效(按生效/失效日期与当前日期派生) */
/** query alias of status (0/1/2) */
private String effectStatus;
}
@@ -47,7 +47,7 @@ public class TmsPriceQuoteResult implements Serializable
/** 附加费合计(按产品关联命中后汇总) */
private BigDecimal surchargeAmount;
private BigDecimal payableTotal;
private String currency = "RMB";
private String currency = "CNY";
/** UP / DOWN / HALF_UP */
private String roundMode;
/** 费用明细:运费/挂号费各一行,附加费按具体名称各一行 */
@@ -17,9 +17,13 @@ public interface BmsProductCountryMapper
public int insertBmsProductCountry(BmsProductCountry country);
public int deleteByProductId(Long productId);
public int deleteByProductCode(String productCode);
public int deleteByProductCodes(String[] productCodes);
public int deleteByProductCodeAndEffectiveDate(
@Param("productCode") String productCode,
@Param("effectiveDate") Date effectiveDate);
/** Max open (expire_date is null) effective_date for product */
public Date selectMaxOpenEffectiveDate(@Param("productCode") String productCode);
@@ -29,6 +33,9 @@ public interface BmsProductCountryMapper
@Param("productCode") String productCode,
@Param("newEffectiveDate") Date newEffectiveDate);
/** Recompute status from effective_date / expire_date vs today */
public int refreshStatusByDates();
/** Replace same-day version only */
public int deleteOpenByProductCodeAndEffectiveDate(
@Param("productCode") String productCode,
@@ -1,6 +1,8 @@
package com.ruoyi.price.mapper;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.price.domain.BmsProduct;
/**
@@ -12,8 +14,17 @@ public interface BmsProductMapper
{
public BmsProduct selectBmsProductById(Long productId);
/** Latest row by product_code (effective_date desc) */
public BmsProduct selectBmsProductByCode(String productCode);
/** Unique business key: product_code + effective_date */
public BmsProduct selectBmsProductByCodeAndDate(@Param("productCode") String productCode,
@Param("effectiveDate") Date effectiveDate);
/** Product version effective on bizDate */
public BmsProduct selectEffectiveBmsProductByCode(@Param("productCode") String productCode,
@Param("bizDate") Date bizDate);
public List<BmsProduct> selectBmsProductList(BmsProduct product);
public int insertBmsProduct(BmsProduct product);
@@ -21,4 +32,13 @@ public interface BmsProductMapper
public int updateBmsProduct(BmsProduct product);
public int deleteBmsProductByIds(Long[] productIds);
/** Close previous open versions: expire_date = newEffectiveDate */
public int expireOpenByProductCode(
@Param("productCode") String productCode,
@Param("newEffectiveDate") Date newEffectiveDate,
@Param("excludeId") Long excludeId);
/** Recompute status from effective_date / expire_date vs today */
public int refreshStatusByDates();
}
@@ -25,6 +25,11 @@ public interface PrcCustomerPriceSetMapper
public int countByPriceSetId(Long priceSetId);
/** Count binds of this customer; optional exclude current bind or current set */
public int countByCustomerId(@Param("customerId") Long customerId,
@Param("excludeId") Long excludeId,
@Param("excludePriceSetId") Long excludePriceSetId);
public int countOverlap(@Param("customerId") Long customerId,
@Param("priceSetId") Long priceSetId,
@Param("beginDate") java.util.Date beginDate,
@@ -1,5 +1,6 @@
package com.ruoyi.price.service;
import java.util.Date;
import java.util.List;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.price.domain.BmsProduct;
@@ -24,11 +25,15 @@ public interface IBmsProductService
public List<BmsProductCountry> selectCountryListByProductCode(String productCode);
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate);
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate, Long productId);
/**
* 导入威狮B2C小包价表:产品目录 + 各产品 sheet 国家×重量段(含运费/挂号费/尺寸/尾程/时效)
*
* @param file 上传的 xlsx/xls
* @param updateSupport 是否更新已存在产品
* @param updateSupport unused; same code+date never overwrites, different date always inserts
* @param operName 操作人
* @return 导入结果说明(可含 HTML 换行)
*/
@@ -51,6 +51,10 @@ public class BmsProductServiceImpl implements IBmsProductService
private static final Pattern EFFECTIVE_DATE_PATTERN = Pattern.compile(
"\u751f\u6548\u65e5\u671f[\uff1a:]\\s*(\\d{4})[/.-](\\d{1,2})[/.-](\\d{1,2})");
private static final Pattern EFFECTIVE_DATE_YMD = Pattern.compile(
"(\\d{4})[/.-](\\d{1,2})[/.-](\\d{1,2})");
private static final Pattern EFFECTIVE_DATE_CN = Pattern.compile(
"(\\d{4})\\s*\u5e74\\s*(\\d{1,2})\\s*\u6708\\s*(\\d{1,2})");
/** Parse 0.01\u2264W\u22640.3 / 0.3<W\u22641 */
private static final Pattern WEIGHT_RANGE_PATTERN = Pattern.compile(
@@ -78,26 +82,44 @@ public class BmsProductServiceImpl implements IBmsProductService
@Override
public List<BmsProduct> selectBmsProductList(BmsProduct product)
{
productMapper.refreshStatusByDates();
countryMapper.refreshStatusByDates();
if (product != null)
{
product.setStatus(normalizeStatusCode(product.getStatus()));
product.setEffectStatus(normalizeStatusCode(product.getEffectStatus()));
}
return productMapper.selectBmsProductList(product);
}
@Override
public int insertBmsProduct(BmsProduct product)
{
if (StringUtils.isEmpty(product.getStatus()))
{
product.setStatus("0");
}
if (StringUtils.isEmpty(product.getTaxMode()))
{
product.setTaxMode(resolveTaxMode(product.getProductName()));
}
return productMapper.insertBmsProduct(product);
if (product.getEffectiveDate() == null)
{
throw new ServiceException(MessageUtils.message("price.product.effectiveDate.empty"));
}
applyProductStatus(product);
assertCodeDateUnique(product, null);
int rows = productMapper.insertBmsProduct(product);
productMapper.expireOpenByProductCode(product.getProductCode(), product.getEffectiveDate(), product.getProductId());
productMapper.refreshStatusByDates();
return rows;
}
@Override
public int updateBmsProduct(BmsProduct product)
{
if (product.getEffectiveDate() == null)
{
throw new ServiceException(MessageUtils.message("price.product.effectiveDate.empty"));
}
applyProductStatus(product);
assertCodeDateUnique(product, product.getProductId());
return productMapper.updateBmsProduct(product);
}
@@ -109,31 +131,81 @@ public class BmsProductServiceImpl implements IBmsProductService
{
return 0;
}
List<String> codes = new ArrayList<>();
List<String> codesNoRemain = new ArrayList<>();
for (Long id : productIds)
{
BmsProduct p = productMapper.selectBmsProductById(id);
if (p != null && StringUtils.isNotEmpty(p.getProductCode()))
if (p == null || StringUtils.isEmpty(p.getProductCode()))
{
codes.add(p.getProductCode());
continue;
}
if (p.getProductId() != null)
{
countryMapper.deleteByProductId(p.getProductId());
}
countryMapper.deleteByProductCodeAndEffectiveDate(p.getProductCode(), p.getEffectiveDate());
codesNoRemain.add(p.getProductCode());
}
int rows = productMapper.deleteBmsProductByIds(productIds);
List<String> orphanCodes = new ArrayList<>();
for (String code : codesNoRemain)
{
if (productMapper.selectBmsProductByCode(code) == null)
{
orphanCodes.add(code);
}
}
if (!codes.isEmpty())
if (!orphanCodes.isEmpty())
{
countryMapper.deleteByProductCodes(codes.toArray(new String[0]));
surchargeRelService.deleteRelByProductCodes(codes.toArray(new String[0]));
surchargeRelService.deleteRelByProductCodes(orphanCodes.toArray(new String[0]));
}
return productMapper.deleteBmsProductByIds(productIds);
return rows;
}
@Override
public List<BmsProductCountry> selectCountryListByProductCode(String productCode)
{
return selectCountryListByProductCode(productCode, null);
}
@Override
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate)
{
return selectCountryListByProductCode(productCode, effectiveDate, null);
}
@Override
public List<BmsProductCountry> selectCountryListByProductCode(String productCode, Date effectiveDate, Long productId)
{
if (productId != null)
{
BmsProductCountry byId = new BmsProductCountry();
byId.setProductId(productId);
List<BmsProductCountry> list = countryMapper.selectBmsProductCountryList(byId);
if (!list.isEmpty())
{
return list;
}
}
BmsProductCountry query = new BmsProductCountry();
query.setProductCode(productCode);
query.setEffectiveDate(effectiveDate);
return countryMapper.selectBmsProductCountryList(query);
}
private void assertCodeDateUnique(BmsProduct product, Long excludeId)
{
if (product == null || StringUtils.isEmpty(product.getProductCode()))
{
return;
}
BmsProduct dup = productMapper.selectBmsProductByCodeAndDate(product.getProductCode(), product.getEffectiveDate());
if (dup != null && (excludeId == null || !excludeId.equals(dup.getProductId())))
{
throw new ServiceException(MessageUtils.message("price.product.codeDate.exists"));
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public String importProductExcel(MultipartFile file, boolean updateSupport, String operName) throws Exception
@@ -170,7 +242,6 @@ public class BmsProductServiceImpl implements IBmsProductService
}
int successNum = 0;
int updateNum = 0;
int skipNum = 0;
int countryNum = 0;
Set<String> unmatchedCountries = new HashSet<>();
@@ -185,31 +256,42 @@ public class BmsProductServiceImpl implements IBmsProductService
product.setUpdateBy(operName);
List<BmsProductCountry> countries = parseProductSheet(workbook, product, regionByNameCn, unmatchedCountries);
BmsProduct existing = productMapper.selectBmsProductByCode(product.getProductCode());
// Sheet header date is the price version; catalog column is often stale
Date sheetEff = resolveImportEffectiveDate(countries);
Date newEff = toDateOnly(sheetEff != null ? sheetEff : product.getEffectiveDate());
product.setEffectiveDate(newEff);
if (newEff == null)
{
failureMsg.append("<br/>").append(product.getProductCode()).append("\uff1a")
.append(MessageUtils.message("price.product.import.effEmpty"));
continue;
}
BmsProduct existing = productMapper.selectBmsProductByCodeAndDate(
product.getProductCode(), newEff);
if (existing == null)
if (existing != null)
{
product.setStatus("0");
productMapper.insertBmsProduct(product);
countryNum += saveCountriesVersioned(product.getProductCode(), countries);
successNum++;
}
else if (updateSupport)
{
product.setProductId(existing.getProductId());
productMapper.updateBmsProduct(product);
countryNum += saveCountriesVersioned(product.getProductCode(), countries);
updateNum++;
}
else if (!countries.isEmpty())
{
// Keep product master; version country rows by effective_date
countryNum += saveCountriesVersioned(product.getProductCode(), countries);
updateNum++;
skipNum++;
}
else
{
skipNum++;
applyProductStatus(product);
try
{
productMapper.insertBmsProduct(product);
}
catch (Exception e)
{
if (isProductCodeUniqueViolation(e))
{
throw new ServiceException(MessageUtils.message("price.product.import.needCodeEffUnique"));
}
throw e;
}
productMapper.expireOpenByProductCode(product.getProductCode(), newEff, product.getProductId());
productMapper.refreshStatusByDates();
countryNum += saveCountriesVersioned(product, countries);
successNum++;
}
}
catch (Exception e)
@@ -221,9 +303,9 @@ public class BmsProductServiceImpl implements IBmsProductService
if (failureMsg.length() > 0)
{
throw new ServiceException(MessageUtils.message("price.product.import.partialFail",
successNum, updateNum, skipNum, failureMsg.toString()));
successNum, skipNum, failureMsg.toString()));
}
String msg = MessageUtils.message("price.product.import.success", successNum, updateNum, skipNum, countryNum)
String msg = MessageUtils.message("price.product.import.success", successNum, skipNum, countryNum)
+ "<br/>" + MessageUtils.message("price.product.import.success.detail1")
+ "<br/>" + MessageUtils.message("price.product.import.success.detail2");
if (!unmatchedCountries.isEmpty())
@@ -236,16 +318,18 @@ public class BmsProductServiceImpl implements IBmsProductService
/**
* Versioned country save:
* - newEff &gt; last open eff: close old (expire_date = newEff), insert new (keep history)
* - newEff equals last open: replace that open version only
* - newEff &gt; last open: close old (expire_date = newEff), insert new (keep history)
* - newEff equals last open: skip (never overwrite)
* - newEff &lt; last open: reject
*/
private int saveCountriesVersioned(String productCode, List<BmsProductCountry> countries)
private int saveCountriesVersioned(BmsProduct product, List<BmsProductCountry> countries)
{
if (countries == null || countries.isEmpty())
if (product == null || countries == null || countries.isEmpty())
{
return 0;
}
String productCode = product.getProductCode();
Long productId = product.getProductId();
Date newEff = resolveImportEffectiveDate(countries);
Date lastOpenEff = countryMapper.selectMaxOpenEffectiveDate(productCode);
@@ -259,21 +343,24 @@ public class BmsProductServiceImpl implements IBmsProductService
{
// Previous open version expire_date = this effective_date; keep old rows
countryMapper.expireOpenByProductCode(productCode, newEff);
countryMapper.refreshStatusByDates();
}
else if (lastOpenEff != null)
{
// Same effective day (or both null): replace current open version only
countryMapper.deleteOpenByProductCodeAndEffectiveDate(productCode, newEff);
// Same effective day: never overwrite existing country rows
return 0;
}
for (BmsProductCountry c : countries)
{
c.setProductId(productId);
c.setProductCode(productCode);
c.setExpireDate(null);
if (c.getEffectiveDate() == null)
{
c.setEffectiveDate(newEff);
}
c.setStatus(resolveStoredStatus(c.getEffectiveDate(), c.getExpireDate()));
try
{
countryMapper.insertBmsProductCountry(c);
@@ -321,9 +408,93 @@ public class BmsProductServiceImpl implements IBmsProductService
return new SimpleDateFormat("yyyy-MM-dd").format(d);
}
private Date toDateOnly(Date d)
{
if (d == null)
{
return null;
}
try
{
return new SimpleDateFormat("yyyy-MM-dd").parse(formatDate(d));
}
catch (Exception e)
{
return d;
}
}
/** 0 pending / 1 active / 2 expired from dates vs today */
private String resolveStoredStatus(Date effectiveDate, Date expireDate)
{
Date today = toDateOnly(new Date());
Date expire = toDateOnly(expireDate);
Date eff = toDateOnly(effectiveDate);
if (expire != null && today != null && !expire.after(today))
{
return BmsProduct.STATUS_EXPIRED;
}
if (eff == null || (today != null && eff.after(today)))
{
return BmsProduct.STATUS_PENDING;
}
return BmsProduct.STATUS_ACTIVE;
}
private void applyProductStatus(BmsProduct product)
{
if (product == null)
{
return;
}
product.setStatus(resolveStoredStatus(product.getEffectiveDate(), product.getExpireDate()));
}
/** Map ACTIVE/PENDING/EXPIRED or 0/1/2 to stored status code. */
private String normalizeStatusCode(String status)
{
if (status == null || status.isEmpty())
{
return status;
}
if ("ACTIVE".equalsIgnoreCase(status) || BmsProduct.STATUS_ACTIVE.equals(status))
{
return BmsProduct.STATUS_ACTIVE;
}
if ("PENDING".equalsIgnoreCase(status) || BmsProduct.STATUS_PENDING.equals(status))
{
return BmsProduct.STATUS_PENDING;
}
if ("EXPIRED".equalsIgnoreCase(status) || BmsProduct.STATUS_EXPIRED.equals(status))
{
return BmsProduct.STATUS_EXPIRED;
}
return status;
}
private boolean isProductCodeUniqueViolation(Throwable e)
{
while (e != null)
{
String m = e.getMessage();
if (m != null && m.contains("uk_product_code") && !m.contains("uk_product_code_eff"))
{
return true;
}
if (m != null && m.contains("Duplicate") && m.contains("product_code") && !m.contains("effective"))
{
return true;
}
e = e.getCause();
}
return false;
}
private void saveCountries(String productCode, List<BmsProductCountry> countries)
{
saveCountriesVersioned(productCode, countries);
BmsProduct p = new BmsProduct();
p.setProductCode(productCode);
saveCountriesVersioned(p, countries);
}
private List<BmsProduct> parseCatalog(Sheet sheet)
@@ -590,35 +761,67 @@ public class BmsProductServiceImpl implements IBmsProductService
private Date parseSheetEffectiveDate(Sheet sheet)
{
Row row0 = sheet.getRow(0);
if (row0 == null)
int lastRow = Math.min(sheet.getLastRowNum(), 4);
for (int r = 0; r <= lastRow; r++)
{
return null;
}
for (int c = 0; c < 12; c++)
{
String text = cellString(row0.getCell(c));
if (StringUtils.isEmpty(text))
Row row = sheet.getRow(r);
if (row == null)
{
continue;
}
Matcher m = EFFECTIVE_DATE_PATTERN.matcher(text);
if (m.find())
int lastCell = Math.max(row.getLastCellNum(), 12);
lastCell = Math.min(lastCell, 20);
for (int c = 0; c < lastCell; c++)
{
try
Date parsed = parseEffectiveDateText(cellString(row.getCell(c)));
if (parsed != null)
{
String d = m.group(1) + "-" + pad2(m.group(2)) + "-" + pad2(m.group(3));
return new SimpleDateFormat("yyyy-MM-dd").parse(d);
}
catch (Exception ignored)
{
return null;
return parsed;
}
}
}
return null;
}
private Date parseEffectiveDateText(String text)
{
if (StringUtils.isEmpty(text))
{
return null;
}
Matcher labeled = EFFECTIVE_DATE_PATTERN.matcher(text);
if (labeled.find())
{
return parseYmd(labeled.group(1), labeled.group(2), labeled.group(3));
}
Matcher cn = EFFECTIVE_DATE_CN.matcher(text);
if (cn.find())
{
return parseYmd(cn.group(1), cn.group(2), cn.group(3));
}
if (text.contains("\u751f\u6548"))
{
Matcher ymd = EFFECTIVE_DATE_YMD.matcher(text);
if (ymd.find())
{
return parseYmd(ymd.group(1), ymd.group(2), ymd.group(3));
}
}
return null;
}
private Date parseYmd(String y, String m, String d)
{
try
{
return new SimpleDateFormat("yyyy-MM-dd").parse(y + "-" + pad2(m) + "-" + pad2(d));
}
catch (Exception ignored)
{
return null;
}
}
private static String pad2(String n)
{
return n.length() == 1 ? "0" + n : n;
@@ -406,24 +406,37 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
List<PrcCustomerPriceSet> customers = filterCustomers(set.getCustomers());
int overlap = 0;
Set<Long> seenCustomers = new HashSet<Long>();
int bound = 0;
boolean dupInSet = false;
for (PrcCustomerPriceSet c : customers)
{
overlap += customerMapper.countOverlap(c.getCustomerId(), set.getPriceSetId(),
c.getPrBegindate() != null ? c.getPrBegindate() : set.getEffectiveDate(),
c.getPrEnddate() != null ? c.getPrEnddate() : set.getExpireDate());
if (c.getCustomerId() == null)
{
continue;
}
if (!seenCustomers.add(c.getCustomerId()))
{
dupInSet = true;
continue;
}
bound += customerMapper.countByCustomerId(c.getCustomerId(), c.getCustomerPricesetId(),
set.getPriceSetId());
}
if (overlap > 0)
if (dupInSet || bound > 0)
{
items.add(new PrcPriceSetCheckItem("与同客户现有套餐是否冲突", "WARN", "存在 " + overlap + " 条与已生效套餐日期重叠的客户绑定,可共存但需确认优先级"));
items.add(new PrcPriceSetCheckItem("\u4e00\u4e2a\u5ba2\u6237\u53ea\u80fd\u7ed1\u5b9a\u4e00\u4e2a\u5957\u9910", "FAIL",
MessageUtils.message("price.set.customer.alreadyBound")));
}
else if (customers.isEmpty())
{
items.add(new PrcPriceSetCheckItem("与同客户现有套餐是否冲突", "INFO", "暂未绑定客户,可后续补充"));
items.add(new PrcPriceSetCheckItem("\u4e00\u4e2a\u5ba2\u6237\u53ea\u80fd\u7ed1\u5b9a\u4e00\u4e2a\u5957\u9910", "INFO",
"\u6682\u672a\u7ed1\u5b9a\u5ba2\u6237\uff0c\u53ef\u540e\u7eed\u8865\u5145"));
}
else
{
items.add(new PrcPriceSetCheckItem("与同客户现有套餐是否冲突", "PASS", "未发现与已生效套餐的日期重叠"));
items.add(new PrcPriceSetCheckItem("\u4e00\u4e2a\u5ba2\u6237\u53ea\u80fd\u7ed1\u5b9a\u4e00\u4e2a\u5957\u9910", "PASS",
"\u5ba2\u6237\u672a\u7ed1\u5b9a\u5176\u4ed6\u5957\u9910"));
}
if (TYPE_FIXED.equals(setType) && !details.isEmpty())
@@ -504,7 +517,18 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
customer.setBindPriority(1);
}
return customerMapper.insertPrcCustomerPriceSet(customer);
try
{
return customerMapper.insertPrcCustomerPriceSet(customer);
}
catch (Exception e)
{
if (isCustomerUniqueViolation(e))
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
}
throw e;
}
}
@Override
@@ -557,8 +581,13 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
if (set.getCustomers() != null)
{
customerMapper.deletePrcCustomerPriceSetByPriceSetId(set.getPriceSetId());
Set<Long> seenCustomers = new HashSet<Long>();
for (PrcCustomerPriceSet c : filterCustomers(set.getCustomers()))
{
if (c.getCustomerId() != null && !seenCustomers.add(c.getCustomerId()))
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
}
validateCustomer(c, set);
c.setCustomerPricesetId(null);
c.setPriceSetId(set.getPriceSetId());
@@ -567,7 +596,18 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
c.setBindPriority(1);
}
customerMapper.insertPrcCustomerPriceSet(c);
try
{
customerMapper.insertPrcCustomerPriceSet(c);
}
catch (Exception e)
{
if (isCustomerUniqueViolation(e))
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
}
throw e;
}
}
}
}
@@ -739,6 +779,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
throw new ServiceException(MessageUtils.message("price.set.customer.priority.invalid"));
}
int exists = customerMapper.countByCustomerId(customer.getCustomerId(),
customer.getCustomerPricesetId(), null);
if (exists > 0)
{
throw new ServiceException(MessageUtils.message("price.set.customer.alreadyBound"));
}
}
private void assertBasicEditable(PrcPriceSet db)
@@ -786,6 +832,20 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return db;
}
private boolean isCustomerUniqueViolation(Throwable e)
{
while (e != null)
{
String m = e.getMessage();
if (m != null && (m.contains("uk_customer_one_set") || m.contains("Duplicate")))
{
return m.contains("uk_customer_one_set") || m.contains("customer_id");
}
e = e.getCause();
}
return false;
}
private void fillExpired(PrcPriceSet set)
{
Date today = DateUtils.parseDate(DateUtils.getDate());
@@ -151,7 +151,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
throw new ServiceException(MessageUtils.message("price.quote.priceNotHit"));
}
String productName = resolveProductName(productCode);
String productName = resolveProductName(productCode, bizDate);
List<PrcCustomerPriceSet> binds = customerPriceSetMapper.selectEffectiveByCustomer(
request.getCustomerId(), bizDate);
@@ -329,7 +329,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
r.setFreightAmount(freightAmount);
r.setSurchargeAmount(BigDecimal.ZERO);
r.setPayableTotal(payable);
r.setCurrency("RMB");
r.setCurrency("CNY");
r.setRoundMode(mode);
r.setFromCache(false);
return r;
@@ -340,7 +340,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
BigDecimal freightAmount = nvl(result.getFreightAmount());
BigDecimal registrationFee = nvl(result.getRegistrationFee());
RoundingMode rm = resolveRoundingMode(result.getRoundMode());
String currency = StringUtils.isEmpty(result.getCurrency()) ? "RMB" : result.getCurrency();
String currency = normalizeCurrency(result.getCurrency());
List<PrcSurchargeRel> rels = loadMatchedSurcharges(result);
List<TmsPriceQuoteFeeLine> lines = new ArrayList<TmsPriceQuoteFeeLine>();
@@ -365,7 +365,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
}
String feeType = StringUtils.isEmpty(rel.getFeeType()) ? FEE_SUR : rel.getFeeType();
String feeName = StringUtils.isEmpty(rel.getScName()) ? feeNameOf(FEE_SUR) : rel.getScName();
String itemCcy = StringUtils.isEmpty(rel.getCurrency()) ? currency : rel.getCurrency();
String itemCcy = normalizeCurrency(StringUtils.isEmpty(rel.getCurrency()) ? currency : rel.getCurrency());
String itemRound = StringUtils.isEmpty(rel.getRoundMode()) ? result.getRoundMode() : rel.getRoundMode();
surchargeLines.put(scId, feeLine(feeType, feeName, scId, itemAmount, itemCcy, itemRound, rel.getScNote()));
}
@@ -604,7 +604,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
r.setFreightAmount(quote.getFreightAmount());
r.setSurchargeAmount(quote.getSurchargeAmount());
r.setPayableTotal(quote.getPayableTotal());
r.setCurrency(quote.getCurrency());
r.setCurrency(normalizeCurrency(quote.getCurrency()));
r.setRoundMode(quote.getRoundMode());
List<TmsPriceQuoteFeeLine> lines = new ArrayList<TmsPriceQuoteFeeLine>();
if (fees != null)
@@ -682,12 +682,21 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
line.setFeeName(feeName);
line.setScId(scId == null ? 0L : scId);
line.setAmount(nvl(amount));
line.setCurrency(currency);
line.setCurrency(normalizeCurrency(currency));
line.setRoundMode(roundMode);
line.setRemark(remark);
return line;
}
private static String normalizeCurrency(String currency)
{
if (StringUtils.isEmpty(currency) || "RMB".equalsIgnoreCase(currency))
{
return "CNY";
}
return currency.trim().toUpperCase();
}
private static String feeNameOf(String feeType)
{
if (FEE_BASIC.equals(feeType))
@@ -791,9 +800,13 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
}
}
private String resolveProductName(String productCode)
private String resolveProductName(String productCode, Date bizDate)
{
BmsProduct p = productMapper.selectBmsProductByCode(productCode);
BmsProduct p = productMapper.selectEffectiveBmsProductByCode(productCode, bizDate);
if (p == null)
{
p = productMapper.selectBmsProductByCode(productCode);
}
return p == null ? null : p.getProductName();
}
@@ -6,6 +6,7 @@ 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="productCode" column="product_code" />
<result property="countryName" column="country_name" />
<result property="countryCode" column="country_code" />
@@ -18,31 +19,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="leadTime" column="lead_time" />
<result property="effectiveDate" column="effective_date" />
<result property="expireDate" column="expire_date" />
<result property="status" column="status" />
<result property="createdAt" column="created_at" />
<result property="updatedAt" column="updated_at" />
<result property="effectStatus" column="effect_status" />
<result property="effectStatus" column="status" />
</resultMap>
<!-- 派生状态:已失效(过期) > 待生效(未到生效日) > 已生效 -->
<sql id="effectStatusCase">
case
when expire_date is not null and expire_date &lt;= curdate() then 'EXPIRED'
when effective_date is null or effective_date &gt; curdate() then 'PENDING'
else 'ACTIVE'
end
</sql>
<sql id="selectVo">
select detail_id, product_code, country_name, country_code, weight_min, weight_max,
select detail_id, product_id, product_code, country_name, country_code, weight_min, weight_max,
freight_price, registration_fee, size_limit, last_mile, lead_time,
effective_date, expire_date, created_at, updated_at,
<include refid="effectStatusCase"/> as effect_status
effective_date, expire_date, status, created_at, updated_at
from bms_product_country
</sql>
<select id="selectBmsProductCountryList" parameterType="BmsProductCountry" resultMap="BmsProductCountryResult">
<include refid="selectVo"/>
<where>
<if test="productId != null">
AND product_id = #{productId}
</if>
<if test="productCode != null and productCode != ''">
AND product_code = #{productCode}
</if>
@@ -64,8 +59,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="openOnly != null and openOnly">
AND expire_date is null
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="effectStatus != null and effectStatus != ''">
AND (<include refid="effectStatusCase"/>) = #{effectStatus}
AND status = #{effectStatus}
</if>
</where>
order by effective_date desc, country_name asc, weight_min asc, detail_id asc
@@ -73,16 +71,20 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertBmsProductCountry" parameterType="BmsProductCountry" useGeneratedKeys="true" keyProperty="detailId">
insert into bms_product_country (
product_code, country_name, country_code, weight_min, weight_max,
product_id, product_code, country_name, country_code, weight_min, weight_max,
freight_price, registration_fee, size_limit, last_mile, lead_time,
effective_date, expire_date, created_at
effective_date, expire_date, status, created_at
) values (
#{productCode}, #{countryName}, #{countryCode}, #{weightMin}, #{weightMax},
#{productId}, #{productCode}, #{countryName}, #{countryCode}, #{weightMin}, #{weightMax},
#{freightPrice}, #{registrationFee}, #{sizeLimit}, #{lastMile}, #{leadTime},
#{effectiveDate}, #{expireDate}, sysdate()
#{effectiveDate}, #{expireDate}, IFNULL(#{status}, '0'), sysdate()
)
</insert>
<delete id="deleteByProductId" parameterType="Long">
delete from bms_product_country where product_id = #{productId}
</delete>
<delete id="deleteByProductCode" parameterType="String">
delete from bms_product_country where product_code = #{productCode}
</delete>
@@ -94,6 +96,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
<delete id="deleteByProductCodeAndEffectiveDate">
delete from bms_product_country
where product_code = #{productCode}
and (
(#{effectiveDate} is null and effective_date is null)
or effective_date = #{effectiveDate}
)
</delete>
<select id="selectMaxOpenEffectiveDate" resultType="java.util.Date">
select max(effective_date) from bms_product_country
where product_code = #{productCode}
@@ -109,6 +120,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and (effective_date is null or effective_date &lt; #{newEffectiveDate})
</update>
<update id="refreshStatusByDates">
update bms_product_country
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>
<delete id="deleteOpenByProductCodeAndEffectiveDate">
delete from bms_product_country
where product_code = #{productCode}
@@ -14,6 +14,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateNote" column="update_note" />
<result property="taxMode" column="tax_mode" />
<result property="effectiveDate" column="effective_date" />
<result property="expireDate" column="expire_date" />
<result property="status" column="status" />
<result property="sourceSheet" column="source_sheet" />
<result property="remark" column="remark" />
@@ -28,9 +29,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="selectBmsProductVo">
select p.product_id, p.product_code, p.product_name, p.service_level, p.cargo_attr,
p.dest_region, p.update_note, p.tax_mode, p.effective_date, p.status,
p.dest_region, p.update_note, p.tax_mode, p.effective_date, p.expire_date, p.status,
p.source_sheet, p.remark, p.tms_id, p.created_by, p.created_at, p.updated_by, p.updated_at,
(select count(distinct c.country_name) from bms_product_country c where c.product_code = p.product_code) as country_count,
(select count(distinct c.country_name) from bms_product_country c
where c.product_id = p.product_id
or (c.product_id is null and c.product_code = p.product_code
and (
(p.effective_date is null and c.effective_date is null)
or c.effective_date = p.effective_date
))
) as country_count,
(select count(*) from prc_surcharge_rel r where r.product_id = p.product_id) as surcharge_count
from bms_product p
</sql>
@@ -43,6 +51,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectBmsProductByCode" parameterType="String" resultMap="BmsProductResult">
<include refid="selectBmsProductVo"/>
where p.product_code = #{productCode}
order by p.effective_date desc, p.product_id desc
limit 1
</select>
<select id="selectBmsProductByCodeAndDate" resultMap="BmsProductResult">
<include refid="selectBmsProductVo"/>
where p.product_code = #{productCode}
<choose>
<when test="effectiveDate != null">
AND p.effective_date = #{effectiveDate}
</when>
<otherwise>
AND p.effective_date is null
</otherwise>
</choose>
limit 1
</select>
<select id="selectEffectiveBmsProductByCode" resultMap="BmsProductResult">
<include refid="selectBmsProductVo"/>
where p.product_code = #{productCode}
and (p.effective_date is null or #{bizDate} is null or p.effective_date &lt;= #{bizDate})
and (p.expire_date is null or #{bizDate} is null or p.expire_date &gt; #{bizDate})
order by p.effective_date desc, p.product_id desc
limit 1
</select>
<select id="selectBmsProductList" parameterType="BmsProduct" resultMap="BmsProductResult">
@@ -61,20 +94,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
AND p.status = #{status}
</if>
<if test="effectStatus != null and effectStatus != ''">
<choose>
<when test="effectStatus == 'EXPIRED'">
AND p.status = '1'
</when>
<when test="effectStatus == 'PENDING'">
AND p.status = '0'
AND (p.effective_date is null or p.effective_date &gt; curdate())
</when>
<when test="effectStatus == 'ACTIVE'">
AND p.status = '0'
AND p.effective_date is not null
AND p.effective_date &lt;= curdate()
</when>
</choose>
AND p.status = #{effectStatus}
</if>
<if test="cargoAttr != null and cargoAttr != ''">
AND p.cargo_attr like concat('%', #{cargoAttr}, '%')
@@ -84,7 +104,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
OR p.product_name like concat('%', #{params.keyword}, '%'))
</if>
</where>
order by p.product_id desc
order by p.product_code asc, p.effective_date desc, p.product_id desc
</select>
<insert id="insertBmsProduct" parameterType="BmsProduct" useGeneratedKeys="true" keyProperty="productId">
@@ -97,6 +117,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateNote != null">update_note,</if>
<if test="taxMode != null and taxMode != ''">tax_mode,</if>
<if test="effectiveDate != null">effective_date,</if>
<if test="expireDate != null">expire_date,</if>
<if test="status != null and status != ''">status,</if>
<if test="sourceSheet != null">source_sheet,</if>
<if test="remark != null">remark,</if>
@@ -112,6 +133,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateNote != null">#{updateNote},</if>
<if test="taxMode != null and taxMode != ''">#{taxMode},</if>
<if test="effectiveDate != null">#{effectiveDate},</if>
<if test="expireDate != null">#{expireDate},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="sourceSheet != null">#{sourceSheet},</if>
<if test="remark != null">#{remark},</if>
@@ -132,6 +154,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="updateNote != null">update_note = #{updateNote},</if>
<if test="taxMode != null and taxMode != ''">tax_mode = #{taxMode},</if>
<if test="effectiveDate != null">effective_date = #{effectiveDate},</if>
expire_date = #{expireDate},
<if test="status != null and status != ''">status = #{status},</if>
<if test="sourceSheet != null">source_sheet = #{sourceSheet},</if>
<if test="remark != null">remark = #{remark},</if>
@@ -149,4 +172,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
<update id="expireOpenByProductCode">
update bms_product
set expire_date = #{newEffectiveDate},
updated_at = sysdate()
where product_code = #{productCode}
and expire_date is null
and (effective_date is null or effective_date &lt; #{newEffectiveDate})
<if test="excludeId != null">
and product_id &lt;&gt; #{excludeId}
</if>
</update>
<update id="refreshStatusByDates">
update bms_product
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>
@@ -51,6 +51,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
select count(1) from prc_customer_price_set where price_set_id = #{priceSetId}
</select>
<select id="countByCustomerId" resultType="int">
select count(1) from prc_customer_price_set
where customer_id = #{customerId}
<if test="excludeId != null">
AND customer_priceset_id &lt;&gt; #{excludeId}
</if>
<if test="excludePriceSetId != null">
AND price_set_id &lt;&gt; #{excludePriceSetId}
</if>
</select>
<select id="countOverlap" resultType="int">
select count(1)
from prc_customer_price_set c