50 changed files with 2567 additions and 76 deletions
@ -0,0 +1,111 @@ |
|||
package cc.hiver.mall.controller; |
|||
|
|||
import cc.hiver.core.common.utils.ResultUtil; |
|||
import cc.hiver.core.common.vo.Result; |
|||
import cc.hiver.mall.entity.MallDeliveryOrder; |
|||
import cc.hiver.mall.pojo.query.MallDeliveryOrderPageQuery; |
|||
import cc.hiver.mall.service.mybatis.MallDeliveryOrderService; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import io.swagger.annotations.Api; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
/** |
|||
* 配送订单接口 |
|||
*/ |
|||
@Slf4j |
|||
@RestController |
|||
@Api(tags = "配送订单接口") |
|||
@RequestMapping("/hiver/mall/delivery") |
|||
public class MallDeliveryOrderController { |
|||
|
|||
@Autowired |
|||
private MallDeliveryOrderService mallDeliveryOrderService; |
|||
|
|||
/** |
|||
* 分页查询配送单 |
|||
* hallOnly=true 时查询抢单大厅(未被接单的单) |
|||
*/ |
|||
@PostMapping("/page") |
|||
@ApiOperation(value = "分页查询配送单", notes = "hallOnly=true 查询抢单大厅") |
|||
public Result<Object> page(@RequestBody MallDeliveryOrderPageQuery q) { |
|||
if (Boolean.TRUE.equals(q.getHallOnly())) { |
|||
IPage<MallDeliveryOrder> page = mallDeliveryOrderService.pageDelivery(q); |
|||
java.util.Map<String, Object> result = new java.util.HashMap<>(); |
|||
result.put("records", page.getRecords()); |
|||
result.put("total", page.getTotal()); |
|||
result.put("size", page.getSize()); |
|||
result.put("current", page.getCurrent()); |
|||
result.put("pages", page.getPages()); |
|||
result.put("waimaiCount", mallDeliveryOrderService.countWaitGrabOrders(1)); |
|||
result.put("kuaidiCount", mallDeliveryOrderService.countWaitGrabOrders(2)); |
|||
return new ResultUtil<Object>().setData(result); |
|||
} |
|||
return new ResultUtil<Object>().setData(mallDeliveryOrderService.pageDelivery(q)); |
|||
} |
|||
|
|||
/** |
|||
* 配送员接单 |
|||
*/ |
|||
@PostMapping("/accept") |
|||
@ApiOperation(value = "配送员接单", notes = "抢单大厅单或指派单均通过此接口接单") |
|||
public Result accept(@RequestParam String deliveryId, |
|||
@RequestParam String workerId) { |
|||
try { |
|||
mallDeliveryOrderService.workerAccept(deliveryId, workerId); |
|||
return ResultUtil.success("接单成功"); |
|||
} catch (Exception e) { |
|||
log.error("配送员接单失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 配送员取货 |
|||
*/ |
|||
@PostMapping("/pickup") |
|||
@ApiOperation("配送员取货(待取货 -> 配送中)") |
|||
public Result pickup(@RequestParam String deliveryId, |
|||
@RequestParam String workerId) { |
|||
try { |
|||
mallDeliveryOrderService.workerPickup(deliveryId, workerId); |
|||
return ResultUtil.success("已取货,配送中"); |
|||
} catch (Exception e) { |
|||
log.error("取货失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 配送员送达 |
|||
*/ |
|||
@PostMapping("/complete") |
|||
@ApiOperation("配送员确认送达(配送中 -> 已送达)") |
|||
public Result complete(@RequestParam String deliveryId, |
|||
@RequestParam String workerId) { |
|||
try { |
|||
mallDeliveryOrderService.workerComplete(deliveryId, workerId); |
|||
return ResultUtil.success("已送达"); |
|||
} catch (Exception e) { |
|||
log.error("送达操作失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 取消配送单 |
|||
*/ |
|||
@PostMapping("/cancel") |
|||
@ApiOperation("取消配送单") |
|||
public Result cancel(@RequestParam String deliveryId) { |
|||
try { |
|||
mallDeliveryOrderService.cancelDelivery(deliveryId); |
|||
return ResultUtil.success("已取消"); |
|||
} catch (Exception e) { |
|||
log.error("取消配送失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,189 @@ |
|||
package cc.hiver.mall.controller; |
|||
|
|||
import cc.hiver.core.common.utils.ResultUtil; |
|||
import cc.hiver.core.common.vo.Result; |
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.pojo.dto.CreateOrderDTO; |
|||
import cc.hiver.mall.pojo.query.MallOrderPageQuery; |
|||
import cc.hiver.mall.pojo.vo.MallOrderVO; |
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import io.swagger.annotations.Api; |
|||
import io.swagger.annotations.ApiOperation; |
|||
import io.swagger.annotations.ApiParam; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
|
|||
import java.math.BigDecimal; |
|||
|
|||
/** |
|||
* 订单接口 |
|||
*/ |
|||
@Slf4j |
|||
@RestController |
|||
@Api(tags = "订单接口") |
|||
@RequestMapping("/hiver/mall/order") |
|||
public class MallOrderController { |
|||
|
|||
@Autowired |
|||
private MallOrderService mallOrderService; |
|||
|
|||
/** |
|||
* 下单(普通购买 / 发起拼团 / 参团) |
|||
*/ |
|||
@PostMapping("/create") |
|||
@ApiOperation(value = "下单接口", notes = "支持普通购买、发起拼团、参加拼团三种模式") |
|||
public Result<MallOrderVO> createOrder(@RequestBody CreateOrderDTO dto) { |
|||
try { |
|||
MallOrderVO vo = mallOrderService.createOrder(dto); |
|||
return new ResultUtil<MallOrderVO>().setData(vo); |
|||
} catch (Exception e) { |
|||
log.error("下单失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 分页查询订单列表 |
|||
*/ |
|||
@PostMapping("/page") |
|||
@ApiOperation(value = "分页查询订单列表", notes = "用户端传userId,商家端传shopId") |
|||
public Result<IPage<MallOrderVO>> page(@RequestBody MallOrderPageQuery q) { |
|||
return new ResultUtil<IPage<MallOrderVO>>().setData(mallOrderService.pageOrder(q)); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 根据拼团id查询团长订单配送费信息 |
|||
*/ |
|||
@GetMapping("/selectMallOrderByGroupId/{groupId}") |
|||
@ApiOperation("根据拼团id查询团长订单配送费信息") |
|||
public Result<MallOrder> selectMallOrderByGroupId(@ApiParam("订单ID") @PathVariable String groupId) { |
|||
MallOrder vo = mallOrderService.selectMallOrderByGroupId(groupId); |
|||
if (vo == null) return ResultUtil.error("订单不存在"); |
|||
return new ResultUtil<MallOrder>().setData(vo); |
|||
} |
|||
|
|||
/** |
|||
* 订单详情 |
|||
*/ |
|||
@GetMapping("/detail/{id}") |
|||
@ApiOperation("获取订单详情") |
|||
public Result<MallOrderVO> detail(@ApiParam("订单ID") @PathVariable String id) { |
|||
MallOrderVO vo = mallOrderService.getOrderDetail(id); |
|||
if (vo == null) return ResultUtil.error("订单不存在"); |
|||
return new ResultUtil<MallOrderVO>().setData(vo); |
|||
} |
|||
|
|||
/** |
|||
* 商家接单 |
|||
*/ |
|||
@PostMapping("/shopAccept") |
|||
@ApiOperation(value = "支付成功商家自动接单", notes = "将订单从「待商家接单」推进到下一状态") |
|||
public Result shopAccept(@ApiParam("订单ID") @RequestParam String orderId) { |
|||
try { |
|||
mallOrderService.shopAccept(orderId); |
|||
return ResultUtil.success("接单成功"); |
|||
} catch (Exception e) { |
|||
log.error("接单失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 商家拒单 |
|||
*/ |
|||
@PostMapping("/shopReject") |
|||
@ApiOperation("商家拒单(自动触发退款)") |
|||
public Result shopReject(@RequestParam String orderId, |
|||
@RequestParam(required = false) String reason) { |
|||
try { |
|||
mallOrderService.shopReject(orderId, reason); |
|||
return ResultUtil.success("操作成功"); |
|||
} catch (Exception e) { |
|||
log.error("拒单失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 用户取消订单 |
|||
*/ |
|||
@PostMapping("/cancel") |
|||
@ApiOperation(value = "用户取消订单", notes = "仅待支付/待成团状态可取消") |
|||
public Result cancelOrder(@RequestParam String orderId, |
|||
@RequestParam String userId) { |
|||
try { |
|||
mallOrderService.cancelOrder(orderId, userId); |
|||
return ResultUtil.success("已取消"); |
|||
} catch (Exception e) { |
|||
log.error("取消失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 申请退款 |
|||
*/ |
|||
@PostMapping("/applyRefund") |
|||
@ApiOperation("用户申请退款") |
|||
public Result applyRefund(@RequestParam String orderId, |
|||
@RequestParam String userId, |
|||
@RequestParam(required = false) String reason, |
|||
@RequestParam BigDecimal amount) { |
|||
try { |
|||
mallOrderService.applyRefund(orderId, userId, reason, amount); |
|||
return ResultUtil.success("退款申请已提交"); |
|||
} catch (Exception e) { |
|||
log.error("申请退款失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 商家同意退款 |
|||
*/ |
|||
@PostMapping("/agreeRefund") |
|||
@ApiOperation("商家同意退款") |
|||
public Result agreeRefund(@RequestParam String orderId) { |
|||
try { |
|||
mallOrderService.agreeRefund(orderId); |
|||
return ResultUtil.success("退款成功"); |
|||
} catch (Exception e) { |
|||
log.error("同意退款失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 商家拒绝退款 |
|||
*/ |
|||
@PostMapping("/rejectRefund") |
|||
@ApiOperation("商家拒绝退款") |
|||
public Result rejectRefund(@RequestParam String orderId, |
|||
@RequestParam(required = false) String reason) { |
|||
try { |
|||
mallOrderService.rejectRefund(orderId, reason); |
|||
return ResultUtil.success("操作成功"); |
|||
} catch (Exception e) { |
|||
log.error("拒绝退款失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 用户确认完成 |
|||
*/ |
|||
@PostMapping("/complete") |
|||
@ApiOperation(value = "用户确认完成", notes = "外卖送达或自取后,用户手动点击确认完成") |
|||
public Result completeOrder(@RequestParam String orderId) { |
|||
try { |
|||
mallOrderService.completeOrder(orderId); |
|||
return ResultUtil.success("已完成"); |
|||
} catch (Exception e) { |
|||
log.error("完成订单失败: {}", e.getMessage(), e); |
|||
return ResultUtil.error(e.getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
package cc.hiver.mall.dao.mapper; |
|||
|
|||
import cc.hiver.mall.entity.MallDeliveryOrder; |
|||
import cc.hiver.mall.pojo.query.MallDeliveryOrderPageQuery; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import org.apache.ibatis.annotations.Param; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* 配送订单 Mapper 接口 |
|||
*/ |
|||
@Repository |
|||
public interface MallDeliveryOrderMapper extends BaseMapper<MallDeliveryOrder> { |
|||
|
|||
/** |
|||
* 分页查询配送单 |
|||
*/ |
|||
IPage<MallDeliveryOrder> selectPageVO(IPage<?> page, @Param("q") MallDeliveryOrderPageQuery q); |
|||
|
|||
/** |
|||
* 统计配送员当前活跃单量(待取货+配送中) |
|||
*/ |
|||
Map<String, Integer> countActiveOrdersByWorker(@Param("workerId") String workerId); |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
package cc.hiver.mall.dao.mapper; |
|||
|
|||
import cc.hiver.mall.entity.MallOrderGoods; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.apache.ibatis.annotations.Param; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 订单商品详情 Mapper 接口 |
|||
*/ |
|||
@Repository |
|||
public interface MallOrderGoodsMapper extends BaseMapper<MallOrderGoods> { |
|||
|
|||
/** |
|||
* 按订单ID查询商品明细 |
|||
*/ |
|||
List<MallOrderGoods> selectByOrderId(@Param("orderId") String orderId); |
|||
|
|||
/** |
|||
* 按订单ID集合批量查询 |
|||
*/ |
|||
List<MallOrderGoods> selectByOrderIds(@Param("orderIds") List<String> orderIds); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
package cc.hiver.mall.dao.mapper; |
|||
|
|||
import cc.hiver.mall.entity.MallOrderGroup; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
/** |
|||
* 拼团主表 Mapper 接口 |
|||
*/ |
|||
@Repository |
|||
public interface MallOrderGroupMapper extends BaseMapper<MallOrderGroup> { |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package cc.hiver.mall.dao.mapper; |
|||
|
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.pojo.query.MallOrderPageQuery; |
|||
import cc.hiver.mall.pojo.vo.MallOrderVO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import org.apache.ibatis.annotations.Param; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
/** |
|||
* 核心订单 Mapper 接口 |
|||
*/ |
|||
@Repository |
|||
public interface MallOrderMapper extends BaseMapper<MallOrder> { |
|||
|
|||
/** |
|||
* 分页查询订单(含商品、拼团、配送信息) |
|||
*/ |
|||
IPage<MallOrderVO> selectPageVO(IPage<?> page, @Param("q") MallOrderPageQuery q); |
|||
|
|||
MallOrder selectMallOrderByGroupId(@Param("groupId") String groupId); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
package cc.hiver.mall.dao.mapper; |
|||
|
|||
import cc.hiver.mall.entity.MallRefundRecord; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import org.springframework.stereotype.Repository; |
|||
|
|||
/** |
|||
* 退款记录 Mapper 接口 |
|||
*/ |
|||
@Repository |
|||
public interface MallRefundRecordMapper extends BaseMapper<MallRefundRecord> { |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
package cc.hiver.mall.entity; |
|||
|
|||
import cc.hiver.core.common.utils.SnowFlakeUtil; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import javax.persistence.Entity; |
|||
import javax.persistence.Id; |
|||
import javax.persistence.Table; |
|||
import java.io.Serializable; |
|||
import java.math.BigDecimal; |
|||
|
|||
@Data |
|||
@Entity |
|||
@Table(name = "mall_return_order_goods") |
|||
@TableName("mall_return_order_goods") |
|||
@ApiModel(value = "退货订单商品详情表") |
|||
public class MallReturnOrderGoods implements Serializable { |
|||
@Id |
|||
@TableId |
|||
private String id = SnowFlakeUtil.nextId().toString(); |
|||
@ApiModelProperty(value = "关联退货订单ID") |
|||
private String orderId; |
|||
@ApiModelProperty(value = "关联商品ID") |
|||
private String productId; |
|||
@ApiModelProperty(value = "商品名称") |
|||
private String productName; |
|||
@ApiModelProperty(value = "商品图片") |
|||
private String productPicture; |
|||
@ApiModelProperty(value = "商品规格json") |
|||
private String specs; |
|||
@ApiModelProperty(value = "商品单价") |
|||
private BigDecimal price; |
|||
@ApiModelProperty(value = "商品数量") |
|||
private Integer quantity; |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package cc.hiver.mall.pojo.dto; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 区域匹配规则DTO |
|||
*/ |
|||
@Data |
|||
@ApiModel("区域匹配规则DTO") |
|||
public class AreaRuleDTO { |
|||
|
|||
@ApiModelProperty("取货区域ID") |
|||
private String getAreaId; |
|||
|
|||
@ApiModelProperty("送货区域ID") |
|||
private String putAreaId; |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
package cc.hiver.mall.pojo.dto; |
|||
|
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 下单请求 DTO |
|||
*/ |
|||
@ApiModel("下单请求DTO") |
|||
@Data |
|||
public class CreateOrderDTO { |
|||
|
|||
@ApiModelProperty(value = "用户ID", required = true) |
|||
private String userId; |
|||
|
|||
@ApiModelProperty(value = "店铺ID", required = true) |
|||
private String shopId; |
|||
|
|||
@ApiModelProperty(value = "商品列表", required = true) |
|||
private List<OrderItemDTO> items; |
|||
|
|||
@ApiModelProperty(value = "配送方式 1:外卖配送 2:到店自取", required = true) |
|||
private Integer deliveryType; |
|||
|
|||
@ApiModelProperty(value = "收货地址ID(外卖配送时必填)") |
|||
private String addressId; |
|||
|
|||
@ApiModelProperty(value = "取货区域ID(商户)") |
|||
private String getAreaId; |
|||
|
|||
@ApiModelProperty(value = "送货区域ID(用户收货地址关联区域id)") |
|||
private String putAreaId; |
|||
|
|||
@ApiModelProperty(value = "打包费(默认0)") |
|||
private BigDecimal packageFee; |
|||
|
|||
@ApiModelProperty(value = "备注") |
|||
private String remark; |
|||
|
|||
@ApiModelProperty(value = "要求送达时间(预约配送时填写)") |
|||
private Date mustFinishTime; |
|||
|
|||
@ApiModelProperty(value = "加入已有拼团时传入groupId") |
|||
private String groupId; |
|||
|
|||
@ApiModelProperty(value = "发起拼团时传入(orderType=2且groupId为空时必填)") |
|||
private GroupParam groupParam; |
|||
|
|||
@ApiModelProperty(value = "指定配送员参数(不指定则进入抢单大厅)") |
|||
private WorkerParam workerParam; |
|||
|
|||
// ===================== 内嵌类 =====================
|
|||
|
|||
@Data |
|||
@ApiModel("订单商品条目") |
|||
public static class OrderItemDTO { |
|||
@ApiModelProperty(value = "商品ID", required = true) |
|||
private String productId; |
|||
@ApiModelProperty(value = "商品规格JSON,格式:{\"specPrice\":\"11\",\"specNum\":9999}", required = true) |
|||
private String specs; |
|||
@ApiModelProperty(value = "下单价格(以此为准,由前端传入规格对应价格)", required = true) |
|||
private BigDecimal price; |
|||
@ApiModelProperty(value = "购买数量", required = true) |
|||
private Integer quantity; |
|||
} |
|||
|
|||
@Data |
|||
@ApiModel("发起拼团参数") |
|||
public static class GroupParam { |
|||
@ApiModelProperty(value = "目标拼团人数", required = true) |
|||
private Integer targetMembers; |
|||
@ApiModelProperty(value = "是否面对面团(不公开) 0否 1是", required = true) |
|||
private Integer isFaceToFace; |
|||
@ApiModelProperty(value = "团长自定义配送佣金(不指定配送员、面对面团时填写)") |
|||
private BigDecimal selfCommission; |
|||
} |
|||
|
|||
@Data |
|||
@ApiModel("指定配送员参数") |
|||
public static class WorkerParam { |
|||
@ApiModelProperty(value = "配送员ID", required = true) |
|||
private String workerId; |
|||
@ApiModelProperty(value = "配送员规则佣金 orderBkge", required = true) |
|||
private BigDecimal orderBkge; |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
package cc.hiver.mall.pojo.query; |
|||
|
|||
import cc.hiver.core.base.HiverBasePageQuery; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 配送单分页查询对象 |
|||
*/ |
|||
@ApiModel("配送单分页查询对象") |
|||
@Data |
|||
public class MallDeliveryOrderPageQuery extends HiverBasePageQuery { |
|||
|
|||
@ApiModelProperty("配送员ID") |
|||
private String workerId; |
|||
|
|||
@ApiModelProperty("店铺ID") |
|||
private String shopId; |
|||
|
|||
@ApiModelProperty("配送状态 0:待接单 1:待取货 2:配送中 3:已送达 4:已取消") |
|||
private Integer status; |
|||
|
|||
@ApiModelProperty("取货区域ID") |
|||
private String getAreaId; |
|||
|
|||
@ApiModelProperty("送货区域ID") |
|||
private String putAreaId; |
|||
|
|||
@ApiModelProperty("开始时间") |
|||
private String startDate; |
|||
|
|||
@ApiModelProperty("结束时间") |
|||
private String endDate; |
|||
|
|||
@ApiModelProperty("是否只查询抢单大厅(workerId为空的)") |
|||
private Boolean hallOnly; |
|||
|
|||
@ApiModelProperty("订单类型 1:外卖 2:快递") |
|||
private Integer deliveryType; |
|||
|
|||
@ApiModelProperty("排序规则(如果有值且为deliveryFee,则按佣金降序)") |
|||
private String order; |
|||
|
|||
@ApiModelProperty("外卖抢单匹配规则数组") |
|||
private java.util.List<cc.hiver.mall.pojo.dto.AreaRuleDTO> waimaiData; |
|||
|
|||
@ApiModelProperty("快递抢单匹配规则数组") |
|||
private java.util.List<cc.hiver.mall.pojo.dto.AreaRuleDTO> kuaidiData; |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
package cc.hiver.mall.pojo.query; |
|||
|
|||
import cc.hiver.core.base.HiverBasePageQuery; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
|
|||
/** |
|||
* 订单分页查询对象 |
|||
*/ |
|||
@ApiModel("订单分页查询对象") |
|||
@Data |
|||
public class MallOrderPageQuery extends HiverBasePageQuery { |
|||
|
|||
@ApiModelProperty("用户ID") |
|||
private String userId; |
|||
|
|||
@ApiModelProperty("店铺ID") |
|||
private String shopId; |
|||
|
|||
@ApiModelProperty("订单状态 0:待支付 1:待商家接单 2:待配送员接单 3:待取货/待消费 4:配送中 5:已完成 6:已取消 7:待退款 8:已退款") |
|||
private Integer status; |
|||
|
|||
@ApiModelProperty("订单类型 1:直接购买 2:拼团购买") |
|||
private Integer orderType; |
|||
|
|||
@ApiModelProperty("配送方式 1:外卖配送 2:到店自取") |
|||
private Integer deliveryType; |
|||
|
|||
@ApiModelProperty("拼团ID") |
|||
private String groupId; |
|||
|
|||
@ApiModelProperty("开始时间(创建时间起)") |
|||
private String startDate; |
|||
|
|||
@ApiModelProperty("结束时间(创建时间止)") |
|||
private String endDate; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package cc.hiver.mall.pojo.vo; |
|||
|
|||
import cc.hiver.mall.entity.MallDeliveryOrder; |
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.entity.MallOrderGoods; |
|||
import cc.hiver.mall.entity.MallOrderGroup; |
|||
import io.swagger.annotations.ApiModel; |
|||
import io.swagger.annotations.ApiModelProperty; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
|
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 订单详情/列表 响应 VO |
|||
*/ |
|||
@ApiModel("订单响应VO") |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = false) |
|||
public class MallOrderVO extends MallOrder { |
|||
|
|||
@ApiModelProperty("订单商品列表") |
|||
private List<MallOrderGoods> goodsList; |
|||
|
|||
@ApiModelProperty("拼团信息(拼团订单时返回)") |
|||
private MallOrderGroup groupInfo; |
|||
|
|||
@ApiModelProperty("配送信息(外卖订单时返回)") |
|||
private MallDeliveryOrder deliveryInfo; |
|||
|
|||
@ApiModelProperty("收货地址详情(冗余展示用)") |
|||
private String addressDetail; |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
package cc.hiver.mall.quartz; |
|||
|
|||
import cc.hiver.mall.entity.MallOrderGroup; |
|||
import cc.hiver.mall.service.mybatis.MallOrderGroupService; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 拼团超时自动失败定时任务 |
|||
* 每30分钟扫描一次拼团中(status=0/3)且已超过 expireTime 的拼团 |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
public class GroupBuyExpireTask { |
|||
|
|||
@Autowired |
|||
private MallOrderGroupService mallOrderGroupService; |
|||
|
|||
/** |
|||
* 每5分钟执行一次 |
|||
*/ |
|||
@Scheduled(cron = "0 0/30 * * * ?") |
|||
public void expireOverdueGroups() { |
|||
log.info("[拼团过期任务] 开始扫描超时拼团..."); |
|||
try { |
|||
LambdaQueryWrapper<MallOrderGroup> qw = new LambdaQueryWrapper<>(); |
|||
// 只处理「拼团中(0)」和「面对面团(3)」且已超时的
|
|||
qw.in(MallOrderGroup::getStatus, 0, 3) |
|||
.lt(MallOrderGroup::getExpireTime, new Date()); |
|||
|
|||
List<MallOrderGroup> overdueGroups = mallOrderGroupService.list(qw); |
|||
|
|||
if (overdueGroups.isEmpty()) { |
|||
log.info("[拼团过期任务] 无超时拼团"); |
|||
return; |
|||
} |
|||
|
|||
log.info("[拼团过期任务] 发现 {} 个超时拼团,开始处理...", overdueGroups.size()); |
|||
for (MallOrderGroup group : overdueGroups) { |
|||
try { |
|||
mallOrderGroupService.expireGroup(group.getId()); |
|||
log.info("[拼团过期任务] 拼团 {} 处理完成", group.getId()); |
|||
} catch (Exception e) { |
|||
log.error("[拼团过期任务] 处理拼团 {} 失败: {}", group.getId(), e.getMessage(), e); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
log.error("[拼团过期任务] 任务执行异常: {}", e.getMessage(), e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
package cc.hiver.mall.service.mybatis; |
|||
|
|||
import cc.hiver.mall.entity.MallDeliveryOrder; |
|||
import cc.hiver.mall.pojo.query.MallDeliveryOrderPageQuery; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import com.baomidou.mybatisplus.extension.service.IService; |
|||
|
|||
/** |
|||
* 配送订单 Service 接口 |
|||
*/ |
|||
public interface MallDeliveryOrderService extends IService<MallDeliveryOrder> { |
|||
|
|||
/** |
|||
* 分页查询配送单(可查抢单大厅) |
|||
*/ |
|||
IPage<MallDeliveryOrder> pageDelivery(MallDeliveryOrderPageQuery q); |
|||
|
|||
/** |
|||
* 配送员接单(抢单大厅或指派单) |
|||
*/ |
|||
void workerAccept(String deliveryId, String workerId); |
|||
|
|||
/** |
|||
* 配送员取货 |
|||
*/ |
|||
void workerPickup(String deliveryId, String workerId); |
|||
|
|||
/** |
|||
* 配送员送达完成 |
|||
*/ |
|||
void workerComplete(String deliveryId, String workerId); |
|||
|
|||
/** |
|||
* 取消配送单 |
|||
*/ |
|||
void cancelDelivery(String deliveryId); |
|||
|
|||
/** |
|||
* 统计抢单大厅未被接单的数量 |
|||
* @param deliveryType 1:外卖 2:快递 |
|||
*/ |
|||
long countWaitGrabOrders(int deliveryType); |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
package cc.hiver.mall.service.mybatis; |
|||
|
|||
import cc.hiver.mall.entity.MallOrderGroup; |
|||
import com.baomidou.mybatisplus.extension.service.IService; |
|||
|
|||
/** |
|||
* 拼团主表 Service 接口 |
|||
*/ |
|||
public interface MallOrderGroupService extends IService<MallOrderGroup> { |
|||
|
|||
/** |
|||
* 检查拼团是否达到成团条件,满足则自动激活 |
|||
*/ |
|||
void checkAndActivateGroup(String groupId); |
|||
|
|||
/** |
|||
* 拼团过期处理(发起退款,所有子订单设为已取消) |
|||
*/ |
|||
void expireGroup(String groupId); |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
package cc.hiver.mall.service.mybatis; |
|||
|
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.pojo.dto.CreateOrderDTO; |
|||
import cc.hiver.mall.pojo.query.MallOrderPageQuery; |
|||
import cc.hiver.mall.pojo.vo.MallOrderVO; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import com.baomidou.mybatisplus.extension.service.IService; |
|||
import org.apache.ibatis.annotations.Param; |
|||
|
|||
/** |
|||
* 核心订单 Service 接口 |
|||
*/ |
|||
public interface MallOrderService extends IService<MallOrder> { |
|||
|
|||
/** |
|||
* 创建订单(支持普通购买/拼团发起/拼团参团) |
|||
*/ |
|||
MallOrderVO createOrder(CreateOrderDTO dto); |
|||
|
|||
/** |
|||
* 分页查询订单列表 |
|||
*/ |
|||
IPage<MallOrderVO> pageOrder(MallOrderPageQuery q); |
|||
|
|||
/** |
|||
* 查询订单详情 |
|||
*/ |
|||
MallOrderVO getOrderDetail(String orderId); |
|||
|
|||
/** |
|||
* 商家接单(待商家接单 -> 待配送员接单/待取货/待消费) |
|||
*/ |
|||
void shopAccept(String orderId); |
|||
|
|||
/** |
|||
* 支付成功处理逻辑 |
|||
*/ |
|||
void paySuccess(String orderId); |
|||
|
|||
/** |
|||
* 商家拒单(触发自动退款) |
|||
*/ |
|||
void shopReject(String orderId, String reason); |
|||
|
|||
/** |
|||
* 用户取消订单(仅待支付/待成团状态可取消) |
|||
*/ |
|||
void cancelOrder(String orderId, String userId); |
|||
|
|||
/** |
|||
* 申请退款 |
|||
*/ |
|||
void applyRefund(String orderId, String userId, String reason, java.math.BigDecimal amount); |
|||
|
|||
/** |
|||
* 商家同意退款 |
|||
*/ |
|||
void agreeRefund(String orderId); |
|||
|
|||
/** |
|||
* 商家拒绝退款 |
|||
*/ |
|||
void rejectRefund(String orderId, String reason); |
|||
|
|||
/** |
|||
* 用户确认完成(外卖送达/自取消费后确认) |
|||
*/ |
|||
void completeOrder(String orderId); |
|||
|
|||
MallOrder selectMallOrderByGroupId(@Param("groupId") String groupId); |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
package cc.hiver.mall.service.mybatis; |
|||
|
|||
import cc.hiver.mall.entity.MallRefundRecord; |
|||
import com.baomidou.mybatisplus.extension.service.IService; |
|||
|
|||
/** |
|||
* 退款记录 Service 接口 |
|||
*/ |
|||
public interface MallRefundRecordService extends IService<MallRefundRecord> { |
|||
} |
|||
@ -0,0 +1,193 @@ |
|||
package cc.hiver.mall.serviceimpl.mybatis; |
|||
|
|||
import cc.hiver.mall.dao.mapper.MallDeliveryOrderMapper; |
|||
import cc.hiver.mall.entity.MallDeliveryOrder; |
|||
import cc.hiver.mall.entity.MallOrder; |
|||
import cc.hiver.mall.pojo.query.MallDeliveryOrderPageQuery; |
|||
import cc.hiver.mall.service.mybatis.MallDeliveryOrderService; |
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.math.RoundingMode; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 配送订单 Service 实现 |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class MallDeliveryOrderServiceImpl extends ServiceImpl<MallDeliveryOrderMapper, MallDeliveryOrder> |
|||
implements MallDeliveryOrderService { |
|||
|
|||
// 配送状态常量
|
|||
private static final int STATUS_WAIT_ACCEPT = 0; // 待接单
|
|||
private static final int STATUS_WAIT_PICKUP = 1; // 待取货
|
|||
private static final int STATUS_DELIVERING = 2; // 配送中
|
|||
private static final int STATUS_DONE = 3; // 已送达
|
|||
private static final int STATUS_CANCELLED = 4; // 已取消
|
|||
|
|||
// 订单状态
|
|||
private static final int ORDER_STATUS_WAIT_DELIVERY = 2; // 待配送员接单
|
|||
private static final int ORDER_STATUS_DELIVERING = 4; // 配送中
|
|||
private static final int ORDER_STATUS_DONE = 5; // 已完成
|
|||
|
|||
@Autowired |
|||
@Lazy |
|||
private MallOrderService mallOrderService; |
|||
|
|||
@Override |
|||
public IPage<MallDeliveryOrder> pageDelivery(MallDeliveryOrderPageQuery q) { |
|||
if (Boolean.TRUE.equals(q.getHallOnly())) { |
|||
q.setPageSize(20); |
|||
} |
|||
IPage<MallDeliveryOrder> page = new Page<>(q.getPageNum(), q.getPageSize()); |
|||
return this.baseMapper.selectPageVO(page, q); |
|||
} |
|||
|
|||
/** |
|||
* 配送员接单 |
|||
* - 抢单大厅:workerId 为空的单,任何配送员均可接 |
|||
* - 指派单:workerId 与当前配送员相同才可接 |
|||
*/ |
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void workerAccept(String deliveryId, String workerId) { |
|||
MallDeliveryOrder delivery = this.getById(deliveryId); |
|||
if (delivery == null) throw new RuntimeException("配送单不存在"); |
|||
if (delivery.getStatus() != STATUS_WAIT_ACCEPT) throw new RuntimeException("配送单已被接取或已取消"); |
|||
|
|||
// 指派单校验
|
|||
if (StringUtils.isNotBlank(delivery.getWorkerId()) && !delivery.getWorkerId().equals(workerId)) { |
|||
throw new RuntimeException("该单已指定其他配送员,无法抢单"); |
|||
} |
|||
|
|||
// 更新配送单
|
|||
LambdaUpdateWrapper<MallDeliveryOrder> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(MallDeliveryOrder::getId, deliveryId) |
|||
.set(MallDeliveryOrder::getStatus, STATUS_WAIT_PICKUP) |
|||
.set(MallDeliveryOrder::getWorkerId, workerId) |
|||
.set(MallDeliveryOrder::getAcceptTime, new Date()); |
|||
|
|||
// 若为指派单(直接购买),计算平台额外佣金 = 商品总价 * 2%
|
|||
if (StringUtils.isNotBlank(delivery.getOrderId())) { |
|||
MallOrder order = mallOrderService.getById(delivery.getOrderId()); |
|||
if (order != null && order.getOrderType() == 1) { |
|||
BigDecimal bonus = order.getGoodsAmount() |
|||
.multiply(new BigDecimal("0.02")) |
|||
.setScale(2, RoundingMode.HALF_UP); |
|||
uw.set(MallDeliveryOrder::getDeliveryFeeMarketplace, bonus); |
|||
} |
|||
} |
|||
|
|||
this.update(uw); |
|||
|
|||
// 同步更新关联订单状态:待配送员接单 -> 配送中(或待取货)
|
|||
if (StringUtils.isNotBlank(delivery.getOrderId())) { |
|||
MallOrder order = mallOrderService.getById(delivery.getOrderId()); |
|||
if (order != null && order.getStatus() == ORDER_STATUS_WAIT_DELIVERY) { |
|||
LambdaUpdateWrapper<MallOrder> oUw = new LambdaUpdateWrapper<>(); |
|||
oUw.eq(MallOrder::getId, delivery.getOrderId()) |
|||
.set(MallOrder::getStatus, 3); // 待取货
|
|||
mallOrderService.update(oUw); |
|||
} |
|||
} |
|||
|
|||
log.info("配送员 {} 接单成功,deliveryId={}", workerId, deliveryId); |
|||
} |
|||
|
|||
/** |
|||
* 配送员取货(状态:待取货 -> 配送中) |
|||
*/ |
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void workerPickup(String deliveryId, String workerId) { |
|||
MallDeliveryOrder delivery = checkWorkerAndStatus(deliveryId, workerId, STATUS_WAIT_PICKUP); |
|||
|
|||
LambdaUpdateWrapper<MallDeliveryOrder> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(MallDeliveryOrder::getId, deliveryId) |
|||
.set(MallDeliveryOrder::getStatus, STATUS_DELIVERING) |
|||
.set(MallDeliveryOrder::getGetTime, new Date()); |
|||
this.update(uw); |
|||
|
|||
// 同步订单状态 -> 配送中
|
|||
if (StringUtils.isNotBlank(delivery.getOrderId())) { |
|||
LambdaUpdateWrapper<MallOrder> oUw = new LambdaUpdateWrapper<>(); |
|||
oUw.eq(MallOrder::getId, delivery.getOrderId()) |
|||
.set(MallOrder::getStatus, ORDER_STATUS_DELIVERING); |
|||
mallOrderService.update(oUw); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 配送员送达(状态:配送中 -> 已送达) |
|||
*/ |
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void workerComplete(String deliveryId, String workerId) { |
|||
MallDeliveryOrder delivery = checkWorkerAndStatus(deliveryId, workerId, STATUS_DELIVERING); |
|||
|
|||
LambdaUpdateWrapper<MallDeliveryOrder> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(MallDeliveryOrder::getId, deliveryId) |
|||
.set(MallDeliveryOrder::getStatus, STATUS_DONE) |
|||
.set(MallDeliveryOrder::getFinishTime, new Date()); |
|||
this.update(uw); |
|||
|
|||
// 同步订单状态 -> 已完成
|
|||
if (StringUtils.isNotBlank(delivery.getOrderId())) { |
|||
LambdaUpdateWrapper<MallOrder> oUw = new LambdaUpdateWrapper<>(); |
|||
oUw.eq(MallOrder::getId, delivery.getOrderId()) |
|||
.set(MallOrder::getStatus, ORDER_STATUS_DONE); |
|||
mallOrderService.update(oUw); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 取消配送单 |
|||
*/ |
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void cancelDelivery(String deliveryId) { |
|||
MallDeliveryOrder delivery = this.getById(deliveryId); |
|||
if (delivery == null) throw new RuntimeException("配送单不存在"); |
|||
|
|||
LambdaUpdateWrapper<MallDeliveryOrder> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(MallDeliveryOrder::getId, deliveryId) |
|||
.set(MallDeliveryOrder::getStatus, STATUS_CANCELLED); |
|||
this.update(uw); |
|||
} |
|||
|
|||
// ================================================================
|
|||
// 私有工具
|
|||
// ================================================================
|
|||
|
|||
private MallDeliveryOrder checkWorkerAndStatus(String deliveryId, String workerId, int expectedStatus) { |
|||
MallDeliveryOrder delivery = this.getById(deliveryId); |
|||
if (delivery == null) throw new RuntimeException("配送单不存在"); |
|||
if (delivery.getStatus() != expectedStatus) { |
|||
throw new RuntimeException("配送单状态不正确,当前状态: " + delivery.getStatus()); |
|||
} |
|||
if (!workerId.equals(delivery.getWorkerId())) { |
|||
throw new RuntimeException("非本单配送员,无权操作"); |
|||
} |
|||
return delivery; |
|||
} |
|||
|
|||
@Override |
|||
public long countWaitGrabOrders(int deliveryType) { |
|||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MallDeliveryOrder> query = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>(); |
|||
query.isNull(MallDeliveryOrder::getWorkerId) |
|||
.eq(MallDeliveryOrder::getStatus, 0) |
|||
.eq(MallDeliveryOrder::getDeliveryType, deliveryType); |
|||
return this.count(query); |
|||
} |
|||
} |
|||
@ -0,0 +1,204 @@ |
|||
package cc.hiver.mall.serviceimpl.mybatis; |
|||
|
|||
import cc.hiver.core.entity.Worker; |
|||
import cc.hiver.core.service.WorkerService; |
|||
import cc.hiver.mall.dao.mapper.MallDeliveryOrderMapper; |
|||
import cc.hiver.mall.dao.mapper.MallOrderGroupMapper; |
|||
import cc.hiver.mall.dao.mapper.MallRefundRecordMapper; |
|||
import cc.hiver.mall.dao.mapper.WorkerRelaPriceMapper; |
|||
import cc.hiver.mall.entity.*; |
|||
import cc.hiver.mall.service.mybatis.MallOrderGroupService; |
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
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 lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Lazy; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import java.util.Arrays; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 拼团主表 Service 实现 |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class MallOrderGroupServiceImpl extends ServiceImpl<MallOrderGroupMapper, MallOrderGroup> |
|||
implements MallOrderGroupService { |
|||
|
|||
// 拼团状态
|
|||
private static final int GROUP_STATUS_FORMING = 0; |
|||
private static final int GROUP_STATUS_SUCCESS = 1; |
|||
private static final int GROUP_STATUS_FAIL = 2; |
|||
private static final int GROUP_STATUS_FACE2FACE = 3; |
|||
|
|||
// 订单状态
|
|||
private static final int ORDER_STATUS_WAIT_GROUP = 10; // 待成团(01)
|
|||
private static final int ORDER_STATUS_WAIT_SHOP = 1; |
|||
private static final int ORDER_STATUS_CANCELLED = 6; |
|||
|
|||
@Autowired |
|||
@Lazy |
|||
private MallOrderService mallOrderService; |
|||
|
|||
@Autowired |
|||
private MallRefundRecordMapper mallRefundRecordMapper; |
|||
|
|||
@Autowired |
|||
private MallDeliveryOrderMapper mallDeliveryOrderMapper; |
|||
|
|||
@Autowired |
|||
private WorkerService workerService; |
|||
|
|||
@Autowired |
|||
private WorkerRelaPriceMapper workerRelaPriceMapper; |
|||
|
|||
/** |
|||
* 检查是否成团,满足则自动激活所有子订单 |
|||
*/ |
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void checkAndActivateGroup(String groupId) { |
|||
MallOrderGroup group = this.getById(groupId); |
|||
if (group == null) return; |
|||
|
|||
// 已成团或已失败不重复处理
|
|||
if (group.getStatus() == GROUP_STATUS_SUCCESS) return; |
|||
|
|||
if (group.getCurrentMembers() < group.getTargetMembers()) return; |
|||
|
|||
// 达到成团人数 -> 激活
|
|||
LambdaUpdateWrapper<MallOrderGroup> guw = new LambdaUpdateWrapper<>(); |
|||
guw.eq(MallOrderGroup::getId, groupId) |
|||
.set(MallOrderGroup::getStatus, GROUP_STATUS_SUCCESS); |
|||
this.update(guw); |
|||
|
|||
// 将所有关联子订单从"待成团"改为"待商家接单"
|
|||
// 通过 head_order_id 找到团长订单,再通过 groupUserIds 关联
|
|||
// 策略:查所有 orderId 在 groupOrderIds 中、shopId 相同、status=待成团的订单
|
|||
// 更简洁:直接查 mall_order 中 id in (groupOrderIds split) AND shop_id = ? AND status = 10
|
|||
List<String> orderIdList = Arrays.asList(group.getGroupOrderIds().split(",")); |
|||
|
|||
LambdaQueryWrapper<MallOrder> oqw = new LambdaQueryWrapper<>(); |
|||
oqw.eq(MallOrder::getShopId, group.getShopId()) |
|||
.eq(MallOrder::getStatus, ORDER_STATUS_WAIT_GROUP) |
|||
.in(MallOrder::getId, orderIdList); |
|||
List<MallOrder> waitingOrders = mallOrderService.list(oqw); |
|||
|
|||
boolean isFace2Face = (group.getStatus() == GROUP_STATUS_FACE2FACE |
|||
|| (group.getStatus() == GROUP_STATUS_SUCCESS |
|||
&& this.getById(groupId).getStatus() == GROUP_STATUS_FACE2FACE)); |
|||
|
|||
for (MallOrder order : waitingOrders) { |
|||
int targetStatus = (order.getDeliveryType() != null && order.getDeliveryType() == 1) ? 2 : 3; |
|||
LambdaUpdateWrapper<MallOrder> ouw = new LambdaUpdateWrapper<>(); |
|||
ouw.eq(MallOrder::getId, order.getId()) |
|||
.set(MallOrder::getStatus, targetStatus); |
|||
mallOrderService.update(ouw); |
|||
|
|||
if (order.getDeliveryType() != null && order.getDeliveryType() == 1) { |
|||
if (isFace2Face && !order.getId().equals(group.getHeadOrderId())) { |
|||
// 面对面团参团人共享团长配送单,因此参团人本身没有配送单,直接跳过
|
|||
continue; |
|||
} |
|||
|
|||
// 取出预创建的运单进行校验
|
|||
LambdaQueryWrapper<MallDeliveryOrder> dqw = new LambdaQueryWrapper<>(); |
|||
dqw.eq(MallDeliveryOrder::getOrderId, order.getId()).last("LIMIT 1"); |
|||
MallDeliveryOrder delivery = mallDeliveryOrderMapper.selectOne(dqw); |
|||
|
|||
if (delivery != null) { |
|||
if (StringUtils.isNotBlank(delivery.getWorkerId())) { |
|||
boolean isValid = false; |
|||
Worker worker = workerService.findById(delivery.getWorkerId()); |
|||
if (worker != null && worker.getIsOnLine() != null && worker.getIsOnLine() == 1 |
|||
&& worker.getGetPushOrder() != null && worker.getGetPushOrder() == 1) { |
|||
|
|||
LambdaQueryWrapper<WorkerRelaPrice> ruleQuery = new LambdaQueryWrapper<>(); |
|||
ruleQuery.eq(WorkerRelaPrice::getWorkerId, delivery.getWorkerId()) |
|||
.eq(WorkerRelaPrice::getOrderType, 0) |
|||
.eq(WorkerRelaPrice::getGetPushOrder, 1) |
|||
.eq(WorkerRelaPrice::getGetAreaId, delivery.getGetAreaId()) |
|||
.eq(WorkerRelaPrice::getPutAreaId, delivery.getPutAreaId()) |
|||
.last("LIMIT 1"); |
|||
WorkerRelaPrice rule = workerRelaPriceMapper.selectOne(ruleQuery); |
|||
if (rule != null) { |
|||
isValid = true; |
|||
} |
|||
} |
|||
|
|||
if (!isValid) { |
|||
// 配送员下线或规则关闭 -> 去掉 workerId 进入抢单大厅,保留原有补贴金
|
|||
delivery.setWorkerId(""); |
|||
} |
|||
} |
|||
|
|||
// 统一修改运单状态为0使其生效
|
|||
delivery.setStatus(0); |
|||
mallDeliveryOrderMapper.updateById(delivery); |
|||
} |
|||
} |
|||
} |
|||
|
|||
log.info("拼团 {} 成团成功,激活 {} 条子订单", groupId, waitingOrders.size()); |
|||
} |
|||
|
|||
/** |
|||
* 拼团过期:所有子订单取消并生成退款记录 |
|||
*/ |
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void expireGroup(String groupId) { |
|||
MallOrderGroup group = this.getById(groupId); |
|||
if (group == null) return; |
|||
if (group.getStatus() != GROUP_STATUS_FORMING && group.getStatus() != GROUP_STATUS_FACE2FACE) return; |
|||
|
|||
// 标记拼团失败
|
|||
LambdaUpdateWrapper<MallOrderGroup> guw = new LambdaUpdateWrapper<>(); |
|||
guw.eq(MallOrderGroup::getId, groupId) |
|||
.set(MallOrderGroup::getStatus, GROUP_STATUS_FAIL); |
|||
this.update(guw); |
|||
|
|||
// 找出所有待成团子订单
|
|||
List<String> orderIdList = Arrays.asList(group.getGroupOrderIds().split(",")); |
|||
LambdaQueryWrapper<MallOrder> oqw = new LambdaQueryWrapper<>(); |
|||
oqw.eq(MallOrder::getShopId, group.getShopId()) |
|||
.eq(MallOrder::getStatus, ORDER_STATUS_WAIT_GROUP) |
|||
.in(MallOrder::getId, orderIdList); |
|||
List<MallOrder> waitingOrders = mallOrderService.list(oqw); |
|||
|
|||
for (MallOrder order : waitingOrders) { |
|||
// 取消子订单
|
|||
LambdaUpdateWrapper<MallOrder> ouw = new LambdaUpdateWrapper<>(); |
|||
ouw.eq(MallOrder::getId, order.getId()) |
|||
.set(MallOrder::getStatus, ORDER_STATUS_CANCELLED); |
|||
mallOrderService.update(ouw); |
|||
|
|||
// 取消未生效的配送单
|
|||
if (order.getDeliveryType() != null && order.getDeliveryType() == 1) { |
|||
LambdaUpdateWrapper<MallDeliveryOrder> duw = new LambdaUpdateWrapper<>(); |
|||
duw.eq(MallDeliveryOrder::getOrderId, order.getId()) |
|||
.in(MallDeliveryOrder::getStatus, java.util.Arrays.asList(-1, 0)) |
|||
.set(MallDeliveryOrder::getStatus, 4); |
|||
mallDeliveryOrderMapper.update(null, duw); |
|||
} |
|||
|
|||
// 生成退款记录
|
|||
MallRefundRecord refund = new MallRefundRecord(); |
|||
refund.setOrderId(order.getId()); |
|||
refund.setUserId(order.getUserId()); |
|||
refund.setRefundAmount(order.getTotalAmount()); |
|||
refund.setReason("拼团超时未成团,系统自动退款"); |
|||
refund.setStatus(0); |
|||
refund.setCreateTime(new Date()); |
|||
mallRefundRecordMapper.insert(refund); |
|||
} |
|||
|
|||
log.info("拼团 {} 已过期,取消 {} 条子订单并生成退款记录", groupId, waitingOrders.size()); |
|||
} |
|||
} |
|||
@ -0,0 +1,769 @@ |
|||
package cc.hiver.mall.serviceimpl.mybatis; |
|||
|
|||
import cc.hiver.mall.dao.mapper.*; |
|||
import cc.hiver.mall.entity.*; |
|||
import cc.hiver.mall.pojo.dto.CreateOrderDTO; |
|||
import cc.hiver.mall.pojo.query.MallOrderPageQuery; |
|||
import cc.hiver.mall.pojo.vo.MallOrderVO; |
|||
import cc.hiver.mall.service.mybatis.MallDeliveryOrderService; |
|||
import cc.hiver.mall.service.mybatis.MallOrderGroupService; |
|||
import cc.hiver.mall.service.mybatis.MallOrderService; |
|||
import cc.hiver.mall.service.mybatis.MallRefundRecordService; |
|||
import cn.hutool.json.JSONArray; |
|||
import cn.hutool.json.JSONObject; |
|||
import cn.hutool.json.JSONUtil; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
|||
import com.baomidou.mybatisplus.core.metadata.IPage; |
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.math.RoundingMode; |
|||
import java.util.ArrayList; |
|||
import java.util.Calendar; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* 核心订单 Service 实现 |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class MallOrderServiceImpl extends ServiceImpl<MallOrderMapper, MallOrder> implements MallOrderService { |
|||
|
|||
// 订单状态常量
|
|||
private static final int STATUS_WAIT_PAY = 0; |
|||
private static final int STATUS_WAIT_GROUP = 10; // 数据库存储为 "01",用数字10代替
|
|||
private static final int STATUS_WAIT_SHOP = 1; |
|||
private static final int STATUS_WAIT_DELIVERY = 2; |
|||
private static final int STATUS_WAIT_PICKUP = 3; |
|||
private static final int STATUS_DELIVERING = 4; |
|||
private static final int STATUS_DONE = 5; |
|||
private static final int STATUS_CANCELLED = 6; |
|||
private static final int STATUS_WAIT_REFUND = 7; |
|||
private static final int STATUS_REFUNDED = 8; |
|||
|
|||
// 配送方式常量
|
|||
private static final int DELIVERY_TYPE_EXPRESS = 1; // 外卖
|
|||
private static final int DELIVERY_TYPE_SELF = 2; // 自取
|
|||
|
|||
// 订单类型常量
|
|||
private static final int ORDER_TYPE_NORMAL = 1; // 直接购买
|
|||
private static final int ORDER_TYPE_GROUP = 2; // 拼团购买
|
|||
|
|||
// 拼团状态常量
|
|||
private static final int GROUP_STATUS_FORMING = 0; // 拼团中
|
|||
private static final int GROUP_STATUS_SUCCESS = 1; // 拼团成功
|
|||
private static final int GROUP_STATUS_FAIL = 2; // 拼团失败
|
|||
private static final int GROUP_STATUS_FACE2FACE = 3; // 面对面团
|
|||
|
|||
@Autowired |
|||
private MallOrderGoodsMapper mallOrderGoodsMapper; |
|||
|
|||
@Autowired |
|||
private MallOrderGroupMapper mallOrderGroupMapper; |
|||
|
|||
@Autowired |
|||
private MallDeliveryOrderMapper mallDeliveryOrderMapper; |
|||
|
|||
@Autowired |
|||
private MallRefundRecordMapper mallRefundRecordMapper; |
|||
|
|||
@Autowired |
|||
private MallOrderGroupService mallOrderGroupService; |
|||
|
|||
@Autowired |
|||
private MallDeliveryOrderService mallDeliveryOrderService; |
|||
|
|||
@Autowired |
|||
private MallRefundRecordService mallRefundRecordService; |
|||
|
|||
@Autowired |
|||
private ProductMapper productMapper; |
|||
|
|||
@Autowired |
|||
private UserAddressMapper userAddressMapper; |
|||
|
|||
@Autowired |
|||
private cc.hiver.mall.dao.mapper.ShopMapper shopMapper; |
|||
|
|||
// ================================================================
|
|||
// 核心下单逻辑
|
|||
// ================================================================
|
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public MallOrderVO createOrder(CreateOrderDTO dto) { |
|||
|
|||
// 1. 基础校验
|
|||
if (dto.getItems() == null || dto.getItems().isEmpty()) { |
|||
throw new RuntimeException("订单商品不能为空"); |
|||
} |
|||
if(StringUtils.isBlank(dto.getGroupId())){ |
|||
if (DELIVERY_TYPE_EXPRESS == dto.getDeliveryType() && StringUtils.isBlank(dto.getAddressId())) { |
|||
throw new RuntimeException("外卖配送时收货地址不能为空"); |
|||
} |
|||
} |
|||
// 2. 扣库存 & 计算商品总金额
|
|||
BigDecimal goodsAmount = BigDecimal.ZERO; |
|||
List<MallOrderGoods> goodsSnapshots = new ArrayList<>(); |
|||
|
|||
for (CreateOrderDTO.OrderItemDTO item : dto.getItems()) { |
|||
Product product = productMapper.selectById(item.getProductId()); |
|||
if (product == null) { |
|||
throw new RuntimeException("商品不存在: " + item.getProductId()); |
|||
} |
|||
// 扣减库存(更新 attributeListPrice 中的 specNum)
|
|||
deductStock(product, item.getQuantity()); |
|||
|
|||
// 累计商品金额
|
|||
goodsAmount = goodsAmount.add(item.getPrice().multiply(new BigDecimal(item.getQuantity()))); |
|||
|
|||
// 构建商品快照(orderId 后续填充)
|
|||
MallOrderGoods snapshot = new MallOrderGoods(); |
|||
snapshot.setProductId(item.getProductId()); |
|||
snapshot.setProductName(product.getProductName()); |
|||
snapshot.setProductPicture(product.getProductPicture()); |
|||
snapshot.setSpecs(item.getSpecs()); |
|||
snapshot.setPrice(item.getPrice()); |
|||
snapshot.setQuantity(item.getQuantity()); |
|||
goodsSnapshots.add(snapshot); |
|||
} |
|||
|
|||
BigDecimal packageFee = dto.getPackageFee() != null ? dto.getPackageFee() : BigDecimal.ZERO; |
|||
|
|||
// 3. 判断订单类型
|
|||
boolean isGroupOrder = dto.getGroupParam() != null || StringUtils.isNotBlank(dto.getGroupId()); |
|||
|
|||
if (!isGroupOrder) { |
|||
// ---- 普通订单 ----
|
|||
return createNormalOrder(dto, goodsAmount, packageFee, goodsSnapshots); |
|||
} else if (StringUtils.isNotBlank(dto.getGroupId())) { |
|||
// ---- 参团 ----
|
|||
return joinGroup(dto, goodsAmount, packageFee, goodsSnapshots); |
|||
} else { |
|||
// ---- 发起拼团 ----
|
|||
return startGroup(dto, goodsAmount, packageFee, goodsSnapshots); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 创建普通订单 |
|||
*/ |
|||
private MallOrderVO createNormalOrder(CreateOrderDTO dto, BigDecimal goodsAmount, |
|||
BigDecimal packageFee, List<MallOrderGoods> goodsSnapshots) { |
|||
BigDecimal deliveryFee = BigDecimal.ZERO; |
|||
if (DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
if (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) { |
|||
// 有指定配送员
|
|||
deliveryFee = dto.getWorkerParam().getOrderBkge() != null ? dto.getWorkerParam().getOrderBkge() : BigDecimal.ZERO; |
|||
} else { |
|||
// 没有指定配送员,用户自设佣金
|
|||
if (dto.getWorkerParam() != null && dto.getWorkerParam().getOrderBkge() != null) { |
|||
deliveryFee = dto.getWorkerParam().getOrderBkge(); |
|||
} else if (dto.getGroupParam() != null && dto.getGroupParam().getSelfCommission() != null) { |
|||
deliveryFee = dto.getGroupParam().getSelfCommission(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
MallOrder order = buildBaseOrder(dto, ORDER_TYPE_NORMAL, goodsAmount, deliveryFee, packageFee); |
|||
// 待支付
|
|||
order.setStatus(STATUS_WAIT_PAY); |
|||
this.save(order); |
|||
|
|||
// 保存商品快照
|
|||
saveGoodsSnapshots(goodsSnapshots, order.getId()); |
|||
|
|||
// 若外卖,创建配送单(初始状态 -1:待支付/待成团)
|
|||
if (DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
createDeliveryOrder(order, dto, null, -1, null); |
|||
} |
|||
|
|||
return buildVO(order, goodsSnapshots, null, null); |
|||
} |
|||
|
|||
/** |
|||
* 发起拼团 |
|||
*/ |
|||
private MallOrderVO startGroup(CreateOrderDTO dto, BigDecimal goodsAmount, |
|||
BigDecimal packageFee, List<MallOrderGoods> goodsSnapshots) { |
|||
CreateOrderDTO.GroupParam gp = dto.getGroupParam(); |
|||
if (gp == null || gp.getTargetMembers() == null || gp.getTargetMembers() < 2) { |
|||
throw new RuntimeException("拼团人数不能少于2人"); |
|||
} |
|||
|
|||
boolean isFace2Face = gp.getIsFaceToFace() != null && gp.getIsFaceToFace() == 1; |
|||
|
|||
// 计算面对面团团长个人配送费(当前1人)
|
|||
BigDecimal deliveryFee = BigDecimal.ZERO; |
|||
if (isFace2Face && DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
BigDecimal selfComm = null; |
|||
if (dto.getWorkerParam() != null && StringUtils.isBlank(dto.getWorkerParam().getWorkerId()) && dto.getWorkerParam().getOrderBkge() != null) { |
|||
selfComm = dto.getWorkerParam().getOrderBkge(); |
|||
} else if (gp.getSelfCommission() != null) { |
|||
selfComm = gp.getSelfCommission(); |
|||
} |
|||
CreateOrderDTO.WorkerParam validWorker = (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) ? dto.getWorkerParam() : null; |
|||
Integer targetMembers = gp.getTargetMembers() != null ? gp.getTargetMembers() : 2; |
|||
deliveryFee = calcPersonalFee( |
|||
calcTotalDeliveryFee(selfComm, validWorker, targetMembers), |
|||
targetMembers |
|||
); |
|||
} else if (!isFace2Face && DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
// 普通拼团,团长有独立配送费计算
|
|||
if (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) { |
|||
deliveryFee = dto.getWorkerParam().getOrderBkge() != null ? dto.getWorkerParam().getOrderBkge() : BigDecimal.ZERO; |
|||
} else { |
|||
if (dto.getWorkerParam() != null && dto.getWorkerParam().getOrderBkge() != null) { |
|||
deliveryFee = dto.getWorkerParam().getOrderBkge(); |
|||
} else if (gp != null && gp.getSelfCommission() != null) { |
|||
deliveryFee = gp.getSelfCommission(); |
|||
} |
|||
} |
|||
} |
|||
// 创建团长订单(初始状态:待支付)
|
|||
MallOrder order = buildBaseOrder(dto, ORDER_TYPE_GROUP, goodsAmount, deliveryFee, packageFee); |
|||
order.setStatus(STATUS_WAIT_PAY); |
|||
this.save(order); |
|||
// 创建拼团主记录
|
|||
MallOrderGroup group = new MallOrderGroup(); |
|||
group.setShopId(dto.getShopId()); |
|||
group.setHeadUserId(dto.getUserId()); |
|||
group.setTargetMembers(gp.getTargetMembers()); |
|||
group.setCurrentMembers(1); |
|||
if(goodsSnapshots != null && !goodsSnapshots.isEmpty()){ |
|||
group.setGroupPrice(goodsSnapshots.get(0).getPrice()); |
|||
} |
|||
group.setHeadOrderId(order.getId()); |
|||
group.setStatus(isFace2Face ? GROUP_STATUS_FACE2FACE : GROUP_STATUS_FORMING); |
|||
group.setGroupOrderIds(order.getId()); |
|||
group.setCreateTime(new Date()); |
|||
|
|||
// 拼团过期时间:24小时后
|
|||
Calendar cal = Calendar.getInstance(); |
|||
cal.add(Calendar.HOUR_OF_DAY, 24); |
|||
group.setExpireTime(cal.getTime()); |
|||
|
|||
// 设置配送员和佣金信息
|
|||
if (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) { |
|||
group.setWorkerId(dto.getWorkerParam().getWorkerId()); |
|||
group.setWorkerCommission(dto.getWorkerParam().getOrderBkge()); |
|||
} else { |
|||
if (dto.getWorkerParam() != null && dto.getWorkerParam().getOrderBkge() != null) { |
|||
group.setWorkerCommission(dto.getWorkerParam().getOrderBkge()); |
|||
} else if (gp.getSelfCommission() != null) { |
|||
group.setWorkerCommission(gp.getSelfCommission()); |
|||
} |
|||
} |
|||
|
|||
// 面对面团才有总配送费概念(非面对面团各自结算)
|
|||
if (isFace2Face) { |
|||
CreateOrderDTO.WorkerParam validWorker = (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) ? dto.getWorkerParam() : null; |
|||
group.setTotalDeliveryFee(calcTotalDeliveryFee(group.getWorkerCommission(), validWorker, gp.getTargetMembers())); |
|||
} |
|||
|
|||
mallOrderGroupMapper.insert(group); |
|||
// 保存商品快照
|
|||
saveGoodsSnapshots(goodsSnapshots, order.getId()); |
|||
|
|||
// 若外卖,创建独立或共用配送单(初始状态 -1:待支付/待成团)
|
|||
if (DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
createDeliveryOrder(order, dto, group.getId(), -1, isFace2Face ? group.getTotalDeliveryFee() : null); |
|||
} |
|||
|
|||
// 将 groupId 关联到订单 (MallOrder 没有 groupId 字段,通过 MallOrderGroup 关联已足够)
|
|||
|
|||
return buildVO(order, goodsSnapshots, group, null); |
|||
} |
|||
|
|||
/** |
|||
* 参团 |
|||
*/ |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public MallOrderVO joinGroup(CreateOrderDTO dto, BigDecimal goodsAmount, |
|||
BigDecimal packageFee, List<MallOrderGoods> goodsSnapshots) { |
|||
MallOrderGroup group = mallOrderGroupMapper.selectById(dto.getGroupId()); |
|||
if (group == null) { |
|||
throw new RuntimeException("拼团不存在"); |
|||
} |
|||
if (group.getStatus() != GROUP_STATUS_FORMING && group.getStatus() != GROUP_STATUS_FACE2FACE) { |
|||
throw new RuntimeException("当前拼团状态不可参团"); |
|||
} |
|||
if (group.getCurrentMembers() >= group.getTargetMembers()) { |
|||
throw new RuntimeException("拼团已满员"); |
|||
} |
|||
|
|||
boolean isFace2Face = (group.getStatus() == GROUP_STATUS_FACE2FACE); |
|||
|
|||
// 面对面团:不再重新计算,直接使用根据 targetMembers 均摊的金额
|
|||
BigDecimal deliveryFee = BigDecimal.ZERO; |
|||
if (isFace2Face && DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
deliveryFee = calcPersonalFee(group.getTotalDeliveryFee(), group.getTargetMembers()); |
|||
} else if (!isFace2Face && DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
// 普通拼团参团时独立计算个人配送费
|
|||
if (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) { |
|||
deliveryFee = dto.getWorkerParam().getOrderBkge() != null ? dto.getWorkerParam().getOrderBkge() : BigDecimal.ZERO; |
|||
} else { |
|||
if (dto.getWorkerParam() != null && dto.getWorkerParam().getOrderBkge() != null) { |
|||
deliveryFee = dto.getWorkerParam().getOrderBkge(); |
|||
} else if (dto.getGroupParam() != null && dto.getGroupParam().getSelfCommission() != null) { |
|||
deliveryFee = dto.getGroupParam().getSelfCommission(); |
|||
} |
|||
} |
|||
} |
|||
// 创建参团人订单(初始状态:待支付)
|
|||
MallOrder order = buildBaseOrder(dto, ORDER_TYPE_GROUP, goodsAmount, deliveryFee, packageFee); |
|||
order.setStatus(STATUS_WAIT_PAY); |
|||
this.save(order); |
|||
|
|||
saveGoodsSnapshots(goodsSnapshots, order.getId()); |
|||
// 更新拼团人数和成员列表
|
|||
int newMembers = group.getCurrentMembers() + 1; |
|||
String newOrderIds = group.getGroupOrderIds() + "," + order.getId(); |
|||
group.setCurrentMembers(newMembers); |
|||
group.setGroupOrderIds(newOrderIds); |
|||
|
|||
mallOrderGroupMapper.updateById(group); |
|||
|
|||
// 普通拼团参团(非面对面),若外卖,为参团人创建独立配送单(初始状态 -1:待支付/待成团)
|
|||
if (!isFace2Face && DELIVERY_TYPE_EXPRESS == dto.getDeliveryType()) { |
|||
createDeliveryOrder(order, dto, group.getId(), -1, null); |
|||
} |
|||
|
|||
return buildVO(order, goodsSnapshots, group, null); |
|||
} |
|||
|
|||
// ================================================================
|
|||
// 订单生命周期
|
|||
// ================================================================
|
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void shopAccept(String orderId) { |
|||
MallOrder order = getAndCheckOrder(orderId, STATUS_WAIT_PAY); |
|||
|
|||
if (DELIVERY_TYPE_EXPRESS == order.getDeliveryType()) { |
|||
// 外卖:进入待配送员接单
|
|||
updateOrderStatus(orderId, STATUS_WAIT_DELIVERY); |
|||
} else { |
|||
// 自取:进入待消费
|
|||
updateOrderStatus(orderId, STATUS_WAIT_PICKUP); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void paySuccess(String orderId) { |
|||
MallOrder order = this.getById(orderId); |
|||
if (order == null) throw new RuntimeException("订单不存在"); |
|||
if (order.getStatus() != STATUS_WAIT_PAY) { |
|||
return; // 已经处理过或状态不对
|
|||
} |
|||
|
|||
if (order.getOrderType() == ORDER_TYPE_NORMAL) { |
|||
// 直购支付成功 -> 跳过待商家接单,直接进入待配送或待消费
|
|||
if (DELIVERY_TYPE_EXPRESS == order.getDeliveryType()) { |
|||
updateOrderStatus(orderId, STATUS_WAIT_DELIVERY); |
|||
// 激活配送单,状态改为待接单(0)
|
|||
LambdaUpdateWrapper<MallDeliveryOrder> duw = new LambdaUpdateWrapper<>(); |
|||
duw.eq(MallDeliveryOrder::getOrderId, orderId) |
|||
.set(MallDeliveryOrder::getStatus, 0); |
|||
mallDeliveryOrderMapper.update(null, duw); |
|||
} else { |
|||
updateOrderStatus(orderId, STATUS_WAIT_PICKUP); |
|||
} |
|||
} else if (order.getOrderType() == ORDER_TYPE_GROUP) { |
|||
// 拼团订单支付成功 -> 改为待成团
|
|||
updateOrderStatus(orderId, STATUS_WAIT_GROUP); |
|||
|
|||
// 查找属于该订单的拼团并检查成团条件
|
|||
LambdaQueryWrapper<MallOrderGroup> gq = new LambdaQueryWrapper<>(); |
|||
gq.eq(MallOrderGroup::getHeadOrderId, orderId); |
|||
MallOrderGroup group = mallOrderGroupMapper.selectOne(gq); |
|||
if (group == null) { |
|||
// 如果不是团长,按参团人查找
|
|||
LambdaQueryWrapper<MallOrderGroup> gq2 = new LambdaQueryWrapper<>(); |
|||
gq2.eq(MallOrderGroup::getShopId, order.getShopId()) |
|||
.like(MallOrderGroup::getGroupOrderIds, order.getId()) |
|||
.last("LIMIT 1"); |
|||
group = mallOrderGroupMapper.selectOne(gq2); |
|||
} |
|||
if (group != null) { |
|||
mallOrderGroupService.checkAndActivateGroup(group.getId()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void shopReject(String orderId, String reason) { |
|||
MallOrder order = getById(orderId); |
|||
if (order == null) throw new RuntimeException("订单不存在"); |
|||
updateOrderStatus(orderId, STATUS_CANCELLED); |
|||
// 先取消相关的配送单
|
|||
LambdaUpdateWrapper<MallDeliveryOrder> duw = new LambdaUpdateWrapper<>(); |
|||
duw.eq(MallDeliveryOrder::getOrderId, orderId) |
|||
.in(MallDeliveryOrder::getStatus, java.util.Arrays.asList(-1, 0)) |
|||
.set(MallDeliveryOrder::getStatus, 4); |
|||
mallDeliveryOrderMapper.update(null, duw); |
|||
// 自动生成退款记录
|
|||
createRefundRecord(order, order.getTotalAmount(), "商家拒单: " + reason); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void cancelOrder(String orderId, String userId) { |
|||
MallOrder order = getById(orderId); |
|||
if (order == null) throw new RuntimeException("订单不存在"); |
|||
if (!userId.equals(order.getUserId())) throw new RuntimeException("无权操作该订单"); |
|||
if (order.getStatus() != STATUS_WAIT_PAY && order.getStatus() != STATUS_WAIT_GROUP) { |
|||
throw new RuntimeException("当前订单状态不允许取消"); |
|||
} |
|||
updateOrderStatus(orderId, STATUS_CANCELLED); |
|||
// 先取消相关的配送单
|
|||
LambdaUpdateWrapper<MallDeliveryOrder> duw = new LambdaUpdateWrapper<>(); |
|||
duw.eq(MallDeliveryOrder::getOrderId, orderId) |
|||
.in(MallDeliveryOrder::getStatus, java.util.Arrays.asList(-1, 0)) |
|||
.set(MallDeliveryOrder::getStatus, 4); |
|||
mallDeliveryOrderMapper.update(null, duw); |
|||
// 若已支付(待成团状态),生成退款记录
|
|||
if (order.getStatus() == STATUS_WAIT_GROUP) { |
|||
createRefundRecord(order, order.getTotalAmount(), "用户取消拼团"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void applyRefund(String orderId, String userId, String reason, BigDecimal amount) { |
|||
MallOrder order = getById(orderId); |
|||
if (order == null) throw new RuntimeException("订单不存在"); |
|||
if (!userId.equals(order.getUserId())) throw new RuntimeException("无权操作该订单"); |
|||
updateOrderStatus(orderId, STATUS_WAIT_REFUND); |
|||
createRefundRecord(order, amount, reason); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void agreeRefund(String orderId) { |
|||
MallOrder order = getAndCheckOrder(orderId, STATUS_WAIT_REFUND); |
|||
updateOrderStatus(orderId, STATUS_REFUNDED); |
|||
// 更新退款记录为成功
|
|||
LambdaUpdateWrapper<MallRefundRecord> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(MallRefundRecord::getOrderId, orderId) |
|||
.set(MallRefundRecord::getStatus, 1) |
|||
.set(MallRefundRecord::getSuccessTime, new Date()); |
|||
mallRefundRecordMapper.update(null, uw); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void rejectRefund(String orderId, String reason) { |
|||
getAndCheckOrder(orderId, STATUS_WAIT_REFUND); |
|||
// 退款拒绝,恢复到已完成(或待商家处理状态)
|
|||
updateOrderStatus(orderId, STATUS_DONE); |
|||
// 更新退款记录为拒绝
|
|||
LambdaUpdateWrapper<MallRefundRecord> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(MallRefundRecord::getOrderId, orderId) |
|||
.set(MallRefundRecord::getStatus, 2); |
|||
mallRefundRecordMapper.update(null, uw); |
|||
} |
|||
|
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void completeOrder(String orderId) { |
|||
MallOrder order = getById(orderId); |
|||
if (order == null) throw new RuntimeException("订单不存在"); |
|||
if (order.getStatus() != STATUS_WAIT_PICKUP && order.getStatus() != STATUS_DELIVERING) { |
|||
throw new RuntimeException("当前状态不可完成"); |
|||
} |
|||
updateOrderStatus(orderId, STATUS_DONE); |
|||
} |
|||
|
|||
@Override |
|||
public MallOrder selectMallOrderByGroupId(String groupId) { |
|||
return this.baseMapper.selectMallOrderByGroupId(groupId); |
|||
} |
|||
|
|||
// ================================================================
|
|||
// 查询
|
|||
// ================================================================
|
|||
|
|||
@Override |
|||
public IPage<MallOrderVO> pageOrder(MallOrderPageQuery q) { |
|||
IPage<MallOrderVO> page = new Page<>(q.getPageNum(), q.getPageSize()); |
|||
return this.baseMapper.selectPageVO(page, q); |
|||
} |
|||
|
|||
@Override |
|||
public MallOrderVO getOrderDetail(String orderId) { |
|||
MallOrder order = this.getById(orderId); |
|||
if (order == null) return null; |
|||
|
|||
MallOrderVO vo = new MallOrderVO(); |
|||
org.springframework.beans.BeanUtils.copyProperties(order, vo); |
|||
|
|||
// 商品列表
|
|||
vo.setGoodsList(mallOrderGoodsMapper.selectByOrderId(orderId)); |
|||
|
|||
// 配送信息
|
|||
LambdaQueryWrapper<MallDeliveryOrder> dq = new LambdaQueryWrapper<>(); |
|||
dq.eq(MallDeliveryOrder::getOrderId, orderId).last("LIMIT 1"); |
|||
vo.setDeliveryInfo(mallDeliveryOrderMapper.selectOne(dq)); |
|||
|
|||
// 拼团信息(拼团订单才查)
|
|||
if (ORDER_TYPE_GROUP == order.getOrderType()) { |
|||
// 通过 headOrderId 或 groupUserIds 中包含此 userId 查找拼团
|
|||
LambdaQueryWrapper<MallOrderGroup> gq = new LambdaQueryWrapper<>(); |
|||
// 团长订单匹配 headOrderId
|
|||
gq.eq(MallOrderGroup::getHeadOrderId, orderId); |
|||
MallOrderGroup group = mallOrderGroupMapper.selectOne(gq); |
|||
if (group == null) { |
|||
// 参团人:通过 userId + shopId 匹配
|
|||
LambdaQueryWrapper<MallOrderGroup> gq2 = new LambdaQueryWrapper<>(); |
|||
gq2.eq(MallOrderGroup::getShopId, order.getShopId()) |
|||
.like(MallOrderGroup::getGroupOrderIds, order.getId()) |
|||
.last("LIMIT 1"); |
|||
group = mallOrderGroupMapper.selectOne(gq2); |
|||
} |
|||
vo.setGroupInfo(group); |
|||
} |
|||
|
|||
return vo; |
|||
} |
|||
|
|||
// ================================================================
|
|||
// 私有工具方法
|
|||
// ================================================================
|
|||
|
|||
/** |
|||
* 扣减商品库存(更新 attributeListPrice 中的 specNum) |
|||
*/ |
|||
private void deductStock(Product product, int quantity) { |
|||
String attrJson = product.getAttributeListPrice(); |
|||
if (StringUtils.isBlank(attrJson)) throw new RuntimeException("商品规格信息异常"); |
|||
|
|||
try { |
|||
JSONArray arr = JSONUtil.parseArray(attrJson); |
|||
if (arr.isEmpty()) throw new RuntimeException("商品规格信息异常"); |
|||
JSONObject spec = arr.getJSONObject(0); |
|||
int current = spec.getInt("specNum"); |
|||
if (current < quantity) throw new RuntimeException("商品库存不足: " + product.getProductName()); |
|||
spec.set("specNum", current - quantity); |
|||
|
|||
LambdaUpdateWrapper<Product> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(Product::getId, product.getId()) |
|||
.set(Product::getAttributeListPrice, arr.toString()); |
|||
productMapper.update(null, uw); |
|||
} catch (RuntimeException e) { |
|||
throw e; |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("库存扣减失败: " + e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 构建基础订单对象 |
|||
*/ |
|||
private MallOrder buildBaseOrder(CreateOrderDTO dto, int orderType, BigDecimal goodsAmount, |
|||
BigDecimal deliveryFee, BigDecimal packageFee) { |
|||
MallOrder order = new MallOrder(); |
|||
order.setUserId(dto.getUserId()); |
|||
order.setShopId(dto.getShopId()); |
|||
order.setOrderType(orderType); |
|||
order.setDeliveryType(dto.getDeliveryType()); |
|||
order.setAddressId(dto.getAddressId()); |
|||
order.setGetAreaId(dto.getGetAreaId()); |
|||
order.setPutAreaId(dto.getPutAreaId()); |
|||
order.setGoodsAmount(goodsAmount); |
|||
order.setDeliveryFee(deliveryFee); |
|||
order.setPackageFee(packageFee); |
|||
order.setTotalAmount(goodsAmount.add(deliveryFee).add(packageFee)); |
|||
order.setRemark(dto.getRemark()); |
|||
order.setCreateTime(new Date()); |
|||
|
|||
// ---- 快照收货人信息 ----
|
|||
if (StringUtils.isNotBlank(dto.getAddressId())) { |
|||
UserAddress addr = userAddressMapper.selectById(dto.getAddressId()); |
|||
if (addr != null) { |
|||
order.setReceiverName(addr.getReceiverName()); |
|||
order.setReceiverPhone(addr.getReceiverPhone()); |
|||
// 地址合成:楼座+楼层+门牌
|
|||
String addrStr = (addr.getAreaName() != null ? addr.getAreaName() : "") |
|||
+ (addr.getFloor() != null ? " " + addr.getFloor() + "层" : "") |
|||
+ (addr.getRoomNum() != null ? " " + addr.getRoomNum() : ""); |
|||
order.setReceiverAddress(addrStr.trim()); |
|||
} |
|||
} |
|||
|
|||
// ---- 快照商家信息 ----
|
|||
if (StringUtils.isNotBlank(dto.getShopId())) { |
|||
Shop shop = shopMapper.selectById(dto.getShopId()); |
|||
if (shop != null) { |
|||
order.setShopName(shop.getShopName()); |
|||
order.setShopPhone(shop.getContactPhone()); |
|||
order.setShopAddress(shop.getShopAddress()); |
|||
} |
|||
} |
|||
|
|||
return order; |
|||
} |
|||
|
|||
/** |
|||
* 批量保存商品快照 |
|||
*/ |
|||
private void saveGoodsSnapshots(List<MallOrderGoods> snapshots, String orderId) { |
|||
for (MallOrderGoods snap : snapshots) { |
|||
snap.setOrderId(orderId); |
|||
mallOrderGoodsMapper.insert(snap); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 创建配送单 |
|||
* |
|||
* @param initialStatus 初始化状态(-1: 待支付待成团, 0: 待接单) |
|||
*/ |
|||
private void createDeliveryOrder(MallOrder order, CreateOrderDTO dto, String groupId, int initialStatus, BigDecimal overrideDeliveryFee) { |
|||
MallDeliveryOrder delivery = new MallDeliveryOrder(); |
|||
delivery.setOrderId(order.getId()); |
|||
delivery.setGroupId(groupId); |
|||
delivery.setShopId(dto.getShopId()); |
|||
delivery.setStatus(initialStatus); |
|||
delivery.setDeliveryType(1); // 外卖单
|
|||
|
|||
// 总配送费放入 delivery
|
|||
delivery.setDeliveryFee(overrideDeliveryFee != null ? overrideDeliveryFee : order.getDeliveryFee()); |
|||
delivery.setCreateTime(new Date()); |
|||
|
|||
if (dto.getMustFinishTime() != null) { |
|||
delivery.setMustFinishTime(dto.getMustFinishTime()); |
|||
} |
|||
|
|||
// 指定配送员
|
|||
if (dto.getWorkerParam() != null && StringUtils.isNotBlank(dto.getWorkerParam().getWorkerId())) { |
|||
delivery.setWorkerId(dto.getWorkerParam().getWorkerId()); |
|||
if (order.getOrderType() == ORDER_TYPE_NORMAL) { |
|||
BigDecimal bonus = order.getGoodsAmount() |
|||
.multiply(new BigDecimal("0.02")) |
|||
.setScale(2, RoundingMode.DOWN); |
|||
delivery.setDeliveryFeeMarketplace(bonus); |
|||
} |
|||
} |
|||
|
|||
// 取货区域ID(商户所在区域,前端必传)
|
|||
delivery.setGetAreaId(dto.getGetAreaId()); |
|||
|
|||
// 送货区域ID:优先使用前端传入值,兜底从地址表查询
|
|||
if (StringUtils.isNotBlank(dto.getPutAreaId())) { |
|||
delivery.setPutAreaId(dto.getPutAreaId()); |
|||
} else if (StringUtils.isNotBlank(dto.getAddressId())) { |
|||
UserAddress addr = userAddressMapper.selectById(dto.getAddressId()); |
|||
if (addr != null) { |
|||
delivery.setPutAreaId(addr.getAreaId()); |
|||
} |
|||
} |
|||
|
|||
// ---- 快照字段:从订单复制收货人及商家信息 ----
|
|||
delivery.setReceiverName(order.getReceiverName()); |
|||
delivery.setReceiverPhone(order.getReceiverPhone()); |
|||
delivery.setReceiverAddress(order.getReceiverAddress()); |
|||
delivery.setShopName(order.getShopName()); |
|||
delivery.setShopPhone(order.getShopPhone()); |
|||
delivery.setShopAddress(order.getShopAddress()); |
|||
|
|||
mallDeliveryOrderMapper.insert(delivery); |
|||
} |
|||
|
|||
/** |
|||
* 计算总配送费(面对面团专用) |
|||
*/ |
|||
private BigDecimal calcTotalDeliveryFee(BigDecimal selfCommission, |
|||
CreateOrderDTO.WorkerParam workerParam, int members) { |
|||
if (workerParam != null && workerParam.getOrderBkge() != null) { |
|||
// 指定配送员:基础 + 额外(人数>2时每多一人+0.5)是用户承担的总配送费
|
|||
BigDecimal extra = members > 2 |
|||
? new BigDecimal(members - 2).multiply(new BigDecimal("0.5")) |
|||
: BigDecimal.ZERO; |
|||
return workerParam.getOrderBkge().add(extra); |
|||
} else if (selfCommission != null) { |
|||
// 不指定配送员:团长设定佣金
|
|||
return selfCommission; |
|||
} |
|||
return BigDecimal.ZERO; |
|||
} |
|||
|
|||
/** |
|||
* 从 group 构建 WorkerParam(已存团用) |
|||
*/ |
|||
private CreateOrderDTO.WorkerParam buildWorkerParam(MallOrderGroup group) { |
|||
CreateOrderDTO.WorkerParam wp = new CreateOrderDTO.WorkerParam(); |
|||
wp.setWorkerId(group.getWorkerId()); |
|||
wp.setOrderBkge(group.getWorkerCommission()); |
|||
return wp; |
|||
} |
|||
|
|||
/** |
|||
* 配送费分摊:向上取整保留2位小数 |
|||
*/ |
|||
private BigDecimal calcPersonalFee(BigDecimal totalFee, int members) { |
|||
if (members <= 0) return totalFee; |
|||
BigDecimal result = totalFee |
|||
.divide(new BigDecimal(members), 2, RoundingMode.CEILING); |
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 更新订单状态 |
|||
*/ |
|||
private void updateOrderStatus(String orderId, int status) { |
|||
LambdaUpdateWrapper<MallOrder> uw = new LambdaUpdateWrapper<>(); |
|||
uw.eq(MallOrder::getId, orderId).set(MallOrder::getStatus, status); |
|||
this.update(uw); |
|||
} |
|||
|
|||
/** |
|||
* 获取订单并校验状态 |
|||
*/ |
|||
private MallOrder getAndCheckOrder(String orderId, int expectedStatus) { |
|||
MallOrder order = this.getById(orderId); |
|||
if (order == null) throw new RuntimeException("订单不存在"); |
|||
if (order.getStatus() != expectedStatus) { |
|||
throw new RuntimeException("订单状态不正确,当前状态: " + order.getStatus()); |
|||
} |
|||
return order; |
|||
} |
|||
|
|||
/** |
|||
* 创建退款记录 |
|||
*/ |
|||
private void createRefundRecord(MallOrder order, BigDecimal amount, String reason) { |
|||
MallRefundRecord record = new MallRefundRecord(); |
|||
record.setOrderId(order.getId()); |
|||
record.setUserId(order.getUserId()); |
|||
record.setRefundAmount(amount); |
|||
record.setReason(reason); |
|||
record.setStatus(0); // 待商家同意
|
|||
record.setCreateTime(new Date()); |
|||
mallRefundRecordMapper.insert(record); |
|||
} |
|||
|
|||
/** |
|||
* 构建返回 VO |
|||
*/ |
|||
private MallOrderVO buildVO(MallOrder order, List<MallOrderGoods> goods, |
|||
MallOrderGroup group, MallDeliveryOrder delivery) { |
|||
MallOrderVO vo = new MallOrderVO(); |
|||
org.springframework.beans.BeanUtils.copyProperties(order, vo); |
|||
vo.setGoodsList(goods); |
|||
vo.setGroupInfo(group); |
|||
vo.setDeliveryInfo(delivery); |
|||
return vo; |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
package cc.hiver.mall.serviceimpl.mybatis; |
|||
|
|||
import cc.hiver.mall.dao.mapper.MallRefundRecordMapper; |
|||
import cc.hiver.mall.entity.MallRefundRecord; |
|||
import cc.hiver.mall.service.mybatis.MallRefundRecordService; |
|||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
/** |
|||
* 退款记录 Service 实现 |
|||
*/ |
|||
@Service |
|||
public class MallRefundRecordServiceImpl extends ServiceImpl<MallRefundRecordMapper, MallRefundRecord> |
|||
implements MallRefundRecordService { |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="cc.hiver.mall.dao.mapper.MallDeliveryOrderMapper"> |
|||
|
|||
<resultMap id="deliveryMap" type="cc.hiver.mall.entity.MallDeliveryOrder"> |
|||
<id column="id" property="id"/> |
|||
<result column="order_id" property="orderId"/> |
|||
<result column="group_id" property="groupId"/> |
|||
<result column="worker_id" property="workerId"/> |
|||
<result column="shop_id" property="shopId"/> |
|||
<result column="get_area_id" property="getAreaId"/> |
|||
<result column="put_area_id" property="putAreaId"/> |
|||
<result column="delivery_fee" property="deliveryFee"/> |
|||
<result column="delivery_fee_marketplace" property="deliveryFeeMarketplace"/> |
|||
<result column="status" property="status"/> |
|||
<result column="receiver_name" property="receiverName"/> |
|||
<result column="receiver_phone" property="receiverPhone"/> |
|||
<result column="receiver_address" property="receiverAddress"/> |
|||
<result column="shop_name" property="shopName"/> |
|||
<result column="shop_phone" property="shopPhone"/> |
|||
<result column="shop_address" property="shopAddress"/> |
|||
<result column="create_time" property="createTime"/> |
|||
<result column="accept_time" property="acceptTime"/> |
|||
<result column="get_time" property="getTime"/> |
|||
<result column="must_finish_time" property="mustFinishTime"/> |
|||
<result column="finish_time" property="finishTime"/> |
|||
<result column="delivery_type" property="deliveryType"/> |
|||
</resultMap> |
|||
|
|||
<!-- 分页查询配送单,支持多条件过滤(抢单大厅:hallOnly=true时只查workerId为空的) --> |
|||
<select id="selectPageVO" resultMap="deliveryMap"> |
|||
SELECT |
|||
d.id, d.order_id, d.group_id, d.worker_id, d.shop_id, |
|||
d.get_area_id, d.put_area_id, d.delivery_fee, d.delivery_fee_marketplace, |
|||
d.status, d.create_time, d.accept_time, d.get_time, d.must_finish_time, d.finish_time, |
|||
d.receiver_name, d.receiver_phone, d.receiver_address, |
|||
d.shop_name, d.shop_phone, d.shop_address, d.delivery_type |
|||
FROM mall_delivery_order d |
|||
<where> |
|||
<if test="q.workerId != null and q.workerId != ''"> |
|||
AND d.worker_id = #{q.workerId} |
|||
</if> |
|||
<if test="q.shopId != null and q.shopId != ''"> |
|||
AND d.shop_id = #{q.shopId} |
|||
</if> |
|||
<if test="q.status != null"> |
|||
AND d.status = #{q.status} |
|||
</if> |
|||
<if test="q.getAreaId != null and q.getAreaId != ''"> |
|||
AND d.get_area_id = #{q.getAreaId} |
|||
</if> |
|||
<if test="q.putAreaId != null and q.putAreaId != ''"> |
|||
AND d.put_area_id = #{q.putAreaId} |
|||
</if> |
|||
<if test="q.hallOnly != null and q.hallOnly == true"> |
|||
AND (d.worker_id IS NULL OR d.worker_id = '') |
|||
AND d.status = 0 |
|||
</if> |
|||
<if test="q.startDate != null and q.startDate != ''"> |
|||
AND DATE(d.create_time) >= #{q.startDate} |
|||
</if> |
|||
<if test="q.endDate != null and q.endDate != ''"> |
|||
AND DATE(d.create_time) <= #{q.endDate} |
|||
</if> |
|||
<if test="q.deliveryType != null"> |
|||
AND d.delivery_type = #{q.deliveryType} |
|||
</if> |
|||
</where> |
|||
ORDER BY |
|||
<if test="(q.waimaiData != null and q.waimaiData.size() > 0) or (q.kuaidiData != null and q.kuaidiData.size() > 0)"> |
|||
(CASE |
|||
<if test="q.waimaiData != null and q.waimaiData.size() > 0"> |
|||
<foreach collection="q.waimaiData" item="rule"> |
|||
WHEN d.get_area_id = #{rule.getAreaId} AND d.put_area_id = #{rule.putAreaId} AND d.delivery_type = 1 THEN 1 |
|||
</foreach> |
|||
</if> |
|||
<if test="q.kuaidiData != null and q.kuaidiData.size() > 0"> |
|||
<foreach collection="q.kuaidiData" item="rule"> |
|||
WHEN d.get_area_id = #{rule.getAreaId} AND d.put_area_id = #{rule.putAreaId} AND d.delivery_type = 2 THEN 1 |
|||
</foreach> |
|||
</if> |
|||
ELSE 0 |
|||
END) DESC, |
|||
</if> |
|||
<if test="q.order != null and q.order == 'deliveryFee'"> |
|||
d.delivery_fee DESC, |
|||
</if> |
|||
d.create_time DESC |
|||
</select> |
|||
|
|||
<!-- |
|||
统计配送员当前活跃单量 |
|||
返回:waitPickup(待取货数)、delivering(配送中数) |
|||
--> |
|||
<select id="countActiveOrdersByWorker" resultType="map"> |
|||
SELECT |
|||
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS waitPickup, |
|||
SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) AS delivering |
|||
FROM mall_delivery_order |
|||
WHERE worker_id = #{workerId} |
|||
AND status IN (1, 2) |
|||
</select> |
|||
|
|||
</mapper> |
|||
@ -0,0 +1,34 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="cc.hiver.mall.dao.mapper.MallOrderGoodsMapper"> |
|||
|
|||
<resultMap id="goodsMap" type="cc.hiver.mall.entity.MallOrderGoods"> |
|||
<id column="id" property="id"/> |
|||
<result column="order_id" property="orderId"/> |
|||
<result column="product_id" property="productId"/> |
|||
<result column="product_name" property="productName"/> |
|||
<result column="product_picture" property="productPicture"/> |
|||
<result column="specs" property="specs"/> |
|||
<result column="price" property="price"/> |
|||
<result column="quantity" property="quantity"/> |
|||
</resultMap> |
|||
|
|||
<!-- 按单条订单ID查询商品明细 --> |
|||
<select id="selectByOrderId" resultMap="goodsMap"> |
|||
SELECT id, order_id, product_id, product_name, product_picture, specs, price, quantity |
|||
FROM mall_order_goods |
|||
WHERE order_id = #{orderId} |
|||
</select> |
|||
|
|||
<!-- 按多个订单ID批量查询 --> |
|||
<select id="selectByOrderIds" resultMap="goodsMap"> |
|||
SELECT id, order_id, product_id, product_name, product_picture, specs, price, quantity |
|||
FROM mall_order_goods |
|||
WHERE order_id IN |
|||
<foreach collection="orderIds" item="id" open="(" separator="," close=")"> |
|||
#{id} |
|||
</foreach> |
|||
</select> |
|||
|
|||
</mapper> |
|||
@ -0,0 +1,23 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="cc.hiver.mall.dao.mapper.MallOrderGroupMapper"> |
|||
|
|||
<resultMap id="groupMap" type="cc.hiver.mall.entity.MallOrderGroup"> |
|||
<id column="id" property="id"/> |
|||
<result column="shop_id" property="shopId"/> |
|||
<result column="head_user_id" property="headUserId"/> |
|||
<result column="head_order_id" property="headOrderId"/> |
|||
<result column="group_Order_ids" property="groupOrderIds"/> |
|||
<result column="target_members" property="targetMembers"/> |
|||
<result column="current_members" property="currentMembers"/> |
|||
<result column="status" property="status"/> |
|||
<result column="group_price" property="groupPrice"/> |
|||
<result column="worker_id" property="workerId"/> |
|||
<result column="worker_commission" property="workerCommission"/> |
|||
<result column="total_delivery_fee" property="totalDeliveryFee"/> |
|||
<result column="create_time" property="createTime"/> |
|||
<result column="expire_time" property="expireTime"/> |
|||
</resultMap> |
|||
|
|||
</mapper> |
|||
@ -0,0 +1,80 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="cc.hiver.mall.dao.mapper.MallOrderMapper"> |
|||
|
|||
<!-- 通用结果映射 --> |
|||
<resultMap id="mallOrderVOMap" type="cc.hiver.mall.entity.MallOrder"> |
|||
<id column="id" property="id"/> |
|||
<result column="user_id" property="userId"/> |
|||
<result column="shop_id" property="shopId"/> |
|||
<result column="order_type" property="orderType"/> |
|||
<result column="delivery_type" property="deliveryType"/> |
|||
<result column="status" property="status"/> |
|||
<result column="total_amount" property="totalAmount"/> |
|||
<result column="goods_amount" property="goodsAmount"/> |
|||
<result column="delivery_fee" property="deliveryFee"/> |
|||
<result column="package_fee" property="packageFee"/> |
|||
<result column="address_id" property="addressId"/> |
|||
<result column="remark" property="remark"/> |
|||
<result column="receiver_name" property="receiverName"/> |
|||
<result column="receiver_phone" property="receiverPhone"/> |
|||
<result column="receiver_address" property="receiverAddress"/> |
|||
<result column="shop_name" property="shopName"/> |
|||
<result column="shop_phone" property="shopPhone"/> |
|||
<result column="shop_address" property="shopAddress"/> |
|||
<result column="create_time" property="createTime"/> |
|||
<result column="pay_time" property="payTime"/> |
|||
</resultMap> |
|||
|
|||
<!-- 分页查询订单(不挂载商品明细,由Service层补填) --> |
|||
<select id="selectPageVO" resultMap="mallOrderVOMap"> |
|||
SELECT |
|||
o.id, o.user_id, o.shop_id, o.order_type, o.delivery_type, |
|||
o.status, o.total_amount, o.goods_amount, o.delivery_fee, |
|||
o.package_fee, o.address_id, o.remark, o.create_time, o.pay_time, |
|||
o.receiver_name, o.receiver_phone, o.receiver_address, |
|||
o.shop_name, o.shop_phone, o.shop_address |
|||
FROM mall_order o |
|||
<where> |
|||
<if test="q.userId != null and q.userId != ''"> |
|||
AND o.user_id = #{q.userId} |
|||
</if> |
|||
<if test="q.shopId != null and q.shopId != ''"> |
|||
AND o.shop_id = #{q.shopId} |
|||
</if> |
|||
<if test="q.status != null"> |
|||
AND o.status = #{q.status} |
|||
</if> |
|||
<if test="q.orderType != null"> |
|||
AND o.order_type = #{q.orderType} |
|||
</if> |
|||
<if test="q.deliveryType != null"> |
|||
AND o.delivery_type = #{q.deliveryType} |
|||
</if> |
|||
<if test="q.startDate != null and q.startDate != ''"> |
|||
AND DATE(o.create_time) >= #{q.startDate} |
|||
</if> |
|||
<if test="q.endDate != null and q.endDate != ''"> |
|||
AND DATE(o.create_time) <= #{q.endDate} |
|||
</if> |
|||
</where> |
|||
ORDER BY o.create_time DESC |
|||
</select> |
|||
|
|||
<select id="selectMallOrderByGroupId" resultMap="mallOrderVOMap"> |
|||
SELECT |
|||
o.id, o.user_id, o.shop_id, o.order_type, o.delivery_type, |
|||
o.status, o.total_amount, o.goods_amount, o.delivery_fee, |
|||
o.package_fee, o.address_id, o.remark, o.create_time, o.pay_time, |
|||
o.receiver_name, o.receiver_phone, o.receiver_address, |
|||
o.shop_name, o.shop_phone, o.shop_address |
|||
FROM mall_order o LEFT JOIN mall_order_group og ON o.id = og.head_order_id |
|||
<where> |
|||
<if test="groupId != null and groupId != ''"> |
|||
AND og.id = #{groupId} |
|||
</if> |
|||
</where> |
|||
</select> |
|||
|
|||
</mapper> |
|||
@ -0,0 +1,17 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
|||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="cc.hiver.mall.dao.mapper.MallRefundRecordMapper"> |
|||
|
|||
<resultMap id="refundMap" type="cc.hiver.mall.entity.MallRefundRecord"> |
|||
<id column="id" property="id"/> |
|||
<result column="order_id" property="orderId"/> |
|||
<result column="user_id" property="userId"/> |
|||
<result column="refund_amount" property="refundAmount"/> |
|||
<result column="reason" property="reason"/> |
|||
<result column="status" property="status"/> |
|||
<result column="create_time" property="createTime"/> |
|||
<result column="success_time" property="successTime"/> |
|||
</resultMap> |
|||
|
|||
</mapper> |
|||
Loading…
Reference in new issue