Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -149,6 +149,7 @@ public class CreditCompanyController extends BaseController {
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
int insertedCount = 0;
|
||||
Set<String> touchedMatchNames = new HashSet<>();
|
||||
String refreshWarning = null;
|
||||
|
||||
try {
|
||||
List<CreditCompanyImportParam> list = null;
|
||||
@@ -324,13 +325,31 @@ public class CreditCompanyController extends BaseController {
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
||||
@@ -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<Integer> chunk = ids.subList(i, Math.min(ids.size(), i + inChunkSize));
|
||||
namedJdbc.update(sql, new MapSqlParameterSource("ids", chunk));
|
||||
|
||||
@@ -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("修改失败");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
<where>
|
||||
<if test="param.id != null">
|
||||
|
||||
@@ -168,9 +168,6 @@ public class GltTicketIssueService {
|
||||
new LambdaUpdateWrapper<ShopOrder>()
|
||||
.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)
|
||||
);
|
||||
|
||||
@@ -64,6 +64,20 @@ public interface GltTicketOrderService extends IService<GltTicketOrder> {
|
||||
*/
|
||||
void accept(Integer id, Integer riderId, Integer tenantId);
|
||||
|
||||
/**
|
||||
* 指派/接单成功后,同步关联商城订单发货状态为“已发货”(deliveryStatus=20)。
|
||||
*
|
||||
* <p>用于后台指派配送员(不走接单接口)等场景的状态兜底同步。</p>
|
||||
*/
|
||||
void markShopOrderShippedAfterRiderAssigned(Integer ticketOrderId, Integer tenantId, Integer riderId);
|
||||
|
||||
/**
|
||||
* 送水订单完成后,同步关联商城订单为“已完成”(orderStatus=1)。
|
||||
*
|
||||
* <p>用于后台直接改 deliveryStatus=40 等不经过 confirmReceive/autoConfirmTimeout 的兜底同步。</p>
|
||||
*/
|
||||
void markShopOrderCompletedAfterTicketFinished(Integer ticketOrderId, Integer tenantId);
|
||||
|
||||
/**
|
||||
* 配送员开始配送:10 -> 20,并写 sendStartTime。
|
||||
*/
|
||||
|
||||
@@ -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<GltTicketOrderMapper,
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
@Resource
|
||||
private ShopOrderGoodsService shopOrderGoodsService;
|
||||
|
||||
@Override
|
||||
public PageResult<GltTicketOrder> pageRel(GltTicketOrderParam param) {
|
||||
PageParam<GltTicketOrder, GltTicketOrderParam> page = new PageParam<>(param);
|
||||
@@ -192,6 +204,7 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void accept(Integer id, Integer riderId, Integer tenantId) {
|
||||
if (id == null) {
|
||||
throw new BusinessException("订单id不能为空");
|
||||
@@ -204,9 +217,10 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
}
|
||||
|
||||
// 原子接单:避免并发抢单
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
boolean ok = this.lambdaUpdate()
|
||||
.set(GltTicketOrder::getRiderId, riderId)
|
||||
.set(GltTicketOrder::getUpdateTime, LocalDateTime.now())
|
||||
.set(GltTicketOrder::getUpdateTime, now)
|
||||
.eq(GltTicketOrder::getId, id)
|
||||
.eq(GltTicketOrder::getTenantId, tenantId)
|
||||
.eq(GltTicketOrder::getDeleted, 0)
|
||||
@@ -215,6 +229,8 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
.and(w -> 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<GltTicketOrderMapper,
|
||||
throw new BusinessException("订单状态不允许接单");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markShopOrderShippedAfterRiderAssigned(Integer ticketOrderId, Integer tenantId, Integer riderId) {
|
||||
updateShopOrderDeliveryStatusAfterAccept(ticketOrderId, tenantId, riderId, LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markShopOrderCompletedAfterTicketFinished(Integer ticketOrderId, Integer tenantId) {
|
||||
updateShopOrderOrderStatusAfterTicketFinished(ticketOrderId, tenantId, LocalDateTime.now());
|
||||
}
|
||||
|
||||
private void updateShopOrderDeliveryStatusAfterAccept(Integer ticketOrderId, Integer tenantId, Integer riderId, LocalDateTime now) {
|
||||
if (ticketOrderId == null || tenantId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到关联水票的商城订单(glt_user_ticket.orderId / orderNo)
|
||||
GltTicketOrder ticketOrder = this.lambdaQuery()
|
||||
.select(GltTicketOrder::getId, GltTicketOrder::getUserTicketId, GltTicketOrder::getRiderId)
|
||||
.eq(GltTicketOrder::getId, ticketOrderId)
|
||||
.eq(GltTicketOrder::getTenantId, tenantId)
|
||||
.eq(GltTicketOrder::getDeleted, 0)
|
||||
.last("limit 1")
|
||||
.one();
|
||||
if (ticketOrder == null || ticketOrder.getUserTicketId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Integer actualRiderId = (riderId != null && riderId > 0) ? riderId : ticketOrder.getRiderId();
|
||||
|
||||
GltUserTicket userTicket = gltUserTicketService.getOne(
|
||||
new LambdaQueryWrapper<GltUserTicket>()
|
||||
.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<ShopOrderGoods>()
|
||||
.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<ShopOrder>()
|
||||
.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<GltUserTicket> backfill = new LambdaUpdateWrapper<GltUserTicket>()
|
||||
.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<ShopOrder> uw = new LambdaUpdateWrapper<ShopOrder>()
|
||||
.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<ShopOrder> qw = new LambdaQueryWrapper<ShopOrder>()
|
||||
.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<GltTicketOrderMapper,
|
||||
if (ok) {
|
||||
// 配送员拍照上传送达后可触发提成结算(幂等,重复调用不会重复入账)
|
||||
settleRiderCommissionIfEligible(id, tenantId, true);
|
||||
// 配送员确认送达后,同步商城订单为“已完成”(orderStatus=1)
|
||||
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, now);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -329,6 +487,7 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
|| order.getDeliveryStatus() == DELIVERY_STATUS_FINISHED)) {
|
||||
// 幂等:重复送达视为成功
|
||||
settleRiderCommissionIfEligible(id, tenantId, true);
|
||||
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, LocalDateTime.now());
|
||||
return;
|
||||
}
|
||||
throw new BusinessException("订单状态不允许确认送达");
|
||||
@@ -362,6 +521,8 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
if (ok) {
|
||||
// 用户确认收货完成后触发配送员提成结算(幂等,重复调用不会重复入账)
|
||||
settleRiderCommissionIfEligible(id, tenantId, false);
|
||||
// 送水订单完成后,同步商城订单为“已完成”(orderStatus=1)
|
||||
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, now);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -379,6 +540,7 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
if (order.getDeliveryStatus() != null && order.getDeliveryStatus() == DELIVERY_STATUS_FINISHED) {
|
||||
// 幂等:重复确认收货视为成功
|
||||
settleRiderCommissionIfEligible(id, tenantId, false);
|
||||
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, LocalDateTime.now());
|
||||
return;
|
||||
}
|
||||
throw new BusinessException("订单状态不允许确认收货");
|
||||
@@ -437,6 +599,7 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
}
|
||||
// 超时自动确认收货后,也按“完成”逻辑触发配送员提成结算(幂等)。
|
||||
settleRiderCommissionIfEligible(id, tenantId, false);
|
||||
updateShopOrderOrderStatusAfterTicketFinished(id, tenantId, nowFinal);
|
||||
return true;
|
||||
});
|
||||
if (Boolean.TRUE.equals(ok)) {
|
||||
@@ -449,6 +612,125 @@ public class GltTicketOrderServiceImpl extends ServiceImpl<GltTicketOrderMapper,
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
private void updateShopOrderOrderStatusAfterTicketFinished(Integer ticketOrderId, Integer tenantId, LocalDateTime now) {
|
||||
if (ticketOrderId == null || tenantId == null) {
|
||||
return;
|
||||
}
|
||||
if (now == null) {
|
||||
now = LocalDateTime.now();
|
||||
}
|
||||
|
||||
// 找到关联水票的商城订单(glt_user_ticket.orderId / orderNo)
|
||||
GltTicketOrder ticketOrder = this.lambdaQuery()
|
||||
.select(GltTicketOrder::getId, GltTicketOrder::getUserTicketId)
|
||||
.eq(GltTicketOrder::getId, ticketOrderId)
|
||||
.eq(GltTicketOrder::getTenantId, tenantId)
|
||||
.eq(GltTicketOrder::getDeleted, 0)
|
||||
.last("limit 1")
|
||||
.one();
|
||||
if (ticketOrder == null || ticketOrder.getUserTicketId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GltUserTicket userTicket = gltUserTicketService.getOne(
|
||||
new LambdaQueryWrapper<GltUserTicket>()
|
||||
.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<ShopOrderGoods>()
|
||||
.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<ShopOrder>()
|
||||
.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<GltUserTicket> backfill = new LambdaUpdateWrapper<GltUserTicket>()
|
||||
.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<ShopOrder> uw = new LambdaUpdateWrapper<ShopOrder>()
|
||||
.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<ShopOrder> qw = new LambdaQueryWrapper<ShopOrder>()
|
||||
.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;
|
||||
|
||||
@@ -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<ShopDealerCapital>()
|
||||
.eq(ShopDealerCapital::getTenantId, TENANT_ID)
|
||||
.eq(ShopDealerCapital::getFlowType, 10)
|
||||
.eq(ShopDealerCapital::getOrderNo, orderNo)
|
||||
);
|
||||
if (totalCommissions <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long unfrozenMarkers = shopDealerCapitalService.count(
|
||||
new LambdaQueryWrapper<ShopDealerCapital>()
|
||||
.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<ShopDealerOrder>()
|
||||
.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 + ")";
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ import java.util.*;
|
||||
/**
|
||||
* 租户10584:分销订单结算任务
|
||||
* <p>
|
||||
* 每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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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<ShopUser> get(@PathVariable("userId") Integer userId) {
|
||||
@PreAuthorize("hasAuthority('shop:shopUser:list')")
|
||||
@Operation(summary = "根据id查询用户记录表")
|
||||
@GetMapping("/{id}")
|
||||
public ApiResult<ShopUser> 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("添加成功");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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不启用)")
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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<ShopUser> {
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
SELECT a.*
|
||||
FROM shop_user a
|
||||
<where>
|
||||
<if test="param.id != null">
|
||||
AND a.id = #{param.id}
|
||||
</if>
|
||||
<if test="param.userId != null">
|
||||
AND a.user_id = #{param.userId}
|
||||
</if>
|
||||
@@ -160,6 +157,9 @@
|
||||
<if test="param.isAdmin != null">
|
||||
AND a.is_admin = #{param.isAdmin}
|
||||
</if>
|
||||
<if test="param.isDefault != null">
|
||||
AND a.is_default = #{param.isDefault}
|
||||
</if>
|
||||
<if test="param.isOrganizationAdmin != null">
|
||||
AND a.is_organization_admin = #{param.isOrganizationAdmin}
|
||||
</if>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<ShopUser> {
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<ShopOrderMapper, ShopOrder
|
||||
private ShopOrderDeliveryService shopOrderDeliveryService;
|
||||
@Resource
|
||||
private ShopExpressService shopExpressService;
|
||||
@Resource
|
||||
private ShopGoodsSkuService shopGoodsSkuService;
|
||||
|
||||
private static final long USER_ORDER_STATS_CACHE_SECONDS = 60L;
|
||||
private static final long WECHAT_PREPAY_SNAPSHOT_TTL_MINUTES = 30L;
|
||||
private static final String WECHAT_PREPAY_SNAPSHOT_KEY_PREFIX = "wxpay:prepay:snapshot:";
|
||||
private static final int DEDUCT_STOCK_TYPE_PAY = 20; // 付款减库存
|
||||
|
||||
@Data
|
||||
private static class WechatPrepaySnapshot {
|
||||
@@ -769,10 +774,15 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
try {
|
||||
Transaction result = service.queryOrderByOutTradeNo(queryRequest);
|
||||
if (result.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)) {
|
||||
if (Boolean.TRUE.equals(shopOrder.getPayStatus())) {
|
||||
// 已是支付成功状态,避免重复触发支付成功后的业务逻辑(销量/库存等)
|
||||
return true;
|
||||
}
|
||||
shopOrder.setPayStatus(true);
|
||||
shopOrder.setPayTime(LocalDateTime.now());
|
||||
shopOrder.setTransactionId(result.getTransactionId());
|
||||
updateById(shopOrder);
|
||||
handlePaymentSuccess(shopOrder);
|
||||
return true;
|
||||
}
|
||||
} catch (ServiceException e) {
|
||||
@@ -801,6 +811,9 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
*/
|
||||
private void handlePaymentSuccess(ShopOrder order) {
|
||||
try {
|
||||
// 0. 付款减库存:支付成功后扣库存(下单时不扣)
|
||||
deductStockAfterPaidIfNeeded(order);
|
||||
|
||||
// 1. 使用优惠券
|
||||
if (order.getCouponId() != null && order.getCouponId() > 0) {
|
||||
markCouponAsUsed(order);
|
||||
@@ -816,6 +829,90 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款减库存:支付成功后按商品设置扣减库存。
|
||||
*
|
||||
* 注意:此处使用忽略租户隔离 + 显式 tenantId 条件,避免支付回调没有 tenantId header 时更新失败。
|
||||
* 若扣减失败(库存不足/数据缺失),仅记录日志,不阻断支付回调主流程。
|
||||
*/
|
||||
private void deductStockAfterPaidIfNeeded(ShopOrder order) {
|
||||
if (order == null || order.getOrderId() == null || order.getTenantId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<ShopOrderGoods> 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<ShopGoodsSku>()
|
||||
.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<ShopGoodsSku>()
|
||||
.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<ShopGoods>()
|
||||
.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<ShopGoods>()
|
||||
.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,7 +1549,6 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
|
||||
@Override
|
||||
public boolean syncPaymentStatus(String orderNo, Integer paymentStatus, String transactionId, String payTime, Integer tenantId) {
|
||||
try {
|
||||
// 查询订单
|
||||
ShopOrder order = getByOrderNo(orderNo, tenantId);
|
||||
if (order == null) {
|
||||
@@ -1461,7 +1557,7 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
}
|
||||
|
||||
// 如果订单已经是支付成功状态,不需要更新
|
||||
if (order.getPayStatus() && paymentStatus == 1) {
|
||||
if (Boolean.TRUE.equals(order.getPayStatus()) && paymentStatus == 1) {
|
||||
log.info("订单已经是支付成功状态,无需更新: orderNo={}", orderNo);
|
||||
return true;
|
||||
}
|
||||
@@ -1479,16 +1575,15 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
|
||||
if (updated) {
|
||||
log.info("订单支付状态同步成功: orderNo={}, paymentStatus={}, transactionId={}",
|
||||
orderNo, paymentStatus, transactionId);
|
||||
// paymentStatus=1时,同步触发支付成功后业务逻辑(包括付款减库存/销量等)
|
||||
if (paymentStatus != null && paymentStatus == 1) {
|
||||
handlePaymentSuccess(order);
|
||||
}
|
||||
} else {
|
||||
log.warn("订单支付状态同步失败: orderNo={}, paymentStatus={}", orderNo, paymentStatus);
|
||||
}
|
||||
|
||||
return updated;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("同步订单支付状态异常: orderNo={}, error={}", orderNo, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.gxwebsoft.shop.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.gxwebsoft.shop.mapper.ShopUserMapper;
|
||||
import com.gxwebsoft.shop.service.ShopUserService;
|
||||
import com.gxwebsoft.shop.entity.ShopUser;
|
||||
import com.gxwebsoft.shop.param.ShopUserParam;
|
||||
import com.gxwebsoft.common.core.web.PageParam;
|
||||
import com.gxwebsoft.common.core.web.PageResult;
|
||||
import com.gxwebsoft.shop.entity.ShopUser;
|
||||
import com.gxwebsoft.shop.mapper.ShopUserMapper;
|
||||
import com.gxwebsoft.shop.param.ShopUserParam;
|
||||
import com.gxwebsoft.shop.service.ShopUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
@@ -15,7 +15,7 @@ import java.util.List;
|
||||
* 用户记录表Service实现
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-10-03 13:41:09
|
||||
* @since 2026-02-10 00:37:07
|
||||
*/
|
||||
@Service
|
||||
public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> implements ShopUserService {
|
||||
|
||||
Reference in New Issue
Block a user