Procházet zdrojové kódy

feat: add AfterSales module (entity+mapper+service+controllers)

Add after-sales request system with shop-facing and admin-facing controllers.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
liaoxg před 2 měsíci
rodič
revize
861f463d67

+ 49 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminAfterSalesController.java

@@ -0,0 +1,49 @@
+package com.etotem.cfc.controller.admin;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.AfterSalesRequest;
+import com.etotem.cfc.service.AfterSalesService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/admin/shop/after-sales")
+public class AdminAfterSalesController {
+
+    @Resource
+    private AfterSalesService afterSalesService;
+
+    @PostMapping("/list")
+    public Result<List<AfterSalesRequest>> list(@RequestBody Map<String, Object> params) {
+        String status = params != null ? (String) params.get("status") : null;
+        return afterSalesService.adminList(status);
+    }
+
+    @PostMapping("/detail")
+    public Result<AfterSalesRequest> detail(@RequestBody Map<String, Object> params) {
+        Long id = Long.valueOf(params.get("id").toString());
+        return afterSalesService.adminDetail(id);
+    }
+
+    @PostMapping("/review")
+    public Result<String> review(@RequestBody Map<String, Object> params,
+                                 @RequestAttribute("userId") Long adminId) {
+        Long id = Long.valueOf(params.get("id").toString());
+        String action = (String) params.get("action");
+        String rejectReason = (String) params.get("rejectReason");
+        return afterSalesService.review(id, action, rejectReason, adminId);
+    }
+
+    @PostMapping("/complete")
+    public Result<String> complete(@RequestBody Map<String, Object> params) {
+        Long id = Long.valueOf(params.get("id").toString());
+        return afterSalesService.complete(id);
+    }
+}

+ 40 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/shop/AfterSalesController.java

@@ -0,0 +1,40 @@
+package com.etotem.cfc.controller.shop;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.AfterSalesRequest;
+import com.etotem.cfc.service.AfterSalesService;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestAttribute;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/shop/after-sales")
+public class AfterSalesController {
+
+    @Resource
+    private AfterSalesService afterSalesService;
+
+    @PostMapping("/create")
+    public Result<AfterSalesRequest> create(@RequestBody AfterSalesRequest request,
+                                            @RequestAttribute("userId") Long userId) {
+        return afterSalesService.create(request, userId);
+    }
+
+    @PostMapping("/list")
+    public Result<List<AfterSalesRequest>> list(@RequestAttribute("userId") Long userId) {
+        return afterSalesService.list(userId);
+    }
+
+    @PostMapping("/detail")
+    public Result<AfterSalesRequest> detail(@RequestBody Map<String, Object> params,
+                                            @RequestAttribute("userId") Long userId) {
+        Long id = Long.valueOf(params.get("id").toString());
+        return afterSalesService.detail(id, userId);
+    }
+}

+ 30 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/AfterSalesRequest.java

@@ -0,0 +1,30 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("after_sales_requests")
+public class AfterSalesRequest implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long orderId;
+    private String orderNo;
+    private Long userId;
+    private String type;
+    private String reason;
+    private String description;
+    private String images;
+    private Integer refundAmount;
+    private String status;
+    private String rejectReason;
+    private Long handledBy;
+    private Date handledAt;
+    private Date createdAt;
+    private Date updatedAt;
+}

+ 7 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/AfterSalesRequestMapper.java

@@ -0,0 +1,7 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.AfterSalesRequest;
+
+public interface AfterSalesRequestMapper extends BaseMapper<AfterSalesRequest> {
+}

+ 118 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/AfterSalesService.java

