فهرست منبع

【优化】支持登陆用户,直接读取昵称、部门等信息,也支持自定义字段

libin 1 سال پیش
والد
کامیت
0a7bf6e455

BIN
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/resources/ip2region.xdb


+ 66 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/app/aftersale/AppGuideAfterSaleController.java

@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.guide.controller.app.aftersale;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.guide.controller.app.aftersale.vo.AppGuideAfterSaleCreateReqVO;
+import cn.iocoder.yudao.module.guide.controller.app.aftersale.vo.AppGuideAfterSaleRespVO;
+import cn.iocoder.yudao.module.guide.service.aftersale.GuideAfterSaleService;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
+import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
+import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.sl.draw.geom.Guide;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 App - 交易售后")
+@RestController
+@RequestMapping("/guide/trade/after-sale")
+@Validated
+@Slf4j
+public class AppGuideAfterSaleController {
+
+    @Resource
+    private GuideAfterSaleService afterSaleService;
+
+    @GetMapping(value = "/page")
+    @Operation(summary = "获得售后分页")
+    public CommonResult<PageResult<AppGuideAfterSaleRespVO>> getAfterSalePage(PageParam pageParam) {
+        return success(BeanUtils.toBean(
+                afterSaleService.getAfterSalePage(getLoginUserId(), pageParam),AppGuideAfterSaleRespVO.class));
+    }
+
+    @GetMapping(value = "/get")
+    @Operation(summary = "获得售后订单")
+    @Parameter(name = "id", description = "售后编号", required = true, example = "1")
+    public CommonResult<AppGuideAfterSaleRespVO> getAfterSale(@RequestParam("id") Long id) {
+        return success(BeanUtils.toBean(afterSaleService.getAfterSale(getLoginUserId(), id),AppGuideAfterSaleRespVO.class));
+    }
+
+    @PostMapping(value = "/create")
+    @Operation(summary = "申请售后")
+    public CommonResult<Long> createAfterSale(@RequestBody AppGuideAfterSaleCreateReqVO createReqVO) {
+        return success(afterSaleService.createAfterSale(getLoginUserId(), createReqVO));
+    }
+
+
+    @DeleteMapping(value = "/cancel")
+    @Operation(summary = "取消售后")
+    @Parameter(name = "id", description = "售后编号", required = true, example = "1")
+    public CommonResult<Boolean> cancelAfterSale(@RequestParam("id") Long id) {
+        afterSaleService.cancelAfterSale(getLoginUserId(), id);
+        return success(true);
+    }
+
+}

+ 42 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/app/aftersale/AppGuideAfterSaleLogController.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.guide.controller.app.aftersale;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.guide.controller.app.aftersale.vo.log.AppGuideAfterSaleLogRespVO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.aftersale.GuideAfterSaleLogDO;
+import cn.iocoder.yudao.module.guide.service.aftersale.GuideAfterSaleLogService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 售后日志")
+@RestController
+@RequestMapping("/guide/trade/after-sale-log")
+@Validated
+@Slf4j
+public class AppGuideAfterSaleLogController {
+
+    @Resource
+    private GuideAfterSaleLogService afterSaleLogService;
+
+    @GetMapping("/list")
+    @Operation(summary = "获得售后日志列表")
+    @Parameter(name = "afterSaleId", description = "售后编号", required = true, example = "1")
+    public CommonResult<List<AppGuideAfterSaleLogRespVO>> getAfterSaleLogList(
+            @RequestParam("afterSaleId") Long afterSaleId) {
+        List<GuideAfterSaleLogDO> logs = afterSaleLogService.getAfterSaleLogList(afterSaleId);
+        return success(BeanUtils.toBean(logs, AppGuideAfterSaleLogRespVO.class));
+    }
+
+}

+ 40 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/app/aftersale/vo/AppGuideAfterSaleCreateReqVO.java

