diff --git a/src/main/java/com/gxwebsoft/credit/controller/CreditCompanyController.java b/src/main/java/com/gxwebsoft/credit/controller/CreditCompanyController.java index 828fa0e..968bdfc 100644 --- a/src/main/java/com/gxwebsoft/credit/controller/CreditCompanyController.java +++ b/src/main/java/com/gxwebsoft/credit/controller/CreditCompanyController.java @@ -149,6 +149,7 @@ public class CreditCompanyController extends BaseController { List errorMessages = new ArrayList<>(); int insertedCount = 0; Set touchedMatchNames = new HashSet<>(); + String refreshWarning = null; try { List list = null; @@ -324,13 +325,31 @@ public class CreditCompanyController extends BaseController { } } } - creditCompanyRecordCountService.refreshAll(touchedCompanyIds); + try { + creditCompanyRecordCountService.refreshAll(touchedCompanyIds); + } catch (Exception ex) { + // 导入本身已经成功写入,回填计数字段失败不应导致整个导入失败(可后续单独重试刷新)。 + String msg = ex.getMessage(); + if (msg != null && msg.length() > 300) { + msg = msg.substring(0, 300) + "..."; + } + refreshWarning = "关联记录数回填失败:" + (msg != null ? msg : ex.getClass().getSimpleName()); + ex.printStackTrace(); + } } if (errorMessages.isEmpty()) { - return success("成功入库" + insertedCount + "条数据", null); + String msg = "成功入库" + insertedCount + "条数据"; + if (refreshWarning != null) { + msg = msg + ";" + refreshWarning; + } + return success(msg, null); } else { - return success("导入完成,入库" + insertedCount + "条,失败" + errorMessages.size() + "条", errorMessages); + String msg = "导入完成,入库" + insertedCount + "条,失败" + errorMessages.size() + "条"; + if (refreshWarning != null) { + msg = msg + ";" + refreshWarning; + } + return success(msg, errorMessages); } } catch (Exception e) { diff --git a/src/main/java/com/gxwebsoft/credit/service/CreditCompanyRecordCountService.java b/src/main/java/com/gxwebsoft/credit/service/CreditCompanyRecordCountService.java index 0326a22..9cd21cc 100644 --- a/src/main/java/com/gxwebsoft/credit/service/CreditCompanyRecordCountService.java +++ b/src/main/java/com/gxwebsoft/credit/service/CreditCompanyRecordCountService.java @@ -82,16 +82,24 @@ public class CreditCompanyRecordCountService { } // 表/字段名来自固定枚举,不允许外部传入,避免 SQL 注入。 + // + // 这里不要用「UPDATE ... SET col=(SELECT COUNT... WHERE t.company_id=c.id)」的相关子查询: + // 如果关联表缺少 (company_id, deleted) 索引,会导致对每个 company_id 都进行一次全表扫描,极慢, + // 进而容易触发 JDBC/MySQL 侧的超时/断链(Communications link failure)。 + // + // 用聚合后再 JOIN 的方式:每个 chunk 只扫描一次关联表。 String sql = "" + "UPDATE credit_company c " - + "SET " + type.getCompanyColumn() + " = (" - + " SELECT COUNT(1) " - + " FROM " + type.getSourceTable() + " t " - + " WHERE t.company_id = c.id AND t.deleted = 0" - + ") " + + "LEFT JOIN (" + + " SELECT company_id, COUNT(1) AS cnt " + + " FROM " + type.getSourceTable() + " " + + " WHERE deleted = 0 AND company_id IN (:ids) " + + " GROUP BY company_id" + + ") t ON t.company_id = c.id " + + "SET c." + type.getCompanyColumn() + " = IFNULL(t.cnt, 0) " + "WHERE c.id IN (:ids)"; - final int inChunkSize = 1000; + final int inChunkSize = 500; for (int i = 0; i < ids.size(); i += inChunkSize) { List chunk = ids.subList(i, Math.min(ids.size(), i + inChunkSize)); namedJdbc.update(sql, new MapSqlParameterSource("ids", chunk)); diff --git a/src/main/java/com/gxwebsoft/glt/controller/GltTicketOrderController.java b/src/main/java/com/gxwebsoft/glt/controller/GltTicketOrderController.java index f7e49ee..4502142 100644 --- a/src/main/java/com/gxwebsoft/glt/controller/GltTicketOrderController.java +++ b/src/main/java/com/gxwebsoft/glt/controller/GltTicketOrderController.java @@ -242,6 +242,25 @@ public class GltTicketOrderController extends BaseController { @PutMapping() public ApiResult update(@RequestBody GltTicketOrder gltTicketOrder) { if (gltTicketOrderService.updateById(gltTicketOrder)) { + Integer tenantId = getTenantId(); + // 后台指派配送员(直接改 riderId)时,同步商城订单为“已发货”(deliveryStatus=20) + if (gltTicketOrder != null + && gltTicketOrder.getId() != null + && gltTicketOrder.getRiderId() != null + && gltTicketOrder.getRiderId() > 0) { + gltTicketOrderService.markShopOrderShippedAfterRiderAssigned( + gltTicketOrder.getId(), + tenantId, + gltTicketOrder.getRiderId() + ); + } + // 后台直接改“已完成”(deliveryStatus=40)时,同步商城订单为“已完成”(orderStatus=1) + if (gltTicketOrder != null + && gltTicketOrder.getId() != null + && gltTicketOrder.getDeliveryStatus() != null + && gltTicketOrder.getDeliveryStatus() == GltTicketOrderService.DELIVERY_STATUS_FINISHED) { + gltTicketOrderService.markShopOrderCompletedAfterTicketFinished(gltTicketOrder.getId(), tenantId); + } return success("修改成功"); } return fail("修改失败"); diff --git a/src/main/java/com/gxwebsoft/glt/entity/GltTicketOrder.java b/src/main/java/com/gxwebsoft/glt/entity/GltTicketOrder.java index dfae59e..9038bb1 100644 --- a/src/main/java/com/gxwebsoft/glt/entity/GltTicketOrder.java +++ b/src/main/java/com/gxwebsoft/glt/entity/GltTicketOrder.java @@ -31,6 +31,10 @@ public class GltTicketOrder implements Serializable { @Schema(description = "用户水票ID") private Integer userTicketId; + @Schema(description = "订单编号") + @TableField(exist = false) + private String orderNo; + @Schema(description = "门店ID") private Integer storeId; diff --git a/src/main/java/com/gxwebsoft/glt/mapper/xml/GltTicketOrderMapper.xml b/src/main/java/com/gxwebsoft/glt/mapper/xml/GltTicketOrderMapper.xml index a38798e..91312a4 100644 --- a/src/main/java/com/gxwebsoft/glt/mapper/xml/GltTicketOrderMapper.xml +++ b/src/main/java/com/gxwebsoft/glt/mapper/xml/GltTicketOrderMapper.xml @@ -10,13 +10,16 @@ u.nickname, u.phone, u.avatar, d.name as receiverName, d.phone as receiverPhone, d.province as receiverProvince, d.city as receiverCity, d.region as receiverRegion, - d.address as receiverAddress, d.full_address as receiverFullAddress, d.lat as receiverLat, d.lng as receiverLng + d.address as receiverAddress, d.full_address as receiverFullAddress, d.lat as receiverLat, d.lng as receiverLng, + COALESCE(o.order_no, f.order_no) as orderNo FROM glt_ticket_order a LEFT JOIN shop_store b ON a.store_id = b.id LEFT JOIN shop_store_warehouse w ON a.warehouse_id = w.id LEFT JOIN shop_store_rider c ON a.rider_id = c.user_id LEFT JOIN gxwebsoft_core.sys_user u ON a.user_id = u.user_id LEFT JOIN shop_user_address d ON a.address_id = d.id + LEFT JOIN glt_user_ticket f ON a.user_ticket_id = f.id + LEFT JOIN shop_order o ON f.order_id = o.order_id AND f.tenant_id = o.tenant_id AND o.deleted = 0 diff --git a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java index 004abf2..2751277 100644 --- a/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java +++ b/src/main/java/com/gxwebsoft/glt/service/GltTicketIssueService.java @@ -168,9 +168,6 @@ public class GltTicketIssueService { new LambdaUpdateWrapper() .eq(ShopOrder::getOrderId, order.getOrderId()) .eq(ShopOrder::getTenantId, tenantId) - .set(ShopOrder::getOrderStatus, 1) - // 同步更新发货状态为“已发货” - .set(ShopOrder::getDeliveryStatus, 20) .set(ShopOrder::getHasTakeGift, true) .set(ShopOrder::getUpdateTime, now) ); diff --git a/src/main/java/com/gxwebsoft/glt/service/GltTicketOrderService.java b/src/main/java/com/gxwebsoft/glt/service/GltTicketOrderService.java index 92511a0..9f3fc81 100644 --- a/src/main/java/com/gxwebsoft/glt/service/GltTicketOrderService.java +++ b/src/main/java/com/gxwebsoft/glt/service/GltTicketOrderService.java @@ -64,6 +64,20 @@ public interface GltTicketOrderService extends IService { */ void accept(Integer id, Integer riderId, Integer tenantId); + /** + * 指派/接单成功后,同步关联商城订单发货状态为“已发货”(deliveryStatus=20)。 + * + *

用于后台指派配送员(不走接单接口)等场景的状态兜底同步。

+ */ + void markShopOrderShippedAfterRiderAssigned(Integer ticketOrderId, Integer tenantId, Integer riderId); + + /** + * 送水订单完成后,同步关联商城订单为“已完成”(orderStatus=1)。 + * + *

用于后台直接改 deliveryStatus=40 等不经过 confirmReceive/autoConfirmTimeout 的兜底同步。

+ */ + void markShopOrderCompletedAfterTicketFinished(Integer ticketOrderId, Integer tenantId); + /** * 配送员开始配送:10 -> 20,并写 sendStartTime。 */ diff --git a/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java b/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java index 82f8f08..cca8400 100644 --- a/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java +++ b/src/main/java/com/gxwebsoft/glt/service/impl/GltTicketOrderServiceImpl.java @@ -1,6 +1,8 @@ package com.gxwebsoft.glt.service.impl; import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.gxwebsoft.common.core.exception.BusinessException; import com.gxwebsoft.common.core.web.PageParam; @@ -18,8 +20,12 @@ import com.gxwebsoft.glt.service.GltUserTicketLogService; import com.gxwebsoft.glt.service.GltUserTicketService; import com.gxwebsoft.shop.entity.ShopDealerCapital; import com.gxwebsoft.shop.entity.ShopDealerUser; +import com.gxwebsoft.shop.entity.ShopOrder; +import com.gxwebsoft.shop.entity.ShopOrderGoods; import com.gxwebsoft.shop.service.ShopDealerCapitalService; import com.gxwebsoft.shop.service.ShopDealerUserService; +import com.gxwebsoft.shop.service.ShopOrderGoodsService; +import com.gxwebsoft.shop.service.ShopOrderService; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; import org.springframework.stereotype.Service; @@ -70,6 +76,12 @@ public class GltTicketOrderServiceImpl extends ServiceImpl pageRel(GltTicketOrderParam param) { PageParam page = new PageParam<>(param); @@ -192,6 +204,7 @@ public class GltTicketOrderServiceImpl extends ServiceImpl w.isNull(GltTicketOrder::getRiderId).or().eq(GltTicketOrder::getRiderId, 0)) .update(); if (ok) { + // 接单成功后,同步商城订单发货状态:10未发货 -> 20已发货 + updateShopOrderDeliveryStatusAfterAccept(id, tenantId, riderId, now); return; } @@ -233,6 +249,146 @@ public class GltTicketOrderServiceImpl extends ServiceImpl 0) ? riderId : ticketOrder.getRiderId(); + + GltUserTicket userTicket = gltUserTicketService.getOne( + new LambdaQueryWrapper() + .eq(GltUserTicket::getTenantId, tenantId) + .eq(GltUserTicket::getDeleted, 0) + .eq(GltUserTicket::getId, ticketOrder.getUserTicketId()) + .last("limit 1") + ); + if (userTicket == null) { + return; + } + + Integer shopOrderId = userTicket.getOrderId(); + String shopOrderNo = userTicket.getOrderNo(); + boolean resolvedByOrderGoodsId = false; + // 兼容历史数据:部分水票可能只写了 orderGoodsId(未写 orderId/orderNo),此处兜底通过 orderGoodsId 反查 ShopOrder.orderId。 + if (shopOrderId == null && !StringUtils.hasText(shopOrderNo) && userTicket.getOrderGoodsId() != null) { + ShopOrderGoods og = shopOrderGoodsService.getOne( + new LambdaQueryWrapper() + .select(ShopOrderGoods::getOrderId) + .eq(ShopOrderGoods::getTenantId, tenantId) + .eq(ShopOrderGoods::getId, userTicket.getOrderGoodsId()) + .last("limit 1") + ); + if (og != null) { + shopOrderId = og.getOrderId(); + resolvedByOrderGoodsId = shopOrderId != null; + } + } + if (shopOrderId == null && !StringUtils.hasText(shopOrderNo)) { + log.warn("同步商城订单发货状态失败:未找到关联商城订单 - tenantId={}, ticketOrderId={}, userTicketId={}, userTicket.orderId={}, userTicket.orderNo={}, userTicket.orderGoodsId={}", + tenantId, ticketOrderId, userTicket.getId(), userTicket.getOrderId(), userTicket.getOrderNo(), userTicket.getOrderGoodsId()); + return; + } + // 若是通过 orderGoodsId 兜底反查到 orderId,则顺便回填 glt_user_ticket.order_id/order_no,减少后续同步/查询依赖兜底分支。 + if (resolvedByOrderGoodsId && userTicket.getOrderId() == null && shopOrderId != null) { + if (!StringUtils.hasText(shopOrderNo)) { + ShopOrder order = shopOrderService.getOne( + new LambdaQueryWrapper() + .select(ShopOrder::getOrderNo) + .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getDeleted, 0) + .eq(ShopOrder::getOrderId, shopOrderId) + .last("limit 1") + ); + if (order != null) { + shopOrderNo = order.getOrderNo(); + } + } + + LambdaUpdateWrapper backfill = new LambdaUpdateWrapper() + .eq(GltUserTicket::getTenantId, tenantId) + .eq(GltUserTicket::getDeleted, 0) + .eq(GltUserTicket::getId, userTicket.getId()); + backfill.set(GltUserTicket::getOrderId, shopOrderId); + if (!StringUtils.hasText(userTicket.getOrderNo()) && StringUtils.hasText(shopOrderNo)) { + backfill.set(GltUserTicket::getOrderNo, shopOrderNo); + } + backfill.set(GltUserTicket::getUpdateTime, now); + try { + gltUserTicketService.update(backfill); + } catch (Exception e) { + log.debug("回填水票关联商城订单信息失败(不影响主流程) - tenantId={}, userTicketId={}, orderId={}, orderNo={}", + tenantId, userTicket.getId(), shopOrderId, shopOrderNo, e); + } + } + + LambdaUpdateWrapper uw = new LambdaUpdateWrapper() + .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getDeleted, 0) + // deliveryStatus 已经是 20 时也可能需要补写/更新 riderId,因此条件包含 riderId 不一致的场景 + .and(w -> { + w.ne(ShopOrder::getDeliveryStatus, 20).or().isNull(ShopOrder::getDeliveryStatus); + if (actualRiderId != null && actualRiderId > 0) { + w.or().ne(ShopOrder::getRiderId, actualRiderId).or().isNull(ShopOrder::getRiderId); + } + }) + .set(ShopOrder::getDeliveryStatus, 20) + .set(ShopOrder::getUpdateTime, now); + if (actualRiderId != null && actualRiderId > 0) { + uw.set(ShopOrder::getRiderId, actualRiderId); + } + if (shopOrderId != null) { + uw.eq(ShopOrder::getOrderId, shopOrderId); + } else { + uw.eq(ShopOrder::getOrderNo, shopOrderNo); + } + + boolean updated = shopOrderService.update(uw); + if (updated) { + return; + } + + // 幂等:若已是 20,则视为成功;否则记录日志便于排查关联关系/数据缺失 + LambdaQueryWrapper qw = new LambdaQueryWrapper() + .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getDeleted, 0) + .eq(ShopOrder::getDeliveryStatus, 20); + if (actualRiderId != null && actualRiderId > 0) { + qw.eq(ShopOrder::getRiderId, actualRiderId); + } + if (shopOrderId != null) { + qw.eq(ShopOrder::getOrderId, shopOrderId); + } else { + qw.eq(ShopOrder::getOrderNo, shopOrderNo); + } + if (shopOrderService.count(qw) <= 0) { + log.warn("接单/指派成功但同步商城订单发货状态/配送员失败 - tenantId={}, ticketOrderId={}, riderId={}, shopOrderId={}, shopOrderNo={}", + tenantId, ticketOrderId, actualRiderId, shopOrderId, shopOrderNo); + } + } + @Override public void start(Integer id, Integer riderId, Integer tenantId) { if (id == null) { @@ -310,6 +466,8 @@ public class GltTicketOrderServiceImpl extends ServiceImpl() + .eq(GltUserTicket::getTenantId, tenantId) + .eq(GltUserTicket::getDeleted, 0) + .eq(GltUserTicket::getId, ticketOrder.getUserTicketId()) + .last("limit 1") + ); + if (userTicket == null) { + return; + } + + Integer shopOrderId = userTicket.getOrderId(); + String shopOrderNo = userTicket.getOrderNo(); + boolean resolvedByOrderGoodsId = false; + // 兼容历史数据:部分水票可能只写了 orderGoodsId(未写 orderId/orderNo),此处兜底通过 orderGoodsId 反查 ShopOrder.orderId。 + if (shopOrderId == null && !StringUtils.hasText(shopOrderNo) && userTicket.getOrderGoodsId() != null) { + ShopOrderGoods og = shopOrderGoodsService.getOne( + new LambdaQueryWrapper() + .select(ShopOrderGoods::getOrderId) + .eq(ShopOrderGoods::getTenantId, tenantId) + .eq(ShopOrderGoods::getId, userTicket.getOrderGoodsId()) + .last("limit 1") + ); + if (og != null) { + shopOrderId = og.getOrderId(); + resolvedByOrderGoodsId = shopOrderId != null; + } + } + if (shopOrderId == null && !StringUtils.hasText(shopOrderNo)) { + log.warn("送水订单完成但未找到关联商城订单,无法同步完成状态 - tenantId={}, ticketOrderId={}, userTicketId={}, userTicket.orderId={}, userTicket.orderNo={}, userTicket.orderGoodsId={}", + tenantId, ticketOrderId, userTicket.getId(), userTicket.getOrderId(), userTicket.getOrderNo(), userTicket.getOrderGoodsId()); + return; + } + // 若是通过 orderGoodsId 兜底反查到 orderId,则顺便回填 glt_user_ticket.order_id/order_no,减少后续同步/查询依赖兜底分支。 + if (resolvedByOrderGoodsId && userTicket.getOrderId() == null && shopOrderId != null) { + if (!StringUtils.hasText(shopOrderNo)) { + ShopOrder order = shopOrderService.getOne( + new LambdaQueryWrapper() + .select(ShopOrder::getOrderNo) + .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getDeleted, 0) + .eq(ShopOrder::getOrderId, shopOrderId) + .last("limit 1") + ); + if (order != null) { + shopOrderNo = order.getOrderNo(); + } + } + + LambdaUpdateWrapper backfill = new LambdaUpdateWrapper() + .eq(GltUserTicket::getTenantId, tenantId) + .eq(GltUserTicket::getDeleted, 0) + .eq(GltUserTicket::getId, userTicket.getId()); + backfill.set(GltUserTicket::getOrderId, shopOrderId); + if (!StringUtils.hasText(userTicket.getOrderNo()) && StringUtils.hasText(shopOrderNo)) { + backfill.set(GltUserTicket::getOrderNo, shopOrderNo); + } + backfill.set(GltUserTicket::getUpdateTime, now); + try { + gltUserTicketService.update(backfill); + } catch (Exception e) { + log.debug("回填水票关联商城订单信息失败(不影响主流程) - tenantId={}, userTicketId={}, orderId={}, orderNo={}", + tenantId, userTicket.getId(), shopOrderId, shopOrderNo, e); + } + } + + LambdaUpdateWrapper uw = new LambdaUpdateWrapper() + .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getDeleted, 0) + .and(w -> w.ne(ShopOrder::getOrderStatus, 1).or().isNull(ShopOrder::getOrderStatus)) + .set(ShopOrder::getOrderStatus, 1) + .set(ShopOrder::getUpdateTime, now); + if (shopOrderId != null) { + uw.eq(ShopOrder::getOrderId, shopOrderId); + } else { + uw.eq(ShopOrder::getOrderNo, shopOrderNo); + } + + boolean updated = shopOrderService.update(uw); + if (updated) { + return; + } + + // 幂等:若已是 1,则视为成功;否则记录日志便于排查关联关系/数据缺失 + LambdaQueryWrapper qw = new LambdaQueryWrapper() + .eq(ShopOrder::getTenantId, tenantId) + .eq(ShopOrder::getDeleted, 0) + .eq(ShopOrder::getOrderStatus, 1); + if (shopOrderId != null) { + qw.eq(ShopOrder::getOrderId, shopOrderId); + } else { + qw.eq(ShopOrder::getOrderNo, shopOrderNo); + } + if (shopOrderService.count(qw) <= 0) { + log.warn("送水订单完成但同步商城订单完成状态失败 - tenantId={}, ticketOrderId={}, shopOrderId={}, shopOrderNo={}", + tenantId, ticketOrderId, shopOrderId, shopOrderNo); + } + } + private void settleRiderCommissionIfEligible(Integer ticketOrderId, Integer tenantId, boolean requirePhoto) { if (ticketOrderId == null || tenantId == null) { return; diff --git a/src/main/java/com/gxwebsoft/glt/task/DealerCommissionUnfreeze10584Task.java b/src/main/java/com/gxwebsoft/glt/task/DealerCommissionUnfreeze10584Task.java index 7fc6d1f..79f1438 100644 --- a/src/main/java/com/gxwebsoft/glt/task/DealerCommissionUnfreeze10584Task.java +++ b/src/main/java/com/gxwebsoft/glt/task/DealerCommissionUnfreeze10584Task.java @@ -1,6 +1,7 @@ package com.gxwebsoft.glt.task; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.gxwebsoft.common.core.annotation.IgnoreTenant; import com.gxwebsoft.glt.entity.GltTicketOrder; import com.gxwebsoft.glt.entity.GltTicketTemplate; @@ -9,9 +10,11 @@ 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.ShopDealerOrder; import com.gxwebsoft.shop.entity.ShopDealerUser; import com.gxwebsoft.shop.entity.ShopOrder; import com.gxwebsoft.shop.service.ShopDealerCapitalService; +import com.gxwebsoft.shop.service.ShopDealerOrderService; import com.gxwebsoft.shop.service.ShopDealerUserService; import com.gxwebsoft.shop.service.ShopOrderService; import lombok.extern.slf4j.Slf4j; @@ -65,6 +68,9 @@ public class DealerCommissionUnfreeze10584Task { @Resource private ShopDealerCapitalService shopDealerCapitalService; + @Resource + private ShopDealerOrderService shopDealerOrderService; + @Resource private ShopDealerUserService shopDealerUserService; @@ -79,7 +85,7 @@ public class DealerCommissionUnfreeze10584Task { private final AtomicBoolean running = new AtomicBoolean(false); - @Scheduled(cron = "${dealer.commission.unfreeze10584.cron:0 */1 * * * ?}") + @Scheduled(cron = "${dealer.commission.unfreeze10584.cron:0/30 * * * * ?}") @IgnoreTenant("定时任务无登录态,需忽略租户隔离;内部使用 tenantId=10584 精确过滤") public void run() { if (!running.compareAndSet(false, true)) { @@ -355,12 +361,56 @@ public class DealerCommissionUnfreeze10584Task { marker.setUpdateTime(now); shopDealerCapitalService.save(marker); + // 佣金全部解冻完成后,将分销订单状态置为“已解冻”(0)。 + // 以当前任务生成的 flowType=50 marker 数量作为完成度判断,避免提前将订单置为已解冻。 + setDealerOrderUnfrozenIfCompleted(orderNo, now); + log.info("佣金解冻成功 - tenantId={}, orderNo={}, dealerUserId={}, amount={}, capitalId={}", TENANT_ID, orderNo, dealerUserId, amount, capitalId); return true; })); } + private void setDealerOrderUnfrozenIfCompleted(String orderNo, LocalDateTime now) { + if (orderNo == null || orderNo.isBlank()) { + return; + } + + long totalCommissions = shopDealerCapitalService.count( + new LambdaQueryWrapper() + .eq(ShopDealerCapital::getTenantId, TENANT_ID) + .eq(ShopDealerCapital::getFlowType, 10) + .eq(ShopDealerCapital::getOrderNo, orderNo) + ); + if (totalCommissions <= 0) { + return; + } + + long unfrozenMarkers = shopDealerCapitalService.count( + new LambdaQueryWrapper() + .eq(ShopDealerCapital::getTenantId, TENANT_ID) + .eq(ShopDealerCapital::getFlowType, 50) + .eq(ShopDealerCapital::getOrderNo, orderNo) + .like(ShopDealerCapital::getComments, "佣金解冻(capitalId=") + ); + + if (unfrozenMarkers < totalCommissions) { + return; + } + + boolean updated = shopDealerOrderService.update( + new LambdaUpdateWrapper() + .eq(ShopDealerOrder::getTenantId, TENANT_ID) + .eq(ShopDealerOrder::getOrderNo, orderNo) + .set(ShopDealerOrder::getIsUnfreeze, 1) + .set(ShopDealerOrder::getUnfreezeTime, now) + .set(ShopDealerOrder::getUpdateTime, now) + ); + if (!updated) { + log.warn("已完成佣金解冻,但更新分销订单isUnfreeze失败/无记录 - tenantId={}, orderNo={}", TENANT_ID, orderNo); + } + } + private String buildUnfreezeMarkerComment(Integer capitalId) { return "佣金解冻(capitalId=" + capitalId + ")"; } diff --git a/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java b/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java index 6ccfb49..d74f4c1 100644 --- a/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java +++ b/src/main/java/com/gxwebsoft/glt/task/DealerOrderSettlement10584Task.java @@ -38,7 +38,7 @@ import java.util.*; /** * 租户10584:分销订单结算任务 *

- * 每20秒执行一次,查询“已付款且未结算”的订单,按指定规则计算佣金并先计入分销商冻结金额(freezeMoney),并将订单置为已结算。 + * 每10秒执行一次,查询“已付款且未结算”的订单,按指定规则计算佣金并先计入分销商冻结金额(freezeMoney),并将订单置为已结算。 */ @Slf4j @Component @@ -88,9 +88,9 @@ public class DealerOrderSettlement10584Task { private UserMapper userMapper; /** - * 每30秒执行一次。 + * 每10秒执行一次。 */ - @Scheduled(cron = "0/20 * * * * ?") + @Scheduled(cron = "0/10 * * * * ?") @IgnoreTenant("该定时任务仅处理租户10584,但需要显式按tenantId过滤,避免定时任务线程无租户上下文导致查询异常") public void settleTenant10584Orders() { try { diff --git a/src/main/java/com/gxwebsoft/glt/task/GltTicketIssue10584Task.java b/src/main/java/com/gxwebsoft/glt/task/GltTicketIssue10584Task.java index bb34a8d..8dd9f28 100644 --- a/src/main/java/com/gxwebsoft/glt/task/GltTicketIssue10584Task.java +++ b/src/main/java/com/gxwebsoft/glt/task/GltTicketIssue10584Task.java @@ -16,7 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * GLT 套票发放任务: - * - 每分钟扫描一次今日订单(tenantId=10584, formId in 套票模板 goodsId, payStatus=1, orderStatus=0) + * - 每30秒扫描一次今日订单(tenantId=10584, formId in 套票模板 goodsId, payStatus=1, orderStatus=0) * - 为订单生成用户套票账户 + 释放计划(幂等) * - 若模板配置了 startSendQty,则发放时自动核销对应数量(用于“第一次送水”场景) */ @@ -33,7 +33,7 @@ public class GltTicketIssue10584Task { private final AtomicBoolean running = new AtomicBoolean(false); - @Scheduled(cron = "${glt.ticket.issue10584.cron:0 */1 * * * ?}") + @Scheduled(cron = "${glt.ticket.issue10584.cron:0/15 * * * * ?}") @IgnoreTenant("定时任务无登录态,需忽略租户隔离;内部使用 tenantId=10584 精确过滤") public void run() { if (!running.compareAndSet(false, true)) { diff --git a/src/main/java/com/gxwebsoft/glt/task/GltTicketOrderAutoConfirm10584Task.java b/src/main/java/com/gxwebsoft/glt/task/GltTicketOrderAutoConfirm10584Task.java index a6f54c4..4866fce 100644 --- a/src/main/java/com/gxwebsoft/glt/task/GltTicketOrderAutoConfirm10584Task.java +++ b/src/main/java/com/gxwebsoft/glt/task/GltTicketOrderAutoConfirm10584Task.java @@ -34,7 +34,7 @@ public class GltTicketOrderAutoConfirm10584Task { private final AtomicBoolean running = new AtomicBoolean(false); - @Scheduled(cron = "${glt.ticket-order.auto-confirm10584.cron:0 */1 * * * ?}") + @Scheduled(cron = "${glt.ticket-order.auto-confirm10584.cron:0/33 * * * * ?}") @IgnoreTenant("定时任务无登录态,需忽略租户隔离;内部使用 tenantId=10584 精确过滤") public void run() { if (!running.compareAndSet(false, true)) { @@ -53,4 +53,3 @@ public class GltTicketOrderAutoConfirm10584Task { } } } - diff --git a/src/main/java/com/gxwebsoft/glt/task/GltUserTicketAutoReleaseTask.java b/src/main/java/com/gxwebsoft/glt/task/GltUserTicketAutoReleaseTask.java index 4450929..91855db 100644 --- a/src/main/java/com/gxwebsoft/glt/task/GltUserTicketAutoReleaseTask.java +++ b/src/main/java/com/gxwebsoft/glt/task/GltUserTicketAutoReleaseTask.java @@ -30,7 +30,7 @@ public class GltUserTicketAutoReleaseTask { private final AtomicBoolean running = new AtomicBoolean(false); - @Scheduled(cron = "${glt.ticket.auto-release.cron:0 */1 * * * ?}") + @Scheduled(cron = "${glt.ticket.auto-release.cron:0 */10 * * * ?}") @IgnoreTenant("定时任务无登录态,需忽略租户隔离;释放记录自带 tenantId,更新时会校验 tenantId") public void run() { if (!running.compareAndSet(false, true)) { diff --git a/src/main/java/com/gxwebsoft/shop/controller/ShopUserController.java b/src/main/java/com/gxwebsoft/shop/controller/ShopUserController.java index 10f53a5..7926f98 100644 --- a/src/main/java/com/gxwebsoft/shop/controller/ShopUserController.java +++ b/src/main/java/com/gxwebsoft/shop/controller/ShopUserController.java @@ -1,14 +1,14 @@ package com.gxwebsoft.shop.controller; -import com.gxwebsoft.common.core.annotation.OperationLog; -import com.gxwebsoft.common.core.web.ApiResult; import com.gxwebsoft.common.core.web.BaseController; -import com.gxwebsoft.common.core.web.BatchParam; -import com.gxwebsoft.common.core.web.PageResult; -import com.gxwebsoft.common.system.entity.User; +import com.gxwebsoft.shop.service.ShopUserService; import com.gxwebsoft.shop.entity.ShopUser; import com.gxwebsoft.shop.param.ShopUserParam; -import com.gxwebsoft.shop.service.ShopUserService; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.PageResult; +import com.gxwebsoft.common.core.web.PageParam; +import com.gxwebsoft.common.core.web.BatchParam; +import com.gxwebsoft.common.core.annotation.OperationLog; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.security.access.prepost.PreAuthorize; @@ -21,7 +21,7 @@ import java.util.List; * 用户记录表控制器 * * @author 科技小王子 - * @since 2025-10-03 13:41:09 + * @since 2026-02-10 00:37:07 */ @Tag(name = "用户记录表管理") @RestController @@ -46,11 +46,12 @@ public class ShopUserController extends BaseController { return success(shopUserService.listRel(param)); } - @Operation(summary = "根据userId查询用户记录表") - @GetMapping("/{userId}") - public ApiResult get(@PathVariable("userId") Integer userId) { + @PreAuthorize("hasAuthority('shop:shopUser:list')") + @Operation(summary = "根据id查询用户记录表") + @GetMapping("/{id}") + public ApiResult get(@PathVariable("id") Integer id) { // 使用关联查询 - return success(shopUserService.getByIdRel(userId)); + return success(shopUserService.getByIdRel(id)); } @PreAuthorize("hasAuthority('shop:shopUser:save')") @@ -59,10 +60,10 @@ public class ShopUserController extends BaseController { @PostMapping() public ApiResult save(@RequestBody ShopUser shopUser) { // 记录当前登录用户id - User loginUser = getLoginUser(); - if (loginUser != null) { - shopUser.setUserId(loginUser.getUserId()); - } + // User loginUser = getLoginUser(); + // if (loginUser != null) { + // shopUser.setUserId(loginUser.getUserId()); + // } if (shopUserService.save(shopUser)) { return success("添加成功"); } diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java index 32be34d..e3c0c74 100644 --- a/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopDealerOrder.java @@ -120,9 +120,15 @@ public class ShopDealerOrder implements Serializable { @Schema(description = "佣金结算(0未结算 1已结算)") private Integer isSettled; + @Schema(description = "佣金冻结(1解冻中 0已解冻)") + private Integer isUnfreeze; + @Schema(description = "结算时间") private LocalDateTime settleTime; + @Schema(description = "解冻时间") + private LocalDateTime unfreezeTime; + @Schema(description = "备注") private String comments; diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java b/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java index 9d3f303..7fe522f 100644 --- a/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopGoods.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonFormat; import java.io.Serializable; import io.swagger.v3.oas.annotations.media.Schema; @@ -101,6 +102,7 @@ public class ShopGoods implements Serializable { private BigDecimal secondDividend; @Schema(description = "库存计算方式(10下单减库存 20付款减库存)") + @JsonAlias({"cdeductStockType"}) private Integer deductStockType; @Schema(description = "交付方式(0不启用)") diff --git a/src/main/java/com/gxwebsoft/shop/entity/ShopUser.java b/src/main/java/com/gxwebsoft/shop/entity/ShopUser.java index f540f64..cd90cb4 100644 --- a/src/main/java/com/gxwebsoft/shop/entity/ShopUser.java +++ b/src/main/java/com/gxwebsoft/shop/entity/ShopUser.java @@ -1,37 +1,34 @@ package com.gxwebsoft.shop.entity; +import java.math.BigDecimal; import com.baomidou.mybatisplus.annotation.IdType; +import java.time.LocalDate; import com.baomidou.mybatisplus.annotation.TableId; +import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.TableLogic; +import java.io.Serializable; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; +import com.fasterxml.jackson.annotation.JsonFormat; /** * 用户记录表 * * @author 科技小王子 - * @since 2025-10-03 13:41:09 + * @since 2026-02-10 00:37:07 */ @Data @EqualsAndHashCode(callSuper = false) -@Schema(description = "用户记录表") +@Schema(name = "ShopUser对象", description = "用户记录表") public class ShopUser implements Serializable { private static final long serialVersionUID = 1L; - @Schema(description = "id") - @TableId(value = "id", type = IdType.AUTO) - private Integer id; - @Schema(description = "用户id") + @TableId(value = "user_id", type = IdType.AUTO) private Integer userId; - @Schema(description = "用户类型 0个人用户 1企业用户 2其他") + @Schema(description = "用户类型 0普通用户 1企业用户 2特殊用户") private Integer type; @Schema(description = "账号") @@ -71,6 +68,7 @@ public class ShopUser implements Serializable { private String idCard; @Schema(description = "出生日期") + @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate birthday; @Schema(description = "所在国家") @@ -143,7 +141,7 @@ public class ShopUser implements Serializable { private Integer age; @Schema(description = "是否线下会员") - private Boolean offline; + private Integer offline; @Schema(description = "关注数") private Integer followers; @@ -178,6 +176,9 @@ public class ShopUser implements Serializable { @Schema(description = "是否管理员") private Boolean isAdmin; + @Schema(description = "默认账号(适用于不同租户存在相同的手机号码)") + private Boolean isDefault; + @Schema(description = "是否企业管理员") private Boolean isOrganizationAdmin; @@ -209,6 +210,7 @@ public class ShopUser implements Serializable { private Integer expireTime; @Schema(description = "最后结算时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime settlementTime; @Schema(description = "资质") @@ -246,9 +248,11 @@ public class ShopUser implements Serializable { private Integer tenantId; @Schema(description = "注册时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; @Schema(description = "修改时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; } diff --git a/src/main/java/com/gxwebsoft/shop/mapper/ShopUserMapper.java b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserMapper.java index e7a09c4..d6c093f 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/ShopUserMapper.java +++ b/src/main/java/com/gxwebsoft/shop/mapper/ShopUserMapper.java @@ -12,7 +12,7 @@ import java.util.List; * 用户记录表Mapper * * @author 科技小王子 - * @since 2025-10-03 13:41:09 + * @since 2026-02-10 00:37:07 */ public interface ShopUserMapper extends BaseMapper { diff --git a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserMapper.xml b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserMapper.xml index 0ccbb57..fa3f83c 100644 --- a/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserMapper.xml +++ b/src/main/java/com/gxwebsoft/shop/mapper/xml/ShopUserMapper.xml @@ -7,9 +7,6 @@ SELECT a.* FROM shop_user a - - AND a.id = #{param.id} - AND a.user_id = #{param.userId} @@ -160,6 +157,9 @@ AND a.is_admin = #{param.isAdmin} + + AND a.is_default = #{param.isDefault} + AND a.is_organization_admin = #{param.isOrganizationAdmin} diff --git a/src/main/java/com/gxwebsoft/shop/param/ShopUserParam.java b/src/main/java/com/gxwebsoft/shop/param/ShopUserParam.java index 2b3ac02..bceec72 100644 --- a/src/main/java/com/gxwebsoft/shop/param/ShopUserParam.java +++ b/src/main/java/com/gxwebsoft/shop/param/ShopUserParam.java @@ -1,37 +1,32 @@ package com.gxwebsoft.shop.param; -import com.fasterxml.jackson.annotation.JsonInclude; +import java.math.BigDecimal; import com.gxwebsoft.common.core.annotation.QueryField; import com.gxwebsoft.common.core.annotation.QueryType; import com.gxwebsoft.common.core.web.BaseParam; +import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -import java.math.BigDecimal; - /** * 用户记录表查询参数 * * @author 科技小王子 - * @since 2025-10-03 13:41:08 + * @since 2026-02-10 00:37:06 */ @Data @EqualsAndHashCode(callSuper = false) @JsonInclude(JsonInclude.Include.NON_NULL) -@Schema(description = "用户记录表查询参数") +@Schema(name = "ShopUserParam对象", description = "用户记录表查询参数") public class ShopUserParam extends BaseParam { private static final long serialVersionUID = 1L; - @Schema(description = "id") - @QueryField(type = QueryType.EQ) - private Integer id; - @Schema(description = "用户id") @QueryField(type = QueryType.EQ) private Integer userId; - @Schema(description = "用户类型 0个人用户 1企业用户 2其他") + @Schema(description = "用户类型 0普通用户 1企业用户 2特殊用户") @QueryField(type = QueryType.EQ) private Integer type; @@ -157,7 +152,7 @@ public class ShopUserParam extends BaseParam { @Schema(description = "是否线下会员") @QueryField(type = QueryType.EQ) - private Boolean offline; + private Integer offline; @Schema(description = "关注数") @QueryField(type = QueryType.EQ) @@ -199,6 +194,10 @@ public class ShopUserParam extends BaseParam { @QueryField(type = QueryType.EQ) private Boolean isAdmin; + @Schema(description = "默认账号(适用于不同租户存在相同的手机号码)") + @QueryField(type = QueryType.EQ) + private Boolean isDefault; + @Schema(description = "是否企业管理员") @QueryField(type = QueryType.EQ) private Boolean isOrganizationAdmin; diff --git a/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java b/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java index 337b32c..ed8b43c 100644 --- a/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java +++ b/src/main/java/com/gxwebsoft/shop/service/OrderBusinessService.java @@ -32,6 +32,8 @@ import java.util.Map; @Slf4j @Service public class OrderBusinessService { + private static final int DEDUCT_STOCK_TYPE_ORDER = 10; // 下单减库存 + private static final int DEDUCT_STOCK_TYPE_PAY = 20; // 付款减库存 @Resource private ShopOrderService shopOrderService; @@ -713,6 +715,15 @@ public class OrderBusinessService { */ private void deductStock(OrderCreateRequest request) { for (OrderCreateRequest.OrderGoodsItem item : request.getGoodsItems()) { + // 付款减库存的商品:创建订单时不扣库存 + ShopGoods goodsForType = shopGoodsService.getById(item.getGoodsId()); + Integer deductStockType = goodsForType != null ? goodsForType.getDeductStockType() : null; + if (deductStockType != null && deductStockType == DEDUCT_STOCK_TYPE_PAY) { + log.debug("跳过下单扣库存(付款减库存) - goodsId={}, skuId={}, qty={}", + item.getGoodsId(), item.getSkuId(), item.getQuantity()); + continue; + } + if (item.getSkuId() != null) { // 多规格商品,扣减SKU库存 ShopGoodsSku sku = shopGoodsSkuService.getById(item.getSkuId()); diff --git a/src/main/java/com/gxwebsoft/shop/service/ShopUserService.java b/src/main/java/com/gxwebsoft/shop/service/ShopUserService.java index 52d8fba..03335b3 100644 --- a/src/main/java/com/gxwebsoft/shop/service/ShopUserService.java +++ b/src/main/java/com/gxwebsoft/shop/service/ShopUserService.java @@ -11,7 +11,7 @@ import java.util.List; * 用户记录表Service * * @author 科技小王子 - * @since 2025-10-03 13:41:09 + * @since 2026-02-10 00:37:07 */ public interface ShopUserService extends IService { diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java index 46901d2..f327838 100644 --- a/src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java +++ b/src/main/java/com/gxwebsoft/shop/service/impl/OrderCancelServiceImpl.java @@ -24,6 +24,7 @@ import java.util.List; @Slf4j @Service public class OrderCancelServiceImpl implements OrderCancelService { + private static final int DEDUCT_STOCK_TYPE_PAY = 20; // 付款减库存:下单不扣库存,因此未支付取消无需回退 @Autowired private ShopOrderService shopOrderService; @@ -182,6 +183,20 @@ public class OrderCancelServiceImpl implements OrderCancelService { } for (ShopOrderGoods orderGood : orderGoods) { + // 付款减库存的商品:创建订单时未扣库存,未支付取消时无需回退(避免库存被“加多”) + try { + ShopGoods goods = shopGoodsService.getById(orderGood.getGoodsId()); + if (goods != null && goods.getDeductStockType() != null && goods.getDeductStockType() == DEDUCT_STOCK_TYPE_PAY) { + log.debug("跳过未支付取消的库存回退(付款减库存) - orderId={}, goodsId={}, skuId={}, qty={}", + order.getOrderId(), orderGood.getGoodsId(), orderGood.getSkuId(), orderGood.getTotalNum()); + continue; + } + } catch (Exception e) { + // 查不到商品或查询异常时,保持原有回退逻辑,避免出现“库存无法回退”的更坏情况 + log.warn("读取商品扣库存方式失败,继续执行库存回退 - orderId={}, goodsId={}", + order.getOrderId(), orderGood.getGoodsId(), e); + } + if (orderGood.getSkuId() != null && orderGood.getSkuId() > 0) { // 多规格商品,恢复SKU库存 restoreSkuStock(orderGood); diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java index 24322b7..9f91ffb 100644 --- a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java @@ -1,8 +1,10 @@ package com.gxwebsoft.shop.service.impl; import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gxwebsoft.common.core.context.TenantContext; import com.gxwebsoft.common.core.config.ConfigProperties; import com.gxwebsoft.common.core.config.CertificateProperties; import com.gxwebsoft.common.core.utils.*; @@ -86,10 +88,13 @@ public class ShopOrderServiceImpl extends ServiceImpl 0) { markCouponAsUsed(order); @@ -816,6 +829,90 @@ public class ShopOrderServiceImpl extends ServiceImpl orderGoodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId()); + if (CollectionUtils.isEmpty(orderGoodsList)) { + return; + } + + TenantContext.runIgnoreTenant(() -> { + for (ShopOrderGoods og : orderGoodsList) { + if (og == null || og.getGoodsId() == null) { + continue; + } + int qty = og.getTotalNum() == null ? 0 : og.getTotalNum(); + if (qty <= 0) { + continue; + } + + ShopGoods goods = shopGoodsService.getById(og.getGoodsId()); + Integer deductStockType = goods != null ? goods.getDeductStockType() : null; + if (deductStockType == null || deductStockType != DEDUCT_STOCK_TYPE_PAY) { + continue; + } + + try { + if (og.getSkuId() != null && og.getSkuId() > 0) { + // 多规格:扣 SKU 库存 + boolean updated = shopGoodsSkuService.update( + new LambdaUpdateWrapper() + .eq(ShopGoodsSku::getId, og.getSkuId()) + .eq(ShopGoodsSku::getTenantId, order.getTenantId()) + .apply("IFNULL(stock,0) >= {0}", qty) + .setSql("stock = IFNULL(stock,0) - " + qty) + ); + if (!updated) { + log.warn("支付成功后扣SKU库存失败 - tenantId={}, orderId={}, skuId={}, goodsId={}, qty={}", + order.getTenantId(), order.getOrderId(), og.getSkuId(), og.getGoodsId(), qty); + // 兜底:库存不足时至少将库存置0,避免出现“明明已售出但库存仍大于0”的情况 + shopGoodsSkuService.update( + new LambdaUpdateWrapper() + .eq(ShopGoodsSku::getId, og.getSkuId()) + .eq(ShopGoodsSku::getTenantId, order.getTenantId()) + .apply("IFNULL(stock,0) < {0}", qty) + .setSql("stock = 0") + ); + } + } else { + // 单规格:扣商品库存 + boolean updated = shopGoodsService.update( + new LambdaUpdateWrapper() + .eq(ShopGoods::getGoodsId, og.getGoodsId()) + .eq(ShopGoods::getTenantId, order.getTenantId()) + .apply("IFNULL(stock,0) >= {0}", qty) + .setSql("stock = IFNULL(stock,0) - " + qty) + ); + if (!updated) { + log.warn("支付成功后扣商品库存失败 - tenantId={}, orderId={}, goodsId={}, qty={}", + order.getTenantId(), order.getOrderId(), og.getGoodsId(), qty); + // 兜底:库存不足时至少将库存置0,避免出现“明明已售出但库存仍大于0”的情况 + shopGoodsService.update( + new LambdaUpdateWrapper() + .eq(ShopGoods::getGoodsId, og.getGoodsId()) + .eq(ShopGoods::getTenantId, order.getTenantId()) + .apply("IFNULL(stock,0) < {0}", qty) + .setSql("stock = 0") + ); + } + } + } catch (Exception e) { + log.warn("支付成功后扣库存异常 - tenantId={}, orderId={}, goodsId={}, skuId={}, qty={}", + order.getTenantId(), order.getOrderId(), og.getGoodsId(), og.getSkuId(), qty, e); + } + } + }); + } + /** * 标记优惠券为已使用 */ @@ -1452,43 +1549,41 @@ public class ShopOrderServiceImpl extends ServiceImpl implements ShopUserService {