增加一个打单功能套餐

This commit is contained in:
wuweihua
2026-11-09 23:00:48 +08:00
parent 2632e5aa78
commit 16afa0d590
30 changed files with 3418 additions and 132 deletions
@@ -20,9 +20,6 @@ public class ChannelProductMap
private Long productId;
/** 应收套餐 prc_price_set.price_set_id */
private Long priceSetId;
/** AR 应收,AP 应付 */
private String payType;
@@ -0,0 +1,39 @@
package com.bms.price.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
/**
* 打单套餐价卡版本(按 pay_product_term.version_no 聚合,不走价格套餐 prc_price_set_version)。
*/
@Data
public class LabelPackageVersion
{
private Long priceSetId;
private Integer versionNo;
/** 展示用,如 V1 */
private String versionLabel;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date effectiveTime;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date expireTime;
/** 与 pay_product_term.status 一致:0待生效 1生效 2已失效 */
private String status;
private Integer productCount;
/** status=1 生效条数 */
private Integer activeCount;
/** status=0 待生效条数 */
private Integer pendingCount;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createTime;
}
@@ -12,6 +12,9 @@ public class PayExtraLine
private Long productId;
/** 价卡周期,对应 pay_product_term.term_id;与 productId 组成明细维度 */
private Long termId;
/** 邮编 或 配置 */
private String lineType;
@@ -22,6 +22,9 @@ public class PayProductTerm extends BaseEntity
private Long productId;
/** 打单套餐ID;应付公共模板为空 */
private Long priceSetId;
private Integer versionNo;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@@ -30,7 +33,7 @@ public class PayProductTerm extends BaseEntity
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date effectiveEnd;
/** 0 生效,1 失效 */
/** 0待生效 1生效 2已失效 */
private String status;
private String productName;
@@ -13,5 +13,17 @@ public class PaySaveCommand
/** 为 true 时,同文件同表同名的旧产品先删后插 */
private boolean overwrite;
/** 打单套餐ID;有值时走 LABEL 导入(稳定 product_id + term.price_set_id) */
private Long priceSetId;
/** 打单套餐名称;priceSetId 为空时新建套餐壳 */
private String priceSetName;
/** 打单套餐备注 */
private String priceSetNote;
/** 编辑待生效版本时传入,覆盖该 version_no,不新建版本号 */
private Integer versionNo;
private List<PayProduct> products = new ArrayList<>();
}
@@ -15,7 +15,10 @@ public class PaySurcharge
private Long productId;
/** 所属价卡版本,跟产品版本一起变 */
/** 价卡周期,对应 pay_product_term.term_id;与 productId 组成明细维度 */
private Long termId;
/** 所属价卡版本,跟产品版本一起变(展示兼容) */
private Integer versionNo;
private String sectionName;
@@ -13,7 +13,10 @@ public class PayWeightBand
private Long productId;
/** 所属价卡版本,跟产品版本一起变 */
/** 价卡周期,对应 pay_product_term.term_id;与 productId 组成明细维度 */
private Long termId;
/** 所属价卡版本,跟产品版本一起变(展示兼容) */
private Integer versionNo;
/** 报价区块,例如派送费、偏远附加费 */
@@ -19,6 +19,8 @@ public class PrcPriceSet extends BaseEntity
public static final String CATEGORY_PACKAGE = "PACKAGE";
/** Boss exclusive quote. */
public static final String CATEGORY_EXCLUSIVE = "EXCLUSIVE";
/** Label / 打单套餐(应收价卡套餐). */
public static final String CATEGORY_LABEL = "LABEL";
private Long priceSetId;
private String priceSetCode;
@@ -26,7 +28,7 @@ public class PrcPriceSet extends BaseEntity
private String priceSetNote;
/** FIXED / DISCOUNT */
private String setType;
/** PACKAGE=ordinary set / EXCLUSIVE=boss exclusive quote */
/** PACKAGE=ordinary set / EXCLUSIVE=boss exclusive quote / LABEL=打单套餐 */
private String setCategory;
private BigDecimal defaultDiscount;
private String defaultType;
@@ -38,16 +38,33 @@ public interface PayProductMapper
List<PayWeightBand> selectWeightBands(@Param("productIds") List<Long> productIds);
List<PayWeightBand> selectWeightBandsByTerm(@Param("termIds") List<Long> termIds);
List<PaySurcharge> selectSurcharges(@Param("productIds") List<Long> productIds);
List<PaySurcharge> selectSurchargesByTerm(@Param("termIds") List<Long> termIds);
List<PayExtraLine> selectExtraLines(@Param("productIds") List<Long> productIds);
List<PayExtraLine> selectExtraLinesByTerm(@Param("termIds") List<Long> termIds);
int deleteWeightBands(@Param("productIds") List<Long> productIds);
int deleteSurcharges(@Param("productIds") List<Long> productIds);
int deleteExtraLines(@Param("productIds") List<Long> productIds);
int deleteWeightBandsByTerm(@Param("termIds") List<Long> termIds);
int deleteSurchargesByTerm(@Param("termIds") List<Long> termIds);
int deleteExtraLinesByTerm(@Param("termIds") List<Long> termIds);
Long selectTemplateIdByType(@Param("dictValue") String dictValue, @Param("productType") String productType);
/** 打单专用:命中仅属于打单套餐的 AR 产品,不命中应付公共模板 */
Long selectLabelProductIdByDict(@Param("dictValue") String dictValue);
int deletePayProducts(@Param("productIds") List<Long> productIds);
List<Long> selectFamilyIds(@Param("rootId") Long rootId);
@@ -3,6 +3,7 @@ package com.bms.price.mapper;
import java.util.Date;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.bms.price.domain.LabelPackageVersion;
import com.bms.price.domain.PayProductTerm;
/**
@@ -16,6 +17,24 @@ public interface PayProductTermMapper
PayProductTerm selectByProductId(Long productId);
List<PayProductTerm> selectByPriceSetId(@Param("priceSetId") Long priceSetId);
List<PayProductTerm> selectByPriceSetIdAndVersion(@Param("priceSetId") Long priceSetId,
@Param("versionNo") Integer versionNo);
/** 打单套餐:按 version_no 聚合版本(含已失效) */
List<LabelPackageVersion> selectLabelVersionGroups(@Param("priceSetId") Long priceSetId);
/** 打单套餐:批量改某一版的生效起止与状态 */
int updateLabelVersionPeriod(@Param("priceSetId") Long priceSetId, @Param("versionNo") Integer versionNo,
@Param("effectiveStart") Date effectiveStart, @Param("effectiveEnd") Date effectiveEnd,
@Param("status") String status, @Param("updateBy") String updateBy);
Integer selectMaxVersionByProduct(@Param("productId") Long productId, @Param("priceSetId") Long priceSetId);
/** 打单套餐下最大 version_no */
Integer selectMaxVersionByPriceSet(@Param("priceSetId") Long priceSetId);
int insertTerm(PayProductTerm term);
int updatePeriod(PayProductTerm term);
@@ -25,7 +44,29 @@ public interface PayProductTermMapper
/** 新版本生效时,把同一产品仍生效的旧版本关掉,结束日收到新版本开始的前一天 */
int closeOpenTerms(@Param("dictValue") String dictValue, @Param("endDate") Date endDate, @Param("updateBy") String updateBy);
/** 打单套餐:按套餐+产品关闭仍生效的旧周期 */
int closeOpenTermsBySetProduct(@Param("priceSetId") Long priceSetId, @Param("productId") Long productId,
@Param("endDate") Date endDate, @Param("updateBy") String updateBy);
/** 打单:只关掉某产品本套餐待生效 term */
int closePendingBySetProduct(@Param("priceSetId") Long priceSetId, @Param("productId") Long productId,
@Param("endDate") Date endDate, @Param("updateBy") String updateBy);
/** 打单:启动某版本时,关掉本套餐其它版本的生效 term */
int closeEffectExceptVersion(@Param("priceSetId") Long priceSetId, @Param("versionNo") Integer versionNo,
@Param("endDate") Date endDate, @Param("updateBy") String updateBy);
/** 打单:预约未来生效时,只写旧版失效日,仍保持生效;并恢复被提前关掉但未到期的旧版 */
int scheduleEndExceptVersion(@Param("priceSetId") Long priceSetId, @Param("versionNo") Integer versionNo,
@Param("endDate") Date endDate, @Param("today") Date today, @Param("updateBy") String updateBy);
/** 打单:恢复「失效日未到却被标成已失效」的 term */
int restorePrematureClosed(@Param("priceSetId") Long priceSetId, @Param("today") Date today,
@Param("updateBy") String updateBy);
int deleteByProductIds(@Param("productIds") List<Long> productIds);
int deleteByTermIds(@Param("termIds") List<Long> termIds);
int updateProductEffectiveDate(@Param("productId") Long productId, @Param("effectiveDate") Date effectiveDate);
}
@@ -3,6 +3,7 @@ package com.bms.price.service;
import java.io.OutputStream;
import java.util.List;
import java.util.Date;
import com.bms.price.domain.LabelPackageVersion;
import com.bms.price.domain.PayParseProgress;
import com.bms.price.domain.PayPreview;
import com.bms.price.domain.PayPriceCompare;
@@ -54,7 +55,34 @@ public interface IPayProductService
PayProduct selectChannelPrice(String channelId, String payType);
/** 只绑定产品编号,不产生新版本;同时落 channel_code。payType 为空时按应付 AP */
void bindChannel(String channelId, Long productId, Long priceSetId, String payType);
void bindChannel(String channelId, Long productId, String payType);
/**
* 打单套餐预览:解析复用应付解析器,但「已存在」只按本套餐 price_set_id 判断,
* 不与应付公共模板互相挡导入。
*/
PayPreview previewLabel(byte[] bytes, String filename, String dictValue, String progressId, Long priceSetId);
/** 打单套餐导入:稳定 product_id,明细挂 term_id,term 写 price_set_id;跳过仅限本套餐已有生效版 */
String saveLabel(PaySaveCommand command);
/** 打单套餐价卡版本列表(按 term.version_no 聚合) */
List<LabelPackageVersion> selectLabelVersions(Long priceSetId);
/** 启动/修改打单套餐某一版生效时间,并视情况启用套餐 */
String activateLabelVersion(Long priceSetId, Integer versionNo, Date effectiveTime);
/** 打单套餐内当前生效/待生效产品列表 */
List<PayProduct> selectLabelProducts(Long priceSetId);
/** 打单套餐某一版本的产品列表 */
List<PayProduct> selectLabelProductsByVersion(Long priceSetId, Integer versionNo);
/** 按 term 加载明细 */
PayProduct selectPayProductDetailByTerm(Long termId);
/** 从打单套餐移除产品(失效/清理对应 term 明细) */
int removeLabelProducts(Long priceSetId, Long[] productIds);
/** 取消渠道上指定收付类型的产品。payType 为空时按应付 AP */
void unbindChannel(String channelId, String payType);
@@ -25,6 +25,7 @@ import com.bms.common.utils.SecurityUtils;
import com.bms.common.utils.file.OssStorage;
import com.bms.common.utils.uuid.IdUtils;
import com.bms.price.domain.ChannelProductMap;
import com.bms.price.domain.LabelPackageVersion;
import com.bms.price.domain.PayExtraLine;
import com.bms.price.domain.PayParseProgress;
import com.bms.price.domain.PayPreview;
@@ -35,11 +36,13 @@ import com.bms.price.domain.PayProductTerm;
import com.bms.price.domain.PaySaveCommand;
import com.bms.price.domain.PaySurcharge;
import com.bms.price.domain.PayWeightBand;
import com.bms.price.domain.PrcPriceSet;
import com.bms.masterdata.domain.ServiceChannel;
import com.bms.masterdata.mapper.ServiceChannelMapper;
import com.bms.price.mapper.ChannelProductMapMapper;
import com.bms.price.mapper.PayProductMapper;
import com.bms.price.mapper.PayProductTermMapper;
import com.bms.price.mapper.PrcPriceSetMapper;
import com.bms.price.pay.PayParseProgressListener;
import com.bms.price.pay.PayWorkbookParser;
import com.bms.price.service.IPayProductService;
@@ -59,6 +62,13 @@ public class PayProductServiceImpl implements IPayProductService
private static final String PAY_TYPE_AR = "AR";
/** pay_product_term.status:0待生效 1生效 2已失效 */
private static final String TERM_PENDING = "0";
private static final String TERM_EFFECT = "1";
private static final String TERM_EXPIRED = "2";
@Autowired
private PayProductMapper payProductMapper;
@@ -71,6 +81,9 @@ public class PayProductServiceImpl implements IPayProductService
@Autowired
private PayProductTermMapper payProductTermMapper;
@Autowired
private PrcPriceSetMapper priceSetMapper;
@Autowired
private ISysDictTypeService dictTypeService;
@@ -97,6 +110,21 @@ public class PayProductServiceImpl implements IPayProductService
@Override
public PayPreview preview(byte[] bytes, String filename, String dictValue, String progressId)
{
return doPreview(bytes, filename, dictValue, progressId, false, null);
}
@Override
public PayPreview previewLabel(byte[] bytes, String filename, String dictValue, String progressId, Long priceSetId)
{
return doPreview(bytes, filename, dictValue, progressId, true, priceSetId);
}
/**
* @param labelMode true=打单套餐:已存在仅看本套餐;false=应付:看公共模板
*/
private PayPreview doPreview(byte[] bytes, String filename, String dictValue, String progressId,
boolean labelMode, Long priceSetId)
{
String progressKey = progressKey(progressId);
PayParseProgressListener listener = progressKey == null ? null : (percent, message) ->
@@ -115,9 +143,24 @@ public class PayProductServiceImpl implements IPayProductService
for (PayProduct product : preview.getProducts())
{
product.setFilePath(stored);
Long existsId = isBlank(product.getDictValue()) ? null : payProductMapper.selectTemplateId(product.getDictValue());
product.setDuplicated(existsId != null);
product.setExistsId(existsId);
if (labelMode)
{
product.setProductType(PAY_TYPE_AR);
// 只认「打单侧」产品,绝不把应付公共模板标成已存在
Long existsId = isBlank(product.getDictValue()) ? null
: payProductMapper.selectLabelProductIdByDict(product.getDictValue());
product.setExistsId(existsId);
// 仅待生效占用标「已存在」;已生效可继续建新版本
boolean pendingInSet = priceSetId != null && existsId != null
&& hasLabelTermStatus(priceSetId, existsId, TERM_PENDING);
product.setDuplicated(pendingInSet);
}
else
{
Long existsId = isBlank(product.getDictValue()) ? null : payProductMapper.selectTemplateId(product.getDictValue());
product.setDuplicated(existsId != null);
product.setExistsId(existsId);
}
}
writeProgress(progressKey, 100, "解析完成", PayParseProgress.SUCCESS);
return preview;
@@ -220,7 +263,6 @@ public class PayProductServiceImpl implements IPayProductService
product.setFilePath(resolveSource(product));
prepareCounts(product);
fitColumns(product);
product.setProductId(null);
product.setChannelId(null);
product.setCreateBy(operator);
product.setCreateTime(new Date());
@@ -231,13 +273,14 @@ public class PayProductServiceImpl implements IPayProductService
}
else
{
product.setProductId(null);
product.setOriginId(null);
product.setVersionNo(1);
payProductMapper.insertPayProduct(product);
openTerm(product, false, null);
insertBands(product);
insertSurcharges(product);
insertExtras(product);
openTerm(product, false);
}
saved++;
}
@@ -426,11 +469,29 @@ public class PayProductServiceImpl implements IPayProductService
private void replaceChildren(Long productId, PayProduct product)
{
List<Long> ids = Collections.singletonList(productId);
payProductMapper.deleteWeightBands(ids);
payProductMapper.deleteSurcharges(ids);
payProductMapper.deleteExtraLines(ids);
product.setProductId(productId);
if (product.getTermId() == null)
{
PayProductTerm term = payProductTermMapper.selectByProductId(productId);
if (term != null)
{
product.setTermId(term.getTermId());
}
}
if (product.getTermId() != null)
{
List<Long> termIds = Collections.singletonList(product.getTermId());
payProductMapper.deleteWeightBandsByTerm(termIds);
payProductMapper.deleteSurchargesByTerm(termIds);
payProductMapper.deleteExtraLinesByTerm(termIds);
}
else
{
List<Long> ids = Collections.singletonList(productId);
payProductMapper.deleteWeightBands(ids);
payProductMapper.deleteSurcharges(ids);
payProductMapper.deleteExtraLines(ids);
}
insertBands(product);
insertSurcharges(product);
insertExtras(product);
@@ -511,7 +572,7 @@ public class PayProductServiceImpl implements IPayProductService
{
term.setDictValue(termKey(product));
term.setVersionNo(product.getVersionNo() == null ? 1 : product.getVersionNo());
term.setStatus("0");
term.setStatus(TERM_EFFECT);
term.setCreateBy(operator);
payProductTermMapper.insertTerm(term);
}
@@ -535,9 +596,10 @@ public class PayProductServiceImpl implements IPayProductService
@Override
public int changeTermStatus(PayProductTerm term)
{
if (term == null || (!"0".equals(term.getStatus()) && !"1".equals(term.getStatus())))
if (term == null || (!TERM_PENDING.equals(term.getStatus()) && !TERM_EFFECT.equals(term.getStatus())
&& !TERM_EXPIRED.equals(term.getStatus())))
{
throw new ServiceException("请选择生效或失效");
throw new ServiceException("状态只能是待生效、生效或已失效");
}
if (term.getTermId() == null && term.getProductId() != null)
{
@@ -556,27 +618,48 @@ public class PayProductServiceImpl implements IPayProductService
}
/** 给刚保存的这一版记一条生效记录。新版本从生效日开始,旧版本收到生效日前一天。 */
private void openTerm(PayProduct product, boolean replacePrevious)
private PayProductTerm openTerm(PayProduct product, boolean replacePrevious, Long priceSetId)
{
String key = termKey(product);
if (product.getEffectiveDate() == null)
boolean labelMode = priceSetId != null;
// 应付:无生效日则默认今天;打单:创建不写默认生效日,由详情「启动」设置
if (!labelMode && product.getEffectiveDate() == null)
{
product.setEffectiveDate(today());
}
if (replacePrevious)
{
java.time.LocalDate start = toLocalDate(product.getEffectiveDate());
payProductTermMapper.closeOpenTerms(key, toDate(start.minusDays(1)), product.getCreateBy());
Date endDate;
if (product.getEffectiveDate() != null)
{
endDate = toDate(toLocalDate(product.getEffectiveDate()).minusDays(1));
}
else
{
endDate = toDate(toLocalDate(today()).minusDays(1));
}
if (labelMode)
{
payProductTermMapper.closeOpenTermsBySetProduct(priceSetId, product.getProductId(), endDate, product.getCreateBy());
}
else
{
payProductTermMapper.closeOpenTerms(key, endDate, product.getCreateBy());
}
}
PayProductTerm term = new PayProductTerm();
term.setDictValue(key);
term.setProductId(product.getProductId());
term.setPriceSetId(priceSetId);
term.setVersionNo(product.getVersionNo() == null ? 1 : product.getVersionNo());
term.setEffectiveStart(product.getEffectiveDate());
term.setEffectiveStart(labelMode ? null : product.getEffectiveDate());
term.setFilePath(sourcePath(product.getFilePath()));
term.setStatus("0");
// 打单套餐新建为待生效,详情启动后再变生效;应付公共模板直接生效
term.setStatus(labelMode ? TERM_PENDING : TERM_EFFECT);
term.setCreateBy(product.getCreateBy());
payProductTermMapper.insertTerm(term);
product.setTermId(term.getTermId());
return term;
}
private String termKey(PayProduct product)
@@ -730,6 +813,20 @@ public class PayProductServiceImpl implements IPayProductService
{
return null;
}
PayProductTerm term = payProductTermMapper.selectByProductId(productId);
if (term != null)
{
product.setTermId(term.getTermId());
product.setTermStatus(term.getStatus());
product.setTermStart(term.getEffectiveStart());
product.setTermEnd(term.getEffectiveEnd());
product.setVersionNo(term.getVersionNo() == null ? product.getVersionNo() : term.getVersionNo());
List<Long> termIds = Collections.singletonList(term.getTermId());
product.setWeightBands(payProductMapper.selectWeightBandsByTerm(termIds));
product.setSurcharges(payProductMapper.selectSurchargesByTerm(termIds));
product.setExtraLines(payProductMapper.selectExtraLinesByTerm(termIds));
return product;
}
List<Long> ids = Collections.singletonList(productId);
product.setWeightBands(payProductMapper.selectWeightBands(ids));
product.setSurcharges(payProductMapper.selectSurcharges(ids));
@@ -737,6 +834,450 @@ public class PayProductServiceImpl implements IPayProductService
return product;
}
@Override
public PayProduct selectPayProductDetailByTerm(Long termId)
{
if (termId == null)
{
return null;
}
PayProductTerm term = payProductTermMapper.selectById(termId);
if (term == null || term.getProductId() == null)
{
return null;
}
PayProduct product = payProductMapper.selectPayProductById(term.getProductId());
if (product == null)
{
return null;
}
product.setTermId(term.getTermId());
product.setTermStatus(term.getStatus());
product.setTermStart(term.getEffectiveStart());
product.setTermEnd(term.getEffectiveEnd());
product.setVersionNo(term.getVersionNo() == null ? product.getVersionNo() : term.getVersionNo());
if (!isBlank(term.getFilePath()))
{
product.setFilePath(term.getFilePath());
}
List<Long> termIds = Collections.singletonList(termId);
product.setWeightBands(payProductMapper.selectWeightBandsByTerm(termIds));
product.setSurcharges(payProductMapper.selectSurchargesByTerm(termIds));
product.setExtraLines(payProductMapper.selectExtraLinesByTerm(termIds));
return product;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String saveLabel(PaySaveCommand command)
{
if (command == null || command.getProducts() == null || command.getProducts().isEmpty())
{
throw new ServiceException("请先勾选要保存的产品");
}
Long priceSetId = ensureLabelPriceSet(command);
String operator = SecurityUtils.getUsername();
Date keepStart = null;
String keepStatus = TERM_PENDING;
int nextVersionNo;
boolean editPendingVersion = command.getVersionNo() != null;
if (editPendingVersion)
{
// 编辑待生效版:清掉该 version 后按原号重写,保留原生效日
LabelVersionKeep keep = clearPendingLabelVersion(priceSetId, command.getVersionNo(), operator);
keepStart = keep.effectiveStart;
keepStatus = keep.status == null ? TERM_PENDING : keep.status;
nextVersionNo = command.getVersionNo();
}
else
{
// 整批统一套餐级 version_no:新建套餐从 1,新建版本 = max+1
Integer maxSetVer = payProductTermMapper.selectMaxVersionByPriceSet(priceSetId);
nextVersionNo = (maxSetVer == null ? 0 : maxSetVer) + 1;
}
int saved = 0;
int replaced = 0;
int skipped = 0;
for (PayProduct product : command.getProducts())
{
if (product == null || isBlank(product.getProductName()) || isBlank(product.getSheetName()) || isBlank(product.getFileMd5()))
{
throw new ServiceException("产品名称、工作表和文件摘要不能为空");
}
product.setProductType(PAY_TYPE_AR);
String dict = blankTo(product.getDictValue(), product.getProductName());
product.setDictValue(dict);
// 独立产品族:只复用打单侧 AR,绝不复用/改写应付公共模板
Long existsId = isBlank(dict) ? null : payProductMapper.selectLabelProductIdByDict(dict);
// 编辑待生效版时强制覆盖;新建版本仅「待生效」占用时跳过
boolean hasPending = !editPendingVersion && existsId != null
&& hasLabelTermStatus(priceSetId, existsId, TERM_PENDING);
if (hasPending && !command.isOverwrite())
{
skipped++;
continue;
}
product.setFilePath(resolveSource(product));
prepareCounts(product);
fitColumns(product);
product.setChannelId(null);
product.setCreateBy(operator);
product.setUpdateBy(operator);
product.setCreateTime(new Date());
product.setVersionNo(nextVersionNo);
// 打单不写默认生效日,由详情设置;编辑时下面统一回写 keepStart
product.setEffectiveDate(null);
if (existsId != null)
{
appendLabelVersion(existsId, product, operator, priceSetId, nextVersionNo,
editPendingVersion || command.isOverwrite());
replaced++;
}
else
{
// 新建打单专用产品行,与应付同名字典产品并存互不影响
product.setProductId(null);
product.setOriginId(null);
payProductMapper.insertPayProduct(product);
openTerm(product, false, priceSetId);
insertBands(product);
insertSurcharges(product);
insertExtras(product);
}
saved++;
}
if (editPendingVersion && keepStart != null)
{
payProductTermMapper.updateLabelVersionPeriod(priceSetId, nextVersionNo, keepStart, null, keepStatus, operator);
}
return editPendingVersion
? ("套餐" + priceSetId + "版本V" + nextVersionNo + "已更新" + saved + "个产品")
: ("套餐" + priceSetId + "已保存" + saved + "个产品,新版本" + replaced + "个,跳过" + skipped + "个");
}
private static final class LabelVersionKeep
{
private Date effectiveStart;
private String status;
}
/** 仅允许清待生效版;返回原生效日便于编辑后回写 */
private LabelVersionKeep clearPendingLabelVersion(Long priceSetId, Integer versionNo, String operator)
{
if (priceSetId == null || versionNo == null)
{
throw new ServiceException("请指定要编辑的版本");
}
List<PayProductTerm> terms = payProductTermMapper.selectByPriceSetIdAndVersion(priceSetId, versionNo);
if (terms == null || terms.isEmpty())
{
throw new ServiceException("该版本不存在或没有产品");
}
LabelVersionKeep keep = new LabelVersionKeep();
List<Long> termIds = new ArrayList<>();
for (PayProductTerm term : terms)
{
if (!TERM_PENDING.equals(term.getStatus()))
{
// 兼容:生效日未到但 status 误为 1,仍允许编辑
boolean futureStart = term.getEffectiveStart() != null
&& toLocalDate(term.getEffectiveStart()).isAfter(toLocalDate(today()));
if (!futureStart)
{
throw new ServiceException("只能编辑待生效版本");
}
}
if (keep.effectiveStart == null && term.getEffectiveStart() != null)
{
keep.effectiveStart = term.getEffectiveStart();
}
if (keep.status == null)
{
keep.status = TERM_PENDING;
}
termIds.add(term.getTermId());
}
payProductMapper.deleteWeightBandsByTerm(termIds);
payProductMapper.deleteSurchargesByTerm(termIds);
payProductMapper.deleteExtraLinesByTerm(termIds);
payProductTermMapper.deleteByTermIds(termIds);
return keep;
}
@Override
public List<PayProduct> selectLabelProducts(Long priceSetId)
{
return toLabelProducts(payProductTermMapper.selectByPriceSetId(priceSetId));
}
@Override
public List<PayProduct> selectLabelProductsByVersion(Long priceSetId, Integer versionNo)
{
if (priceSetId == null || versionNo == null)
{
return Collections.emptyList();
}
return toLabelProducts(payProductTermMapper.selectByPriceSetIdAndVersion(priceSetId, versionNo));
}
private List<PayProduct> toLabelProducts(List<PayProductTerm> terms)
{
if (terms == null || terms.isEmpty())
{
return Collections.emptyList();
}
List<PayProduct> list = new ArrayList<>();
for (PayProductTerm term : terms)
{
PayProduct product = payProductMapper.selectPayProductById(term.getProductId());
if (product == null)
{
continue;
}
product.setTermId(term.getTermId());
product.setTermStatus(term.getStatus());
product.setTermStart(term.getEffectiveStart());
product.setTermEnd(term.getEffectiveEnd());
product.setVersionNo(term.getVersionNo() == null ? product.getVersionNo() : term.getVersionNo());
product.setSourceFile(blankTo(term.getSourceFile(), product.getSourceFile()));
if (!isBlank(term.getFilePath()))
{
product.setFilePath(term.getFilePath());
}
product.setWeightCount(term.getWeightCount() == null ? product.getWeightCount() : term.getWeightCount());
product.setSurchargeCount(term.getSurchargeCount() == null ? product.getSurchargeCount() : term.getSurchargeCount());
list.add(product);
}
return list;
}
@Override
@Transactional(rollbackFor = Exception.class)
public int removeLabelProducts(Long priceSetId, Long[] productIds)
{
if (priceSetId == null || productIds == null || productIds.length == 0)
{
return 0;
}
int n = 0;
List<PayProductTerm> allTerms = payProductTermMapper.selectByPriceSetId(priceSetId);
if (allTerms == null || allTerms.isEmpty())
{
return 0;
}
Set<Long> removeIds = new LinkedHashSet<>(Arrays.asList(productIds));
List<Long> termIds = new ArrayList<>();
for (PayProductTerm term : allTerms)
{
if (term.getProductId() != null && removeIds.contains(term.getProductId()))
{
termIds.add(term.getTermId());
}
}
if (termIds.isEmpty())
{
return 0;
}
payProductMapper.deleteWeightBandsByTerm(termIds);
payProductMapper.deleteSurchargesByTerm(termIds);
payProductMapper.deleteExtraLinesByTerm(termIds);
payProductTermMapper.deleteByTermIds(termIds);
return termIds.size();
}
private boolean hasActiveLabelTerm(Long priceSetId, Long productId)
{
return hasLabelTermStatus(priceSetId, productId, TERM_PENDING)
|| hasLabelTermStatus(priceSetId, productId, TERM_EFFECT);
}
private boolean hasLabelTermStatus(Long priceSetId, Long productId, String status)
{
List<PayProductTerm> terms = payProductTermMapper.selectByPriceSetId(priceSetId);
if (terms == null || productId == null || status == null)
{
return false;
}
for (PayProductTerm term : terms)
{
if (productId.equals(term.getProductId()) && status.equals(term.getStatus()))
{
return true;
}
}
return false;
}
/**
* 打单追加版本:不关闭已生效 term,只在覆盖时关掉待生效;version_no 用套餐级批次号。
*/
private PayProduct appendLabelVersion(Long rootId, PayProduct incoming, String operator, Long priceSetId,
int versionNo, boolean overwritePending)
{
Long root = resolveRoot(rootId);
PayProduct base = payProductMapper.selectPayProductById(root);
PayProduct copy = copyHeader(base, incoming == null ? base : incoming);
copy.setProductId(root);
copy.setOriginId(null);
copy.setChannelId(null);
copy.setDictValue(blankTo(copy.getDictValue(), base.getDictValue()));
copy.setVersionNo(versionNo);
copy.setEffectiveDate(null);
copy.setCreateBy(operator);
copy.setUpdateBy(operator);
copy.setCreateTime(new Date());
prepareCounts(copy);
fitColumns(copy);
payProductMapper.updatePayProduct(copy);
if (overwritePending)
{
Date endDate = toDate(toLocalDate(today()).minusDays(1));
payProductTermMapper.closePendingBySetProduct(priceSetId, root, endDate, operator);
}
openTerm(copy, false, priceSetId);
insertBands(copy);
insertSurcharges(copy);
insertExtras(copy);
return selectPayProductDetailByTerm(copy.getTermId());
}
private Long ensureLabelPriceSet(PaySaveCommand command)
{
if (command.getPriceSetId() != null)
{
PrcPriceSet db = priceSetMapper.selectPrcPriceSetById(command.getPriceSetId());
if (db == null)
{
throw new ServiceException("打单套餐不存在");
}
if (!PrcPriceSet.CATEGORY_LABEL.equalsIgnoreCase(String.valueOf(db.getSetCategory())))
{
throw new ServiceException("只能向打单套餐导入价卡");
}
return db.getPriceSetId();
}
if (isBlank(command.getPriceSetName()))
{
throw new ServiceException("请填写打单套餐名称");
}
String operator = SecurityUtils.getUsername();
PrcPriceSet set = new PrcPriceSet();
set.setPriceSetName(command.getPriceSetName().trim());
set.setPriceSetNote(command.getPriceSetNote());
set.setSetType("FIXED");
set.setSetCategory(PrcPriceSet.CATEGORY_LABEL);
// 新建为待启用,详情页设启动时间后再生效(对齐价格套餐)
set.setStatus("D");
set.setCreateBy(operator);
set.setUpdateBy(operator);
priceSetMapper.insertPrcPriceSet(set);
set.setPriceSetCode(String.format("LBL%04d", set.getPriceSetId()));
priceSetMapper.updatePrcPriceSet(set);
command.setPriceSetId(set.getPriceSetId());
return set.getPriceSetId();
}
@Override
public List<LabelPackageVersion> selectLabelVersions(Long priceSetId)
{
if (priceSetId == null)
{
return Collections.emptyList();
}
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(priceSetId);
if (set == null)
{
return Collections.emptyList();
}
Date now = today();
// 纠正历史:未来生效版启动时把旧版提前标成已失效
payProductTermMapper.restorePrematureClosed(priceSetId, now, "system");
List<LabelPackageVersion> groups = payProductTermMapper.selectLabelVersionGroups(priceSetId);
if (groups == null || groups.isEmpty())
{
return Collections.emptyList();
}
for (LabelPackageVersion ver : groups)
{
ver.setPriceSetId(priceSetId);
ver.setVersionLabel("V" + (ver.getVersionNo() == null ? 1 : ver.getVersionNo()));
int active = ver.getActiveCount() == null ? 0 : ver.getActiveCount();
int pending = ver.getPendingCount() == null ? 0 : ver.getPendingCount();
Date start = ver.getEffectiveTime();
Date end = ver.getExpireTime();
// 按日期判定:未到生效日=待生效;已过失效日=已失效;中间段=生效
if (start == null)
{
ver.setStatus(pending > 0 ? TERM_PENDING : (active > 0 ? TERM_EFFECT : TERM_EXPIRED));
}
else if (toLocalDate(start).isAfter(toLocalDate(now)))
{
ver.setStatus(TERM_PENDING);
}
else if (end != null && toLocalDate(end).isBefore(toLocalDate(now)))
{
ver.setStatus(TERM_EXPIRED);
}
else
{
ver.setStatus(TERM_EFFECT);
}
}
return groups;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String activateLabelVersion(Long priceSetId, Integer versionNo, Date effectiveTime)
{
if (priceSetId == null || versionNo == null)
{
throw new ServiceException("请选择要启动的版本");
}
if (effectiveTime == null)
{
throw new ServiceException("请选择生效时间");
}
PrcPriceSet set = priceSetMapper.selectPrcPriceSetById(priceSetId);
if (set == null)
{
throw new ServiceException("打单套餐不存在");
}
if (!PrcPriceSet.CATEGORY_LABEL.equalsIgnoreCase(String.valueOf(set.getSetCategory())))
{
throw new ServiceException("只能启动打单套餐版本");
}
String operator = SecurityUtils.getUsername();
boolean future = toLocalDate(effectiveTime).isAfter(toLocalDate(today()));
Date endDate = toDate(toLocalDate(effectiveTime).minusDays(1));
if (future)
{
// 未到生效日:本版保持待生效;旧版只预约失效日,当前仍生效
payProductTermMapper.scheduleEndExceptVersion(priceSetId, versionNo, endDate, today(), operator);
}
else
{
// 当天或已过:本版立刻生效,旧版关掉
payProductTermMapper.closeEffectExceptVersion(priceSetId, versionNo, endDate, operator);
}
String termStatus = future ? TERM_PENDING : TERM_EFFECT;
int n = payProductTermMapper.updateLabelVersionPeriod(priceSetId, versionNo, effectiveTime, null, termStatus, operator);
if (n <= 0)
{
throw new ServiceException("该版本没有可启动的产品价卡");
}
// 真正生效时才把套餐壳置为生效
if (!future && !"E".equalsIgnoreCase(String.valueOf(set.getStatus())))
{
PrcPriceSet upd = new PrcPriceSet();
upd.setPriceSetId(priceSetId);
upd.setStatus("E");
upd.setUpdateBy(operator);
priceSetMapper.updatePrcPriceSetStatus(upd);
}
return future ? "已设为待生效" : "版本已启动";
}
@Override
@Transactional(rollbackFor = Exception.class)
public int deletePayProducts(Long[] productIds)
@@ -826,6 +1367,7 @@ public class PayProductServiceImpl implements IPayProductService
for (PayWeightBand row : rows)
{
row.setProductId(product.getProductId());
row.setTermId(product.getTermId());
row.setVersionNo(product.getVersionNo() == null ? 1 : product.getVersionNo());
}
for (int i = 0; i < rows.size(); i += BATCH)
@@ -844,6 +1386,7 @@ public class PayProductServiceImpl implements IPayProductService
for (PaySurcharge row : rows)
{
row.setProductId(product.getProductId());
row.setTermId(product.getTermId());
row.setVersionNo(product.getVersionNo() == null ? 1 : product.getVersionNo());
}
for (int i = 0; i < rows.size(); i += BATCH)
@@ -868,6 +1411,7 @@ public class PayProductServiceImpl implements IPayProductService
for (PayExtraLine row : rows)
{
row.setProductId(product.getProductId());
row.setTermId(product.getTermId());
}
for (int i = 0; i < rows.size(); i += BATCH)
{
@@ -938,7 +1482,7 @@ public class PayProductServiceImpl implements IPayProductService
}
@Override
public void bindChannel(String channelId, Long productId, Long priceSetId, String payType)
public void bindChannel(String channelId, Long productId, String payType)
{
if (isBlank(channelId) || productId == null)
{
@@ -955,7 +1499,6 @@ public class PayProductServiceImpl implements IPayProductService
row.setChannelId(channelId);
row.setChannelCode(channel.getChannelCode());
row.setProductId(storedId);
row.setPriceSetId(priceSetId);
row.setPayType(type);
String operator = SecurityUtils.getUsername();
row.setCreateBy(operator);
@@ -1015,37 +1558,42 @@ public class PayProductServiceImpl implements IPayProductService
return compared;
}
/** 在同一个产品编号下追加一版价格,不新开产品编号。 */
/** 在同一个产品编号下追加一版价格:复用稳定 product_id,明细挂新 term_id。 */
private PayProduct appendVersion(Long rootId, PayProduct incoming, String operator)
{
return appendVersion(rootId, incoming, operator, null);
}
private PayProduct appendVersion(Long rootId, PayProduct incoming, String operator, Long priceSetId)
{
Long root = resolveRoot(rootId);
PayProduct base = payProductMapper.selectPayProductById(root);
PayProduct copy = copyHeader(base, incoming == null ? base : incoming);
copy.setProductId(null);
copy.setOriginId(root);
copy.setProductId(root);
copy.setOriginId(null);
copy.setChannelId(null);
copy.setDictValue(blankTo(copy.getDictValue(), base.getDictValue()));
Integer maxVersion = payProductMapper.selectMaxVersion(root);
Integer maxVersion = payProductTermMapper.selectMaxVersionByProduct(root, priceSetId);
if (maxVersion == null || maxVersion < 1)
{
maxVersion = payProductMapper.selectMaxVersion(root);
}
copy.setVersionNo((maxVersion == null ? 1 : maxVersion) + 1);
if (copy.getEffectiveDate() == null)
{
copy.setEffectiveDate(today());
}
copy.setCreateBy(operator);
copy.setUpdateBy(operator);
copy.setCreateTime(new Date());
prepareCounts(copy);
fitColumns(copy);
payProductMapper.insertPayProduct(copy);
payProductMapper.updatePayProduct(copy);
openTerm(copy, true, priceSetId);
insertBands(copy);
insertSurcharges(copy);
insertExtras(copy);
openTerm(copy, true);
PayProduct saved = selectPayProductDetail(copy.getProductId());
if (saved != null)
{
saved.setOriginId(root);
}
return saved;
return selectPayProductDetailByTerm(copy.getTermId());
}
/** 渠道产品绑定记在映射表,产品编号是不变的根产品。 */
@@ -77,6 +77,7 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
private static final String TYPE_FIXED = "FIXED";
private static final String CATEGORY_PACKAGE = PrcPriceSet.CATEGORY_PACKAGE;
private static final String CATEGORY_EXCLUSIVE = PrcPriceSet.CATEGORY_EXCLUSIVE;
private static final String CATEGORY_LABEL = PrcPriceSet.CATEGORY_LABEL;
private static final String TYPE_DISCOUNT = "DISCOUNT";
private static final String VER_DRAFT = "0";
private static final String VER_EFFECT = "1";
@@ -2979,16 +2980,23 @@ public class PrcPriceSetServiceImpl implements IPrcPriceSetService
/** PACKAGE (default) or EXCLUSIVE. */
public static String normalizeSetCategory(String setCategory)
{
if (CATEGORY_EXCLUSIVE.equalsIgnoreCase(StringUtils.trim(setCategory)))
String raw = StringUtils.trim(setCategory);
if (CATEGORY_EXCLUSIVE.equalsIgnoreCase(raw))
{
return CATEGORY_EXCLUSIVE;
}
if (CATEGORY_LABEL.equalsIgnoreCase(raw))
{
return CATEGORY_LABEL;
}
return CATEGORY_PACKAGE;
}
private static String buildPriceSetCode(String setCategory, Long priceSetId)
{
String prefix = CATEGORY_EXCLUSIVE.equals(normalizeSetCategory(setCategory)) ? "EXC" : "SET";
String category = normalizeSetCategory(setCategory);
String prefix = CATEGORY_EXCLUSIVE.equals(category) ? "EXC"
: (CATEGORY_LABEL.equals(category) ? "LBL" : "SET");
return String.format("%s%04d", prefix, priceSetId);
}
@@ -6,7 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectByChannelAndType" resultType="com.bms.price.domain.ChannelProductMap">
select id, channel_id as channelId, channel_code as channelCode, product_id as productId,
price_set_id as priceSetId, pay_type as payType, create_by as createBy, create_time as createTime,
pay_type as payType, create_by as createBy, create_time as createTime,
update_by as updateBy, update_time as updateTime
from srv_channel_product_map
where channel_id = #{channelId} and pay_type = #{payType}
@@ -15,14 +15,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="upsert">
insert into srv_channel_product_map (
channel_id, channel_code, product_id, price_set_id, pay_type, create_by, create_time, update_by, update_time
channel_id, channel_code, product_id, pay_type, create_by, create_time, update_by, update_time
) values (
#{channelId}, #{channelCode}, #{productId}, #{priceSetId}, #{payType}, #{createBy}, sysdate(), #{updateBy}, sysdate()
#{channelId}, #{channelCode}, #{productId}, #{payType}, #{createBy}, sysdate(), #{updateBy}, sysdate()
)
on duplicate key update
channel_code = values(channel_code),
product_id = values(product_id),
price_set_id = values(price_set_id),
update_by = values(update_by),
update_time = sysdate()
</insert>
@@ -49,6 +49,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<where>
<if test="templateOnly != null and templateOnly">
and (pay_product.channel_id is null or pay_product.channel_id = '')
and not (
exists (
select 1 from pay_product_term t_label
where t_label.product_id = pay_product.product_id
and t_label.price_set_id is not null
)
and not exists (
select 1 from pay_product_term t_pay
where t_pay.product_id = pay_product.product_id
and t_pay.price_set_id is null
)
)
</if>
<if test="currentOnly != null and currentOnly">
and pay_product.origin_id is null
@@ -129,7 +141,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
inner join pay_product v on v.product_id = t2.product_id
where (v.channel_id is null or v.channel_id = '')
and (v.product_id = pay_product.product_id or v.origin_id = pay_product.product_id)
order by case when t2.status = '0' then 0 else 1 end, ifnull(v.version_no, 1) desc, t2.term_id desc
and t2.price_set_id is null
order by case when t2.status = '1' then 0 when t2.status = '0' then 1 else 2 end, ifnull(v.version_no, 1) desc, t2.term_id desc
limit 1
)
left join pay_product cur on cur.product_id = t.product_id
@@ -202,15 +215,65 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where product_id = #{productId}
</update>
<!-- 应付公共模板:排除「仅打单套餐」产品(有 label term、无公共 term) -->
<sql id="payPublicTemplateOnly">
and not (
exists (
select 1 from pay_product_term t_label
where t_label.product_id = pay_product.product_id
and t_label.price_set_id is not null
)
and not exists (
select 1 from pay_product_term t_pay
where t_pay.product_id = pay_product.product_id
and t_pay.price_set_id is null
)
)
</sql>
<select id="selectTemplateId" resultType="long">
select product_id from pay_product
where dict_value = #{dictValue}
and (channel_id is null or channel_id = '')
and origin_id is null
<include refid="payPublicTemplateOnly"/>
order by product_id
limit 1
</select>
<select id="selectTemplateIdByType" resultType="long">
select product_id from pay_product
where dict_value = #{dictValue}
and ifnull(product_type, 'AP') = #{productType}
and (channel_id is null or channel_id = '')
and origin_id is null
<include refid="payPublicTemplateOnly"/>
order by product_id
limit 1
</select>
<!-- 打单套餐专用:只命中已有 label term 的 AR 产品,绝不碰应付公共模板 -->
<select id="selectLabelProductIdByDict" resultType="long">
select p.product_id
from pay_product p
where p.dict_value = #{dictValue}
and ifnull(p.product_type, 'AP') = 'AR'
and (p.channel_id is null or p.channel_id = '')
and p.origin_id is null
and exists (
select 1 from pay_product_term t
where t.product_id = p.product_id
and t.price_set_id is not null
)
and not exists (
select 1 from pay_product_term t2
where t2.product_id = p.product_id
and t2.price_set_id is null
)
order by p.product_id
limit 1
</select>
<select id="selectTemplateOptions" resultMap="PayProductResult">
select p.product_id, p.product_name, p.product_type, p.dict_value, p.sheet_name, p.supplier_name, p.effective_date,
p.currency, ifnull(cur.version_no, p.version_no) as version_no
@@ -221,13 +284,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
inner join pay_product v on v.product_id = t2.product_id
where (v.channel_id is null or v.channel_id = '')
and (v.product_id = p.product_id or v.origin_id = p.product_id)
order by case when t2.status = '0' then 0 else 1 end, ifnull(v.version_no, 1) desc, t2.term_id desc
and t2.price_set_id is null
order by case when t2.status = '1' then 0 when t2.status = '0' then 1 else 2 end, ifnull(v.version_no, 1) desc, t2.term_id desc
limit 1
)
left join pay_product cur on cur.product_id = t.product_id
where (p.channel_id is null or p.channel_id = '')
and p.origin_id is null
and ifnull(p.product_type, 'AP') = #{payType}
and not (
exists (
select 1 from pay_product_term t_label
where t_label.product_id = p.product_id
and t_label.price_set_id is not null
)
and not exists (
select 1 from pay_product_term t_pay
where t_pay.product_id = p.product_id
and t_pay.price_set_id is null
)
)
order by p.product_name
</select>
@@ -238,11 +314,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertWeightBands">
insert into pay_weight_band (
product_id, version_no, section_name, country_name, country_code, zone_name, weight_label, weight_min, weight_max,
product_id, term_id, version_no, section_name, country_name, country_code, zone_name, weight_label, weight_min, weight_max,
volume_text, charge_name, price, price_unit, sort_no
) values
<foreach collection="list" item="row" separator=",">
(#{row.productId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.countryName}, #{row.countryCode}, #{row.zoneName},
(#{row.productId}, #{row.termId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.countryName}, #{row.countryCode}, #{row.zoneName},
#{row.weightLabel}, #{row.weightMin}, #{row.weightMax}, #{row.volumeText}, #{row.chargeName}, #{row.price},
#{row.priceUnit}, #{row.sortNo})
</foreach>
@@ -250,11 +326,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<insert id="insertSurcharges">
insert into pay_surcharge (
product_id, version_no, section_name, fee_name, zone_name, charge_text, amount, currency, price_unit, fee_desc, period_text,
product_id, term_id, version_no, section_name, fee_name, zone_name, charge_text, amount, currency, price_unit, fee_desc, period_text,
effective_start, effective_end, charge_timing, calc_rule, calc_param, weight_min, weight_max, postcode_param, sort_no
) values
<foreach collection="list" item="row" separator=",">
(#{row.productId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.feeName}, #{row.zoneName}, #{row.chargeText}, #{row.amount},
(#{row.productId}, #{row.termId}, ifnull(#{row.versionNo}, 1), #{row.sectionName}, #{row.feeName}, #{row.zoneName}, #{row.chargeText}, #{row.amount},
#{row.currency}, #{row.priceUnit}, #{row.feeDesc}, #{row.periodText}, #{row.effectiveStart}, #{row.effectiveEnd},
#{row.chargeTiming}, ifnull(#{row.calcRule}, 'MANUAL'), #{row.calcParam}, #{row.weightMin}, #{row.weightMax},
#{row.postcodeParam}, #{row.sortNo})
@@ -262,14 +338,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</insert>
<insert id="insertExtraLines">
insert into pay_extra_line (product_id, line_type, line_text, sort_no) values
insert into pay_extra_line (product_id, term_id, line_type, line_text, sort_no) values
<foreach collection="list" item="row" separator=",">
(#{row.productId}, #{row.lineType}, #{row.lineText}, #{row.sortNo})
(#{row.productId}, #{row.termId}, #{row.lineType}, #{row.lineText}, #{row.sortNo})
</foreach>
</insert>
<select id="selectWeightBands" resultType="com.bms.price.domain.PayWeightBand">
select band_id as bandId, product_id as productId, version_no as versionNo, section_name as sectionName, country_name as countryName,
select band_id as bandId, product_id as productId, term_id as termId, version_no as versionNo, section_name as sectionName, country_name as countryName,
country_code as countryCode, zone_name as zoneName, weight_label as weightLabel, weight_min as weightMin,
weight_max as weightMax, volume_text as volumeText, charge_name as chargeName, price, price_unit as priceUnit, sort_no as sortNo
from pay_weight_band
@@ -278,8 +354,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by product_id, sort_no, band_id
</select>
<select id="selectWeightBandsByTerm" resultType="com.bms.price.domain.PayWeightBand">
select band_id as bandId, product_id as productId, term_id as termId, version_no as versionNo, section_name as sectionName, country_name as countryName,
country_code as countryCode, zone_name as zoneName, weight_label as weightLabel, weight_min as weightMin,
weight_max as weightMax, volume_text as volumeText, charge_name as chargeName, price, price_unit as priceUnit, sort_no as sortNo
from pay_weight_band
where term_id in
<foreach collection="termIds" item="id" open="(" separator="," close=")">#{id}</foreach>
order by product_id, sort_no, band_id
</select>
<select id="selectSurcharges" resultType="com.bms.price.domain.PaySurcharge">
select surcharge_id as surchargeId, product_id as productId, version_no as versionNo, section_name as sectionName, fee_name as feeName,
select surcharge_id as surchargeId, product_id as productId, term_id as termId, version_no as versionNo, section_name as sectionName, fee_name as feeName,
zone_name as zoneName, charge_text as chargeText, amount, currency, price_unit as priceUnit, fee_desc as feeDesc,
period_text as periodText, effective_start as effectiveStart, effective_end as effectiveEnd,
charge_timing as chargeTiming, calc_rule as calcRule, calc_param as calcParam,
@@ -290,14 +376,47 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by product_id, sort_no, surcharge_id
</select>
<select id="selectSurchargesByTerm" resultType="com.bms.price.domain.PaySurcharge">
select surcharge_id as surchargeId, product_id as productId, term_id as termId, version_no as versionNo, section_name as sectionName, fee_name as feeName,
zone_name as zoneName, charge_text as chargeText, amount, currency, price_unit as priceUnit, fee_desc as feeDesc,
period_text as periodText, effective_start as effectiveStart, effective_end as effectiveEnd,
charge_timing as chargeTiming, calc_rule as calcRule, calc_param as calcParam,
weight_min as weightMin, weight_max as weightMax, postcode_param as postcodeParam, sort_no as sortNo
from pay_surcharge
where term_id in
<foreach collection="termIds" item="id" open="(" separator="," close=")">#{id}</foreach>
order by product_id, sort_no, surcharge_id
</select>
<select id="selectExtraLines" resultType="com.bms.price.domain.PayExtraLine">
select line_id as lineId, product_id as productId, line_type as lineType, line_text as lineText, sort_no as sortNo
select line_id as lineId, product_id as productId, term_id as termId, line_type as lineType, line_text as lineText, sort_no as sortNo
from pay_extra_line
where product_id in
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
order by product_id, sort_no, line_id
</select>
<select id="selectExtraLinesByTerm" resultType="com.bms.price.domain.PayExtraLine">
select line_id as lineId, product_id as productId, term_id as termId, line_type as lineType, line_text as lineText, sort_no as sortNo
from pay_extra_line
where term_id in
<foreach collection="termIds" item="id" open="(" separator="," close=")">#{id}</foreach>
order by product_id, sort_no, line_id
</select>
<delete id="deleteWeightBandsByTerm">
delete from pay_weight_band where term_id in
<foreach collection="termIds" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
<delete id="deleteSurchargesByTerm">
delete from pay_surcharge where term_id in
<foreach collection="termIds" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
<delete id="deleteExtraLinesByTerm">
delete from pay_extra_line where term_id in
<foreach collection="termIds" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
<delete id="deleteWeightBands">
delete from pay_weight_band where product_id in
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
@@ -323,13 +442,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where product_id = #{rootId} or origin_id = #{rootId}
</select>
<!-- 取价只看有效期。生效/失效开关不参与,和历史页说明一致。 -->
<!-- 取价只看有效期。生效/失效开关不参与,和历史页说明一致。仅应付公共 term。 -->
<select id="selectEffectiveVersionId" resultType="long">
select v.product_id
from pay_product v
inner join pay_product_term t on t.product_id = v.product_id
where (v.channel_id is null or v.channel_id = '')
and (v.product_id = #{stableId} or v.origin_id = #{stableId})
and t.price_set_id is null
and t.status = '1'
and (t.effective_start is null or t.effective_start &lt;= date(#{bizDate}))
and (t.effective_end is null or t.effective_end &gt;= date(#{bizDate}))
order by ifnull(v.version_no, 1) desc, v.product_id desc
@@ -5,7 +5,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<mapper namespace="com.bms.price.mapper.PayProductTermMapper">
<select id="selectByDict" resultType="com.bms.price.domain.PayProductTerm">
select t.term_id as termId, t.dict_value as dictValue, t.product_id as productId, t.version_no as versionNo,
select t.term_id as termId, t.dict_value as dictValue, t.product_id as productId, t.price_set_id as priceSetId,
t.version_no as versionNo,
t.effective_start as effectiveStart, t.effective_end as effectiveEnd, t.status,
t.created_by as createBy, t.create_time as createTime, t.remark,
p.product_name as productName, p.source_file as sourceFile,
@@ -15,29 +16,107 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
join pay_product p on p.product_id = t.product_id
where t.dict_value = #{dictValue}
and (p.channel_id is null or p.channel_id = '')
and t.price_set_id is null
order by t.version_no desc, t.term_id desc
</select>
<select id="selectById" resultType="com.bms.price.domain.PayProductTerm">
select term_id as termId, dict_value as dictValue, product_id as productId, version_no as versionNo,
select term_id as termId, dict_value as dictValue, product_id as productId, price_set_id as priceSetId,
version_no as versionNo,
effective_start as effectiveStart, effective_end as effectiveEnd, status
from pay_product_term
where term_id = #{termId}
</select>
<!-- 应付侧当前版:只取公共 term;优先生效(1),其次待生效(0) -->
<select id="selectByProductId" resultType="com.bms.price.domain.PayProductTerm">
select term_id as termId, dict_value as dictValue, product_id as productId, version_no as versionNo,
select term_id as termId, dict_value as dictValue, product_id as productId, price_set_id as priceSetId,
version_no as versionNo,
effective_start as effectiveStart, effective_end as effectiveEnd, status
from pay_product_term
where product_id = #{productId}
and price_set_id is null
and status in ('0', '1')
order by case when status = '1' then 0 when status = '0' then 1 else 2 end, version_no desc, term_id desc
limit 1
</select>
<!-- 打单套餐产品列表:待生效+生效 -->
<select id="selectByPriceSetId" resultType="com.bms.price.domain.PayProductTerm">
select t.term_id as termId, t.dict_value as dictValue, t.product_id as productId, t.price_set_id as priceSetId,
t.version_no as versionNo,
t.effective_start as effectiveStart, t.effective_end as effectiveEnd, t.status,
p.product_name as productName, p.source_file as sourceFile,
ifnull(t.file_path, p.file_path) as filePath, p.weight_count as weightCount,
p.surcharge_count as surchargeCount, p.currency
from pay_product_term t
join pay_product p on p.product_id = t.product_id
where t.price_set_id = #{priceSetId}
and t.status in ('0', '1')
order by p.product_name, t.term_id desc
</select>
<select id="selectByPriceSetIdAndVersion" resultType="com.bms.price.domain.PayProductTerm">
select t.term_id as termId, t.dict_value as dictValue, t.product_id as productId, t.price_set_id as priceSetId,
t.version_no as versionNo,
t.effective_start as effectiveStart, t.effective_end as effectiveEnd, t.status,
p.product_name as productName, p.source_file as sourceFile,
ifnull(t.file_path, p.file_path) as filePath, p.weight_count as weightCount,
p.surcharge_count as surchargeCount, p.currency
from pay_product_term t
join pay_product p on p.product_id = t.product_id
where t.price_set_id = #{priceSetId}
and t.version_no = #{versionNo}
order by case when t.status = '1' then 0 when t.status = '0' then 1 else 2 end, p.product_name, t.term_id desc
</select>
<select id="selectLabelVersionGroups" resultType="com.bms.price.domain.LabelPackageVersion">
select #{priceSetId} as priceSetId,
t.version_no as versionNo,
min(t.effective_start) as effectiveTime,
max(t.effective_end) as expireTime,
count(distinct t.product_id) as productCount,
sum(case when t.status = '1' then 1 else 0 end) as activeCount,
sum(case when t.status = '0' then 1 else 0 end) as pendingCount,
min(t.create_time) as createTime
from pay_product_term t
where t.price_set_id = #{priceSetId}
group by t.version_no
order by t.version_no desc
</select>
<!-- 启动:写入生效日;未到日 status=0,已到/当天 status=1 -->
<update id="updateLabelVersionPeriod">
update pay_product_term
set effective_start = #{effectiveStart},
effective_end = #{effectiveEnd},
status = #{status},
updated_by = #{updateBy},
update_time = sysdate()
where price_set_id = #{priceSetId}
and version_no = #{versionNo}
and status in ('0', '1')
</update>
<select id="selectMaxVersionByProduct" resultType="int">
select ifnull(max(version_no), 0) from pay_product_term where product_id = #{productId}
<if test="priceSetId != null">
and price_set_id = #{priceSetId}
</if>
<if test="priceSetId == null">
and price_set_id is null
</if>
</select>
<select id="selectMaxVersionByPriceSet" resultType="int">
select ifnull(max(version_no), 0) from pay_product_term where price_set_id = #{priceSetId}
</select>
<insert id="insertTerm" parameterType="com.bms.price.domain.PayProductTerm" useGeneratedKeys="true" keyProperty="termId">
insert into pay_product_term (
dict_value, product_id, version_no, effective_start, effective_end, file_path, status, created_by, create_time, remark
dict_value, product_id, price_set_id, version_no, effective_start, effective_end, file_path, status, created_by, create_time, remark
) values (
#{dictValue}, #{productId}, ifnull(#{versionNo}, 1), #{effectiveStart}, #{effectiveEnd}, #{filePath},
#{dictValue}, #{productId}, #{priceSetId}, ifnull(#{versionNo}, 1), #{effectiveStart}, #{effectiveEnd}, #{filePath},
ifnull(#{status}, '0'), #{createBy}, sysdate(), #{remark}
)
</insert>
@@ -59,9 +138,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where term_id = #{termId}
</update>
<!-- 截断旧版:生效/待生效 → 已失效(2) -->
<update id="closeOpenTerms">
update pay_product_term
set status = '1',
set status = '2',
effective_end = case
when effective_end is not null and effective_end &lt; #{endDate} then effective_end
when effective_start is not null and effective_start &gt; #{endDate} then effective_start
@@ -70,14 +150,90 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
updated_by = #{updateBy},
update_time = sysdate()
where dict_value = #{dictValue}
and status in ('0', '1')
and price_set_id is null
</update>
<update id="closeOpenTermsBySetProduct">
update pay_product_term
set status = '2',
effective_end = case
when effective_end is not null and effective_end &lt; #{endDate} then effective_end
when effective_start is not null and effective_start &gt; #{endDate} then effective_start
else #{endDate}
end,
updated_by = #{updateBy},
update_time = sysdate()
where product_id = #{productId}
and price_set_id = #{priceSetId}
and status in ('0', '1')
</update>
<update id="closePendingBySetProduct">
update pay_product_term
set status = '2',
effective_end = case
when effective_end is not null and effective_end &lt; #{endDate} then effective_end
when effective_start is not null and effective_start &gt; #{endDate} then effective_start
else #{endDate}
end,
updated_by = #{updateBy},
update_time = sysdate()
where product_id = #{productId}
and price_set_id = #{priceSetId}
and status = '0'
</update>
<update id="closeEffectExceptVersion">
update pay_product_term
set status = '2',
effective_end = case
when effective_end is not null and effective_end &lt; #{endDate} then effective_end
when effective_start is not null and effective_start &gt; #{endDate} then effective_start
else #{endDate}
end,
updated_by = #{updateBy},
update_time = sysdate()
where price_set_id = #{priceSetId}
and status = '1'
and version_no &lt;&gt; #{versionNo}
</update>
<update id="scheduleEndExceptVersion">
update pay_product_term
set status = '1',
effective_end = #{endDate},
updated_by = #{updateBy},
update_time = sysdate()
where price_set_id = #{priceSetId}
and version_no &lt;&gt; #{versionNo}
and (
status = '1'
or (status = '2' and effective_end is not null and effective_end &gt;= #{today})
)
</update>
<update id="restorePrematureClosed">
update pay_product_term
set status = '1',
updated_by = #{updateBy},
update_time = sysdate()
where price_set_id = #{priceSetId}
and status = '2'
and effective_end is not null
and effective_end &gt;= #{today}
</update>
<delete id="deleteByProductIds">
delete from pay_product_term where product_id in
<foreach collection="productIds" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
<delete id="deleteByTermIds">
delete from pay_product_term where term_id in
<foreach collection="termIds" item="id" open="(" separator="," close=")">#{id}</foreach>
</delete>
<update id="updateProductEffectiveDate">
update pay_product
set effective_date = #{effectiveDate},
@@ -31,11 +31,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
s.default_discount, s.default_type, s.calculate_sign,
s.status, s.tms_id,
s.created_by, s.create_time, s.updated_by, s.update_time,
(select count(distinct p.product_code)
from prc_price_set_product p
inner join prc_price_set_version v on v.psv_id = p.psv_id
where v.price_set_id = s.price_set_id
and ifnull(p.is_del, '0') = '0') as product_count,
case
when ifnull(s.set_category, 'PACKAGE') = 'LABEL' then
(select count(distinct t.product_id)
from pay_product_term t
where t.price_set_id = s.price_set_id
and t.status = '0')
else
(select count(distinct p.product_code)
from prc_price_set_product p
inner join prc_price_set_version v on v.psv_id = p.psv_id
where v.price_set_id = s.price_set_id
and ifnull(p.is_del, '0') = '0')
end as product_count,
(select count(1) from prc_customer_price_set c where c.price_set_id = s.price_set_id) as customer_count,
(select group_concat(
concat(ifnull(c.customer_code, ''),