@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.guide.controller.app.aftersale.vo;
+
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import cn.iocoder.yudao.module.guide.enums.aftersale.AfterSaleWayEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Min;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "用户 App - 交易售后创建 Request VO")
+@Data
+public class AppGuideAfterSaleCreateReqVO {
+
+    @Schema(description = "订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @NotNull(message = "订单项编号不能为空")
+    private Long orderItemId;
+
+    @Schema(description = "售后方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "售后方式不能为空")
+    @InEnum(value = AfterSaleWayEnum.class, message = "售后方式必须是 {value}")
+    private Integer way;
+
+    @Schema(description = "退款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "退款金额不能为空")
+    @Min(value = 1, message = "退款金额必须大于 0")
+    private Integer refundPrice;
+
+    @Schema(description = "申请原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "申请原因不能为空")
+    private String applyReason;
+
+    @Schema(description = "补充描述", example = "商品质量不好")
+    private String applyDescription;
+
+    @Schema(description = "补充凭证图片", example = "https://www.iocoder.cn/1.png, https://www.iocoder.cn/2.png")
+    private List<String> applyPicUrls;
+
+}

+ 85 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/app/aftersale/vo/AppGuideAfterSaleRespVO.java

@@ -0,0 +1,85 @@
+package cn.iocoder.yudao.module.guide.controller.app.aftersale.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "用户 App - 交易售后 Response VO")
+@Data
+public class AppGuideAfterSaleRespVO {
+
+    @Schema(description = "售后编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "售后流水号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1146347329394184195")
+    private String no;
+
+    @Schema(description = "售后状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "售后方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer way;
+
+    @Schema(description = "售后类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer type;
+
+    @Schema(description = "申请原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private String applyReason;
+
+    @Schema(description = "补充描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private String applyDescription;
+
+    @Schema(description = "补充凭证图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private List<String> applyPicUrls;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime updateTime;
+
+    // ========== 交易订单相关 ==========
+
+    @Schema(description = "交易订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long orderId;
+
+    @Schema(description = "交易订单流水号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private String orderNo;
+
+    @Schema(description = "交易订单项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long orderItemId;
+
+    @Schema(description = "Guide 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long guideId;
+
+    @Schema(description = "Trip 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long tripId;
+
+    @Schema(description = "booking Date", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private LocalDateTime bookingDate;
+
+    private String picUrl;
+
+    private String guideName;
+
+    private String tripTitle;
+    // ========== 审批相关 ==========
+
+    /**
+     * 审批备注
+     *
+     * 注意,只有审批不通过才会填写
+     */
+    private String auditReason;
+
+    // ========== 退款相关 ==========
+
+    @Schema(description = "退款金额,单位:分", example = "100")
+    private Integer refundPrice;
+
+    @Schema(description = "退款时间")
+    private LocalDateTime refundTime;
+
+}

+ 21 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/controller/app/aftersale/vo/log/AppGuideAfterSaleLogRespVO.java

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.guide.controller.app.aftersale.vo.log;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - App 交易售后日志 Response VO")
+@Data
+public class AppGuideAfterSaleLogRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20669")
+    private Long id;
+
+    @Schema(description = "操作明细", requiredMode = Schema.RequiredMode.REQUIRED, example = "维权完成,退款金额:¥37776.00")
+    private String content;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 189 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/order/GuideTradeOrderUpdateService.java

@@ -0,0 +1,189 @@
+package cn.iocoder.yudao.module.guide.service.order;
+
+
+import cn.iocoder.yudao.module.guide.controller.admin.order.vo.GuideTradeOrderRemarkReqVO;
+import cn.iocoder.yudao.module.guide.controller.admin.order.vo.GuideTradeOrderUpdatePriceReqVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.AppGuideTradeOrderSettlementRespVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.AppGuideTradeOrderCreateReqVO;
+
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.AppGuideTradeOrderSettlementRespVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.OrderSettlementReqVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.item.AppGuideTradeOrderItemCommentCreateReqVO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.order.GuideTradeOrderDO;
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+/**
+ * 交易订单【写】Service 接口
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+public interface GuideTradeOrderUpdateService {
+
+    // =================== Order ===================
+
+    /**
+     * 获得订单结算信息
+     *
+     * @param userId          登录用户
+     * @param settlementReqVOs 订单结算请求
+     * @return 订单结算结果
+     */
+    AppGuideTradeOrderSettlementRespVO settlementOrder(Long userId, List<OrderSettlementReqVO> settlementReqVOs);
+
+    /**
+     * 【会员】创建交易订单
+     *
+     * @param userId      登录用户
+     * @param createReqVO 创建交易订单请求模型
+     * @return 交易订单的
+     */
+    GuideTradeOrderDO createOrder(Long userId, AppGuideTradeOrderCreateReqVO createReqVO);
+
+    /**
+     * 更新交易订单已支付
+     *
+     * @param id         交易订单编号
+     * @param payOrderId 支付订单编号
+     */
+    void updateOrderPaid(Long id, Long payOrderId);
+
+    /**
+     * 【管理员】发货交易订单
+     *
+     * @param deliveryReqVO 发货请求
+     */
+//    void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO);
+
+    /**
+     * 【会员】收货交易订单
+     *
+     * @param userId 用户编号
+     * @param id     订单编号
+     */
+//    void receiveOrderByMember(Long userId, Long id);
+
+    /**
+     * 【系统】自动收货交易订单
+     *
+     * @return 收货数量
+     */
+//    int receiveOrderBySystem();
+
+    /**
+     * 【会员】取消交易订单
+     *
+     * @param userId 用户编号
+     * @param id     订单编号
+     */
+    void cancelOrderByMember(Long userId, Long id);
+
+    /**
+     * 【系统】自动取消订单
+     *
+     * @return 取消数量
+     */
+    int cancelOrderBySystem();
+
+    /**
+     * 【会员】删除订单
+     *
+     * @param userId 用户编号
+     * @param id     订单编号
+     */
+    void deleteOrder(Long userId, Long id);
+
+    /**
+     * 【管理员】交易订单备注
+     *
+     * @param reqVO 请求
+     */
+    void updateOrderRemark(GuideTradeOrderRemarkReqVO reqVO);
+
+    /**
+     * 【管理员】调整价格
+     *
+     * @param reqVO 请求
+     */
+//    void updateOrderPrice(GuideTradeOrderUpdatePriceReqVO reqVO);
+
+    /**
+     * 【管理员】调整地址
+     *
+     * @param reqVO 请求
+     */
+//    void updateOrderAddress(TradeOrderUpdateAddressReqVO reqVO);
+
+    /**
+     * 【管理员】核销订单
+     *
+     * @param id 订单编号
+     */
+//    void pickUpOrderByAdmin(Long id);
+
+    /**
+     * 【管理员】核销订单
+     *
+     * @param pickUpVerifyCode 自提核销码
+     */
+//    void pickUpOrderByAdmin(String pickUpVerifyCode);
+
+    /**
+     * 【管理员】根据自提核销码,查询订单
+     *
+     * @param pickUpVerifyCode 自提核销码
+     */
+//    TradeOrderDO getByPickUpVerifyCode(String pickUpVerifyCode);
+
+    // =================== Order Item ===================
+
+    /**
+     * 当售后申请后,更新交易订单项的售后状态
+     *
+     * @param id          交易订单项编号
+     * @param afterSaleId 售后单编号
+     */
+    void updateOrderItemWhenAfterSaleCreate(@NotNull Long id, @NotNull Long afterSaleId);
+
+    /**
+     * 当售后完成后,更新交易订单项的售后状态
+     *
+     * @param id          交易订单项编号
+     * @param refundPrice 退款金额
+     */
+    void updateOrderItemWhenAfterSaleSuccess(@NotNull Long id, @NotNull Integer refundPrice);
+
+    /**
+     * 当售后取消(用户取消、管理员驳回、管理员拒绝收货)后,更新交易订单项的售后状态
+     *
+     * @param id 交易订单项编号
+     */
+    void updateOrderItemWhenAfterSaleCancel(@NotNull Long id);
+
+    /**
+     * 【会员】创建订单项的评论
+     *
+     * @param userId      用户编号
+     * @param createReqVO 创建请求
+     * @return 得到评价 id
+     */
+    Long createOrderItemCommentByMember(Long userId, AppGuideTradeOrderItemCommentCreateReqVO createReqVO);
+
+    /**
+     * 【系统】创建订单项的评论
+     *
+     * @return 被评论的订单数
+     */
+    int createOrderItemCommentBySystem();
+
+
+    // TODO 芋艿:拼团取消,不调这个接口哈;
+    /**
+     * 取消支付订单
+     *
+     * @param userId  用户编号
+     * @param orderId 订单编号
+     */
+    void cancelPaidOrder(Long userId, Long orderId);
+
+}

+ 668 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/order/GuideTradeOrderUpdateServiceImpl.java

@@ -0,0 +1,668 @@
+package cn.iocoder.yudao.module.guide.service.order;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.framework.common.util.string.StrUtils;
+import cn.iocoder.yudao.module.guide.controller.admin.order.vo.GuideTradeOrderRemarkReqVO;
+import cn.iocoder.yudao.module.guide.controller.admin.order.vo.GuideTradeOrderUpdatePriceReqVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.AppGuideTradeOrderCreateReqVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.AppGuideTradeOrderSettlementRespVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.OrderSettlementReqVO;
+import cn.iocoder.yudao.module.guide.controller.app.order.vo.item.AppGuideTradeOrderItemCommentCreateReqVO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.order.GuideTradeOrderDO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.order.GuideTradeOrderItemDO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.schedule.ScheduleDO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.trip.TripDO;
+import cn.iocoder.yudao.module.guide.dal.dataobject.users.UsersDO;
+
+import cn.iocoder.yudao.module.guide.dal.mysql.order.GuideTradeOrderItemMapper;
+import cn.iocoder.yudao.module.guide.dal.mysql.order.GuideTradeOrderMapper;
+import cn.iocoder.yudao.module.guide.dal.mysql.schedule.ScheduleMapper;
+import cn.iocoder.yudao.module.guide.dal.mysql.trip.TripI18nExtensionMapper;
+import cn.iocoder.yudao.module.guide.dal.mysql.trip.TripMapper;
+import cn.iocoder.yudao.module.guide.dal.mysql.users.UsersMapper;
+import cn.iocoder.yudao.module.guide.dal.redis.no.GuideTradeNoRedisDAO;
+
+import cn.iocoder.yudao.module.guide.enums.order.TradeOrderCancelTypeEnum;
+import cn.iocoder.yudao.module.guide.enums.order.TradeOrderOperateTypeEnum;
+import cn.iocoder.yudao.module.guide.enums.order.TradeOrderRefundStatusEnum;
+import cn.iocoder.yudao.module.guide.enums.order.TradeOrderStatusEnum;
+import cn.iocoder.yudao.module.guide.framework.order.config.GuideTradeOrderProperties;
+import cn.iocoder.yudao.module.guide.framework.order.core.annotations.GuideTradeOrderLog;
+import cn.iocoder.yudao.module.guide.framework.order.core.utils.GuideTradeOrderLogUtils;
+import cn.iocoder.yudao.module.guide.service.cart.GuideCartService;
+import cn.iocoder.yudao.module.guide.service.message.GuideTradeMessageService;
+
+import cn.iocoder.yudao.module.member.api.address.MemberAddressApi;
+import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
+
+
+import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
+import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import jakarta.annotation.Resource;
+import jakarta.validation.constraints.NotNull;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.minusTime;
+import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getTerminal;
+import static cn.iocoder.yudao.module.guide.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL;
+
+/**
+ * 交易订单【写】Service 实现类
+ *
+ * @author LeeYan9
+ * @since 2022-08-26
+ */
+@Service
+@Slf4j
+public class GuideTradeOrderUpdateServiceImpl implements GuideTradeOrderUpdateService {
+
+    @Resource
+    private GuideTradeOrderMapper guideTradeOrderMapper;
+    @Resource
+    private GuideTradeOrderItemMapper guideTradeOrderItemMapper;
+
+    @Resource
+    private ScheduleMapper scheduleMapper;
+    @Resource
+    private GuideTradeNoRedisDAO guideTradeNoRedisDAO;
+
+    @Resource
+    private GuideCartService guideCartService;
+    @Resource
+    private GuideTradeMessageService guideTradeMessageService;
+
+    @Resource
+    private PayOrderApi payOrderApi;
+    @Resource
+    private MemberAddressApi addressApi;
+
+
+    @Resource
+    private TripMapper tripMapper;
+
+    @Resource
+    private TripI18nExtensionMapper tripI18nExtensionMapper;
+
+    @Resource
+    private UsersMapper usersMapper;
+
+    @Resource
+    private GuideTradeOrderProperties guideTradeOrderProperties;
+
+    // =================== Order ===================
+
+    @Override
+    public AppGuideTradeOrderSettlementRespVO settlementOrder(Long userId, List<OrderSettlementReqVO> settlementReqVOs) {
+        //检查OrderSettlementReqVO项目是否已经被被预约,是的话,报不能建立order的错误。orderschedule
+        checkOrderSettlementReqVO(userId,settlementReqVOs);
+        AppGuideTradeOrderSettlementRespVO returnValue=new AppGuideTradeOrderSettlementRespVO();
+        List<AppGuideTradeOrderSettlementRespVO.Item> responseItems=new ArrayList<AppGuideTradeOrderSettlementRespVO.Item>();
+        returnValue.setType(0);//普通订单
+
+        Integer price=0;
+
+        for (OrderSettlementReqVO requestItem : settlementReqVOs) {
+            AppGuideTradeOrderSettlementRespVO.Item resItem = new AppGuideTradeOrderSettlementRespVO.Item();
+            resItem.setBookingDate(requestItem.getBookingDate());
+            resItem.setCartId(requestItem.getCartId());
+            UsersDO user = usersMapper.selectById(requestItem.getGuideId());
+            resItem.setGuideInfo(user);
+            TripDO trip = tripMapper.selectById(requestItem.getTripId());
+            Integer itemPrice = trip!=null? trip.getPrice():user.getPrice();
+            resItem.setPrice(itemPrice);
+            price += itemPrice;
+            if (null != trip) {
+                trip.setTripI18nExtensionDOList(tripI18nExtensionMapper.selectList("trip_id", requestItem.getTripId()));
+                resItem.setTripInfo(trip);
+            }responseItems.add(resItem);
+        }
+        returnValue.setItems(responseItems);
+        returnValue.setPrice(price);
+        // 3. 拼接返回
+        return returnValue;//TradeOrderConvert.INSTANCE.convert(calculateRespBO, address);
+    }
+    private void checkOrderSettlementReqVO(Long userId, List<OrderSettlementReqVO> settlementReqVOs){
+
+        //TradeOrderItem中有相同数据存在的check 已经有相同的注文,二重注文防止,抛异常
+        for (OrderSettlementReqVO requestItem : settlementReqVOs) {
+            GuideTradeOrderItemDO orderItem = guideTradeOrderItemMapper.selectOne(GuideTradeOrderItemDO::getUserId,userId,
+                    GuideTradeOrderItemDO::getGuideId,requestItem.getGuideId(),
+                    GuideTradeOrderItemDO::getBookingDate,requestItem.getBookingDate());
+            if (orderItem !=null){
+                throw exception(ORDER_SETTLEMENT_FAIL_FOUND);
+            }
+        }
+        //ScheduleMapper 查看是否被其他人预约
+        for (OrderSettlementReqVO requestItem : settlementReqVOs) {
+            ScheduleDO scheduleItem = scheduleMapper.selectOne(ScheduleDO::getGuideId,requestItem.getGuideId(),
+                    ScheduleDO::getBooked,0,
+                    ScheduleDO::getBookingDate,requestItem.getBookingDate());
+            if (scheduleItem ==null){
+                throw exception(ORDER_SETTLEMENT_FAIL_SAMEDATE);
+            }
+        }
+    }
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @GuideTradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CREATE)
+    public GuideTradeOrderDO createOrder(Long userId, AppGuideTradeOrderCreateReqVO createReqVO) {
+        //1.1 检查相对应的订单是否已经存在,存在并且是未支付状态的话,将订单返回,不存在的话,建立订单。
+        GuideTradeOrderDO guideTradeOrderDO = checkOrder(userId,createReqVO);
+        if (guideTradeOrderDO !=null){
+            return guideTradeOrderDO;
+        }
+        // 1.2 构建订单
+        GuideTradeOrderDO order = buildTradeOrder(userId, createReqVO);
+        List<GuideTradeOrderItemDO> orderItems = buildTradeOrderItems(order, createReqVO);
+
+        // 2. 订单创建前的逻辑
+//        tradeOrderHandlers.forEach(handler -> handler.beforeOrderCreate(order, orderItems));
+
+        // 3. 保存订单
+        guideTradeOrderMapper.insert(order);
+        orderItems.forEach(orderItem -> orderItem.setOrderId(order.getId()));
+        guideTradeOrderItemMapper.insertBatch(orderItems);
+
+        // 4. 订单创建后的逻辑
+        afterCreateTradeOrder(order, orderItems, createReqVO);
+        return order;
+    }
+    public GuideTradeOrderDO checkOrder(Long userId, AppGuideTradeOrderCreateReqVO createReqVO) {
+        //检查订单明细中的所有项目是否在订单明细表中存在,并且对应的orderID是唯一的时候,返回order,如果不存在(正常情况)返回null。如果交叉存在,则抛出异常
+        List<OrderSettlementReqVO> OrderSettlements = createReqVO.getOrderSettlementReqVOs();
+
+        Set<Long> uniqueOrderSet = new HashSet<>();
+        for (OrderSettlementReqVO orderSettlement : OrderSettlements) {
+            GuideTradeOrderItemDO orderItem = guideTradeOrderItemMapper.selectOne(GuideTradeOrderItemDO::getUserId,userId,
+                    GuideTradeOrderItemDO::getGuideId,orderSettlement.getGuideId(),
+                    GuideTradeOrderItemDO::getBookingDate,orderSettlement.getBookingDate(),
+                    GuideTradeOrderItemDO::getStatus,TradeOrderStatusEnum.UNPAID.getStatus()
+            );
+            if (orderItem !=null){
+                uniqueOrderSet.add(orderItem.getOrderId());
+            }
+        }
+        if (uniqueOrderSet.isEmpty()) {
+            return null;
+        }else if (uniqueOrderSet.size()==1){
+            Long orderId = uniqueOrderSet.toArray(new Long[0])[0];
+            return guideTradeOrderMapper.selectById(orderId);
+        }else{
+            throw exception(ORDER_ITEM_FAIL_EXISTS);
+        }
+    }
+    private GuideTradeOrderDO buildTradeOrder(Long userId, AppGuideTradeOrderCreateReqVO createReqVO) {
+        GuideTradeOrderDO order = new GuideTradeOrderDO();
+        order.setType(0);
+
+        order.setUserId(userId);
+        order.setUserRemark(createReqVO.getRemark());
+        order.setNo(guideTradeNoRedisDAO.generate(GuideTradeNoRedisDAO.TRADE_ORDER_NO_PREFIX));
+        order.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
+        order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus());
+        order.setProductCount(createReqVO.getOrderSettlementReqVOs().size());
+        order.setUserIp(getClientIP()).setTerminal(getTerminal());
+        // 支付 + 退款信息
+        order.setTotalPrice(createReqVO.getPrice()).setPayStatus(false);
+        order.setRefundStatus(TradeOrderRefundStatusEnum.NONE.getStatus()).setRefundPrice(0);
+        return order;
+    }
+
+    private List<GuideTradeOrderItemDO> buildTradeOrderItems(GuideTradeOrderDO tradeOrderDO,
+                                                             AppGuideTradeOrderCreateReqVO createReqVO) {
+        List<GuideTradeOrderItemDO> orderItems = new ArrayList<GuideTradeOrderItemDO>();
+        for (OrderSettlementReqVO item:createReqVO.getOrderSettlementReqVOs()) {
+            GuideTradeOrderItemDO orderItem = new GuideTradeOrderItemDO();
+            orderItem.setGuideId(item.getGuideId());
+            orderItem.setTripId(item.getTripId());
+            orderItem.setCartId(item.getCartId());
+            orderItem.setBookingDate(item.getBookingDate());
+            orderItem.setPicUrl(item.getPicUrl());
+            orderItem.setPrice(item.getPrice());
+            orderItem.setGuideName(item.getGuideName());
+            orderItem.setTripTitle(item.getTripTitle());
+            orderItem.setOrderId(tradeOrderDO.getId());
+            orderItem.setUserId(tradeOrderDO.getUserId());
+            orderItem.setStatus(TradeOrderStatusEnum.UNPAID.getStatus());
+            orderItem.setCommentStatus(false);
+            orderItems.add(orderItem);
+        }
+        return orderItems;
+    }
+
+    /**
+     * 订单创建后,执行后置逻辑
+     * <p>
+     * 例如说:优惠劵的扣减、积分的扣减、支付单的创建等等
+     *
+     * @param order       订单
+     * @param orderItems  订单项
+     * @param createReqVO 创建订单请求
+     */
+    private void afterCreateTradeOrder(GuideTradeOrderDO order, List<GuideTradeOrderItemDO> orderItems,
+                                       AppGuideTradeOrderCreateReqVO createReqVO) {
+        // 1. 执行订单创建后置处理器
+//        tradeOrderHandlers.forEach(handler -> handler.afterOrderCreate(order, orderItems));
+        //1. guide schedule 设置成被预约的状态。
+        for (GuideTradeOrderItemDO orderItemDO :orderItems) {
+            ScheduleDO schedule = scheduleMapper.selectOne(ScheduleDO::getGuideId,orderItemDO.getGuideId()
+                    ,ScheduleDO::getBookingDate,orderItemDO.getBookingDate());
+            schedule.setBooked(true);
+            scheduleMapper.updateById(schedule);
+        }
+        // 2. 删除购物车商品
+        Set<Long> cartIds = convertSet(createReqVO.getOrderSettlementReqVOs(), OrderSettlementReqVO::getCartId);
+        if (CollUtil.isNotEmpty(cartIds)) {
+            guideCartService.deleteCart(order.getUserId(), cartIds);
+        }
+        // 3. 生成预支付
+        createPayOrder(order, orderItems);
+
+        // 4. 插入订单日志
+        GuideTradeOrderLogUtils.setOrderInfo(order.getId(), null, order.getStatus());
+
+        // TODO @LeeYan9: 是可以思考下, 订单的营销优惠记录, 应该记录在哪里, 微信讨论起来!
+    }
+
+    private void createPayOrder(GuideTradeOrderDO order, List<GuideTradeOrderItemDO> orderItems) {
+        // 创建支付单,用于后续的支付
+        PayOrderCreateReqDTO createReqDTO = new PayOrderCreateReqDTO()
+                .setAppId(guideTradeOrderProperties.getAppId())
+                .setUserIp(order.getUserIp());
+        // 商户相关字段
+        createReqDTO.setMerchantOrderId(String.valueOf(order.getId()));
+        String subject = "["+orderItems.get(0).getGuideName()+"]-["+orderItems.get(0).getBookingDate()+"]";
+        subject = StrUtils.maxLength(subject, PayOrderCreateReqDTO.SUBJECT_MAX_LENGTH); // 避免超过 32 位
+        createReqDTO.setSubject(subject);
+        createReqDTO.setBody(subject); // TODO 芋艿:临时写死
+        // 订单相关字段
+        createReqDTO.setPrice(order.getTotalPrice()).setExpireTime(addTime(guideTradeOrderProperties.getPayExpireTime()));
+        Long payOrderId = payOrderApi.createOrder(createReqDTO);
+
+        // 更新到交易单上
+        guideTradeOrderMapper.updateById(new GuideTradeOrderDO().setId(order.getId()).setPayOrderId(payOrderId));
+        order.setPayOrderId(payOrderId);
+    }
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @GuideTradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_PAY)
+    public void updateOrderPaid(Long id, Long payOrderId) {
+        // 1. 校验并获得交易订单(可支付)
+        KeyValue<GuideTradeOrderDO, PayOrderRespDTO> orderResult = validateOrderPayable(id, payOrderId);
+        GuideTradeOrderDO order = orderResult.getKey();
+        PayOrderRespDTO payOrder = orderResult.getValue();
+
+        // 2. 更新 TradeOrderDO 状态为已支付,等待发货
+        int updateCount = guideTradeOrderMapper.updateByIdAndStatus(id, order.getStatus(),
+                new GuideTradeOrderDO().setStatus(TradeOrderStatusEnum.UNDELIVERED.getStatus()).setPayStatus(true)
+                        .setPayTime(LocalDateTime.now()).setPayChannelCode(payOrder.getChannelCode()));
+        if (updateCount == 0) {
+            throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
+        }
+
+        // 3. 执行 TradeOrderHandler 的后置处理
+
+        guideTradeOrderItemMapper.updateStatusByOrderId(id,TradeOrderStatusEnum.UNDELIVERED.getStatus());
+//        List<GuideTradeOrderItemDO> orderItems = guideTradeOrderItemMapper.selectListByOrderId(id);
+//        tradeOrderHandlers.forEach(handler -> handler.afterPayOrder(order, orderItems));
+
+        // 4. 记录订单日志
+        GuideTradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.UNDELIVERED.getStatus());
+//        GuideTradeOrderLogUtils.setUserInfo(order.getUserId(), UserTypeEnum.MEMBER.getValue());
+    }
+
+    /**
+     * 校验交易订单满足被支付的条件
+     * <p>
+     * 1. 交易订单未支付
+     * 2. 支付单已支付
+     *
+     * @param id         交易订单编号
+     * @param payOrderId 支付订单编号
+     * @return 交易订单
+     */
+    private KeyValue<GuideTradeOrderDO, PayOrderRespDTO> validateOrderPayable(Long id, Long payOrderId) {
+        // 校验订单是否存在
+        GuideTradeOrderDO order = validateOrderExists(id);
+        // 校验订单未支付
+        if (!TradeOrderStatusEnum.isUnpaid(order.getStatus()) || order.getPayStatus()) {
+            log.error("[validateOrderPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
+                    id, JsonUtils.toJsonString(order));
+            throw exception(ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
+        }
+        // 校验支付订单匹配
+        if (ObjectUtil.notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
+            log.error("[validateOrderPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
+                    id, payOrderId, JsonUtils.toJsonString(order));
+            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
+        }
+
+        // 校验支付单是否存在
+        PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
+        if (payOrder == null) {
+            log.error("[validateOrderPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
+            throw exception(ORDER_NOT_FOUND);
+        }
+        // 校验支付单已支付
+        if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
+            log.error("[validateOrderPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
+                    id, payOrderId, JsonUtils.toJsonString(payOrder));
+            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
+        }
+        // 校验支付金额一致
+        if (ObjectUtil.notEqual(payOrder.getPrice(), order.getTotalPrice())) {
+            log.error("[validateOrderPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
+                    id, payOrderId, JsonUtils.toJsonString(order), JsonUtils.toJsonString(payOrder));
+            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
+        }
+        // 校验支付订单匹配(二次)
+        if (ObjectUtil.notEqual(payOrder.getMerchantOrderId(), id.toString())) {
+            log.error("[validateOrderPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
+                    id, payOrderId, JsonUtils.toJsonString(payOrder));
+            throw exception(ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
+        }
+        return new KeyValue<>(order, payOrder);
+    }
+    @NotNull
+    private GuideTradeOrderDO validateOrderExists(Long id) {
+        // 校验订单是否存在
+        GuideTradeOrderDO order = guideTradeOrderMapper.selectById(id);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        return order;
+    }
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @GuideTradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_CANCEL)
+    public void cancelOrderByMember(Long userId, Long id) {
+        // 1.1 校验存在
+        GuideTradeOrderDO order = guideTradeOrderMapper.selectOrderByIdAndUserId(id, userId);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        // 1.2 校验状态
+        if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
+            throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
+        }
+
+        // 2. 取消订单
+        cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
+    }
+
+    /**
+     * 取消订单的核心实现
+     *
+     * @param order      订单
+     * @param cancelType 取消类型
+     */
+    private void cancelOrder0(GuideTradeOrderDO order, TradeOrderCancelTypeEnum cancelType) {
+        // 1. 更新 TradeOrderDO 状态为已取消
+        int updateCount = guideTradeOrderMapper.updateByIdAndStatus(order.getId(), order.getStatus(),
+                new GuideTradeOrderDO().setStatus(TradeOrderStatusEnum.CANCELED.getStatus())
+                        .setCancelType(cancelType.getType()).setCancelTime(LocalDateTime.now()));
+        if (updateCount == 0) {
+            throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
+        }
+
+        // 2. 执行 TradeOrderHandler 的后置处理
+        guideTradeOrderItemMapper.updateStatusByOrderId(order.getId(),TradeOrderStatusEnum.CANCELED.getStatus());
+//        List<GuideTradeOrderItemDO> orderItems = guideTradeOrderItemMapper.selectListByOrderId(order.getId());
+//        tradeOrderHandlers.forEach(handler -> handler.afterCancelOrder(order, orderItems));
+        //2. (New)需要释放schedule(已经被预约变成没有被预约)
+        List<GuideTradeOrderItemDO> orderItems = guideTradeOrderItemMapper.selectListByOrderId(order.getId());
+        for (GuideTradeOrderItemDO orderItemDO :orderItems) {
+            ScheduleDO schedule = scheduleMapper.selectOne(ScheduleDO::getGuideId,orderItemDO.getGuideId()
+                    ,ScheduleDO::getBookingDate,orderItemDO.getBookingDate());
+            if (ObjectUtil.isNotNull(schedule)){
+                schedule.setBooked(false);
+                scheduleMapper.updateById(schedule);
+            }
+        }
+        // 3. 增加订单日志
+        GuideTradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus());
+    }
+    @Override
+    public int cancelOrderBySystem() {
+        // 1. 查询过期的待支付订单
+        LocalDateTime expireTime = minusTime(guideTradeOrderProperties.getPayExpireTime());
+        List<GuideTradeOrderDO> orders = guideTradeOrderMapper.selectListByStatusAndCreateTimeLt(
+                TradeOrderStatusEnum.UNPAID.getStatus(), expireTime);
+        if (CollUtil.isEmpty(orders)) {
+            return 0;
+        }
+
+        // 2. 遍历执行,逐个取消
+        int count = 0;
+        for (GuideTradeOrderDO order : orders) {
+            try {
+                getSelf().cancelOrderBySystem(order);
+                count++;
+            } catch (Throwable e) {
+                log.error("[cancelOrderBySystem][order({}) 过期订单异常]", order.getId(), e);
+            }
+        }
+        return count;
+    }
+
+    /**
+     * 自动取消单个订单
+     *
+     * @param order 订单
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @GuideTradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL)
+    public void cancelOrderBySystem(GuideTradeOrderDO order) {
+        cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @GuideTradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_DELETE)
+    public void deleteOrder(Long userId, Long id) {
+        // 1.1 校验存在
+        GuideTradeOrderDO order = guideTradeOrderMapper.selectOrderByIdAndUserId(id, userId);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        // 1.2 校验状态
+        if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.CANCELED.getStatus())) {
+            throw exception(ORDER_DELETE_FAIL_STATUS_NOT_CANCEL);
+        }
+        // 2. 删除订单
+        guideTradeOrderMapper.deleteById(id);
+        // 删除guideTradeOrderItem
+        guideTradeOrderItemMapper.delete(new QueryWrapper<GuideTradeOrderItemDO>()
+                .lambda()
+                .eq(GuideTradeOrderItemDO::getOrderId, id));
+        //schedule更新
+        List<GuideTradeOrderItemDO> orderItems = guideTradeOrderItemMapper.selectListByOrderId(order.getId());
+        for (GuideTradeOrderItemDO orderItemDO :orderItems) {
+            ScheduleDO schedule = scheduleMapper.selectOne(ScheduleDO::getGuideId,orderItemDO.getGuideId()
+                    ,ScheduleDO::getBookingDate,orderItemDO.getBookingDate());
+            if (ObjectUtil.isNotNull(schedule)) {
+                schedule.setBooked(false);
+                scheduleMapper.updateById(schedule);
+            }
+        }
+        // 3. 记录日志
+        GuideTradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
+    }
+    @Override
+    public void updateOrderRemark(GuideTradeOrderRemarkReqVO reqVO) {
+        // 校验并获得交易订单
+        validateOrderExists(reqVO.getId());
+        // 更新
+        GuideTradeOrderDO order = BeanUtils.toBean(reqVO, GuideTradeOrderDO.class);
+//        GuideTradeOrderDO order = TradeOrderConvert.INSTANCE.convert(reqVO);
+        guideTradeOrderMapper.updateById(order);
+    }
+
+    @Override
+    public void updateOrderItemWhenAfterSaleCreate(Long id, Long afterSaleId) {
+        // 更新订单项
+        updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(),
+                TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(), afterSaleId);
+    }
+
+    @Override
+    public void updateOrderItemWhenAfterSaleSuccess(Long id, Integer refundPrice) {
+        updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
+                TradeOrderItemAfterSaleStatusEnum.SUCCESS.getStatus(), null);
+    }
+
+    @Override
+    public void updateOrderItemWhenAfterSaleCancel(Long id) {
+        // 更新订单项
+        updateOrderItemAfterSaleStatus(id, TradeOrderItemAfterSaleStatusEnum.APPLY.getStatus(),
+                TradeOrderItemAfterSaleStatusEnum.NONE.getStatus(), null);
+    }
+    private void updateOrderItemAfterSaleStatus(Long id, Integer oldAfterSaleStatus, Integer newAfterSaleStatus,
+                                                Long afterSaleId) {
+        // 更新订单项
+        int updateCount = guideTradeOrderItemMapper.updateAfterSaleStatus(id, oldAfterSaleStatus, newAfterSaleStatus, afterSaleId);
+        if (updateCount <= 0) {
+            throw exception(ORDER_ITEM_UPDATE_AFTER_SALE_STATUS_FAIL);
+        }
+
+    }
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @GuideTradeOrderLog(operateType = TradeOrderOperateTypeEnum.MEMBER_COMMENT)
+    public Long createOrderItemCommentByMember(Long userId, AppGuideTradeOrderItemCommentCreateReqVO createReqVO) {
+        // 1.1 先通过订单项 ID,查询订单项是否存在
+        GuideTradeOrderItemDO orderItem = guideTradeOrderItemMapper.selectByIdAndUserId(createReqVO.getOrderItemId(), userId);
+        if (orderItem == null) {
+            throw exception(ORDER_ITEM_NOT_FOUND);
+        }
+        // 1.2 校验订单相关状态
+        GuideTradeOrderDO order = guideTradeOrderMapper.selectOrderByIdAndUserId(orderItem.getOrderId(), userId);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.COMPLETED.getStatus())) {
+            throw exception(ORDER_COMMENT_FAIL_STATUS_NOT_COMPLETED);
+        }
+        if (ObjectUtil.notEqual(order.getCommentStatus(), Boolean.FALSE)) {
+            throw exception(ORDER_COMMENT_STATUS_NOT_FALSE);
+        }
+
+        // 2. 创建评价
+        Long commentId = createOrderItemComment0(orderItem, createReqVO);
+
+        // 3. 如果订单项都评论了,则更新订单评价状态
+        List<GuideTradeOrderItemDO> orderItems = guideTradeOrderItemMapper.selectListByOrderId(order.getId());
+        if (!anyMatch(orderItems, item -> Objects.equals(item.getCommentStatus(), Boolean.FALSE))) {
+            guideTradeOrderMapper.updateById(new GuideTradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE)
+                    .setFinishTime(LocalDateTime.now()));
+            // 增加订单日志。注意:只有在所有订单项都评价后,才会增加
+            GuideTradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
+        }
+        return commentId;
+    }
+
+    /**
+     * 创建订单项的评论的核心实现
+     *
+     * @param orderItem   订单项
+     * @param createReqVO 评论内容
+     * @return 评论编号
+     */
+    private Long createOrderItemComment0(GuideTradeOrderItemDO orderItem, AppGuideTradeOrderItemCommentCreateReqVO createReqVO) {
+       // TODO must
+        // 1. 创建评价
+//        ProductCommentCreateReqDTO productCommentCreateReqDTO = TradeOrderConvert.INSTANCE.convert04(createReqVO, orderItem);
+//        Long commentId = productCommentApi.createComment(productCommentCreateReqDTO);
+
+        // 2. 更新订单项评价状态
+        guideTradeOrderItemMapper.updateById(new GuideTradeOrderItemDO().setId(orderItem.getId()).setCommentStatus(Boolean.TRUE));
+//        return commentId;
+        return 0L;
+    }
+    @Override
+    public int createOrderItemCommentBySystem() {
+        // 1. 查询过期的待支付订单
+        LocalDateTime expireTime = minusTime(guideTradeOrderProperties.getCommentExpireTime());
+        List<GuideTradeOrderDO> orders = guideTradeOrderMapper.selectListByStatusAndReceiveTimeLt(
+                TradeOrderStatusEnum.COMPLETED.getStatus(), expireTime, false);
+        if (CollUtil.isEmpty(orders)) {
+            return 0;
+        }
+
+        // 2. 遍历执行,逐个取消
+        int count = 0;
+        for (GuideTradeOrderDO order : orders) {
+            try {
+                getSelf().createOrderItemCommentBySystemBySystem(order);
+                count++;
+            } catch (Throwable e) {
+                log.error("[createOrderItemCommentBySystem][order({}) 过期订单异常]", order.getId(), e);
+            }
+        }
+        return count;
+    }
+    @Transactional(rollbackFor = Exception.class)
+    @GuideTradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_COMMENT)
+    public void createOrderItemCommentBySystemBySystem(GuideTradeOrderDO order) {
+        // 1. 查询未评论的订单项
+        List<GuideTradeOrderItemDO> orderItems = guideTradeOrderItemMapper.selectListByOrderIdAndCommentStatus(
+                order.getId(), Boolean.FALSE);
+        if (CollUtil.isEmpty(orderItems)) {
+            return;
+        }
+
+        // 2. 逐个评论
+        for (GuideTradeOrderItemDO orderItem : orderItems) {
+            // 2.1 创建评价
+            AppGuideTradeOrderItemCommentCreateReqVO commentCreateReqVO = new AppGuideTradeOrderItemCommentCreateReqVO()
+                    .setOrderItemId(orderItem.getId()).setAnonymous(false).setContent("")
+                    .setBenefitScores(5).setDescriptionScores(5);
+            createOrderItemComment0(orderItem, commentCreateReqVO);
+
+            // 2.2 更新订单项评价状态
+            guideTradeOrderItemMapper.updateById(new GuideTradeOrderItemDO().setId(orderItem.getId()).setCommentStatus(Boolean.TRUE));
+        }
+
+        // 3. 所有订单项都评论了,则更新订单评价状态
+        guideTradeOrderMapper.updateById(new GuideTradeOrderDO().setId(order.getId()).setCommentStatus(Boolean.TRUE)
+                .setFinishTime(LocalDateTime.now()));
+        // 增加订单日志。注意:只有在所有订单项都评价后,才会增加
+        GuideTradeOrderLogUtils.setOrderInfo(order.getId(), order.getStatus(), order.getStatus());
+    }
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelPaidOrder(Long userId, Long orderId) {
+        // TODO 芋艿:这里实现要优化下;
+        GuideTradeOrderDO order = guideTradeOrderMapper.selectOrderByIdAndUserId(orderId, userId);
+        if (order == null) {
+            throw exception(ORDER_NOT_FOUND);
+        }
+        cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
+    }
+    private GuideTradeOrderUpdateServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+}

+ 53 - 0
yudao-module-guide/yudao-module-guide-biz/src/main/java/cn/iocoder/yudao/module/guide/service/order/bo/GuideTradeOrderLogCreateReqBO.java

@@ -0,0 +1,53 @@
+package cn.iocoder.yudao.module.guide.service.order.bo;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 订单日志的创建 Request BO
+ *
+ * @author 陈賝
+ * @since 2023/7/6 15:27
+ */
+@Data
+public class GuideTradeOrderLogCreateReqBO {
+
+    /**
+     * 用户编号
+     */
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+    /**
+     * 用户类型
+     */
+    @NotNull(message = "用户类型不能为空")
+    private Integer userType;
+
+    /**
+     * 订单编号
+     */
+    @NotNull(message = "订单编号不能为空")
+    private Long orderId;
+    /**
+     * 操作前状态
+     */
+    private Integer beforeStatus;
+    /**
+     * 操作后状态
+     */
+    @NotNull(message = "操作后的状态不能为空")
+    private Integer afterStatus;
+
+    /**
+     * 操作类型
+     */
+    @NotNull(message = "操作类型不能为空")
+    private Integer operateType;
+    /**
+     * 操作明细
+     */
+    @NotEmpty(message = "操作明细不能为空")
+    private String content;
+
+}