@@ -0,0 +1,118 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.AfterSalesRequest;
+import com.etotem.cfc.entity.ProductOrder;
+import com.etotem.cfc.mapper.AfterSalesRequestMapper;
+import com.etotem.cfc.mapper.ProductOrderMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class AfterSalesService {
+
+    @Resource
+    private AfterSalesRequestMapper afterSalesRequestMapper;
+
+    @Resource
+    private ProductOrderMapper productOrderMapper;
+
+    public Result<AfterSalesRequest> create(AfterSalesRequest request, Long userId) {
+        ProductOrder order = productOrderMapper.selectById(request.getOrderId());
+        if (order == null) {
+            return Result.error("订单不存在");
+        }
+        if (!order.getBuyerId().equals(userId)) {
+            return Result.error("无权操作此订单");
+        }
+        if (!"paid".equals(order.getStatus())) {
+            return Result.error("仅已支付的订单可申请售后");
+        }
+        request.setUserId(userId);
+        request.setOrderNo(order.getOrderNo());
+        request.setRefundAmount(order.getTotalAmount());
+        request.setStatus("pending");
+        request.setCreatedAt(new Date());
+        request.setUpdatedAt(new Date());
+        afterSalesRequestMapper.insert(request);
+        return Result.success(request);
+    }
+
+    public Result<List<AfterSalesRequest>> list(Long userId) {
+        List<AfterSalesRequest> list = afterSalesRequestMapper.selectList(
+            new LambdaQueryWrapper<AfterSalesRequest>()
+                .eq(AfterSalesRequest::getUserId, userId)
+                .orderByDesc(AfterSalesRequest::getCreatedAt)
+        );
+        return Result.success(list);
+    }
+
+    public Result<AfterSalesRequest> detail(Long id, Long userId) {
+        AfterSalesRequest request = afterSalesRequestMapper.selectById(id);
+        if (request == null) {
+            return Result.error("售后申请不存在");
+        }
+        if (!request.getUserId().equals(userId)) {
+            return Result.error("无权查看");
+        }
+        return Result.success(request);
+    }
+
+    public Result<List<AfterSalesRequest>> adminList(String status) {
+        LambdaQueryWrapper<AfterSalesRequest> wrapper = new LambdaQueryWrapper<AfterSalesRequest>()
+            .orderByDesc(AfterSalesRequest::getCreatedAt);
+        if (status != null && !status.isEmpty()) {
+            wrapper.eq(AfterSalesRequest::getStatus, status);
+        }
+        return Result.success(afterSalesRequestMapper.selectList(wrapper));
+    }
+
+    public Result<AfterSalesRequest> adminDetail(Long id) {
+        AfterSalesRequest request = afterSalesRequestMapper.selectById(id);
+        if (request == null) {
+            return Result.error("售后申请不存在");
+        }
+        return Result.success(request);
+    }
+
+    public Result<String> review(Long id, String action, String rejectReason, Long adminId) {
+        AfterSalesRequest request = afterSalesRequestMapper.selectById(id);
+        if (request == null) {
+            return Result.error("售后申请不存在");
+        }
+        if (!"pending".equals(request.getStatus())) {
+            return Result.error("仅待审核的申请可处理");
+        }
+        if ("approve".equals(action)) {
+            request.setStatus("approved");
+        } else if ("reject".equals(action)) {
+            request.setStatus("rejected");
+            request.setRejectReason(rejectReason);
+        } else {
+            return Result.error("无效操作");
+        }
+        request.setHandledBy(adminId);
+        request.setHandledAt(new Date());
+        request.setUpdatedAt(new Date());
+        afterSalesRequestMapper.updateById(request);
+        return Result.success("操作成功");
+    }
+
+    public Result<String> complete(Long id) {
+        AfterSalesRequest request = afterSalesRequestMapper.selectById(id);
+        if (request == null) {
+            return Result.error("售后申请不存在");
+        }
+        if (!"approved".equals(request.getStatus())) {
+            return Result.error("仅已批准的申请可完成");
+        }
+        request.setStatus("completed");
+        request.setUpdatedAt(new Date());
+        afterSalesRequestMapper.updateById(request);
+        return Result.success("已完成");
+    }
+}