package cn.iocoder.yudao.module.aftersales.service.returnobj; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnPageReqVO; import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnSaveReqVO; import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO; import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnItemDO; import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnItemMapper; import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnMapper; import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper; import cn.iocoder.yudao.module.aftersales.dal.redis.no.AfterSaleNoRedisDAO; import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum; import cn.iocoder.yudao.module.aftersales.enums.AfterSaleReturnStatusEnum; import cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum; import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum; import cn.iocoder.yudao.module.aftersales.service.permission.AfterSalePermissionService; import cn.iocoder.yudao.module.aftersales.service.permission.bo.AfterSalePermissionCreateReqBO; import cn.iocoder.yudao.module.mes.api.returnsales.MesWmReturnSalesApi; import cn.iocoder.yudao.module.mes.api.returnsales.dto.MesWmReturnSalesCreateReqDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.*; /** * 售后退货申请 Service 实现类 */ @Service @Validated @Slf4j public class AfterSaleReturnServiceImpl implements AfterSaleReturnService { @Resource private AfterSaleReturnMapper returnMapper; @Resource private AfterSaleReturnItemMapper returnItemMapper; @Resource private AfterSaleTicketMapper ticketMapper; @Resource private AfterSaleNoRedisDAO noRedisDAO; @Resource private AfterSalePermissionService permissionService; @Resource private MesWmReturnSalesApi mesWmReturnSalesApi; @Override @Transactional(rollbackFor = Exception.class) public Long createReturn(AfterSaleReturnSaveReqVO createReqVO, Long userId) { // 1. 校验工单存在 AfterSaleTicketDO ticket = ticketMapper.selectById(createReqVO.getTicketId()); if (ticket == null) { throw exception(TICKET_NOT_EXISTS); } // 2. 生成单号 String no = noRedisDAO.generate(AfterSaleNoRedisDAO.RETURN_NO_PREFIX); if (returnMapper.selectByNo(no) != null) { throw exception(RETURN_NO_EXISTS); } // 3. 计算应退总金额 BigDecimal totalReturnPrice = BigDecimal.ZERO; if (CollUtil.isNotEmpty(createReqVO.getItems())) { totalReturnPrice = createReqVO.getItems().stream() .map(item -> item.getReturnPrice() != null ? item.getReturnPrice() : BigDecimal.ZERO) .reduce(BigDecimal.ZERO, BigDecimal::add); } // 4. 插入退货申请 AfterSaleReturnDO returnDO = BeanUtils.toBean(createReqVO, AfterSaleReturnDO.class) .setNo(no) .setTotalReturnPrice(totalReturnPrice) .setStatus(AfterSaleReturnStatusEnum.DRAFT.getStatus()); returnMapper.insert(returnDO); // 5. 插入退货明细行 if (CollUtil.isNotEmpty(createReqVO.getItems())) { createReqVO.getItems().forEach(item -> { AfterSaleReturnItemDO itemDO = BeanUtils.toBean(item, AfterSaleReturnItemDO.class); itemDO.setReturnId(returnDO.getId()); returnItemMapper.insert(itemDO); }); } // 6. 创建数据权限 AfterSalePermissionCreateReqBO permBO = new AfterSalePermissionCreateReqBO(); permBO.setBizType(AfterSaleBizTypeEnum.AFTER_SALE_RETURN.getType()); permBO.setBizId(returnDO.getId()); permBO.setUserId(userId); permBO.setLevel(AfterSalePermissionLevelEnum.OWNER.getLevel()); permissionService.createPermission(permBO); // 7. 更新工单状态为待退货 ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId()) .setReturnId(returnDO.getId()) .setStatus(AfterSaleTicketStatusEnum.WAITING_RETURN.getStatus())); return returnDO.getId(); } @Override @Transactional(rollbackFor = Exception.class) public void approveReturn(Long id) { // 1. 校验退货申请 AfterSaleReturnDO returnDO = validateReturnExists(id); if (!AfterSaleReturnStatusEnum.DRAFT.getStatus().equals(returnDO.getStatus())) { throw exception(RETURN_APPROVE_FAIL); } // 2. 获取关联工单(用于获取销售订单号等冗余信息) AfterSaleTicketDO ticket = ticketMapper.selectById(returnDO.getTicketId()); // 3. 调用 MES 创建销售退货单 MesWmReturnSalesCreateReqDTO mesReqDTO = new MesWmReturnSalesCreateReqDTO(); mesReqDTO.setCode(returnDO.getNo()); mesReqDTO.setName("售后退货-" + returnDO.getNo()); mesReqDTO.setSaleOrderId(returnDO.getSaleOrderId()); mesReqDTO.setSalesOrderCode(ticket != null ? ticket.getSaleOrderNo() : null); mesReqDTO.setClientId(returnDO.getCustomerId()); mesReqDTO.setReturnDate(LocalDate.now()); mesReqDTO.setReturnReason(returnDO.getReturnReason()); mesReqDTO.setRemark("售后工单退货 - 工单号: " + (ticket != null ? ticket.getNo() : "")); mesReqDTO.setAfterSaleReturnId(returnDO.getId()); Long mesReturnId = mesWmReturnSalesApi.createReturnSales(mesReqDTO); // 4. 更新退货申请状态 returnMapper.updateById(new AfterSaleReturnDO().setId(id) .setStatus(AfterSaleReturnStatusEnum.APPROVED.getStatus()) .setMesReturnId(mesReturnId) .setMesReturnCode(returnDO.getNo()) .setAuditTime(LocalDateTime.now())); } @Override public AfterSaleReturnDO getReturn(Long id) { return validateReturnExists(id); } @Override public List getReturnList(Collection ids) { if (CollUtil.isEmpty(ids)) { return List.of(); } return returnMapper.selectBatchIds(ids); } @Override public PageResult getReturnPage(AfterSaleReturnPageReqVO pageReqVO, Long userId) { return returnMapper.selectPage(pageReqVO, userId, pageReqVO.getSceneType(), pageReqVO.getNo(), pageReqVO.getTicketId(), pageReqVO.getCustomerId(), pageReqVO.getStatus()); } @Override public List getReturnItemListByReturnId(Long returnId) { return returnItemMapper.selectListByReturnId(returnId); } @Override @Transactional(rollbackFor = Exception.class) public void onQualityFinished(Long returnId, String qualityResult, String reportUrl) { AfterSaleReturnDO returnDO = validateReturnExists(returnId); returnMapper.updateById(new AfterSaleReturnDO().setId(returnId) .setQualityResult(qualityResult) .setQualityReportUrl(reportUrl) .setStatus(AfterSaleReturnStatusEnum.INSPECTED.getStatus())); } @Override @Transactional(rollbackFor = Exception.class) public void onInboundFinished(Long returnId) { AfterSaleReturnDO returnDO = validateReturnExists(returnId); returnMapper.updateById(new AfterSaleReturnDO().setId(returnId) .setStatus(AfterSaleReturnStatusEnum.INBOUND.getStatus()) .setReturnInboundTime(LocalDateTime.now())); } @Override @Transactional(rollbackFor = Exception.class) public void onRefundCompleted(Long returnId, BigDecimal actualRefundPrice) { AfterSaleReturnDO returnDO = validateReturnExists(returnId); returnMapper.updateById(new AfterSaleReturnDO().setId(returnId) .setActualRefundPrice(actualRefundPrice) .setStatus(AfterSaleReturnStatusEnum.REFUNDED.getStatus()) .setRefundTime(LocalDateTime.now())); // 更新关联工单状态为待退款/换货完成 AfterSaleTicketDO ticket = ticketMapper.selectById(returnDO.getTicketId()); if (ticket != null) { ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId()) .setStatus(AfterSaleTicketStatusEnum.WAITING_REFUND.getStatus())); } } // ========== 内部方法 ========== private AfterSaleReturnDO validateReturnExists(Long id) { AfterSaleReturnDO returnDO = returnMapper.selectById(id); if (returnDO == null) { throw exception(RETURN_NOT_EXISTS); } return returnDO; } }