feat(task): 更新分销佣金解冻和套票发放任务逻辑

- 引入GltTicketTemplate和GltTicketTemplateService用于动态获取水票模板
- 将硬编码的formId=10074替换为从水票模板表动态获取的goodsId集合
- 修改经销商佣金解冻规则适配新的模板配置方式
- 更新套票发放任务支持多商品ID配置
- 添加水票模板数据加载和验证逻辑
- 增强任务执行前的模板配置检查机制
This commit is contained in:
2026-02-09 17:30:43 +08:00
parent 3d8169c55a
commit f7a96724c6
3 changed files with 88 additions and 23 deletions

View File

@@ -19,8 +19,10 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* 套票发放(从订单生成用户套票 + 释放计划)的业务逻辑。
@@ -57,13 +59,35 @@ public class GltTicketIssueService {
* 扫描“今日订单”,执行套票发放。
*/
public void issueTodayOrders(Integer tenantId, Integer formId) {
if (formId == null) {
return;
}
issueTodayOrders(tenantId, List.of(formId));
}
/**
* 扫描“今日订单”,执行套票发放(支持多个商品/模板)。
*/
public void issueTodayOrders(Integer tenantId, List<Integer> goodsIds) {
if (tenantId == null || goodsIds == null || goodsIds.isEmpty()) {
return;
}
List<Integer> uniqueGoodsIds = goodsIds.stream()
.filter(Objects::nonNull)
.distinct()
.toList();
if (uniqueGoodsIds.isEmpty()) {
return;
}
Set<Integer> goodsIdSet = new HashSet<>(uniqueGoodsIds);
LocalDateTime todayStart = LocalDate.now().atStartOfDay();
LocalDateTime tomorrowStart = todayStart.plusDays(1);
List<ShopOrder> orders = shopOrderService.list(
new LambdaQueryWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, tenantId)
.eq(ShopOrder::getFormId, formId)
.in(ShopOrder::getFormId, uniqueGoodsIds)
.eq(ShopOrder::getPayStatus, true)
.eq(ShopOrder::getOrderStatus, 0)
// 今日订单(兼容:以 create_time 或 pay_time 任一落在今日即可)
@@ -77,7 +101,7 @@ public class GltTicketIssueService {
);
if (orders.isEmpty()) {
log.debug("套票发放扫描:今日无符合条件的订单 tenantId={}, formId={}", tenantId, formId);
log.debug("套票发放扫描:今日无符合条件的订单 tenantId={}, goodsIds={}", tenantId, uniqueGoodsIds);
return;
}
@@ -87,7 +111,7 @@ public class GltTicketIssueService {
for (ShopOrder order : orders) {
try {
int issuedCount = issueForOrder(tenantId, formId, order);
int issuedCount = issueForOrder(tenantId, goodsIdSet, order);
if (issuedCount > 0) {
success += issuedCount;
} else {
@@ -100,11 +124,11 @@ public class GltTicketIssueService {
}
}
log.info("套票发放扫描完成 - tenantId={}, formId={}, 订单数={}, 发放成功={}, 跳过={}, 失败={}",
tenantId, formId, orders.size(), success, skipped, failed);
log.info("套票发放扫描完成 - tenantId={}, goodsIds={}, 订单数={}, 发放成功={}, 跳过={}, 失败={}",
tenantId, uniqueGoodsIds, orders.size(), success, skipped, failed);
}
private int issueForOrder(Integer tenantId, Integer formId, ShopOrder order) {
private int issueForOrder(Integer tenantId, Set<Integer> goodsIds, ShopOrder order) {
List<ShopOrderGoods> goodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId());
if (goodsList == null || goodsList.isEmpty()) {
return 0;
@@ -114,7 +138,7 @@ public class GltTicketIssueService {
boolean shouldCompleteOrder = false;
for (ShopOrderGoods og : goodsList) {
if (!Objects.equals(og.getGoodsId(), formId)) {
if (og.getGoodsId() == null || !goodsIds.contains(og.getGoodsId())) {
continue;
}

View File

@@ -3,8 +3,10 @@ package com.gxwebsoft.glt.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
import com.gxwebsoft.glt.entity.GltTicketOrder;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.service.GltTicketOrderService;
import com.gxwebsoft.glt.service.GltTicketTemplateService;
import com.gxwebsoft.glt.service.GltUserTicketService;
import com.gxwebsoft.shop.entity.ShopDealerCapital;
import com.gxwebsoft.shop.entity.ShopDealerUser;
@@ -34,8 +36,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
* 租户10584分销佣金解冻任务
*
* <p>规则:</p>
* <p>1) 送水套餐(formId=10074):订单号关联的水票产生了第一次送水订单,且该第一次送水订单状态=已完成(40) -> 解冻。</p>
* <p>2) 非送水套餐(formId!=10074):订单已确认收货(orderStatus=1) -> 解冻。</p>
* <p>1) 送水套餐(formId in 水票模板 goodsId):订单号关联的水票产生了第一次送水订单,且该第一次送水订单状态=已完成(40) -> 解冻。</p>
* <p>2) 非送水套餐(formId not in 水票模板 goodsId):订单已确认收货(orderStatus=1) -> 解冻。</p>
*
* <p>实现策略:以 ShopDealerCapital(flowType=10) 的“佣金明细”为解冻粒度,
* 每条佣金明细对应生成一条 ShopDealerCapital(flowType=50) 作为幂等标记,并执行
@@ -47,7 +49,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class DealerCommissionUnfreeze10584Task {
private static final int TENANT_ID = 10584;
private static final int WATER_FORM_ID = 10074;
private static final int ORDER_STATUS_CONFIRMED_RECEIVE = 1;
@@ -73,6 +74,9 @@ public class DealerCommissionUnfreeze10584Task {
@Resource
private GltTicketOrderService gltTicketOrderService;
@Resource
private GltTicketTemplateService gltTicketTemplateService;
private final AtomicBoolean running = new AtomicBoolean(false);
@Scheduled(cron = "${dealer.commission.unfreeze10584.cron:0 */1 * * * ?}")
@@ -84,10 +88,17 @@ public class DealerCommissionUnfreeze10584Task {
}
try {
Set<Integer> waterFormIds = loadWaterFormIds();
if (waterFormIds.isEmpty()) {
// 送水/非送水的判断依赖模板 goodsId拿不到会导致误判宁可跳过本轮。
log.warn("分销佣金解冻任务跳过:未找到水票模板 goodsId - tenantId={}", TENANT_ID);
return;
}
// 先按“最近确认收货”的订单扫描,避免总是卡在很早的历史订单上。
Set<String> eligibleOrderNos = new HashSet<>();
eligibleOrderNos.addAll(findEligibleNonWaterOrderNos(true));
eligibleOrderNos.addAll(findEligibleWaterOrderNosByFirstFinishedTicketOrder());
eligibleOrderNos.addAll(findEligibleNonWaterOrderNos(waterFormIds, true));
eligibleOrderNos.addAll(findEligibleWaterOrderNosByFirstFinishedTicketOrder(waterFormIds));
if (eligibleOrderNos.isEmpty()) {
return;
@@ -97,8 +108,8 @@ public class DealerCommissionUnfreeze10584Task {
if (capitals.isEmpty()) {
// 若本轮没有取到佣金明细,回退再按“最早确认收货”的订单扫一轮,尽量覆盖历史遗留未解冻。
eligibleOrderNos.clear();
eligibleOrderNos.addAll(findEligibleNonWaterOrderNos(false));
eligibleOrderNos.addAll(findEligibleWaterOrderNosByFirstFinishedTicketOrder());
eligibleOrderNos.addAll(findEligibleNonWaterOrderNos(waterFormIds, false));
eligibleOrderNos.addAll(findEligibleWaterOrderNosByFirstFinishedTicketOrder(waterFormIds));
capitals = findCapitalsByEligibleOrderNos(eligibleOrderNos);
}
@@ -128,13 +139,24 @@ public class DealerCommissionUnfreeze10584Task {
}
}
private List<String> findEligibleNonWaterOrderNos(boolean newestFirst) {
private Set<Integer> loadWaterFormIds() {
return gltTicketTemplateService.list(
new LambdaQueryWrapper<GltTicketTemplate>()
.eq(GltTicketTemplate::getTenantId, TENANT_ID)
.eq(GltTicketTemplate::getDeleted, 0)
.isNotNull(GltTicketTemplate::getGoodsId)
).stream()
.map(GltTicketTemplate::getGoodsId)
.collect(java.util.stream.Collectors.toSet());
}
private List<String> findEligibleNonWaterOrderNos(Set<Integer> waterFormIds, boolean newestFirst) {
LambdaQueryWrapper<ShopOrder> qw = new LambdaQueryWrapper<ShopOrder>()
.eq(ShopOrder::getTenantId, TENANT_ID)
.eq(ShopOrder::getDeleted, 0)
.eq(ShopOrder::getPayStatus, true)
.eq(ShopOrder::getOrderStatus, ORDER_STATUS_CONFIRMED_RECEIVE)
.and(w -> w.ne(ShopOrder::getFormId, WATER_FORM_ID).or().isNull(ShopOrder::getFormId))
.and(w -> w.notIn(ShopOrder::getFormId, waterFormIds).or().isNull(ShopOrder::getFormId))
.isNotNull(ShopOrder::getOrderNo);
if (newestFirst) {
@@ -150,7 +172,7 @@ public class DealerCommissionUnfreeze10584Task {
.toList();
}
private Set<String> findEligibleWaterOrderNosByFirstFinishedTicketOrder() {
private Set<String> findEligibleWaterOrderNosByFirstFinishedTicketOrder(Set<Integer> waterFormIds) {
// 缓存减少 DB 往返userTicketId -> 是否“第一次送水单已完成”
Map<Integer, Boolean> firstFinishedCache = new HashMap<>();
Map<Integer, String> userTicketOrderNoCache = new HashMap<>();
@@ -191,7 +213,7 @@ public class DealerCommissionUnfreeze10584Task {
.eq(ShopOrder::getOrderNo, orderNo)
.last("limit 1")
);
if (shopOrder == null || shopOrder.getFormId() == null || shopOrder.getFormId() != WATER_FORM_ID) {
if (shopOrder == null || shopOrder.getFormId() == null || !waterFormIds.contains(shopOrder.getFormId())) {
continue;
}

View File

@@ -1,18 +1,22 @@
package com.gxwebsoft.glt.task;
import com.gxwebsoft.common.core.annotation.IgnoreTenant;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.service.GltTicketIssueService;
import com.gxwebsoft.glt.service.GltTicketTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* GLT 套票发放任务:
* - 每分钟扫描一次今日订单tenantId=10584, formId=10074, payStatus=1, orderStatus=0
* - 每分钟扫描一次今日订单tenantId=10584, formId in 套票模板 goodsId, payStatus=1, orderStatus=0
* - 为订单生成用户套票账户 + 释放计划(幂等)
*/
@Slf4j
@@ -22,9 +26,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class GltTicketIssue10584Task {
private static final int TENANT_ID = 10584;
private static final int FORM_ID = 10074;
private final GltTicketIssueService gltTicketIssueService;
private final GltTicketTemplateService gltTicketTemplateService;
private final AtomicBoolean running = new AtomicBoolean(false);
@@ -32,15 +36,30 @@ public class GltTicketIssue10584Task {
@IgnoreTenant("定时任务无登录态,需忽略租户隔离;内部使用 tenantId=10584 精确过滤")
public void run() {
if (!running.compareAndSet(false, true)) {
log.warn("套票发放任务仍在执行中,本轮跳过 - tenantId={}, formId={}", TENANT_ID, FORM_ID);
log.warn("套票发放任务仍在执行中,本轮跳过 - tenantId={}", TENANT_ID);
return;
}
try {
gltTicketIssueService.issueTodayOrders(TENANT_ID, FORM_ID);
List<Integer> goodsIds = gltTicketTemplateService.list(
new LambdaQueryWrapper<GltTicketTemplate>()
.eq(GltTicketTemplate::getTenantId, TENANT_ID)
.eq(GltTicketTemplate::getDeleted, 0)
.eq(GltTicketTemplate::getEnabled, true)
.isNotNull(GltTicketTemplate::getGoodsId)
).stream()
.map(GltTicketTemplate::getGoodsId)
.distinct()
.toList();
if (goodsIds.isEmpty()) {
log.warn("套票发放任务跳过:未配置/未启用套票模板 - tenantId={}", TENANT_ID);
return;
}
gltTicketIssueService.issueTodayOrders(TENANT_ID, goodsIds);
} finally {
running.set(false);
}
}
}