diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminOrderService.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminOrderService.java index 374419ef..b2c28de3 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminOrderService.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/service/AdminOrderService.java @@ -165,7 +165,7 @@ public class AdminOrderService { notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND, new String[]{order.getOrderSn().substring(8, 14)}); - logHelper.logOrderSucceed("退款", "订单编号 " + orderId); + logHelper.logOrderSucceed("退款", "订单编号 " + order.getOrderSn()); return ResponseUtil.ok(); } @@ -210,7 +210,7 @@ public class AdminOrderService { // "您的订单已经发货,快递公司 {1},快递单 {2} ,请注意查收" notifyService.notifySmsTemplate(order.getMobile(), NotifyType.SHIP, new String[]{shipChannel, shipSn}); - logHelper.logOrderSucceed("发货", "订单编号 " + orderId); + logHelper.logOrderSucceed("发货", "订单编号 " + order.getOrderSn()); return ResponseUtil.ok(); } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java index 3722c1ca..3f7e42a4 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/util/AdminResponseCode.java @@ -24,5 +24,6 @@ public class AdminResponseCode { public static final Integer GROUPON_GOODS_EXISTED = 651; public static final Integer GROUPON_GOODS_OFFLINE = 652; public static final Integer NOTICE_UPDATE_NOT_ALLOWED = 660; + public static final Integer AFTERSALE_NOT_ALLOWED = 670; } diff --git a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAftersaleController.java b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAftersaleController.java index 7ce39c77..8694625f 100644 --- a/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAftersaleController.java +++ b/litemall-admin-api/src/main/java/org/linlinjava/litemall/admin/web/AdminAftersaleController.java @@ -1,20 +1,38 @@ package org.linlinjava.litemall.admin.web; +import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; +import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.linlinjava.litemall.admin.annotation.RequiresPermissionsDesc; +import org.linlinjava.litemall.admin.service.LogHelper; +import org.linlinjava.litemall.admin.util.AdminResponseCode; +import org.linlinjava.litemall.core.notify.NotifyService; +import org.linlinjava.litemall.core.notify.NotifyType; import org.linlinjava.litemall.core.util.JacksonUtil; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.core.validator.Order; import org.linlinjava.litemall.core.validator.Sort; import org.linlinjava.litemall.db.domain.LitemallAftersale; +import org.linlinjava.litemall.db.domain.LitemallGoodsProduct; +import org.linlinjava.litemall.db.domain.LitemallOrder; +import org.linlinjava.litemall.db.domain.LitemallOrderGoods; import org.linlinjava.litemall.db.service.*; +import org.linlinjava.litemall.db.util.AftersaleConstant; +import org.linlinjava.litemall.db.util.OrderUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.List; +import static org.linlinjava.litemall.admin.util.AdminResponseCode.ORDER_REFUND_FAILED; + @RestController @RequestMapping("/admin/aftersale") @Validated @@ -27,34 +45,188 @@ public class AdminAftersaleController { private LitemallOrderService orderService; @Autowired private LitemallOrderGoodsService orderGoodsService; + @Autowired + private LitemallGoodsProductService goodsProductService; + @Autowired + private LogHelper logHelper; + @Autowired + private WxPayService wxPayService; + @Autowired + private NotifyService notifyService; @RequiresPermissions("admin:aftersale:list") @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "查询") @GetMapping("/list") - public Object list(Integer orderId, String aftersaleSn, + public Object list(Integer orderId, String aftersaleSn, Short status, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, @Sort @RequestParam(defaultValue = "add_time") String sort, @Order @RequestParam(defaultValue = "desc") String order) { - List aftersaleList = aftersaleService.querySelective(orderId, aftersaleSn, page, limit, sort, order); + List aftersaleList = aftersaleService.querySelective(orderId, aftersaleSn, status, page, limit, sort, order); return ResponseUtil.okList(aftersaleList); } + @RequiresPermissions("admin:aftersale:recept") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "审核通过") + @PostMapping("/recept") + public Object recept(@RequestBody LitemallAftersale aftersale) { + Integer id = aftersale.getId(); + LitemallAftersale aftersaleOne = aftersaleService.findById(id); + if(aftersaleOne == null){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不存在"); + } + Short status = aftersaleOne.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不能进行审核通过操作"); + } + aftersaleOne.setStatus(AftersaleConstant.STATUS_RECEPT); + aftersaleOne.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersaleOne); - @RequiresPermissions("admin:aftersale:delete") - @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "删除") - @PostMapping("/delete") - public Object delete(@RequestBody LitemallAftersale aftersale) { - aftersaleService.deleteById(aftersale.getId()); + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersaleOne.getOrderId(), AftersaleConstant.STATUS_RECEPT); return ResponseUtil.ok(); } - @RequiresPermissions("admin:aftersale:batch-delete") - @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量删除") - @PostMapping("/batch-delete") - public Object batchDelete(@RequestBody String body) { + @RequiresPermissions("admin:aftersale:batch-recept") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量通过") + @PostMapping("/batch-recept") + public Object batchRecept(@RequestBody String body) { List ids = JacksonUtil.parseIntegerList(body, "ids"); - aftersaleService.deleteByIds(ids); + // NOTE + // 批量操作中,如果一部分数据项失败,应该如何处理 + // 这里采用忽略失败,继续处理其他项。 + // 当然开发者可以采取其他处理方式,具体情况具体分析,例如利用事务回滚所有操作然后返回用户失败信息 + for(Integer id : ids) { + LitemallAftersale aftersale = aftersaleService.findById(id); + if(aftersale == null){ + continue; + } + Short status = aftersale.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + continue; + } + aftersale.setStatus(AftersaleConstant.STATUS_RECEPT); + aftersale.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersale); + + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersale.getOrderId(), AftersaleConstant.STATUS_RECEPT); + } + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:aftersale:reject") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "审核拒绝") + @PostMapping("/reject") + public Object reject(@RequestBody LitemallAftersale aftersale) { + Integer id = aftersale.getId(); + LitemallAftersale aftersaleOne = aftersaleService.findById(id); + if(aftersaleOne == null){ + return ResponseUtil.badArgumentValue(); + } + Short status = aftersaleOne.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不能进行审核拒绝操作"); + } + aftersaleOne.setStatus(AftersaleConstant.STATUS_REJECT); + aftersaleOne.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersaleOne); + + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersaleOne.getOrderId(), AftersaleConstant.STATUS_REJECT); + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:aftersale:batch-reject") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量拒绝") + @PostMapping("/batch-reject") + public Object batchReject(@RequestBody String body) { + List ids = JacksonUtil.parseIntegerList(body, "ids"); + for(Integer id : ids) { + LitemallAftersale aftersale = aftersaleService.findById(id); + if(aftersale == null){ + continue; + } + Short status = aftersale.getStatus(); + if(!status.equals(AftersaleConstant.STATUS_REQUEST)){ + continue; + } + aftersale.setStatus(AftersaleConstant.STATUS_REJECT); + aftersale.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersale); + + // 订单也要更新售后状态 + orderService.updateAftersaleStatus(aftersale.getOrderId(), AftersaleConstant.STATUS_REJECT); + } + return ResponseUtil.ok(); + } + + @RequiresPermissions("admin:aftersale:refund") + @RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "退款") + @PostMapping("/refund") + public Object refund(@RequestBody LitemallAftersale aftersale) { + Integer id = aftersale.getId(); + LitemallAftersale aftersaleOne = aftersaleService.findById(id); + if(aftersaleOne == null){ + return ResponseUtil.badArgumentValue(); + } + if(!aftersaleOne.getStatus().equals(AftersaleConstant.STATUS_RECEPT)){ + return ResponseUtil.fail(AdminResponseCode.AFTERSALE_NOT_ALLOWED, "售后不能进行退款操作"); + } + Integer orderId = aftersaleOne.getOrderId(); + LitemallOrder order = orderService.findById(orderId); + + // 微信退款 + WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest(); + wxPayRefundRequest.setOutTradeNo(order.getOrderSn()); + wxPayRefundRequest.setOutRefundNo("refund_" + order.getOrderSn()); + // 元转成分 + Integer totalFee = aftersaleOne.getAmount().multiply(new BigDecimal(100)).intValue(); + wxPayRefundRequest.setTotalFee(totalFee); + wxPayRefundRequest.setRefundFee(totalFee); + + WxPayRefundResult wxPayRefundResult; + try { + wxPayRefundResult = wxPayService.refund(wxPayRefundRequest); + } catch (WxPayException e) { + logger.error(e.getMessage(), e); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + if (!wxPayRefundResult.getReturnCode().equals("SUCCESS")) { + logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + if (!wxPayRefundResult.getResultCode().equals("SUCCESS")) { + logger.warn("refund fail: " + wxPayRefundResult.getReturnMsg()); + return ResponseUtil.fail(ORDER_REFUND_FAILED, "订单退款失败"); + } + + aftersaleOne.setStatus(AftersaleConstant.STATUS_REFUND); + aftersaleOne.setHandleTime(LocalDateTime.now()); + aftersaleService.updateById(aftersaleOne); + + orderService.updateAftersaleStatus(orderId, AftersaleConstant.STATUS_REFUND); + + // NOTE + // 如果是“退货退款”类型的售后,这里退款说明用户的货已经退回,则需要商品货品数量增加 + // 开发者也可以删除一下代码,在其他地方增加商品货品入库操作 + if(aftersale.getType().equals(AftersaleConstant.TYPE_GOODS_REQUIRED)) { + List orderGoodsList = orderGoodsService.queryByOid(orderId); + for (LitemallOrderGoods orderGoods : orderGoodsList) { + Integer productId = orderGoods.getProductId(); + Short number = orderGoods.getNumber(); + goodsProductService.addStock(productId, number); + } + } + + // 发送短信通知,这里采用异步发送 + // 退款成功通知用户, 例如“您申请的订单退款 [ 单号:{1} ] 已成功,请耐心等待到账。” + // TODO 注意订单号只发后6位 + notifyService.notifySmsTemplate(order.getMobile(), NotifyType.REFUND, + new String[]{order.getOrderSn().substring(8, 14)}); + + logHelper.logOrderSucceed("退款", "订单编号 " + order.getOrderSn() + " 售后编号 " + aftersale.getAftersaleSn()); return ResponseUtil.ok(); } } diff --git a/litemall-admin/src/api/aftersale.js b/litemall-admin/src/api/aftersale.js index 1c8eb3c8..11e4a200 100644 --- a/litemall-admin/src/api/aftersale.js +++ b/litemall-admin/src/api/aftersale.js @@ -8,17 +8,41 @@ export function listAftersale(query) { }) } -export function deleteAftersale(data) { +export function receptAftersale(data) { return request({ - url: '/aftersale/delete', + url: '/aftersale/recept', method: 'post', data }) } -export function batchDeleteAftersale(data) { +export function batchReceptAftersale(data) { return request({ - url: '/aftersale/batch-delete', + url: '/aftersale/batch-recept', + method: 'post', + data + }) +} + +export function rejectAftersale(data) { + return request({ + url: '/aftersale/reject', + method: 'post', + data + }) +} + +export function batchRejectAftersale(data) { + return request({ + url: '/aftersale/batch-reject', + method: 'post', + data + }) +} + +export function refundAftersale(data) { + return request({ + url: '/aftersale/refund', method: 'post', data }) diff --git a/litemall-admin/src/views/mall/aftersale.vue b/litemall-admin/src/views/mall/aftersale.vue index 0294f756..32755964 100644 --- a/litemall-admin/src/views/mall/aftersale.vue +++ b/litemall-admin/src/views/mall/aftersale.vue @@ -3,21 +3,27 @@
- + 查找 导出
- 批量删除 + 批量通过 + 批量拒绝
- - + + + + + + + - + @@ -29,10 +35,12 @@ - + @@ -47,24 +55,26 @@