feat(ticket): 优化套票发放逻辑支持购买量与赠送量分离处理
- 引入 GltTicketOrder 实体和服务类处理送水订单 - 新增 DateTimeFormatter 用于时间格式化 - 修改购买数量计算逻辑,优先使用订单总数量提高准确性 - 实现购买量与赠送量分离,支持 includeBuyQty 配置决定是否将购买量计入水票账户 - 更新用户水票的可用量、冻结量和已释放数量字段逻辑 - 优化起始送水功能,支持从可用和冻结票中同时扣除 - 添加订单总数量与订单商品数量不一致的提示日志 - 在起始送水时自动生成对应的送水订单记录 - 调整释放计划生成逻辑,基于实际冻结量进行计算
This commit is contained in:
@@ -3,6 +3,7 @@ package com.gxwebsoft.glt.service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.gxwebsoft.glt.entity.GltTicketTemplate;
|
||||
import com.gxwebsoft.glt.entity.GltTicketOrder;
|
||||
import com.gxwebsoft.glt.entity.GltUserTicket;
|
||||
import com.gxwebsoft.glt.entity.GltUserTicketLog;
|
||||
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
|
||||
@@ -18,6 +19,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -55,6 +57,7 @@ public class GltTicketIssueService {
|
||||
private final GltUserTicketService gltUserTicketService;
|
||||
private final GltUserTicketReleaseService gltUserTicketReleaseService;
|
||||
private final GltUserTicketLogService gltUserTicketLogService;
|
||||
private final GltTicketOrderService gltTicketOrderService;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
/**
|
||||
@@ -217,29 +220,38 @@ public class GltTicketIssueService {
|
||||
return IssueOutcome.ALREADY_ISSUED;
|
||||
}
|
||||
|
||||
int buyQty = og.getTotalNum() != null ? og.getTotalNum() : 0;
|
||||
if (buyQty <= 0) {
|
||||
// water 套餐场景:订单表 totalNum 往往更可信(有些订单商品行会出现 totalNum != 订单总数的情况)
|
||||
int rawBuyQty = og.getTotalNum() != null ? og.getTotalNum() : 0;
|
||||
if (order.getTotalNum() != null && order.getTotalNum() > 0 && Objects.equals(order.getFormId(), og.getGoodsId())) {
|
||||
rawBuyQty = order.getTotalNum();
|
||||
}
|
||||
if (rawBuyQty <= 0) {
|
||||
log.warn("套票发放跳过:购买数量无效 - tenantId={}, orderNo={}, orderGoodsId={}, totalNum={}",
|
||||
tenantId, order.getOrderNo(), og.getId(), og.getTotalNum());
|
||||
return IssueOutcome.SKIPPED;
|
||||
}
|
||||
|
||||
Integer minBuyQty = template.getMinBuyQty();
|
||||
if (minBuyQty != null && minBuyQty > 0 && buyQty < minBuyQty) {
|
||||
if (minBuyQty != null && minBuyQty > 0 && rawBuyQty < minBuyQty) {
|
||||
log.info("套票发放跳过:未达到最小购买数量 - tenantId={}, orderNo={}, buyQty={}, minBuyQty={}",
|
||||
tenantId, order.getOrderNo(), buyQty, minBuyQty);
|
||||
tenantId, order.getOrderNo(), rawBuyQty, minBuyQty);
|
||||
return IssueOutcome.SKIPPED;
|
||||
}
|
||||
|
||||
int giftMultiplier = template.getGiftMultiplier() != null ? template.getGiftMultiplier() : 0;
|
||||
int giftQty = buyQty * Math.max(giftMultiplier, 0);
|
||||
int giftQty = rawBuyQty * Math.max(giftMultiplier, 0);
|
||||
|
||||
// 购买量(buyQty)应立即可用;赠送量(giftQty)进入冻结并按计划释放。
|
||||
int totalQty = buyQty + giftQty;
|
||||
// 默认仅把“赠送量”计入水票账户;如需把购买量也纳入,设置 includeBuyQty=true。
|
||||
int buyQtyForTicket = Boolean.TRUE.equals(template.getIncludeBuyQty()) ? rawBuyQty : 0;
|
||||
|
||||
// 购买量(buyQtyForTicket)应立即可用;赠送量(giftQty)进入冻结并按计划释放。
|
||||
int availableQty = buyQtyForTicket;
|
||||
int frozenQty = giftQty;
|
||||
int totalQty = availableQty + frozenQty;
|
||||
|
||||
if (totalQty <= 0) {
|
||||
log.info("套票发放跳过:计算结果为0 - tenantId={}, orderNo={}, buyQty={}, giftMultiplier={}, includeBuyQty={}",
|
||||
tenantId, order.getOrderNo(), buyQty, giftMultiplier, template.getIncludeBuyQty());
|
||||
tenantId, order.getOrderNo(), rawBuyQty, giftMultiplier, template.getIncludeBuyQty());
|
||||
return IssueOutcome.SKIPPED;
|
||||
}
|
||||
|
||||
@@ -252,11 +264,11 @@ public class GltTicketIssueService {
|
||||
userTicket.setOrderNo(order.getOrderNo());
|
||||
userTicket.setOrderGoodsId(og.getId());
|
||||
userTicket.setTotalQty(totalQty);
|
||||
userTicket.setAvailableQty(buyQty);
|
||||
userTicket.setFrozenQty(giftQty);
|
||||
userTicket.setAvailableQty(availableQty);
|
||||
userTicket.setFrozenQty(frozenQty);
|
||||
userTicket.setUsedQty(0);
|
||||
// 初始可用量来自“购买量”,视为已释放
|
||||
userTicket.setReleasedQty(buyQty);
|
||||
// 已释放数量:仅用于统计“从冻结释放到可用”的累计值;初始为0(释放任务会递增它)
|
||||
userTicket.setReleasedQty(0);
|
||||
userTicket.setUserId(order.getUserId());
|
||||
userTicket.setSortNumber(0);
|
||||
userTicket.setComments("订单发放套票");
|
||||
@@ -268,25 +280,15 @@ public class GltTicketIssueService {
|
||||
|
||||
gltUserTicketService.save(userTicket);
|
||||
|
||||
// 生成释放计划(按月)
|
||||
LocalDateTime baseTime = order.getPayTime() != null ? order.getPayTime() : order.getCreateTime();
|
||||
if (baseTime == null) {
|
||||
baseTime = now;
|
||||
}
|
||||
List<GltUserTicketRelease> releases = buildReleasePlan(template, userTicket, baseTime, giftQty, now);
|
||||
if (!releases.isEmpty()) {
|
||||
gltUserTicketReleaseService.saveBatch(releases);
|
||||
}
|
||||
|
||||
// 发放流水
|
||||
GltUserTicketLog issueLog = new GltUserTicketLog();
|
||||
issueLog.setUserTicketId(userTicket.getId());
|
||||
issueLog.setChangeType(CHANGE_TYPE_ISSUE);
|
||||
issueLog.setChangeAvailable(buyQty);
|
||||
issueLog.setChangeFrozen(giftQty);
|
||||
issueLog.setChangeAvailable(availableQty);
|
||||
issueLog.setChangeFrozen(frozenQty);
|
||||
issueLog.setChangeUsed(0);
|
||||
issueLog.setAvailableAfter(buyQty);
|
||||
issueLog.setFrozenAfter(giftQty);
|
||||
issueLog.setAvailableAfter(availableQty);
|
||||
issueLog.setFrozenAfter(frozenQty);
|
||||
issueLog.setUsedAfter(0);
|
||||
issueLog.setOrderId(order.getOrderId());
|
||||
issueLog.setOrderNo(order.getOrderNo());
|
||||
@@ -300,31 +302,69 @@ public class GltTicketIssueService {
|
||||
issueLog.setUpdateTime(now);
|
||||
gltUserTicketLogService.save(issueLog);
|
||||
|
||||
if (order.getTotalNum() != null && og.getTotalNum() != null && !Objects.equals(order.getTotalNum(), og.getTotalNum())) {
|
||||
log.info("套票发放提示:订单总数量与订单商品数量不一致 - tenantId={}, orderNo={}, orderTotalNum={}, orderGoodsId={}, goodsId={}, orderGoodsTotalNum={}",
|
||||
tenantId, order.getOrderNo(), order.getTotalNum(), og.getId(), og.getGoodsId(), og.getTotalNum());
|
||||
}
|
||||
|
||||
// 按模板配置:自动“使用掉第一次水票”(起始送水数量)
|
||||
Integer startSendQtyObj = template.getStartSendQty();
|
||||
int startSendQty = startSendQtyObj != null ? startSendQtyObj : 0;
|
||||
if (startSendQty > 0) {
|
||||
int availableBefore = userTicket.getAvailableQty() != null ? userTicket.getAvailableQty() : 0;
|
||||
int frozenBefore = userTicket.getFrozenQty() != null ? userTicket.getFrozenQty() : 0;
|
||||
int usedBefore = userTicket.getUsedQty() != null ? userTicket.getUsedQty() : 0;
|
||||
int toUse = Math.min(startSendQty, availableBefore);
|
||||
int toUse = Math.min(startSendQty, availableBefore + frozenBefore);
|
||||
if (toUse > 0) {
|
||||
userTicket.setAvailableQty(availableBefore - toUse);
|
||||
int useFromAvailable = Math.min(toUse, availableBefore);
|
||||
int useFromFrozen = Math.min(toUse - useFromAvailable, frozenBefore);
|
||||
|
||||
userTicket.setAvailableQty(availableBefore - useFromAvailable);
|
||||
userTicket.setFrozenQty(frozenBefore - useFromFrozen);
|
||||
userTicket.setUsedQty(usedBefore + toUse);
|
||||
userTicket.setUpdateTime(now);
|
||||
gltUserTicketService.updateById(userTicket);
|
||||
if (!gltUserTicketService.updateById(userTicket)) {
|
||||
throw new IllegalStateException("起始送水核销失败:更新用户水票失败 userTicketId=" + userTicket.getId());
|
||||
}
|
||||
|
||||
// 同步生成一条“送水订单”(待配送),用于承载这次起始送水的核销。
|
||||
// 这里不走 createWithWriteOff(它会再次扣减水票),而是复用本次已完成的扣减结果。
|
||||
GltTicketOrder firstOrder = new GltTicketOrder();
|
||||
firstOrder.setUserTicketId(userTicket.getId());
|
||||
firstOrder.setStoreId(order.getStoreId());
|
||||
firstOrder.setWarehouseId(order.getWarehouseId());
|
||||
firstOrder.setAddressId(order.getAddressId());
|
||||
firstOrder.setAddress(order.getAddress());
|
||||
firstOrder.setBuyerRemarks(order.getBuyerRemarks());
|
||||
firstOrder.setPrice(order.getPrice());
|
||||
firstOrder.setTotalNum(toUse);
|
||||
LocalDateTime sendTimeBase = order.getPayTime() != null ? order.getPayTime() : (order.getCreateTime() != null ? order.getCreateTime() : now);
|
||||
firstOrder.setSendTime(sendTimeBase.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
firstOrder.setDeliveryStatus(GltTicketOrderService.DELIVERY_STATUS_WAITING);
|
||||
firstOrder.setUserId(order.getUserId());
|
||||
firstOrder.setSortNumber(0);
|
||||
firstOrder.setComments("起始送水自动生成");
|
||||
firstOrder.setStatus(0);
|
||||
firstOrder.setDeleted(0);
|
||||
firstOrder.setTenantId(tenantId);
|
||||
firstOrder.setCreateTime(now);
|
||||
firstOrder.setUpdateTime(now);
|
||||
if (!gltTicketOrderService.save(firstOrder)) {
|
||||
throw new IllegalStateException("起始送水核销失败:生成送水订单失败 userTicketId=" + userTicket.getId());
|
||||
}
|
||||
|
||||
GltUserTicketLog writeOffLog = new GltUserTicketLog();
|
||||
writeOffLog.setUserTicketId(userTicket.getId());
|
||||
writeOffLog.setChangeType(CHANGE_TYPE_START_SEND_WRITE_OFF);
|
||||
writeOffLog.setChangeAvailable(-toUse);
|
||||
writeOffLog.setChangeFrozen(0);
|
||||
writeOffLog.setChangeAvailable(-useFromAvailable);
|
||||
writeOffLog.setChangeFrozen(-useFromFrozen);
|
||||
writeOffLog.setChangeUsed(toUse);
|
||||
writeOffLog.setAvailableAfter(userTicket.getAvailableQty());
|
||||
writeOffLog.setFrozenAfter(userTicket.getFrozenQty() != null ? userTicket.getFrozenQty() : 0);
|
||||
writeOffLog.setUsedAfter(userTicket.getUsedQty());
|
||||
// 关联原始商城订单,便于追溯该次自动核销来源
|
||||
writeOffLog.setOrderId(order.getOrderId());
|
||||
writeOffLog.setOrderNo(order.getOrderNo());
|
||||
// 关联本次生成的送水订单(与 createWithWriteOff 行为一致)
|
||||
writeOffLog.setOrderId(firstOrder.getId());
|
||||
writeOffLog.setOrderNo(firstOrder.getId() == null ? null : String.valueOf(firstOrder.getId()));
|
||||
writeOffLog.setUserId(order.getUserId());
|
||||
writeOffLog.setSortNumber(0);
|
||||
writeOffLog.setComments("起始送水自动核销");
|
||||
@@ -337,6 +377,19 @@ public class GltTicketIssueService {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成释放计划(按月):以“当前冻结量”为准(若起始送水消耗了冻结票,则相应减少释放计划)
|
||||
int frozenToRelease = userTicket.getFrozenQty() != null ? userTicket.getFrozenQty() : 0;
|
||||
if (frozenToRelease > 0) {
|
||||
LocalDateTime baseTime = order.getPayTime() != null ? order.getPayTime() : order.getCreateTime();
|
||||
if (baseTime == null) {
|
||||
baseTime = now;
|
||||
}
|
||||
List<GltUserTicketRelease> releases = buildReleasePlan(template, userTicket, baseTime, frozenToRelease, now);
|
||||
if (!releases.isEmpty()) {
|
||||
gltUserTicketReleaseService.saveBatch(releases);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("套票发放成功 - tenantId={}, orderNo={}, orderGoodsId={}, templateId={}, userTicketId={}, totalQty={}",
|
||||
tenantId, order.getOrderNo(), og.getId(), template.getId(), userTicket.getId(), totalQty);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user