修改产品套餐

This commit is contained in:
wuweihua
2026-09-29 23:07:38 +08:00
parent 38f371742a
commit 52d0a13d83
25 changed files with 1229 additions and 351 deletions
@@ -60,6 +60,11 @@ public interface IPrcPriceSetService
public List<PrcPriceSetDetail> selectDetailList(PrcPriceSetDetail detail);
/**
* Load all detail rows for one product under a version (no pagination).
*/
public List<PrcPriceSetDetail> selectDetailListByProduct(Long psvId, String productCode);
/**
* 版本明细搜索下拉选项:按版本去重后的 产品/国家/分区 列表。
* 返回 { products: [{code,name}], countries: [{code,name}], zones: [{code,name}] }
@@ -105,11 +110,20 @@ public interface IPrcPriceSetService
public String submitImportDetailPricesAsync(Long priceSetId, Long psvId, MultipartFile file, String operName) throws Exception;
/**
* Sync worker: parse bytes and update FIXED detail prices matched by
* product + country + zone(if any) + weight band.
* Sync worker: parse bytes and upsert FIXED detail prices matched by
* product + country + zone(if any) + weight band (insert when missing).
*/
public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName) throws Exception;
/** Sync worker with optional taskId for progress reporting. */
public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName, String taskId) throws Exception;
/**
* Submit async pull of product library into version details.
* @return taskId for polling getSaveTask
*/
public String submitPullFromLibraryAsync(Long priceSetId, Long psvId, boolean force, String operName);
/** Sync worker: replace (or skip) version details from product / product_country. */
public String pullFromLibrary(Long priceSetId, Long psvId, boolean force, String operName, String taskId);
}
@@ -1,8 +1,10 @@
package com.bms.price.service;
import com.bms.price.domain.PrcPriceSetProduct;
import com.bms.price.domain.PrcQuote;
import com.bms.price.domain.vo.TmsPriceQuoteRequest;
import com.bms.price.domain.vo.TmsPriceQuoteResult;
import java.util.Date;
import java.util.List;
/**
@@ -27,4 +29,11 @@ public interface ITmsPriceQuoteService
List<PrcQuote> selectQuoteList(PrcQuote query);
TmsPriceQuoteResult getQuote(String quoteId);
/**
* Products under the customer's bound effective package version (for trial dropdown).
* @param customerCode required
* @param bizDate optional; default today (same as-of window as quote)
*/
List<PrcPriceSetProduct> listBoundPackageProducts(String customerCode, Date bizDate);
}
@@ -22,20 +22,24 @@ import com.bms.common.utils.DateUtils;
import com.bms.common.utils.MessageUtils;
import com.bms.common.utils.StringUtils;
import com.bms.common.utils.uuid.IdUtils;
import com.bms.price.domain.BmsProduct;
import com.bms.price.domain.BmsProductCountry;
import com.bms.price.domain.PrcCustomerPriceSet;
import com.bms.price.domain.PrcPriceSet;
import com.bms.price.domain.PrcPriceSetCheckItem;
import com.bms.price.domain.PrcPriceSetDetail;
import com.bms.price.domain.PrcPriceSetProduct;
import com.bms.price.domain.PrcPriceSetSaveTask;
import com.bms.price.domain.PrcPriceSetVersion;
import com.bms.price.domain.PrcPriceSetVersionSurcharge;
import com.bms.price.domain.PrcSurcharge;
import com.bms.price.domain.PrcSurchargeRel;
import com.bms.price.mapper.BmsProductCountryMapper;
import com.bms.price.mapper.BmsProductMapper;
import com.bms.price.mapper.PrcCustomerPriceSetMapper;
import com.bms.price.mapper.PrcPriceSetDetailMapper;
import com.bms.price.mapper.PrcPriceSetMapper;
import com.bms.price.mapper.PrcPriceSetProductMapper;
import com.bms.price.mapper.PrcPriceSetVersionMapper;
import com.bms.price.mapper.PrcPriceSetVersionSurchargeMapper;
import com.bms.price.mapper.PrcSurchargeMapper;
@@ -90,6 +94,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
@Autowired
private BmsProductCountryMapper productCountryMapper;
@Autowired
private PrcPriceSetProductMapper setProductMapper;
@Autowired
private BmsProductMapper productMapper;
@Lazy
@Autowired
private IBmsProductService productService;
@@ -399,9 +409,11 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
nv.setCreateBy(createBy);
versionMapper.insertPrcPriceSetVersion(nv);
setProductMapper.copyFromVersion(sourcePsvId, nv.getPsvId());
int detailRows = detailMapper.copyDetailsFromVersion(sourcePsvId, nv.getPsvId());
if (detailRows > 0)
{
setProductMapper.bindDetailPspByPsvId(nv.getPsvId());
versionSurchargeMapper.copySurchargesFromVersion(sourcePsvId, nv.getPsvId(), createBy);
}
refreshVersionStatus(set.getPriceSetId());
@@ -428,6 +440,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
requireSet(version.getPriceSetId());
versionSurchargeMapper.deleteByPsvId(psvId);
detailMapper.deletePrcPriceSetDetailByPsvId(psvId);
setProductMapper.deleteByPsvId(psvId);
int rows = versionMapper.deletePrcPriceSetVersionByIds(new Long[] { psvId });
refreshVersionStatus(version.getPriceSetId());
return rows;
@@ -587,16 +600,16 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
if (details.isEmpty())
{
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "PASS",
"\u65e0\u5957\u9910\u660e\u7ec6\uff1a\u8ba1\u8d39\u53d6 product / product_country \u9ed8\u8ba4\u8fd0\u8d39\u4e0e\u6302\u53f7\u8d39"));
items.add(new PrcPriceSetCheckItem("FIXED \u660e\u7ec6\u5b8c\u6574\u6027", "FAIL",
"\u65e0\u5957\u9910\u660e\u7ec6\uff1a\u56fa\u5b9a\u4ef7\u987b\u5148\u62c9\u53d6\u4ea7\u54c1\u5217\u8868\u6216\u5bfc\u5165 Excel \u5efa\u4ef7"));
}
else if (incomplete > 0)
{
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "FAIL", incomplete + " \u6761\u660e\u7ec6\u7f3a\u5c11\u4ea7\u54c1/\u56fd\u5bb6/\u91cd\u91cf\u6bb5"));
items.add(new PrcPriceSetCheckItem("FIXED \u660e\u7ec6\u5b8c\u6574\u6027", "FAIL", incomplete + " \u6761\u660e\u7ec6\u7f3a\u5c11\u4ea7\u54c1/\u56fd\u5bb6/\u91cd\u91cf\u6bb5"));
}
else
{
items.add(new PrcPriceSetCheckItem("FIXED 明细完整性", "PASS", details.size() + " \u6761\u8986\u76d6\u660e\u7ec6\uff08\u672a\u8986\u76d6\u7684\u4ea7\u54c1\u56fd\u5bb6\u91cd\u91cf\u6bb5\u4ecd\u8d70\u9ed8\u8ba4\u4ef7\uff09"));
items.add(new PrcPriceSetCheckItem("FIXED \u660e\u7ec6\u5b8c\u6574\u6027", "PASS", details.size() + " \u6761\u5957\u9910\u9500\u552e\u4ef7\u660e\u7ec6\uff08\u4e0d\u56de\u9000 product_country\uff09"));
}
}
else
@@ -673,11 +686,13 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
if (TYPE_FIXED.equals(setType) && !details.isEmpty())
{
items.add(new PrcPriceSetCheckItem("\u8fd0\u8d39\u5355\u4ef7 vs \u9ed8\u8ba4\u7b49\u7ea7\u4ef7\u504f\u5dee", "INFO", "\u6709\u8986\u76d6\u660e\u7ec6\u65f6\u4f18\u5148\u53d6\u5957\u9910\u56fa\u5b9a\u4ef7\uff1b\u672a\u547d\u4e2d\u5219\u56de\u9000 product_country"));
items.add(new PrcPriceSetCheckItem("\u8fd0\u8d39\u5355\u4ef7 vs \u9ed8\u8ba4\u7b49\u7ea7\u4ef7\u504f\u5dee", "INFO",
"\u56fa\u5b9a\u4ef7\u4ec5\u4f7f\u7528\u5957\u9910\u660e\u7ec6\uff1b\u672a\u547d\u4e2d\u5219\u4e0d\u8ba1\u8d39\uff08\u4e0d\u56de\u9000 product_country\uff09"));
}
else if (TYPE_FIXED.equals(setType))
{
items.add(new PrcPriceSetCheckItem("\u8fd0\u8d39\u5355\u4ef7 vs \u9ed8\u8ba4\u7b49\u7ea7\u4ef7\u504f\u5dee", "INFO", "\u65e0\u5957\u9910\u660e\u7ec6\uff0c\u5168\u90e8\u8d70 product_country \u9ed8\u8ba4\u8fd0\u8d39/\u6302\u53f7\u8d39"));
items.add(new PrcPriceSetCheckItem("\u8fd0\u8d39\u5355\u4ef7 vs \u9ed8\u8ba4\u7b49\u7ea7\u4ef7\u504f\u5dee", "INFO",
"\u65e0\u5957\u9910\u660e\u7ec6\u65f6\u56fa\u5b9a\u4ef7\u4e0d\u8ba1\u8d39\uff0c\u8bf7\u5148\u62c9\u53d6\u6216\u5bfc\u5165\u5efa\u4ef7"));
}
else if (TYPE_DISCOUNT.equals(setType))
{
@@ -695,6 +710,19 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return list;
}
@Override
public List<PrcPriceSetDetail> selectDetailListByProduct(Long psvId, String productCode)
{
if (psvId == null || StringUtils.isEmpty(productCode))
{
return new ArrayList<PrcPriceSetDetail>();
}
PrcPriceSetDetail q = new PrcPriceSetDetail();
q.setPsvId(psvId);
q.setProductCode(productCode.trim());
return selectDetailList(q);
}
@Override
public Map<String, Object> selectDetailOptions(Long psvId)
{
@@ -797,6 +825,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
assertChildEditable(db);
validateDetail(db, detail);
detail.setPsvId(resolvePsvId(detail.getPriceSetId(), detail.getPsvId()));
detail.setPspId(lookupSetProductId(detail.getPsvId(), detail.getProductCode()));
int rows = detailMapper.insertPrcPriceSetDetail(detail);
PrcPriceSetVersion ver = new PrcPriceSetVersion();
ver.setPsvId(detail.getPsvId());
@@ -835,6 +864,56 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return taskId;
}
@Override
public String submitPullFromLibraryAsync(Long priceSetId, Long psvId, boolean force, String operName)
{
PrcPriceSet db = requireSet(priceSetId);
assertChildEditable(db);
if (psvId == null)
{
psvId = resolveCurrentPsvId(db);
}
if (psvId == null)
{
throw new ServiceException(MessageUtils.message("price.set.version.notExists"));
}
String taskId = IdUtils.fastSimpleUUID();
saveAsyncRunner.put(taskId, PrcPriceSetSaveTask.STATUS_PENDING, 0, "queued", priceSetId, null);
saveAsyncRunner.runPullLibrary(taskId, priceSetId, psvId, force, operName);
return taskId;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String pullFromLibrary(Long priceSetId, Long psvId, boolean force, String operName, String taskId)
{
PrcPriceSet set = requireSet(priceSetId);
assertChildEditable(set);
set.setUpdateBy(operName);
set.setCreateBy(operName);
set.setSaveTaskId(taskId);
set.setPullFromLibrary("Y");
set.setForcePullLibrary(force ? "Y" : "N");
if (psvId == null)
{
psvId = resolveCurrentPsvId(set);
}
PrcPriceSetVersion version = null;
if (psvId != null)
{
version = versionMapper.selectPrcPriceSetVersionById(psvId);
}
if (version == null)
{
throw new ServiceException(MessageUtils.message("price.set.version.notExists"));
}
reportImportProgress(taskId, priceSetId, 20, "pulling library");
replaceVersionDetailsFromLibrary(set, version);
reportImportProgress(taskId, priceSetId, 95, "finishing");
int count = detailMapper.countByPsvId(version.getPsvId());
return MessageUtils.message("price.set.pull.success", Integer.valueOf(count));
}
@Override
@Transactional(rollbackFor = Exception.class)
public String importDetailPrices(Long priceSetId, Long psvId, byte[] bytes, String filename, String operName) throws Exception
@@ -856,19 +935,24 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
psvId = resolveCurrentPsvId(db);
}
if (psvId == null)
{
throw new ServiceException(MessageUtils.message("price.set.version.notExists"));
}
reportImportProgress(taskId, priceSetId, 20, "parsing excel");
Map<String, List<BmsProductCountry>> byProduct = productService.parseQuoteExcelByProduct(bytes, filename);
reportImportProgress(taskId, priceSetId, 50, "matching details");
if (byProduct == null || byProduct.isEmpty())
{
throw new ServiceException(MessageUtils.message("price.set.import.noPriceRows"));
}
PrcPriceSetDetail q = new PrcPriceSetDetail();
q.setPriceSetId(priceSetId);
if (psvId != null)
{
q.setPsvId(psvId);
}
q.setPsvId(psvId);
List<PrcPriceSetDetail> details = detailMapper.selectPrcPriceSetDetailList(q);
if (details == null || details.isEmpty())
if (details == null)
{
throw new ServiceException(MessageUtils.message("price.set.import.noDetails"));
details = new ArrayList<PrcPriceSetDetail>();
}
Map<String, List<PrcPriceSetDetail>> detailIndex = new HashMap<String, List<PrcPriceSetDetail>>();
for (PrcPriceSetDetail d : details)
@@ -886,61 +970,139 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
list.add(d);
}
int updated = 0;
int unmatched = 0;
int skippedProduct = 0;
int inserted = 0;
int skippedFee = 0;
Set<Long> touched = new HashSet<Long>();
int productTotal = byProduct.size();
int productIndex = 0;
for (Map.Entry<String, List<BmsProductCountry>> e : byProduct.entrySet())
{
productIndex++;
String productCode = e.getKey();
reportImportProgress(taskId, priceSetId,
20 + (int) Math.floor(70.0 * productIndex / Math.max(productTotal, 1)),
"importing " + productCode + " (" + productIndex + "/" + productTotal + ")");
List<PrcPriceSetDetail> candidates = detailIndex.get(productCode);
if (candidates == null || candidates.isEmpty())
if (candidates == null)
{
skippedProduct++;
continue;
candidates = new ArrayList<PrcPriceSetDetail>();
detailIndex.put(productCode, candidates);
}
String productName = resolveImportProductName(psvId, productCode, candidates);
// Excel import is one of the only two paths that create set-product rows
Long pspId = ensureSetProduct(priceSetId, psvId, productCode, productName);
for (BmsProductCountry row : e.getValue())
{
if (row == null)
{
continue;
}
BigDecimal freight = row.getFreightPrice();
BigDecimal registration = row.getRegistrationFee();
List<PrcPriceSetDetail> hits = matchImportDetails(candidates, row);
if (hits.isEmpty())
if (!hits.isEmpty())
{
unmatched++;
continue;
}
for (PrcPriceSetDetail hit : hits)
{
if (hit.getPsdId() == null || touched.contains(hit.getPsdId()))
{
continue;
}
// Empty import freight/registration: do not overwrite existing detail values.
BigDecimal freight = row.getFreightPrice();
BigDecimal registration = row.getRegistrationFee();
if (freight == null && registration == null)
{
skippedFee++;
continue;
}
detailMapper.updateDetailPrices(hit.getPsdId(), freight, registration);
if (freight != null)
for (PrcPriceSetDetail hit : hits)
{
hit.setFvValue(freight);
if (hit.getPsdId() == null || touched.contains(hit.getPsdId()))
{
continue;
}
detailMapper.updateDetailPrices(hit.getPsdId(), freight, registration);
if (freight != null)
{
hit.setFvValue(freight);
}
if (registration != null)
{
hit.setFvRegistrationFee(registration);
}
touched.add(hit.getPsdId());
updated++;
}
if (registration != null)
{
hit.setFvRegistrationFee(registration);
}
touched.add(hit.getPsdId());
updated++;
continue;
}
if (freight == null && registration == null)
{
skippedFee++;
continue;
}
PrcPriceSetDetail created = buildDetailFromImportRow(
priceSetId, psvId, productCode, productName, row, freight, registration);
created.setPspId(pspId);
detailMapper.insertPrcPriceSetDetail(created);
candidates.add(created);
if (created.getPsdId() != null)
{
touched.add(created.getPsdId());
}
inserted++;
}
}
setProductMapper.bindDetailPspByPsvId(psvId);
reportImportProgress(taskId, priceSetId, 95, "finishing");
return MessageUtils.message("price.set.import.upsertSuccess",
Integer.valueOf(inserted), Integer.valueOf(updated), Integer.valueOf(skippedFee));
}
private String resolveImportProductName(Long psvId, String productCode, List<PrcPriceSetDetail> candidates)
{
if (candidates != null)
{
for (PrcPriceSetDetail d : candidates)
{
if (d != null && StringUtils.isNotEmpty(d.getProductName()))
{
return d.getProductName();
}
}
}
reportImportProgress(taskId, priceSetId, 95, "finishing");
return MessageUtils.message("price.set.import.success",
Integer.valueOf(updated), Integer.valueOf(unmatched), Integer.valueOf(skippedProduct));
if (psvId != null && StringUtils.isNotEmpty(productCode))
{
PrcPriceSetProduct sp = setProductMapper.selectByPsvIdAndCode(psvId, productCode);
if (sp != null && StringUtils.isNotEmpty(sp.getProductName()))
{
return sp.getProductName();
}
}
BmsProduct lib = productMapper.selectBmsProductByCode(productCode);
if (lib != null && StringUtils.isNotEmpty(lib.getProductName()))
{
return lib.getProductName();
}
return productCode;
}
private PrcPriceSetDetail buildDetailFromImportRow(Long priceSetId, Long psvId, String productCode,
String productName, BmsProductCountry row, BigDecimal freight, BigDecimal registration)
{
PrcPriceSetDetail d = new PrcPriceSetDetail();
d.setPriceSetId(priceSetId);
d.setPsvId(psvId);
d.setProductCode(productCode);
d.setProductName(productName);
d.setCountryCode(row.getCountryCode());
d.setCountryName(row.getCountryName());
d.setZoneId(row.getZoneId());
d.setZoneCode(row.getZoneCode());
d.setZoneName(row.getZoneName());
d.setWeightMin(row.getWeightMin());
d.setWeightMax(row.getWeightMax());
d.setWeightLabel(formatWeightLabel(row.getWeightMin(), row.getWeightMax()));
d.setProductCountryId(row.getDetailId());
d.setFvValue(freight);
d.setFvRegistrationFee(registration);
d.setRoundMode("HALF_UP");
d.setEffectiveDate(row.getEffectiveDate());
d.setExpireDate(row.getExpireDate());
return d;
}
private void reportImportProgress(String taskId, Long priceSetId, int percent, String message)
@@ -1097,6 +1259,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
detail.setPsvId(old.getPsvId());
}
validateDetail(db, detail);
Long pspId = lookupSetProductId(detail.getPsvId(), detail.getProductCode());
if (pspId == null)
{
pspId = old.getPspId();
}
detail.setPspId(pspId);
int rows = detailMapper.updatePrcPriceSetDetail(detail);
// non-null surcharges = replace snapshot for this detail (empty list clears)
if (detail.getSurcharges() != null)
@@ -1112,6 +1280,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
@Override
public int deleteDetailByIds(Long[] psdIds)
{
Set<String> touchKeys = new HashSet<String>();
for (Long id : psdIds)
{
PrcPriceSetDetail old = detailMapper.selectPrcPriceSetDetailById(id);
@@ -1121,8 +1290,31 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
PrcPriceSet db = requireSet(old.getPriceSetId());
assertChildEditable(db);
if (old.getPsvId() != null && StringUtils.isNotEmpty(old.getProductCode()))
{
touchKeys.add(old.getPsvId() + "|" + old.getProductCode());
}
}
return detailMapper.deletePrcPriceSetDetailByIds(psdIds);
int rows = detailMapper.deletePrcPriceSetDetailByIds(psdIds);
for (String key : touchKeys)
{
int sep = key.indexOf('|');
if (sep <= 0)
{
continue;
}
Long psvId = Long.valueOf(key.substring(0, sep));
String productCode = key.substring(sep + 1);
PrcPriceSetDetail q = new PrcPriceSetDetail();
q.setPsvId(psvId);
q.setProductCode(productCode);
List<PrcPriceSetDetail> left = detailMapper.selectPrcPriceSetDetailList(q);
if (left == null || left.isEmpty())
{
setProductMapper.deleteByPsvIdAndCode(psvId, productCode);
}
}
return rows;
}
@Override
@@ -1818,7 +2010,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
private PrcPriceSet requireSet(Long priceSetId)
{
PrcPriceSet db = setMapper.selectPrcPriceSetById(priceSetId);
// Header only: avoid count(detail) waiting on long pull/import locks.
PrcPriceSet db = setMapper.selectPrcPriceSetHeaderById(priceSetId);
if (db == null)
{
throw new ServiceException(MessageUtils.message("price.set.notExists"));
@@ -1906,16 +2099,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
set.setVersions(versions);
PrcPriceSetVersion current = pickCurrentVersion(versions);
set.setVersion(current);
if (current != null && current.getDetails() != null)
{
set.setDetails(current.getDetails());
}
else
{
PrcPriceSetDetail query = new PrcPriceSetDetail();
query.setPriceSetId(priceSetId);
set.setDetails(detailMapper.selectPrcPriceSetDetailList(query));
}
// Do not load full details here (large sets). Front loads by product via /detail/byProduct.
set.setDetails(new ArrayList<PrcPriceSetDetail>());
PrcCustomerPriceSet cquery = new PrcCustomerPriceSet();
cquery.setPriceSetId(priceSetId);
set.setCustomers(customerMapper.selectPrcCustomerPriceSetList(cquery));
@@ -1923,41 +2108,13 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
private void fillVersionChildren(PrcPriceSetVersion version)
{
PrcPriceSetDetail dq = new PrcPriceSetDetail();
dq.setPsvId(version.getPsvId());
List<PrcPriceSetDetail> details = detailMapper.selectPrcPriceSetDetailList(dq);
PrcPriceSetVersionSurcharge sq = new PrcPriceSetVersionSurcharge();
sq.setPsvId(version.getPsvId());
List<PrcPriceSetVersionSurcharge> surcharges = versionSurchargeMapper.selectPrcPriceSetVersionSurchargeList(sq);
if (surcharges == null)
{
surcharges = new ArrayList<PrcPriceSetVersionSurcharge>();
}
version.setSurcharges(surcharges);
Map<Long, List<PrcPriceSetVersionSurcharge>> byPsd = new HashMap<Long, List<PrcPriceSetVersionSurcharge>>();
for (PrcPriceSetVersionSurcharge sc : surcharges)
{
if (sc.getPsdId() == null)
{
continue;
}
List<PrcPriceSetVersionSurcharge> list = byPsd.get(sc.getPsdId());
if (list == null)
{
list = new ArrayList<PrcPriceSetVersionSurcharge>();
byPsd.put(sc.getPsdId(), list);
}
list.add(sc);
}
if (details != null)
{
for (PrcPriceSetDetail d : details)
{
List<PrcPriceSetVersionSurcharge> list = byPsd.get(d.getPsdId());
d.setSurcharges(list != null ? list : new ArrayList<PrcPriceSetVersionSurcharge>());
}
}
version.setDetails(details);
PrcPriceSetProduct pq = new PrcPriceSetProduct();
pq.setPsvId(version.getPsvId());
List<PrcPriceSetProduct> products = setProductMapper.selectPrcPriceSetProductList(pq);
version.setProducts(products == null ? new ArrayList<PrcPriceSetProduct>() : products);
// details + surcharge snapshots are loaded on demand (edit price / detail drawer)
version.setDetails(new ArrayList<PrcPriceSetDetail>());
version.setSurcharges(new ArrayList<PrcPriceSetVersionSurcharge>());
}
private PrcPriceSetVersion pickCurrentVersion(List<PrcPriceSetVersion> versions)
@@ -1983,14 +2140,11 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
// null details = leave existing rows alone; empty list = clear / replace with nothing
boolean detailsProvided = set.getDetails() != null;
List<PrcPriceSetDetail> details = filterDetails(set.getDetails());
// Allow creating an empty draft version when client sends version (Step1 -> Step2).
if (version == null && !detailsProvided && !pullLibrary)
{
return;
}
if (version == null && details.isEmpty() && !pullLibrary)
{
return;
}
if (version == null)
{
version = new PrcPriceSetVersion();
@@ -2097,18 +2251,21 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
if (existing > 0 && !force)
{
reportSaveProgress(set, 80, "details exist, skip pull");
PrcPriceSetDetail query = new PrcPriceSetDetail();
query.setPsvId(version.getPsvId());
List<PrcPriceSetDetail> details = detailMapper.selectPrcPriceSetDetailList(query);
set.setDetails(details);
version.setDetails(details);
fillVersionChildren(version);
set.setDetails(new ArrayList<PrcPriceSetDetail>());
return;
}
versionSurchargeMapper.deleteByPsvId(version.getPsvId());
detailMapper.deletePrcPriceSetDetailByPsvId(version.getPsvId());
setProductMapper.deleteByPsvId(version.getPsvId());
// clean legacy rows without psv_id under this set (would look like duplicates in UI)
detailMapper.deleteOrphanDetailsByPriceSetId(set.getPriceSetId());
int productRows = setProductMapper.insertFromLibrary(set.getPriceSetId(), version.getPsvId());
if (productRows <= 0)
{
throw new ServiceException(MessageUtils.message("price.set.library.empty"));
}
reportSaveProgress(set, 45, "pulling library");
int inserted;
if (TYPE_DISCOUNT.equals(set.getSetType()))
{
@@ -2130,15 +2287,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
throw new ServiceException(MessageUtils.message("price.set.library.empty"));
}
setProductMapper.bindDetailPspByPsvId(version.getPsvId());
reportSaveProgress(set, 75, "saving details " + inserted + "/" + inserted);
reportSaveProgress(set, 85, "saving surcharges");
versionSurchargeMapper.insertFromLibraryRels(version.getPsvId(), version.getCreateBy());
PrcPriceSetDetail query = new PrcPriceSetDetail();
query.setPsvId(version.getPsvId());
List<PrcPriceSetDetail> details = detailMapper.selectPrcPriceSetDetailList(query);
set.setDetails(details);
version.setDetails(details);
fillVersionChildren(version);
set.setDetails(new ArrayList<PrcPriceSetDetail>());
}
/**
@@ -2171,6 +2325,11 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
validateDetail(set, d);
d.setPriceSetId(set.getPriceSetId());
d.setPsvId(version.getPsvId());
Long pspId = lookupSetProductId(version.getPsvId(), d.getProductCode());
if (pspId != null)
{
d.setPspId(pspId);
}
if (d.getPsdId() != null && byId.containsKey(d.getPsdId()))
{
detailMapper.updatePrcPriceSetDetail(d);
@@ -2213,9 +2372,107 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
detailMapper.deletePrcPriceSetDetailByIds(orphanIds.toArray(new Long[0]));
}
// drop set-product rows whose codes no longer have any detail
Set<String> keepCodes = new HashSet<String>();
if (details != null)
{
for (PrcPriceSetDetail d : details)
{
if (d != null && StringUtils.isNotEmpty(d.getProductCode()))
{
keepCodes.add(d.getProductCode().trim());
}
}
}
PrcPriceSetProduct pq = new PrcPriceSetProduct();
pq.setPsvId(version.getPsvId());
List<PrcPriceSetProduct> setProducts = setProductMapper.selectPrcPriceSetProductList(pq);
if (setProducts != null)
{
for (PrcPriceSetProduct sp : setProducts)
{
if (sp == null || StringUtils.isEmpty(sp.getProductCode()))
{
continue;
}
if (!keepCodes.contains(sp.getProductCode()))
{
setProductMapper.deleteByPsvIdAndCode(version.getPsvId(), sp.getProductCode());
}
}
}
version.setDetails(details);
}
/**
* Lookup existing set-product id only (no insert).
* Insert happens only on pull-from-library / Excel import.
*/
private Long lookupSetProductId(Long psvId, String productCode)
{
if (psvId == null || StringUtils.isEmpty(productCode))
{
return null;
}
PrcPriceSetProduct existing = setProductMapper.selectByPsvIdAndCode(psvId, productCode.trim());
return existing == null ? null : existing.getPspId();
}
/**
* Ensure package-side product master row exists for this version+code; return psp_id.
* Only called from pull-from-library / Excel import.
*/
private Long ensureSetProduct(Long priceSetId, Long psvId, String productCode, String productNameHint)
{
if (psvId == null || StringUtils.isEmpty(productCode))
{
return null;
}
String code = productCode.trim();
PrcPriceSetProduct existing = setProductMapper.selectByPsvIdAndCode(psvId, code);
if (existing != null)
{
if (StringUtils.isEmpty(existing.getProductName()) && StringUtils.isNotEmpty(productNameHint)
&& !code.equals(productNameHint))
{
existing.setProductName(productNameHint);
setProductMapper.updatePrcPriceSetProduct(existing);
}
return existing.getPspId();
}
PrcPriceSetProduct row = new PrcPriceSetProduct();
row.setPriceSetId(priceSetId);
row.setPsvId(psvId);
row.setProductCode(code);
BmsProduct lib = null;
try
{
lib = productMapper.selectBmsProductByCode(code);
}
catch (Exception ignore)
{
lib = null;
}
if (lib != null)
{
row.setProductName(lib.getProductName());
row.setServiceLevel(lib.getServiceLevel());
row.setCargoAttr(lib.getCargoAttr());
row.setDestRegion(lib.getDestRegion());
row.setUpdateNote(lib.getUpdateNote());
row.setTaxMode(StringUtils.isNotEmpty(lib.getTaxMode()) ? lib.getTaxMode() : "TAX_INCLUDED");
row.setSourceProductId(lib.getProductId());
row.setSourceSheet(lib.getSourceSheet());
}
else
{
row.setProductName(StringUtils.isNotEmpty(productNameHint) ? productNameHint : code);
row.setTaxMode("TAX_INCLUDED");
}
setProductMapper.insertPrcPriceSetProduct(row);
return row.getPspId();
}
/** Only rewrite surcharge snapshots for rows that explicitly sent surcharges. */
private void resnapshotSurchargesIfNeeded(PrcPriceSetVersion version, List<PrcPriceSetDetail> details)
{
@@ -28,6 +28,7 @@ import com.bms.price.domain.BmsProductCountry;
import com.bms.price.domain.PrcCustomerPriceSet;
import com.bms.price.domain.PrcPriceSet;
import com.bms.price.domain.PrcPriceSetDetail;
import com.bms.price.domain.PrcPriceSetProduct;
import com.bms.price.domain.PrcPriceSetVersion;
import com.bms.price.domain.PrcPriceSetVersionSurcharge;
import com.bms.price.domain.PrcQuote;
@@ -45,6 +46,7 @@ import com.bms.price.mapper.BmsProductMapper;
import com.bms.price.mapper.PrcCustomerPriceSetMapper;
import com.bms.price.mapper.PrcPriceSetDetailMapper;
import com.bms.price.mapper.PrcPriceSetMapper;
import com.bms.price.mapper.PrcPriceSetProductMapper;
import com.bms.price.mapper.PrcPriceSetVersionMapper;
import com.bms.price.mapper.PrcPriceSetVersionSurchargeMapper;
import com.bms.price.mapper.PrcQuoteFeeMapper;
@@ -90,6 +92,9 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
@Autowired
private PrcPriceSetDetailMapper detailMapper;
@Autowired
private PrcPriceSetProductMapper setProductMapper;
@Autowired
private BmsProductCountryMapper productCountryMapper;
@@ -195,6 +200,98 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
return toResult(quote, quoteFeeMapper.selectByQuoteId(quoteId), true);
}
@Override
public List<PrcPriceSetProduct> listBoundPackageProducts(String customerCode, Date bizDate)
{
if (StringUtils.isEmpty(customerCode))
{
return Collections.emptyList();
}
List<PrcCustomerPriceSet> binds = customerPriceSetMapper.selectEffectiveByCustomerCode(customerCode.trim());
if (binds == null || binds.isEmpty())
{
return Collections.emptyList();
}
Date day = bizDate != null ? bizDate : new Date();
Date asOf = endOfDay(day);
for (PrcCustomerPriceSet bind : binds)
{
if (bind == null || bind.getPriceSetId() == null)
{
continue;
}
PrcPriceSet set = priceSetMapper.selectPrcPriceSetHeaderById(bind.getPriceSetId());
if (set == null || !"E".equals(set.getStatus()))
{
continue;
}
List<PrcPriceSetProduct> products = loadProductsForPackage(set.getPriceSetId(), asOf);
if (products != null && !products.isEmpty())
{
return products;
}
}
return Collections.emptyList();
}
/** Prefer version active at asOf; else latest version that has set-product rows. */
private List<PrcPriceSetProduct> loadProductsForPackage(Long priceSetId, Date asOf)
{
PrcPriceSetVersion active = versionMapper.selectActiveByPriceSetAt(priceSetId, asOf);
List<PrcPriceSetProduct> rows = listProductsByPsvId(active == null ? null : active.getPsvId());
if (rows != null && !rows.isEmpty())
{
return rows;
}
PrcPriceSetVersion q = new PrcPriceSetVersion();
q.setPriceSetId(priceSetId);
List<PrcPriceSetVersion> versions = versionMapper.selectPrcPriceSetVersionList(q);
if (versions == null || versions.isEmpty())
{
return Collections.emptyList();
}
// newest seq first
versions.sort((a, b) -> {
int sa = a.getVersionSeq() == null ? 0 : a.getVersionSeq().intValue();
int sb = b.getVersionSeq() == null ? 0 : b.getVersionSeq().intValue();
return Integer.compare(sb, sa);
});
for (PrcPriceSetVersion v : versions)
{
rows = listProductsByPsvId(v == null ? null : v.getPsvId());
if (rows != null && !rows.isEmpty())
{
return rows;
}
}
return Collections.emptyList();
}
private List<PrcPriceSetProduct> listProductsByPsvId(Long psvId)
{
if (psvId == null)
{
return Collections.emptyList();
}
PrcPriceSetProduct q = new PrcPriceSetProduct();
q.setPsvId(psvId);
List<PrcPriceSetProduct> rows = setProductMapper.selectPrcPriceSetProductList(q);
if (rows == null || rows.isEmpty())
{
return Collections.emptyList();
}
Map<String, PrcPriceSetProduct> byCode = new LinkedHashMap<String, PrcPriceSetProduct>();
for (PrcPriceSetProduct row : rows)
{
if (row == null || StringUtils.isEmpty(row.getProductCode()))
{
continue;
}
byCode.putIfAbsent(row.getProductCode(), row);
}
return new ArrayList<PrcPriceSetProduct>(byCode.values());
}
private TmsPriceQuoteResult doQuote(TmsPriceQuoteRequest request, Date bizDate)
{
String productCode = request.getProductCode().trim();
@@ -485,14 +582,8 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
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;
// FIXED sales price is version-detail only; no fallback to product_country.
return null;
}
PrcPriceSetDetail detail = detailMapper.selectFixedMatch(