diff --git a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java index 6c8726a..8adfa78 100644 --- a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java +++ b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java @@ -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 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 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);