Refactor surcharge management by replacing PrcPriceSetVersionSurcharge with PrcSurchargeRel, introducing price type handling (PRODUCT, SALES, REDLINE) and updating related services, mappers, and front-end components. Enhance validation and error messaging for price type and detail ID requirements. Update database schema to support new relationships and constraints.

This commit is contained in:
wuweihua
2026-10-09 17:20:10 +08:00
parent 70e9c18182
commit 380577f691
19 changed files with 647 additions and 530 deletions
@@ -52,7 +52,8 @@ public class PrcPriceSetDetail implements Serializable
/** null = open */
@JsonFormat(pattern = "yyyy-MM-dd")
private Date expireDate;
private List<PrcPriceSetVersionSurcharge> surcharges;
/** SALES rels for this detail (detail_id=psd_id, product_id=psp_id) */
private List<PrcSurchargeRel> surcharges;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@@ -34,5 +34,4 @@ public class PrcPriceSetVersion extends BaseEntity
private List<PrcPriceSetDetail> details;
private List<PrcPriceSetProduct> products;
private List<PrcPriceSetVersionSurcharge> surcharges;
}
@@ -1,32 +0,0 @@
package com.bms.price.domain;
import java.math.BigDecimal;
import com.bms.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/** Price set version surcharge snapshot prc_price_set_version_surcharge */
@Data
@EqualsAndHashCode(callSuper = true)
public class PrcPriceSetVersionSurcharge extends BaseEntity
{
private static final long serialVersionUID = 1L;
private Long psvsId;
private Long psvId;
/** source prc_surcharge.sc_id */
private Long scId;
/** prc_price_set_detail.psd_id */
private Long psdId;
/** copied from detail */
private String productCode;
private String scName;
private String feeType;
/** FIXED / WEIGHT / RATE */
private String chargeType;
private BigDecimal chargeValue;
private String currency;
private String roundMode;
private String countryCode;
private String scNote;
}
@@ -6,13 +6,12 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 附加费与产品/明细关联 prc_surcharge_rel
* 同时携带附加费展示字段(JOIN prc_surcharge)与明细展示字段(LEFT JOIN product_country),
* 便于产品列表抽屉直接渲染。
* 附加费与产品/明细/套餐关联 prc_surcharge_rel
* 同时携带附加费展示字段(JOIN prc_surcharge)与明细展示字段(LEFT JOIN product_country,仅 PRODUCT/REDLINE)。
*
* 语义:
* detail_id 为空 = 产品级附加费(作用于该产品全部明细)
* detail_id 有值 = 明细级附加费(作用于该产品某个 国家×重量段 明细)
* price_type 语义:
* PRODUCT / REDLINE : product_id=product.product_id,detail_id=product_country.detail_id(空=产品全部明细)
* SALES : product_id=prc_price_set_product.psp_id,detail_id=prc_price_set_detail.psd_id
*/
@Data
@EqualsAndHashCode(callSuper = true)
@@ -20,11 +19,24 @@ public class PrcSurchargeRel extends BaseEntity
{
private static final long serialVersionUID = 1L;
/** 价格类型:产品价 */
public static final String PRICE_TYPE_PRODUCT = "PRODUCT";
/** 价格类型:销售价(套餐) */
public static final String PRICE_TYPE_SALES = "SALES";
/** 价格类型:红线价 */
public static final String PRICE_TYPE_REDLINE = "REDLINE";
private Long relId;
private Long scId;
/** PRODUCT/REDLINE=product_id;SALES=psp_id */
private Long productId;
private String productCode;
/** PRODUCT/REDLINE=product_country.detail_id;SALES=psd_id */
private Long detailId;
/** PRODUCT / SALES / REDLINE */
private String priceType;
/** 适用国家ISO2(空=全部),rel 表字段 */
private String countryCode;
/** 附加费冗余展示字段 */
private String scName;
@@ -34,10 +46,9 @@ public class PrcSurchargeRel extends BaseEntity
private String currency;
private String roundMode;
private String ssCode;
private String countryCode;
private String scNote;
/** 明细展示字段 */
/** 明细展示字段(PRODUCT/REDLINE LEFT JOIN product_country) */
private String countryName;
private BigDecimal weightMin;
private BigDecimal weightMax;
@@ -1,36 +0,0 @@
package com.bms.price.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.price.domain.PrcPriceSetVersionSurcharge;
/**
* Price set version surcharge snapshot
*/
public interface PrcPriceSetVersionSurchargeMapper
{
public PrcPriceSetVersionSurcharge selectPrcPriceSetVersionSurchargeById(Long psvsId);
public List<PrcPriceSetVersionSurcharge> selectPrcPriceSetVersionSurchargeList(PrcPriceSetVersionSurcharge surcharge);
public int insertPrcPriceSetVersionSurcharge(PrcPriceSetVersionSurcharge surcharge);
public int updatePrcPriceSetVersionSurcharge(PrcPriceSetVersionSurcharge surcharge);
public int deletePrcPriceSetVersionSurchargeByIds(Long[] psvsIds);
public int deleteByPsvId(Long psvId);
public int deleteByPsdId(Long psdId);
public List<PrcPriceSetVersionSurcharge> selectByPsdIds(@Param("psdIds") List<Long> psdIds);
/** Snapshot all applicable product-rel surcharges for details under psvId (library create). */
public int insertFromLibraryRels(@Param("psvId") Long psvId, @Param("createBy") String createBy);
/** Copy surcharge snapshots from src version onto new version details (match by detail keys). */
public int copySurchargesFromVersion(
@Param("srcPsvId") Long srcPsvId,
@Param("newPsvId") Long newPsvId,
@Param("createBy") String createBy);
}
@@ -5,24 +5,36 @@ import org.apache.ibatis.annotations.Param;
import com.bms.price.domain.PrcSurchargeRel;
/**
* 附加费与产品/明细关联 数据层
* 附加费与产品/明细/套餐关联 数据层
*
* @author bms
*/
public interface PrcSurchargeRelMapper
{
/**
* 查询关联列表(JOIN 附加费 + 明细),支持按 productCode / scId 过滤
* 查询关联列表(JOIN 附加费 + 明细),支持按 productCode / scId / priceType / detailId 过滤
*/
public List<PrcSurchargeRel> selectPrcSurchargeRelList(PrcSurchargeRel rel);
public List<PrcSurchargeRel> selectByDetailIdsAndPriceType(
@Param("detailIds") List<Long> detailIds,
@Param("priceType") String priceType);
public int insertPrcSurchargeRel(PrcSurchargeRel rel);
public int deletePrcSurchargeRelByIds(Long[] relIds);
/** 删除产品时清理其关联 */
/** 删除产品时清理 PRODUCT/REDLINE 关联 */
public int deleteRelByProductCodes(String[] productCodes);
public int deleteByDetailIdsAndPriceType(
@Param("detailIds") List<Long> detailIds,
@Param("priceType") String priceType);
public int deleteByProductIdsAndPriceType(
@Param("productIds") List<Long> productIds,
@Param("priceType") String priceType);
/** 附加费版本化编辑后,把产品关联从旧记录重指向新记录 */
public int rebindRelScId(@Param("oldScId") Long oldScId, @Param("newScId") Long newScId, @Param("operator") String operator);
}
@@ -34,7 +34,6 @@ 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;
@@ -44,7 +43,6 @@ 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;
import com.bms.price.mapper.PrcSurchargeRelMapper;
import com.bms.price.service.IBmsProductService;
@@ -91,9 +89,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
@Autowired
private PrcPriceSetVersionMapper versionMapper;
@Autowired
private PrcPriceSetVersionSurchargeMapper versionSurchargeMapper;
@Autowired
private PrcSurchargeRelMapper surchargeRelMapper;
@@ -180,10 +175,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
continue;
}
// Always replace snapshot for this batch API (null would skip in updateDetail)
// Always replace SALES rels for this batch API (null would skip in updateDetail)
if (detail.getSurcharges() == null)
{
detail.setSurcharges(new ArrayList<PrcPriceSetVersionSurcharge>());
detail.setSurcharges(new ArrayList<PrcSurchargeRel>());
}
rows += updateDetail(detail);
}
@@ -474,7 +469,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
if (detailRows > 0)
{
setProductMapper.bindDetailPspByPsvId(nv.getPsvId());
versionSurchargeMapper.copySurchargesFromVersion(sourcePsvId, nv.getPsvId(), createBy);
copySalesSurchargeRelsFromVersion(sourcePsvId, nv.getPsvId(), createBy);
}
refreshVersionStatus(set.getPriceSetId());
return nv.getPsvId();
@@ -527,7 +522,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
throw new ServiceException(MessageUtils.message("price.set.version.delete.statusNotAllowed"));
}
requireSet(version.getPriceSetId());
versionSurchargeMapper.deleteByPsvId(psvId);
deleteSalesRelsByPsvId(psvId);
detailMapper.deletePrcPriceSetDetailByPsvId(psvId);
setProductMapper.deleteByPsvId(psvId);
int rows = versionMapper.deletePrcPriceSetVersionByIds(new Long[] { psvId });
@@ -858,7 +853,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return result;
}
/** Attach version surcharge snapshots to a page of details (for drawer list). */
/** Attach SALES surcharge rels (detail_id=psd_id) to a page of details. */
private void fillDetailSurcharges(List<PrcPriceSetDetail> details)
{
if (details == null || details.isEmpty())
@@ -877,21 +872,22 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
return;
}
List<PrcPriceSetVersionSurcharge> surcharges = versionSurchargeMapper.selectByPsdIds(psdIds);
Map<Long, List<PrcPriceSetVersionSurcharge>> byPsd = new HashMap<Long, List<PrcPriceSetVersionSurcharge>>();
List<PrcSurchargeRel> surcharges =
surchargeRelMapper.selectByDetailIdsAndPriceType(psdIds, PrcSurchargeRel.PRICE_TYPE_SALES);
Map<Long, List<PrcSurchargeRel>> byPsd = new HashMap<Long, List<PrcSurchargeRel>>();
if (surcharges != null)
{
for (PrcPriceSetVersionSurcharge sc : surcharges)
for (PrcSurchargeRel sc : surcharges)
{
if (sc == null || sc.getPsdId() == null)
if (sc == null || sc.getDetailId() == null)
{
continue;
}
List<PrcPriceSetVersionSurcharge> list = byPsd.get(sc.getPsdId());
List<PrcSurchargeRel> list = byPsd.get(sc.getDetailId());
if (list == null)
{
list = new ArrayList<PrcPriceSetVersionSurcharge>();
byPsd.put(sc.getPsdId(), list);
list = new ArrayList<PrcSurchargeRel>();
byPsd.put(sc.getDetailId(), list);
}
list.add(sc);
}
@@ -902,8 +898,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
continue;
}
List<PrcPriceSetVersionSurcharge> list = byPsd.get(d.getPsdId());
d.setSurcharges(list != null ? list : new ArrayList<PrcPriceSetVersionSurcharge>());
List<PrcSurchargeRel> list = byPsd.get(d.getPsdId());
d.setSurcharges(list != null ? list : new ArrayList<PrcSurchargeRel>());
}
}
@@ -916,9 +912,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
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());
snapshotVersionSurcharges(ver, java.util.Collections.singletonList(detail));
if (detail.getSurcharges() != null)
{
replaceSalesSurchargeRels(java.util.Collections.singletonList(detail));
}
return rows;
}
@@ -1819,13 +1816,10 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
detail.setPspId(pspId);
int rows = detailMapper.updatePrcPriceSetDetail(detail);
// non-null surcharges = replace snapshot for this detail (empty list clears)
// non-null surcharges = replace SALES rels for this detail (empty list clears)
if (detail.getSurcharges() != null)
{
versionSurchargeMapper.deleteByPsdId(detail.getPsdId());
PrcPriceSetVersion ver = new PrcPriceSetVersion();
ver.setPsvId(detail.getPsvId());
snapshotVersionSurcharges(ver, java.util.Collections.singletonList(detail));
replaceSalesSurchargeRels(java.util.Collections.singletonList(detail));
}
return rows;
}
@@ -1848,6 +1842,21 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
touchKeys.add(old.getPsvId() + "|" + old.getProductCode());
}
}
if (psdIds != null && psdIds.length > 0)
{
List<Long> ids = new ArrayList<Long>();
for (Long id : psdIds)
{
if (id != null)
{
ids.add(id);
}
}
if (!ids.isEmpty())
{
surchargeRelMapper.deleteByDetailIdsAndPriceType(ids, PrcSurchargeRel.PRICE_TYPE_SALES);
}
}
int rows = detailMapper.deletePrcPriceSetDetailByIds(psdIds);
for (String key : touchKeys)
{
@@ -1864,6 +1873,12 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
List<PrcPriceSetDetail> left = detailMapper.selectPrcPriceSetDetailList(q);
if (left == null || left.isEmpty())
{
Long pspId = lookupSetProductId(psvId, productCode);
if (pspId != null)
{
surchargeRelMapper.deleteByProductIdsAndPriceType(
java.util.Collections.singletonList(pspId), PrcSurchargeRel.PRICE_TYPE_SALES);
}
setProductMapper.deleteByPsvIdAndCode(psvId, productCode);
}
}
@@ -2696,9 +2711,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
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)
// details + SALES surcharge rels are loaded on demand (edit price / detail drawer)
version.setDetails(new ArrayList<PrcPriceSetDetail>());
version.setSurcharges(new ArrayList<PrcPriceSetVersionSurcharge>());
}
private PrcPriceSetVersion pickCurrentVersion(List<PrcPriceSetVersion> versions)
@@ -2782,7 +2796,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
upsertVersionDetails(set, version, details);
reportSaveProgress(set, 85, "saving surcharges");
resnapshotSurchargesIfNeeded(version, details);
replaceSalesSurchargesIfNeeded(details);
}
else
{
@@ -2793,7 +2807,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
/**
* Draft only: when priceBuildMode changes (PULL/IMPORT), delete this version's
* product / detail / surcharge rows. Same mode or non-draft: no-op.
* product / detail / SALES surcharge rel rows. Same mode or non-draft: no-op.
*/
private void clearDraftVersionChildrenIfBuildModeChanged(PrcPriceSetVersion db, PrcPriceSetVersion incoming)
{
@@ -2816,7 +2830,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return;
}
Long psvId = db.getPsvId();
versionSurchargeMapper.deleteByPsvId(psvId);
deleteSalesRelsByPsvId(psvId);
detailMapper.deletePrcPriceSetDetailByPsvId(psvId);
setProductMapper.deleteByPsvId(psvId);
}
@@ -2852,8 +2866,9 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
/**
* Bulk create details + surcharge snapshots via SQL joins (no per-product HTTP roundtrips).
* Bulk create details via SQL joins (no per-product HTTP roundtrips).
* Idempotent: if this version already has details and forcePullLibrary!=Y, skip re-insert.
* SALES surcharge rels are not auto-created; configure via package surcharge picker.
*/
private void replaceVersionDetailsFromLibrary(PrcPriceSet set, PrcPriceSetVersion version)
{
@@ -2867,7 +2882,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
set.setDetails(new ArrayList<PrcPriceSetDetail>());
return;
}
versionSurchargeMapper.deleteByPsvId(version.getPsvId());
deleteSalesRelsByPsvId(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)
@@ -2901,8 +2916,6 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
setProductMapper.bindDetailPspByPsvId(version.getPsvId());
reportSaveProgress(set, 75, "saving details " + inserted + "/" + inserted);
reportSaveProgress(set, 85, "saving surcharges");
versionSurchargeMapper.insertFromLibraryRels(version.getPsvId(), version.getCreateBy());
fillVersionChildren(version);
set.setDetails(new ArrayList<PrcPriceSetDetail>());
}
@@ -3020,10 +3033,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
}
if (!orphanIds.isEmpty())
{
for (Long id : orphanIds)
{
versionSurchargeMapper.deleteByPsdId(id);
}
surchargeRelMapper.deleteByDetailIdsAndPriceType(orphanIds, PrcSurchargeRel.PRICE_TYPE_SALES);
detailMapper.deletePrcPriceSetDetailByIds(orphanIds.toArray(new Long[0]));
}
Set<String> keepCodes = new HashSet<String>();
@@ -3187,8 +3197,8 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
return row.getPspId();
}
/** Only rewrite surcharge snapshots for rows that explicitly sent surcharges. */
private void resnapshotSurchargesIfNeeded(PrcPriceSetVersion version, List<PrcPriceSetDetail> details)
/** Only rewrite SALES surcharge rels for rows that explicitly sent surcharges. */
private void replaceSalesSurchargesIfNeeded(List<PrcPriceSetDetail> details)
{
if (details == null || details.isEmpty())
{
@@ -3199,153 +3209,296 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
{
if (d != null && d.getPsdId() != null && d.getSurcharges() != null)
{
versionSurchargeMapper.deleteByPsdId(d.getPsdId());
withSc.add(d);
}
}
if (!withSc.isEmpty())
{
snapshotVersionSurcharges(version, withSc);
replaceSalesSurchargeRels(withSc);
}
}
private void snapshotVersionSurcharges(PrcPriceSetVersion version, List<PrcPriceSetDetail> details)
/**
* Replace SALES prc_surcharge_rel rows for the given package details.
* product_id = psp_id, detail_id = psd_id, price_type = SALES.
* Empty surcharges list clears associations for that detail.
*/
private void replaceSalesSurchargeRels(List<PrcPriceSetDetail> details)
{
if (details == null || details.isEmpty())
{
return;
}
Set<String> codes = new HashSet<String>();
Map<Long, PrcSurcharge> masterById = null;
for (PrcPriceSetDetail d : details)
{
if (StringUtils.isNotEmpty(d.getProductCode()))
{
codes.add(d.getProductCode());
}
}
Map<String, List<PrcSurchargeRel>> relsByProduct = new HashMap<String, List<PrcSurchargeRel>>();
for (String code : codes)
{
PrcSurchargeRel q = new PrcSurchargeRel();
q.setProductCode(code);
List<PrcSurchargeRel> rels = surchargeRelMapper.selectPrcSurchargeRelList(q);
relsByProduct.put(code, rels == null ? new ArrayList<PrcSurchargeRel>() : rels);
}
// 勾选列表来自「价格基础 → 附加费配置」主表(prc_surcharge),不要求产品已做 rel 关联;
// rel 里没有的勾选项,回退主表取值落快照。
List<PrcSurcharge> surchargeMasters = java.util.Collections.emptyList();
for (PrcPriceSetDetail d0 : details)
{
if (d0.getSurcharges() != null && !d0.getSurcharges().isEmpty())
{
surchargeMasters = surchargeMapper.selectPrcSurchargeList(new PrcSurcharge());
break;
}
}
List<PrcPriceSetVersionSurcharge> saved = new ArrayList<PrcPriceSetVersionSurcharge>();
for (PrcPriceSetDetail d : details)
{
List<PrcSurchargeRel> rels = relsByProduct.get(d.getProductCode());
if (rels == null)
if (d == null || d.getPsdId() == null || d.getSurcharges() == null)
{
continue;
}
// 前端勾选结果:明细带了 surcharges 就按勾选保存(空集合 = 一条都不要);
// 为 null 表示调用方没传勾选(如详情页新增单条明细),沿用「全部命中即快照」。
Set<Long> pickedScIds = null;
if (d.getSurcharges() != null)
Long pspId = d.getPspId();
if (pspId == null && d.getPsvId() != null && StringUtils.isNotEmpty(d.getProductCode()))
{
pickedScIds = new HashSet<Long>();
for (PrcPriceSetVersionSurcharge picked : d.getSurcharges())
{
if (picked != null && picked.getScId() != null)
{
pickedScIds.add(picked.getScId());
}
}
pspId = lookupSetProductId(d.getPsvId(), d.getProductCode());
d.setPspId(pspId);
}
List<PrcPriceSetVersionSurcharge> rowSc = new ArrayList<PrcPriceSetVersionSurcharge>();
if (pspId == null)
{
continue;
}
surchargeRelMapper.deleteByDetailIdsAndPriceType(
java.util.Collections.singletonList(d.getPsdId()), PrcSurchargeRel.PRICE_TYPE_SALES);
List<PrcSurchargeRel> saved = new ArrayList<PrcSurchargeRel>();
Set<Long> written = new HashSet<Long>();
for (PrcSurchargeRel rel : rels)
for (PrcSurchargeRel picked : d.getSurcharges())
{
if (rel == null || StringUtils.isEmpty(rel.getScName()) || rel.getChargeValue() == null)
if (picked == null || picked.getScId() == null || written.contains(picked.getScId()))
{
continue;
}
if ("of".equalsIgnoreCase(rel.getSsCode()))
PrcSurchargeRel row = buildSalesRel(d, pspId, picked.getScId(), picked.getCountryCode());
if (row == null)
{
continue;
}
if (pickedScIds != null && !pickedScIds.contains(rel.getScId()))
{
continue;
}
if (rel.getDetailId() != null && d.getProductCountryId() != null
&& !rel.getDetailId().equals(d.getProductCountryId()))
{
continue;
}
if (rel.getDetailId() != null && d.getProductCountryId() == null)
{
continue;
}
PrcPriceSetVersionSurcharge sc = new PrcPriceSetVersionSurcharge();
sc.setPsvId(version.getPsvId());
sc.setPsdId(d.getPsdId());
sc.setScId(rel.getScId());
sc.setProductCode(d.getProductCode());
sc.setScName(rel.getScName());
sc.setFeeType(StringUtils.isEmpty(rel.getFeeType()) ? "SURCHARGE" : rel.getFeeType());
sc.setChargeType(StringUtils.isEmpty(rel.getChargeType()) ? "FIXED" : rel.getChargeType());
sc.setChargeValue(rel.getChargeValue());
sc.setCurrency(rel.getCurrency());
sc.setRoundMode(rel.getRoundMode());
sc.setCountryCode(rel.getCountryCode());
sc.setScNote(rel.getScNote());
sc.setCreateBy(version.getCreateBy());
versionSurchargeMapper.insertPrcPriceSetVersionSurcharge(sc);
saved.add(sc);
rowSc.add(sc);
written.add(rel.getScId());
}
// 勾选了但产品未关联(rel 无此 scId)的附加费:回退 prc_surcharge 主表配置
if (pickedScIds != null)
{
for (PrcSurcharge m : surchargeMasters)
{
if (m == null || m.getScId() == null || written.contains(m.getScId())
|| !pickedScIds.contains(m.getScId()))
if (masterById == null)
{
continue;
masterById = loadSurchargeMasterMap();
}
if (StringUtils.isEmpty(m.getScName()) || m.getChargeValue() == null
PrcSurcharge m = masterById.get(picked.getScId());
if (m == null || StringUtils.isEmpty(m.getScName()) || m.getChargeValue() == null
|| "of".equalsIgnoreCase(m.getSsCode()))
{
continue;
}
PrcPriceSetVersionSurcharge sc = new PrcPriceSetVersionSurcharge();
sc.setPsvId(version.getPsvId());
sc.setPsdId(d.getPsdId());
sc.setScId(m.getScId());
sc.setProductCode(d.getProductCode());
sc.setScName(m.getScName());
sc.setFeeType(StringUtils.isEmpty(m.getFeeType()) ? "SURCHARGE" : m.getFeeType());
sc.setChargeType(StringUtils.isEmpty(m.getChargeType()) ? "FIXED" : m.getChargeType());
sc.setChargeValue(m.getChargeValue());
sc.setCurrency(m.getCurrency());
sc.setRoundMode(m.getRoundMode());
sc.setCountryCode(m.getCountryCode());
sc.setScNote(m.getScNote());
sc.setCreateBy(version.getCreateBy());
versionSurchargeMapper.insertPrcPriceSetVersionSurcharge(sc);
saved.add(sc);
rowSc.add(sc);
written.add(m.getScId());
row = new PrcSurchargeRel();
row.setScId(m.getScId());
row.setProductId(pspId);
row.setProductCode(d.getProductCode());
row.setDetailId(d.getPsdId());
row.setPriceType(PrcSurchargeRel.PRICE_TYPE_SALES);
row.setCountryCode(normalizeCountry(picked.getCountryCode() != null
? picked.getCountryCode() : m.getCountryCode()));
row.setScName(m.getScName());
row.setFeeType(m.getFeeType());
row.setChargeType(m.getChargeType());
row.setChargeValue(m.getChargeValue());
row.setCurrency(m.getCurrency());
row.setRoundMode(m.getRoundMode());
row.setSsCode(m.getSsCode());
row.setScNote(m.getScNote());
row.setCreateBy(SecurityUtils.getUsername());
}
surchargeRelMapper.insertPrcSurchargeRel(row);
saved.add(row);
written.add(picked.getScId());
}
d.setSurcharges(saved);
}
}
private PrcSurchargeRel buildSalesRel(PrcPriceSetDetail d, Long pspId, Long scId, String countryCode)
{
if (d == null || pspId == null || scId == null)
{
return null;
}
PrcSurcharge m = surchargeMapper.selectPrcSurchargeById(scId);
if (m == null || StringUtils.isEmpty(m.getScName()) || m.getChargeValue() == null
|| "of".equalsIgnoreCase(m.getSsCode()))
{
return null;
}
PrcSurchargeRel row = new PrcSurchargeRel();
row.setScId(m.getScId());
row.setProductId(pspId);
row.setProductCode(d.getProductCode());
row.setDetailId(d.getPsdId());
row.setPriceType(PrcSurchargeRel.PRICE_TYPE_SALES);
row.setCountryCode(normalizeCountry(countryCode != null ? countryCode : m.getCountryCode()));
row.setScName(m.getScName());
row.setFeeType(m.getFeeType());
row.setChargeType(m.getChargeType());
row.setChargeValue(m.getChargeValue());
row.setCurrency(m.getCurrency());
row.setRoundMode(m.getRoundMode());
row.setSsCode(m.getSsCode());
row.setScNote(m.getScNote());
row.setCreateBy(SecurityUtils.getUsername());
return row;
}
private Map<Long, PrcSurcharge> loadSurchargeMasterMap()
{
Map<Long, PrcSurcharge> map = new HashMap<Long, PrcSurcharge>();
List<PrcSurcharge> all = surchargeMapper.selectPrcSurchargeList(new PrcSurcharge());
if (all == null)
{
return map;
}
for (PrcSurcharge m : all)
{
if (m != null && m.getScId() != null)
{
map.put(m.getScId(), m);
}
}
return map;
}
private String normalizeCountry(String code)
{
if (StringUtils.isEmpty(code))
{
return null;
}
return code.trim().toUpperCase();
}
/** Delete all SALES surcharge rels under a package version (by detail + set-product ids). */
private void deleteSalesRelsByPsvId(Long psvId)
{
if (psvId == null)
{
return;
}
PrcPriceSetDetail dq = new PrcPriceSetDetail();
dq.setPsvId(psvId);
List<PrcPriceSetDetail> details = detailMapper.selectPrcPriceSetDetailList(dq);
if (details != null && !details.isEmpty())
{
List<Long> psdIds = new ArrayList<Long>();
for (PrcPriceSetDetail d : details)
{
if (d != null && d.getPsdId() != null)
{
psdIds.add(d.getPsdId());
}
}
d.setSurcharges(rowSc);
if (!psdIds.isEmpty())
{
surchargeRelMapper.deleteByDetailIdsAndPriceType(psdIds, PrcSurchargeRel.PRICE_TYPE_SALES);
}
}
version.setSurcharges(saved);
PrcPriceSetProduct pq = new PrcPriceSetProduct();
pq.setPsvId(psvId);
List<PrcPriceSetProduct> products = setProductMapper.selectPrcPriceSetProductList(pq);
if (products != null && !products.isEmpty())
{
List<Long> pspIds = new ArrayList<Long>();
for (PrcPriceSetProduct p : products)
{
if (p != null && p.getPspId() != null)
{
pspIds.add(p.getPspId());
}
}
if (!pspIds.isEmpty())
{
surchargeRelMapper.deleteByProductIdsAndPriceType(pspIds, PrcSurchargeRel.PRICE_TYPE_SALES);
}
}
}
/**
* Copy SALES surcharge rels from source version details onto newly copied details
* (match by product_code + country + zone + weight + product_country_id).
*/
private void copySalesSurchargeRelsFromVersion(Long srcPsvId, Long newPsvId, String createBy)
{
if (srcPsvId == null || newPsvId == null)
{
return;
}
PrcPriceSetDetail sq = new PrcPriceSetDetail();
sq.setPsvId(srcPsvId);
List<PrcPriceSetDetail> srcDetails = detailMapper.selectPrcPriceSetDetailList(sq);
PrcPriceSetDetail nq = new PrcPriceSetDetail();
nq.setPsvId(newPsvId);
List<PrcPriceSetDetail> newDetails = detailMapper.selectPrcPriceSetDetailList(nq);
if (srcDetails == null || srcDetails.isEmpty() || newDetails == null || newDetails.isEmpty())
{
return;
}
List<Long> srcPsdIds = new ArrayList<Long>();
for (PrcPriceSetDetail d : srcDetails)
{
if (d != null && d.getPsdId() != null)
{
srcPsdIds.add(d.getPsdId());
}
}
List<PrcSurchargeRel> srcRels =
surchargeRelMapper.selectByDetailIdsAndPriceType(srcPsdIds, PrcSurchargeRel.PRICE_TYPE_SALES);
if (srcRels == null || srcRels.isEmpty())
{
return;
}
Map<Long, List<PrcSurchargeRel>> bySrcPsd = new HashMap<Long, List<PrcSurchargeRel>>();
for (PrcSurchargeRel rel : srcRels)
{
if (rel == null || rel.getDetailId() == null)
{
continue;
}
List<PrcSurchargeRel> list = bySrcPsd.get(rel.getDetailId());
if (list == null)
{
list = new ArrayList<PrcSurchargeRel>();
bySrcPsd.put(rel.getDetailId(), list);
}
list.add(rel);
}
Map<String, PrcPriceSetDetail> newByKey = new HashMap<String, PrcPriceSetDetail>();
for (PrcPriceSetDetail nd : newDetails)
{
if (nd != null)
{
newByKey.put(detailMatchKey(nd), nd);
}
}
for (PrcPriceSetDetail od : srcDetails)
{
if (od == null || od.getPsdId() == null)
{
continue;
}
PrcPriceSetDetail nd = newByKey.get(detailMatchKey(od));
if (nd == null || nd.getPsdId() == null || nd.getPspId() == null)
{
continue;
}
List<PrcSurchargeRel> rels = bySrcPsd.get(od.getPsdId());
if (rels == null || rels.isEmpty())
{
continue;
}
for (PrcSurchargeRel src : rels)
{
PrcSurchargeRel row = new PrcSurchargeRel();
row.setScId(src.getScId());
row.setProductId(nd.getPspId());
row.setProductCode(nd.getProductCode());
row.setDetailId(nd.getPsdId());
row.setPriceType(PrcSurchargeRel.PRICE_TYPE_SALES);
row.setCountryCode(normalizeCountry(src.getCountryCode()));
row.setCreateBy(createBy);
surchargeRelMapper.insertPrcSurchargeRel(row);
}
}
}
private String detailMatchKey(PrcPriceSetDetail d)
{
if (d == null)
{
return "";
}
return StringUtils.nvl(d.getProductCode(), "")
+ "|" + StringUtils.nvl(d.getCountryCode(), "")
+ "|" + StringUtils.nvl(d.getZoneCode(), "")
+ "|" + StringUtils.nvl(d.getWeightLabel(), "")
+ "|" + (d.getProductCountryId() == null ? "0" : d.getProductCountryId().toString())
+ "|" + (d.getDiscountValue() == null ? "-1" : d.getDiscountValue().toPlainString());
}
/** True when package already has a true draft (status=0). Pending(3) does not block copy. */
@@ -11,7 +11,7 @@ import com.bms.price.mapper.PrcSurchargeRelMapper;
import com.bms.price.service.IPrcSurchargeRelService;
/**
* 附加费与产品/明细关联 服务层实现
* 附加费与产品/明细/套餐关联 服务层实现
*
* @author bms
*/
@@ -26,7 +26,27 @@ public class PrcSurchargeRelServiceImpl implements IPrcSurchargeRelService
{
PrcSurchargeRel query = new PrcSurchargeRel();
query.setProductCode(productCode);
return relMapper.selectPrcSurchargeRelList(query);
List<PrcSurchargeRel> list = relMapper.selectPrcSurchargeRelList(query);
if (list == null || list.isEmpty())
{
return list;
}
// Product drawer: PRODUCT + REDLINE only (SALES binds psp_id/psd_id)
List<PrcSurchargeRel> filtered = new java.util.ArrayList<PrcSurchargeRel>();
for (PrcSurchargeRel rel : list)
{
if (rel == null)
{
continue;
}
String type = rel.getPriceType();
if (StringUtils.isEmpty(type) || PrcSurchargeRel.PRICE_TYPE_PRODUCT.equals(type)
|| PrcSurchargeRel.PRICE_TYPE_REDLINE.equals(type))
{
filtered.add(rel);
}
}
return filtered;
}
@Override
@@ -39,6 +59,18 @@ public class PrcSurchargeRelServiceImpl implements IPrcSurchargeRelService
public int insertPrcSurchargeRel(PrcSurchargeRel rel)
{
validate(rel);
if (StringUtils.isEmpty(rel.getPriceType()))
{
rel.setPriceType(PrcSurchargeRel.PRICE_TYPE_PRODUCT);
}
if (StringUtils.isNotEmpty(rel.getCountryCode()))
{
rel.setCountryCode(rel.getCountryCode().trim().toUpperCase());
}
else
{
rel.setCountryCode(null);
}
return relMapper.insertPrcSurchargeRel(rel);
}
@@ -76,5 +108,18 @@ public class PrcSurchargeRelServiceImpl implements IPrcSurchargeRelService
{
throw new ServiceException(MessageUtils.message("price.surchargeRel.productCode.empty"));
}
String type = StringUtils.isEmpty(rel.getPriceType())
? PrcSurchargeRel.PRICE_TYPE_PRODUCT : rel.getPriceType().trim().toUpperCase();
if (!PrcSurchargeRel.PRICE_TYPE_PRODUCT.equals(type)
&& !PrcSurchargeRel.PRICE_TYPE_SALES.equals(type)
&& !PrcSurchargeRel.PRICE_TYPE_REDLINE.equals(type))
{
throw new ServiceException(MessageUtils.message("price.surchargeRel.priceType.invalid"));
}
rel.setPriceType(type);
if (PrcSurchargeRel.PRICE_TYPE_SALES.equals(type) && rel.getDetailId() == null)
{
throw new ServiceException(MessageUtils.message("price.surchargeRel.detailId.empty"));
}
}
}
@@ -30,7 +30,6 @@ 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;
import com.bms.price.domain.PrcQuoteFee;
import com.bms.price.domain.PrcSurchargeRel;
@@ -48,7 +47,6 @@ 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;
import com.bms.price.mapper.PrcQuoteMapper;
import com.bms.price.mapper.PrcSurchargeRelMapper;
@@ -87,9 +85,6 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
@Autowired
private PrcPriceSetVersionMapper versionMapper;
@Autowired
private PrcPriceSetVersionSurchargeMapper versionSurchargeMapper;
@Autowired
private PrcPriceSetDetailMapper detailMapper;
@@ -973,7 +968,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
RoundingMode rm = resolveRoundingMode(result.getRoundMode());
String currency = normalizeCurrency(result.getCurrency());
List<PrcSurchargeRel> rels = loadMatchedSurcharges(result);
List<PrcSurchargeRel> salesRels = loadMatchedSurcharges(result, PrcSurchargeRel.PRICE_TYPE_SALES);
List<PrcSurchargeRel> productRels = loadMatchedSurcharges(result, PrcSurchargeRel.PRICE_TYPE_PRODUCT);
List<PrcSurchargeRel> redlineRels = loadMatchedSurcharges(result, PrcSurchargeRel.PRICE_TYPE_REDLINE);
List<TmsPriceQuoteFeeLine> lines = new ArrayList<TmsPriceQuoteFeeLine>();
BigDecimal productFreight = productAmountOf(result.getPayFeeDetails(), FEE_BASIC);
BigDecimal productReg = productAmountOf(result.getPayFeeDetails(), FEE_REG);
@@ -986,7 +984,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
BigDecimal surchargeTotal = BigDecimal.ZERO;
Map<Long, TmsPriceQuoteFeeLine> surchargeLines = new LinkedHashMap<Long, TmsPriceQuoteFeeLine>();
for (PrcSurchargeRel rel : rels)
for (PrcSurchargeRel rel : salesRels)
{
BigDecimal itemAmount = calcSurchargeAmount(rel, result.getWeight(), freightAmount);
if (itemAmount == null)
@@ -1017,41 +1015,90 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
result.setPayableTotal(freightAmount.add(registrationFee).add(surchargeTotal).setScale(2, rm));
result.setIncomeTotal(result.getPayableTotal());
result.setFeeDetails(lines);
BigDecimal productExtra = sumSurchargeAmounts(productRels, result.getWeight(), freightAmount, rm);
BigDecimal redlineExtra = sumSurchargeAmounts(redlineRels, result.getWeight(), freightAmount, rm);
if (result.getProductAmount() != null || productExtra.compareTo(BigDecimal.ZERO) > 0)
{
result.setProductAmount(nvl(result.getProductAmount()).add(productExtra).setScale(2, rm));
}
if (result.getCostAmount() != null || productExtra.compareTo(BigDecimal.ZERO) > 0)
{
result.setCostAmount(nvl(result.getCostAmount()).add(productExtra).setScale(2, rm));
}
if (result.getPayTotal() != null || productExtra.compareTo(BigDecimal.ZERO) > 0)
{
result.setPayTotal(nvl(result.getPayTotal()).add(productExtra).setScale(2, rm));
}
if (result.getRedlineAmount() != null || redlineExtra.compareTo(BigDecimal.ZERO) > 0)
{
result.setRedlineAmount(nvl(result.getRedlineAmount()).add(redlineExtra).setScale(2, rm));
}
else if (redlineExtra.compareTo(BigDecimal.ZERO) > 0)
{
result.setRedlineAmount(redlineExtra);
}
}
private List<PrcSurchargeRel> loadMatchedSurcharges(TmsPriceQuoteResult result)
private BigDecimal sumSurchargeAmounts(List<PrcSurchargeRel> rels, BigDecimal weight,
BigDecimal freightAmount, RoundingMode rm)
{
BigDecimal total = BigDecimal.ZERO;
if (rels == null)
{
return total;
}
for (PrcSurchargeRel rel : rels)
{
BigDecimal item = calcSurchargeAmount(rel, weight, freightAmount);
if (item != null)
{
total = total.add(item);
}
}
return total.setScale(2, rm);
}
/**
* Load matched surcharge rels by price_type.
* SALES: package detail_id = psd_id.
* PRODUCT / REDLINE: product catalog associations by product_code + country/detail.
*/
private List<PrcSurchargeRel> loadMatchedSurcharges(TmsPriceQuoteResult result, String priceType)
{
List<PrcSurchargeRel> matched = new ArrayList<PrcSurchargeRel>();
if (result == null || StringUtils.isEmpty(result.getProductCode()))
if (result == null || StringUtils.isEmpty(priceType))
{
return matched;
}
boolean packageHit = SRC_FIXED.equals(result.getMatchSource()) || SRC_DISCOUNT.equals(result.getMatchSource());
// Package sales use version surcharge snapshot only (empty snapshot = no surcharge).
if (packageHit)
if (PrcSurchargeRel.PRICE_TYPE_SALES.equals(priceType))
{
if (result.getPsdId() == null)
{
return matched;
}
List<PrcPriceSetVersionSurcharge> snaps =
versionSurchargeMapper.selectByPsdIds(Collections.singletonList(result.getPsdId()));
if (snaps == null || snaps.isEmpty())
List<PrcSurchargeRel> rels = surchargeRelMapper.selectByDetailIdsAndPriceType(
Collections.singletonList(result.getPsdId()), PrcSurchargeRel.PRICE_TYPE_SALES);
if (rels == null)
{
return matched;
}
for (PrcPriceSetVersionSurcharge snap : snaps)
for (PrcSurchargeRel rel : rels)
{
PrcSurchargeRel rel = toRelFromVersionSnap(snap);
if (surchargeApplies(rel, result))
if (surchargeApplies(rel, result, true))
{
matched.add(rel);
}
}
return matched;
}
if (StringUtils.isEmpty(result.getProductCode()))
{
return matched;
}
PrcSurchargeRel query = new PrcSurchargeRel();
query.setProductCode(result.getProductCode());
query.setPriceType(priceType);
List<PrcSurchargeRel> rels = surchargeRelMapper.selectPrcSurchargeRelList(query);
if (rels == null)
{
@@ -1059,7 +1106,7 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
}
for (PrcSurchargeRel rel : rels)
{
if (surchargeApplies(rel, result))
if (surchargeApplies(rel, result, false))
{
matched.add(rel);
}
@@ -1067,28 +1114,10 @@ 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)
/**
* @param salesRel true = SALES (detail_id already = psd_id); false = PRODUCT/REDLINE detail match
*/
private boolean surchargeApplies(PrcSurchargeRel rel, TmsPriceQuoteResult result, boolean salesRel)
{
if (rel == null)
{
@@ -1103,6 +1132,10 @@ public class TmsPriceQuoteServiceImpl implements ITmsPriceQuoteService
{
return false;
}
if (salesRel)
{
return true;
}
if (rel.getDetailId() == null)
{
return true;