Commit 11d3b5bd authored by Junling Bu's avatar Junling Bu
Browse files

feat[litemall-admin, litemall-admin-api]: 售后页面实现审核和退款功能

parent 4611d829
......@@ -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();
}
......
......@@ -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;
}
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<LitemallAftersale> aftersaleList = aftersaleService.querySelective(orderId, aftersaleSn, page, limit, sort, order);
List<LitemallAftersale> 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);
// 订单也要更新售后状态
orderService.updateAftersaleStatus(aftersaleOne.getOrderId(), AftersaleConstant.STATUS_RECEPT);
return ResponseUtil.ok();
}
@RequiresPermissions("admin:aftersale:batch-recept")
@RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量通过")
@PostMapping("/batch-recept")
public Object batchRecept(@RequestBody String body) {
List<Integer> ids = JacksonUtil.parseIntegerList(body, "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);
@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_REJECT);
return ResponseUtil.ok();
}
@RequiresPermissions("admin:aftersale:batch-delete")
@RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量删除")
@PostMapping("/batch-delete")
public Object batchDelete(@RequestBody String body) {
@RequiresPermissions("admin:aftersale:batch-reject")
@RequiresPermissionsDesc(menu = {"商城管理", "售后管理"}, button = "批量拒绝")
@PostMapping("/batch-reject")
public Object batchReject(@RequestBody String body) {
List<Integer> ids = JacksonUtil.parseIntegerList(body, "ids");
aftersaleService.deleteByIds(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<LitemallOrderGoods> 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();
}
}
......@@ -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
})
......
......@@ -3,21 +3,27 @@
<!-- 查询和其他操作 -->
<div class="filter-container">
<el-input v-model="listQuery.aftersaleSn" clearable class="filter-item" style="width: 200px;" placeholder="请输入服务编号" />
<el-input v-model="listQuery.aftersaleSn" clearable class="filter-item" style="width: 200px;" placeholder="请输入售后编号" />
<el-input v-model="listQuery.orderId" clearable class="filter-item" style="width: 200px;" placeholder="请输入订单ID" />
<el-button v-permission="['GET /admin/aftersale/list']" class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">查找</el-button>
<el-button :loading="downloadLoading" class="filter-item" type="primary" icon="el-icon-download" @click="handleDownload">导出</el-button>
</div>
<div class="operator-container">
<el-button v-permission="['GET /admin/aftersale/batch-delete']" class="filter-item" type="danger" icon="el-icon-delete" @click="handleBatchDelete">批量删除</el-button>
<el-button v-permission="['GET /admin/aftersale/batch-recept']" class="filter-item" type="success" icon="el-icon-info" @click="handleBatchRecept">批量通过</el-button>
<el-button v-permission="['GET /admin/aftersale/batch-reject']" class="filter-item" type="danger" icon="el-icon-delete" @click="handleBatchReject">批量拒绝</el-button>
</div>
<!-- 查询结果 -->
<el-table v-loading="listLoading" :data="list" element-loading-text="正在查询中。。。" border fit highlight-current-row @selection-change="handleSelectionChange">
<el-tabs v-model="tab" @tab-click="handleClick">
<el-tab-pane label="全部" name="all" />
<el-tab-pane label="待审核" name="uncheck" />
<el-tab-pane label="待退款" name="unrefund" />
</el-tabs>
<el-table v-loading="listLoading" :data="list" element-loading-text="正在查询中。。。" fit highlight-current-row @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column align="center" label="服务编号" prop="aftersaleSn" />
<el-table-column align="center" label="售后编号" prop="aftersaleSn" />
<el-table-column align="center" label="订单ID" prop="orderId" />
<el-table-column align="center" label="用户ID" prop="userId" />
<el-table-column align="center" label="售后类型" prop="type">
......@@ -29,10 +35,12 @@
<el-table-column align="center" label="退款价格" prop="amount" />
<el-table-column align="center" label="申请时间" prop="addTime" />
<el-table-column align="center" label="操作" min-width="100" class-name="small-padding fixed-width">
<el-table-column align="center" label="操作" min-width="120" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleRead(scope.row)">详情</el-button>
<el-button v-permission="['POST /admin/aftersale/delete']" type="danger" size="mini" @click="handleDelete(scope.row)">删除</el-button>
<el-button v-permission="['POST /admin/aftersale/detail']" type="primary" size="mini" @click="handleRead(scope.row)">详情</el-button>
<el-button v-if="scope.row.status === 1" v-permission="['POST /admin/aftersale/recept']" type="success" size="mini" @click="handleRecept(scope.row)">通过</el-button>
<el-button v-if="scope.row.status === 1" v-permission="['POST /admin/aftersale/reject']" type="danger" size="mini" @click="handleReject(scope.row)">拒绝</el-button>
<el-button v-if="scope.row.status === 2" v-permission="['POST /admin/aftersale/refund']" type="warning" size="mini" @click="handleRefund(scope.row)">退款</el-button>
</template>
</el-table-column>
</el-table>
......@@ -47,24 +55,26 @@
</template>
<script>
import { listAftersale, deleteAftersale, batchDeleteAftersale } from '@/api/aftersale'
import { listAftersale, receptAftersale, batchReceptAftersale, rejectAftersale, batchRejectAftersale, refundAftersale } from '@/api/aftersale'
import BackToTop from '@/components/BackToTop'
import Pagination from '@/components/Pagination' // Secondary package based on el-pagination
import _ from 'lodash'
export default {
name: 'Topic',
name: 'Aftersale',
components: { BackToTop, Pagination },
data() {
return {
list: [],
total: 0,
listLoading: true,
tab: 'all',
listQuery: {
page: 1,
limit: 20,
aftersaleSn: undefined,
orderId: undefined,
status: '',
sort: 'add_time',
order: 'desc'
},
......@@ -106,15 +116,27 @@ export default {
this.listQuery.page = 1
this.getList()
},
handleDelete(row) {
deleteAftersale(row)
handleSelectionChange(val) {
this.multipleSelection = val
},
handleClick() {
if (this.tab === 'all') {
this.listQuery.status = ''
} else if (this.tab === 'uncheck') {
this.listQuery.status = '1'
} else if (this.tab === 'unrefund') {
this.listQuery.status = '2'
}
this.getList()
},
handleRecept(row) {
receptAftersale(row)
.then(response => {
this.$notify.success({
title: '成功',
message: '删除专题成功'
message: '审核通过操作成功'
})
const index = this.list.indexOf(row)
this.list.splice(index, 1)
this.getList()
})
.catch(response => {
this.$notify.error({
......@@ -123,14 +145,47 @@ export default {
})
})
},
handleSelectionChange(val) {
this.multipleSelection = val
handleBatchRecept() {
if (this.multipleSelection.length === 0) {
this.$message.error('请选择至少一条记录')
return
}
const ids = []
_.forEach(this.multipleSelection, function(item) {
ids.push(item.id)
})
batchReceptAftersale({ ids: ids })
.then(response => {
this.$notify.success({
title: '成功',
message: '批量通过操作成功'
})
this.getList()
})
.catch(response => {
this.$notify.error({
title: '失败',
message: response.data.errmsg
})
})
},
showContent(content) {
this.contentDetail = content
this.contentDialogVisible = true
handleReject(row) {
rejectAftersale(row)
.then(response => {
this.$notify.success({
title: '成功',
message: '审核拒绝操作成功'
})
this.getList()
})
.catch(response => {
this.$notify.error({
title: '失败',
message: response.data.errmsg
})
})
},
handleBatchDelete() {
handleBatchReject() {
if (this.multipleSelection.length === 0) {
this.$message.error('请选择至少一条记录')
return
......@@ -139,11 +194,27 @@ export default {
_.forEach(this.multipleSelection, function(item) {
ids.push(item.id)
})
batchDeleteAftersale({ ids: ids })
batchRejectAftersale({ ids: ids })
.then(response => {
this.$notify.success({
title: '成功',
message: '批量拒绝操作成功'
})
this.getList()
})
.catch(response => {
this.$notify.error({
title: '失败',
message: response.data.errmsg
})
})
},
handleRefund(row) {
refundAftersale(row)
.then(response => {
this.$notify.success({
title: '成功',
message: '批量删除售后成功'
message: '退款操作成功'
})
this.getList()
})
......@@ -158,7 +229,7 @@ export default {
this.downloadLoading = true
import('@/vendor/Export2Excel').then(excel => {
const tHeader = [
'服务编号',
'售后编号',
'订单ID',
'用户ID',
'售后类型',
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment