记得上下班打卡 | git大法好,push需谨慎

Commit 42a98d6f authored by 姜秀龙's avatar 姜秀龙

Merge remote-tracking branch 'refs/remotes/origin/dev-wyf-partner' into test-ecs

parents cf4aa441 b05fa3b2
......@@ -455,6 +455,12 @@ public class GoblinRedisConst {
*/
public static final String SQB_GOBLIN_ORDER_SN_KEY = PREFIX.concat("sqb:orderSn:");
/**
* 订单退款分布式锁
* {goblin:refund:lock:${orderId}, 1}
*/
public static final String REFUND_ORDER_LOCK = PREFIX.concat("refund:lock:");
/**
* 收钱吧 用户订单列表
*/
......
......@@ -83,6 +83,8 @@ public class GoblinBackOrderVo implements Serializable, Cloneable {
private String auditAt;
@ApiModelProperty(value = "创建时间")
private String createdAt;
@ApiModelProperty(value = "发起方[1-用户|2-商家]")
private Integer operationType;
@ApiModelProperty(value = "过期时间")
private String expireAt;
......
package com.liquidnet.service.goblin.dto.vo;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 店铺退款列表接口返回体:分页数据 + 各审核状态 Tab 数量。
*/
@ApiModel("店铺退款列表分页结果")
@Data
public class GoblinStoreBackOrderListPageVo {
/** 当前页列表及分页信息 */
@ApiModelProperty("分页数据")
private PageInfo<GoblinStoreBackOrderListVo> pageInfo;
/** 顶部审核 Tab 角标数量,不受当前 status 筛选影响 */
@ApiModelProperty("各审核状态数量")
private GoblinStoreBackOrderStatusCountVo statusCount;
}
......@@ -20,11 +20,23 @@ public class GoblinStoreBackOrderListVo implements Cloneable {
private Integer type;
@ApiModelProperty(value = "退款/退货状态[0-商铺发起退款|1-退款申请(用户发送退款请求)|2-退款成功(商家同意退款)|3-退款拒绝(商家拒绝退款)|4-退货申请(用户发起退货请求)|5-退货拒绝(商家拒绝退货)|6-退货审核通过等待用户填写物流(商家审核通过,等待用户寄回商品)|7-待收货(用户已确认)|8-退货完成(商家收货并且同意退款给用户)|9-退货失败(商家不同意退款)|10-退款失败|11-取消退款")
private Integer status;
@ApiModelProperty(value = " 退款金额")
@ApiModelProperty(value = "退款范围[1-整单退款|2-部分退款]")
private Integer refundScope;
@ApiModelProperty(value = "原订单实付金额")
private BigDecimal priceActual;
@ApiModelProperty(value = "实际退款金额")
private BigDecimal realBackPrice;
@ApiModelProperty(value = " 创建时间")
@ApiModelProperty(value = "发起方[1-用户发起|2-商家发起]")
private Integer operationType;
@ApiModelProperty(value = "申请时间")
private String createdAt;
@ApiModelProperty(value = "退款sku")
@ApiModelProperty(value = "审核时间")
private String auditAt;
@ApiModelProperty(value = "退款完成时间")
private String refundAt;
@ApiModelProperty(value = "处理时间(优先退款完成时间,其次审核时间,最后拒绝时间)")
private String processedAt;
@ApiModelProperty(value = "退款商品明细")
private List<GoblinBackOrderSkuVo> backOrderSkuVos;
......
package com.liquidnet.service.goblin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Map;
/**
* 店铺退款列表 - 各 status 数量。
* status 含义见 {@link GoblinBackOrderVo#getStatus()},前端自行组合 Tab。
*/
@ApiModel("店铺退款列表-各审核状态数量")
@Data
public class GoblinStoreBackOrderStatusCountVo {
@ApiModelProperty("全部")
private long all;
@ApiModelProperty("商铺发起退款[status=0]")
private long storeInitiated;
@ApiModelProperty("退款申请[status=1]")
private long refundApply;
@ApiModelProperty("退款成功[status=2]")
private long refundSuccess;
@ApiModelProperty("退款拒绝[status=3]")
private long refundReject;
@ApiModelProperty("退货申请[status=4]")
private long returnApply;
@ApiModelProperty("退货拒绝[status=5]")
private long returnReject;
@ApiModelProperty("退货审核通过待填物流[status=6]")
private long returnWaitLogistics;
@ApiModelProperty("待收货[status=7]")
private long waitReceive;
@ApiModelProperty("退货完成[status=8]")
private long returnCompleted;
@ApiModelProperty("退货失败[status=9]")
private long returnFailed;
@ApiModelProperty("退款失败[status=10]")
private long refundFailed;
@ApiModelProperty("取消退款[status=11]")
private long refundCancelled;
/**
* 从 Mongo 按 status 分组统计结果组装 VO。
*/
public static GoblinStoreBackOrderStatusCountVo of(long all, Map<Integer, Long> grouped) {
GoblinStoreBackOrderStatusCountVo vo = new GoblinStoreBackOrderStatusCountVo();
vo.all = all;
vo.storeInitiated = grouped.getOrDefault(0, 0L);
vo.refundApply = grouped.getOrDefault(1, 0L);
vo.refundSuccess = grouped.getOrDefault(2, 0L);
vo.refundReject = grouped.getOrDefault(3, 0L);
vo.returnApply = grouped.getOrDefault(4, 0L);
vo.returnReject = grouped.getOrDefault(5, 0L);
vo.returnWaitLogistics = grouped.getOrDefault(6, 0L);
vo.waitReceive = grouped.getOrDefault(7, 0L);
vo.returnCompleted = grouped.getOrDefault(8, 0L);
vo.returnFailed = grouped.getOrDefault(9, 0L);
vo.refundFailed = grouped.getOrDefault(10, 0L);
vo.refundCancelled = grouped.getOrDefault(11, 0L);
return vo;
}
}
package com.liquidnet.service.goblin.dto.vo;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 店铺订单列表接口返回体:分页数据 + 各状态 Tab 数量。
*/
@ApiModel("店铺订单列表分页结果")
@Data
public class GoblinStoreOrderListPageVo {
/** 当前页列表及分页信息 */
@ApiModelProperty("分页数据")
private PageInfo<GoblinStoreOrderListVo> pageInfo;
/** 顶部状态 Tab 角标数量,不受当前 status 筛选影响 */
@ApiModelProperty("各状态数量")
private GoblinStoreOrderStatusCountVo statusCount;
}
......@@ -64,6 +64,15 @@ public class GoblinStoreOrderListVo implements Cloneable {
@ApiModelProperty(value = " sku相关")
private List<GoblinStoreOrderListSkuVo> storeOrderListSkuVoList;
@ApiModelProperty(value = " 下单账号-UID")
private String userId;
@ApiModelProperty(value = " 下单账号-昵称")
private String userName;
@ApiModelProperty(value = " 下单账号-手机号")
private String userMobile;
private static final GoblinStoreOrderListVo obj = new GoblinStoreOrderListVo();
......
package com.liquidnet.service.goblin.dto.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Map;
/**
* 店铺订单列表 - 各状态 Tab 数量。
* 对应前端 statusTabs:全部 / 待支付(0) / 待发货(2) / 已发货(3) / 已完成(4) / 已退款(6)
*/
@ApiModel("店铺订单列表-各状态数量")
@Data
public class GoblinStoreOrderStatusCountVo {
/** 全部(含其他 status,如取消单等) */
@ApiModelProperty("全部")
private long all;
/** 待支付 status=0 */
@ApiModelProperty("待支付[status=0]")
private long pendingPay;
/** 待发货 status=2 */
@ApiModelProperty("待发货[status=2]")
private long pendingShip;
/** 已发货 status=3 */
@ApiModelProperty("已发货[status=3]")
private long shipped;
/** 已完成 status=4 */
@ApiModelProperty("已完成[status=4]")
private long completed;
/** 已退款 status=6 */
@ApiModelProperty("已退款[status=6]")
private long refunded;
/**
* 从 Mongo 按 status 分组统计结果组装 VO。
* 新增 Tab 时在此补充 getOrDefault 即可。
*/
public static GoblinStoreOrderStatusCountVo of(long all, Map<Integer, Long> grouped) {
GoblinStoreOrderStatusCountVo vo = new GoblinStoreOrderStatusCountVo();
vo.all = all;
vo.pendingPay = grouped.getOrDefault(0, 0L);
vo.pendingShip = grouped.getOrDefault(2, 0L);
vo.shipped = grouped.getOrDefault(3, 0L);
vo.completed = grouped.getOrDefault(4, 0L);
vo.refunded = grouped.getOrDefault(6, 0L);
return vo;
}
}
package com.liquidnet.service.goblin.param;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
@Data
public class GoblinStoreOrderRefundParam {
@ApiModelProperty(value = "订单id")
@NotNull(message = "订单id不能为空")
private String orderId;
@ApiModelProperty(value = "退款orderSkuId列表[发货后可选;不传为整单退款;发货前强制整单退款]")
private List<String> orderSkuIds;
@ApiModelProperty(value = "退款原因")
private String reason;
@ApiModelProperty(value = "详细描述")
private String describes;
}
package com.liquidnet.service.goblin.service.manage;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.vo.GoblinBackOrderDetailsVo;
import com.liquidnet.service.goblin.dto.vo.GoblinBackOrderVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreBackOrderListPageVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreBackOrderListVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListVo;
import com.liquidnet.service.goblin.param.RefundCallbackParam;
import com.liquidnet.service.goblin.param.GoblinStoreOrderRefundParam;
import java.math.BigDecimal;
public interface IGoblinStoreBackOrderService {
ResponseDto<PageInfo<GoblinStoreBackOrderListVo>> orderBackList(Integer page,
ResponseDto<GoblinStoreBackOrderListPageVo> orderBackList(Integer page,
String orderBackCode,
Integer type,
String cst,
String cet,
String orderCode,
String spuName,
Integer status);
String status);
ResponseDto<GoblinBackOrderDetailsVo> orderDetails(String backOrderId);
......@@ -28,5 +26,7 @@ public interface IGoblinStoreBackOrderService {
ResponseDto<Boolean> refusedRefund(String backOrderId);
ResponseDto<Boolean> refundOrder(GoblinStoreOrderRefundParam param);
ResponseDto<Boolean> changeSkuRefund(String backOrderId, BigDecimal refundPrice, String orderSkuId);
}
package com.liquidnet.service.goblin.service.manage;
import com.github.pagehelper.PageInfo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.vo.GoblinOrderLogVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListPageVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListVo;
import com.liquidnet.service.goblin.param.RefundCallbackParam;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
public interface IGoblinStoreOrderService {
ResponseDto<PageInfo<GoblinStoreOrderListVo>> orderList(Integer page,
ResponseDto<GoblinStoreOrderListPageVo> orderList(Integer page,
String orderCode,
Integer type,
String cst,
......@@ -20,6 +17,14 @@ public interface IGoblinStoreOrderService {
String phone,
Integer status);
void exportOrderList(HttpServletResponse response,
String orderCode,
String cst,
String cet,
String expressContacts,
String phone,
Integer status);
ResponseDto<GoblinStoreOrderListVo> orderDetails(String orderId);
ResponseDto<Boolean> orderCancel(String orderId);
......@@ -32,8 +37,8 @@ public interface IGoblinStoreOrderService {
ResponseDto<Boolean> refundOrderSku(String orderId, String orderSkuId, BigDecimal price);
ResponseDto<Boolean> express(String orderId, String orderSkuIds,String mailNo,String uid,String orderCode);
ResponseDto<Boolean> express(String orderId, String orderSkuIds, String mailNo, String uid, String orderCode);
ResponseDto<Boolean> changeExpressMailNo(String orderId, String mailId,String mailNo);
ResponseDto<Boolean> changeExpressMailNo(String orderId, String mailId, String mailNo);
}
package com.liquidnet.service.goblin.controller.manage;
import com.github.pagehelper.PageInfo;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liquidnet.service.base.ResponseDto;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.vo.GoblinBackOrderDetailsVo;
import com.liquidnet.service.goblin.dto.vo.GoblinBackOrderVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreBackOrderListPageVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreBackOrderListVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListVo;
import com.liquidnet.service.goblin.param.RefundCallbackParam;
import com.liquidnet.service.goblin.param.GoblinStoreOrderRefundParam;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreBackOrderService;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
......@@ -41,17 +37,17 @@ public class GoblinStoreBackOrderController {
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "cet", value = "申请终止时间"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "orderCode", value = "订单编号"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "spuName", value = "商品名称"),
@ApiImplicitParam(type = "form", required = false, dataType = "Integer", name = "status", value = "订单状态[1-待处理|2-已退款|9-已拒绝|11-已取消|0-退款中|7-退货中]"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "status", value = "审核状态,多个用英文逗号分隔。0-商铺发起退款|1-退款申请|2-退款成功|3-退款拒绝|4-退货申请|5-退货拒绝|6-待填物流|7-待收货|8-退货完成|9-退货失败|10-退款失败|11-取消退款"),
})
@GetMapping(value = "list")
public ResponseDto<PageInfo<GoblinStoreBackOrderListVo>> orderBackList(@RequestParam(value = "page", required = true) @Valid Integer page,
public ResponseDto<GoblinStoreBackOrderListPageVo> orderBackList(@RequestParam(value = "page", required = true) @Valid Integer page,
@RequestParam(value = "orderBackCode", required = false) String orderBackCode,
@RequestParam(value = "type", required = false) Integer type,
@RequestParam(value = "cst", required = false) String cst,
@RequestParam(value = "cet", required = false) String cet,
@RequestParam(value = "orderCode", required = false) String orderCode,
@RequestParam(value = "spuName", required = false) String spuName,
@RequestParam(value = "status", required = false) Integer status) {
@RequestParam(value = "status", required = false) String status) {
return goblinStoreBackOrderService.orderBackList(page, orderBackCode, type, cst, cet, orderCode, spuName, status);
}
......@@ -83,6 +79,12 @@ public class GoblinStoreBackOrderController {
return goblinStoreBackOrderService.refusedRefund(backOrderId);
}
@ApiOperation(value = "发起退款")
@PostMapping(value = "refund")
public ResponseDto<Boolean> refundOrder(@RequestBody @Valid GoblinStoreOrderRefundParam param) {
return goblinStoreBackOrderService.refundOrder(param);
}
@ApiOperation(value = "修改金额")
@ApiImplicitParams({
@ApiImplicitParam(type = "form", required = true, dataType = "String", name = "backOrderId", value = "订单id"),
......
package com.liquidnet.service.goblin.controller.manage;
import com.github.pagehelper.PageInfo;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liquidnet.commons.lang.util.CurrentUtil;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.dto.vo.GoblinMailVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListPageVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderListVo;
import com.liquidnet.service.goblin.param.RefundCallbackParam;
import com.liquidnet.service.goblin.service.IGoblinOrderService;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreOrderService;
import io.swagger.annotations.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
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 javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.util.List;
@ApiSupport(order = 149101)
@Api(tags = "店铺-订单")
......@@ -40,10 +43,10 @@ public class GoblinStoreOrderController {
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "cet", value = "下单终止时间"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "expressContacts", value = "收货人姓名"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "phone", value = "手机号"),
@ApiImplicitParam(type = "form", required = false, dataType = "Integer", name = "status", value = "订单状态[0-代付款|2-待发货|3-已发货|4-已完成]"),
@ApiImplicitParam(type = "form", required = false, dataType = "Integer", name = "status", value = "订单状态[0-待支付|2-待发货|3-已发货|4-已完成|6-已退款]"),
})
@GetMapping(value = "list")
public ResponseDto<PageInfo<GoblinStoreOrderListVo>> orderList(@RequestParam(value = "page", required = true) @Valid Integer page,
public ResponseDto<GoblinStoreOrderListPageVo> orderList(@RequestParam(value = "page", required = true) @Valid Integer page,
@RequestParam(value = "orderCode", required = false) String orderCode,
@RequestParam(value = "type", required = false) Integer type,
@RequestParam(value = "cst", required = false) String cst,
......@@ -55,6 +58,26 @@ public class GoblinStoreOrderController {
return goblinStoreOrderService.orderList(page, orderCode, type, cst, cet, expressContacts, phone, status);
}
@ApiOperation(value = "订单列表导出")
@ApiImplicitParams({
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "orderCode", value = "订单编号"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "cst", value = "下单起始时间"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "cet", value = "下单终止时间"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "expressContacts", value = "收货人姓名"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "phone", value = "手机号"),
@ApiImplicitParam(type = "form", required = false, dataType = "Integer", name = "status", value = "订单状态[0-代付款|2-待发货|3-已发货|4-已完成]"),
})
@GetMapping(value = "list/export")
public void exportOrderList(HttpServletResponse response,
@RequestParam(value = "orderCode", required = false) String orderCode,
@RequestParam(value = "cst", required = false) String cst,
@RequestParam(value = "cet", required = false) String cet,
@RequestParam(value = "expressContacts", required = false) String expressContacts,
@RequestParam(value = "phone", required = false) String phone,
@RequestParam(value = "status", required = false) Integer status) {
goblinStoreOrderService.exportOrderList(response, orderCode, cst, cet, expressContacts, phone, status);
}
@ApiOperation(value = "订单详情")
@ApiImplicitParams({
@ApiImplicitParam(type = "form", required = true, dataType = "String", name = "orderId", value = "订单id"),
......@@ -112,7 +135,7 @@ public class GoblinStoreOrderController {
return goblinStoreOrderService.changeAddress(orderId, expressContacts, expressPhone, expressAddressDetail);
}
@ApiOperation(value = "退款(sku/整单)")
@ApiOperation(value = "退款(sku/整单)-废弃")
@ApiImplicitParams({
@ApiImplicitParam(type = "form", required = true, dataType = "String", name = "orderId", value = "订单id"),
@ApiImplicitParam(type = "form", required = false, dataType = "String", name = "orderSkuId", value = "orderSkuId[整单退不传]"),
......@@ -135,7 +158,7 @@ public class GoblinStoreOrderController {
public ResponseDto<Boolean> express(@RequestParam(value = "orderId", required = true) @Valid String orderId,
@RequestParam(value = "orderSkuIds", required = false) @Valid String orderSkuIds,
@RequestParam(value = "mailNo", required = true) @Valid String mailNo) {
return goblinStoreOrderService.express(orderId, orderSkuIds, mailNo, CurrentUtil.getCurrentUid(),"");
return goblinStoreOrderService.express(orderId, orderSkuIds, mailNo, CurrentUtil.getCurrentUid(), "");
}
@ApiOperation(value = "修改快递单号")
......
package com.liquidnet.service.goblin.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import com.google.common.base.Joiner;
import com.liquidnet.commons.lang.util.*;
......@@ -11,20 +9,21 @@ import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.candy.constant.CandyRedisConst;
import com.liquidnet.service.candy.dto.CandyUserCouponBasicDto;
import com.liquidnet.service.dragon.constant.DragonConstant;
import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.constant.GoblinStatusConst;
import com.liquidnet.service.goblin.dto.vo.*;
import com.liquidnet.service.goblin.entity.GoblinBackOrder;
import com.liquidnet.service.goblin.entity.GoblinBackOrderLog;
import com.liquidnet.service.goblin.entity.GoblinStoreOrder;
import com.liquidnet.service.goblin.param.GoblinAppOrderRefundParam;
import com.liquidnet.service.goblin.service.IGoblinOrderAppService;
import com.liquidnet.service.goblin.service.impl.helper.GoblinRefundHelper;
import com.liquidnet.service.goblin.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
......@@ -33,7 +32,8 @@ import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;
import static com.liquidnet.commons.lang.util.DateUtil.*;
import static com.liquidnet.commons.lang.util.DateUtil.DTF_YMD_HMS;
import static com.liquidnet.commons.lang.util.DateUtil.getNowTime;
@Service
......@@ -48,6 +48,8 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
QueueUtils queueUtils;
@Autowired
GoblinOrderUtils orderUtils;
@Autowired
GoblinRefundHelper refundHelper;
@Override
......@@ -241,60 +243,198 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
return ResponseDto.success(true);
}
/**
* 用户发起退款申请。
* <p>
* 发货前(status=2):整单自动退款,含运费,直接调支付网关。
* 发货后(status=3/4):可选整单或指定SKU,生成待审核退款单,不含运费。
*/
@Override
public ResponseDto<Boolean> applyRefund(GoblinAppOrderRefundParam param) {
// ---- 准备 SQL 容器 ----
LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_order.user.applyRefund"));
sqls.add(SqlMapping.get("goblin_order.user.applyRefundAuto"));
sqls.add(SqlMapping.get("goblin_order.store.orderStatus"));
sqls.add(SqlMapping.get("goblin_order.store.orderSkuStatus"));
sqls.add(SqlMapping.get("goblin_order.store.refundLog"));
sqls.add(SqlMapping.get("candy_user_coupon.update_apply_refund"));
LinkedList<Object[]> applyRefund = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> orderStatus = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> orderSkuStatus = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> refundLog = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> updateCandyUserCouponObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> applyRefundObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> orderStatusObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> orderSkuStatusObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> refundLogObjs = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> updateCouponObjs = CollectionUtil.linkedListObjectArr();
String uid = CurrentUtil.getCurrentUid();
LocalDateTime now = LocalDateTime.now();
String nowStr = DateUtil.getNowTime();
// ---- 1. 订单基础校验 ----
GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(param.getOrderId());
// if (!orderVo.getUserId().equals(uid)) {
// return ResponseDto.failure("无权操作");
// }
if (orderVo == null) {
return ResponseDto.failure("不可操作");
}
// ---- 2. 分布式锁防并发重复退款 ----
String lockKey = GoblinRedisConst.REFUND_ORDER_LOCK + param.getOrderId();
if (!redisUtils.redisUtil.lock(lockKey, 1, 30)) {
return ResponseDto.failure("退款处理中,请勿重复操作");
}
try {
// ---- 3. 退款前置校验 ----
// 3a. 是否有处理中的退款单
List<String> backOrderIds = redisUtils.getBackOrderByOrderId(param.getOrderId());
for (String backOrderId : backOrderIds) {
GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
if (!(backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_3.getValue()) || backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_5.getValue()) || backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_9.getValue()))) {
if (backOrderVo != null && !GoblinRefundHelper.isBackOrderFinished(backOrderVo.getStatus())) {
return ResponseDto.failure("申请失败");
}
}
if (!(orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue() ||
// orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_3.getValue() ||
orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue())) {
// 3b. 订单状态:仅允许待发货(2) / 待收货(3) / 已完成(4)
int orderStatus = orderVo.getStatus();
if (!(orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue() || orderStatus == GoblinStatusConst.Status.ORDER_STATUS_3.getValue() || orderStatus == GoblinStatusConst.Status.ORDER_STATUS_4.getValue())) {
return ResponseDto.failure("不可操作");
}
//判断7天
// 3c. 7天可退窗口
LocalDateTime canRefundTime = getCanRefundTime(orderVo);
if (canRefundTime == null) {
if (canRefundTime == null || LocalDateTime.now().isAfter(canRefundTime)) {
return ResponseDto.failure("申请失败");
}
// 3d. 用券订单仅支持整单退款
if (orderHasUsedCoupon(orderVo) && param.getOrderSkuId() != null) {
return ResponseDto.failure("用券仅支持整单退款");
}
// 3e. 发货前仅支持整单退款
if (orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue() && StringUtil.isNotBlank(param.getOrderSkuId())) {
return ResponseDto.failure("发货前仅支持整单退款");
}
// ---- 4. 构建退款单骨架(不含SKU明细和最终金额) ----
GoblinBackOrder backOrder = buildBackOrderSkeleton(orderVo, param, now);
// ---- 5. 构建退款单 VO ----
GoblinBackOrderVo vo = GoblinBackOrderVo.getNew();
BeanUtils.copyProperties(backOrder, vo);
vo.setCreatedAt(nowStr);
// ---- 6. 处理退款SKU列表 ----
List<GoblinBackOrderSkuVo> orderSkuVoList = ObjectUtil.goblinBackOrderSkuVoArrayList();
BigDecimal skuRefundPrice = BigDecimal.ZERO;
boolean isUnshipped = orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue();
boolean hasSpecificSku = StringUtil.isNotBlank(param.getOrderSkuId());
if (isUnshipped) {
// 发货前:仅支持整单退款,遍历所有SKU
orderStatusObjs.add(new Object[]{orderVo.getStatus(), orderVo.getZhengzaiStatus(), now,
orderVo.getOrderId(), now, now});
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
BigDecimal refundPrice = buildAndPersistRefundSku(orderSkuId, orderSkuVo, false,
nowStr, now, orderSkuVoList, orderSkuStatusObjs);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderLog(orderVo, orderSkuVo, now);
}
} else if (hasSpecificSku) {
// 发货后 + 指定SKU:仅退选中的单个商品
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(param.getOrderSkuId());
if (orderSkuVo == null || !orderVo.getOrderId().equals(orderSkuVo.getOrderId())) {
return ResponseDto.failure("不存在");
}
// 券类商品校验:已使用则拒绝,未使用则置为无效
String couponError = validateAndDisableCouponForRefund(orderSkuVo, orderVo.getUserId(), now, updateCouponObjs);
if (couponError != null) {
return ResponseDto.failure(couponError);
}
BigDecimal refundPrice = buildAndPersistRefundSku(param.getOrderSkuId(), orderSkuVo, true,
nowStr, now, orderSkuVoList, orderSkuStatusObjs);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderLog(orderVo, orderSkuVo, now);
} else {
if (LocalDateTime.now().isAfter(canRefundTime)) {
return ResponseDto.failure("申请失败");
// 发货后 + 整单:遍历所有SKU
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
String couponError = validateAndDisableCouponForRefund(orderSkuVo, orderVo.getUserId(), now, updateCouponObjs);
if (couponError != null) {
return ResponseDto.failure(couponError);
}
BigDecimal refundPrice = buildAndPersistRefundSku(orderSkuId, orderSkuVo, true,
nowStr, now, orderSkuVoList, orderSkuStatusObjs);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
backOrderLog(orderVo, orderSkuVo, now);
}
}
//判断券
if ((!(orderVo.getUcouponId() == null || orderVo.getUcouponId().equals("")) || !(orderVo.getStoreCouponId() == null || orderVo.getStoreCouponId().equals("")))
&& param.getOrderSkuId() != null) {
return ResponseDto.failure("用券仅支持整单退款");
// ---- 7. 最终金额与SKU汇总校验 ----
if (skuRefundPrice.compareTo(BigDecimal.ZERO) <= 0) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
fillSkuIdNumsIfBlank(backOrder, orderSkuVoList);
setFinalRefundPrice(backOrder, orderVo, skuRefundPrice);
syncBackOrderVoFields(vo, backOrder, orderSkuVoList, nowStr);
// ---- 8. 操作日志 ----
GoblinBackOrderLog backOrderLog = initBackLog(param.getOrderId(), uid, now);
backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_21.getValue());
backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_1.getValue());
backOrderLog.setMessage("用户发起发起:" + JsonUtils.toJson(param));
// ---- 9. 持久化(Redis + Mongo + MySQL) ----
applyRefundObjs.add(new Object[]{
backOrder.getBackOrderId(), backOrder.getBackCode(), backOrder.getOrderId(),
backOrder.getOrderCode(), backOrder.getStoreId(), backOrder.getUserId(),
backOrder.getSkuIdNums(), backOrder.getType(), backOrder.getReason(),
backOrder.getDescribes(), backOrder.getRealBackPrice(), backOrder.getBackPriceExpress(), backOrder.getStatus(),
backOrder.getLogisCompanyName(), backOrder.getMailNo(), backOrder.getPics(), backOrder.getCreatedAt(),
backOrder.getAuditAt(), backOrder.getErrorReason()
});
refundLogObjs.add(new Object[]{
backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
});
persistRefundOrder(orderVo, backOrder, vo,
sqls, applyRefundObjs, orderStatusObjs, orderSkuStatusObjs, refundLogObjs, updateCouponObjs);
// ---- 10. 发货前自动调支付网关退款 ----
if (isUnshipped) {
return executeAutoRefund(orderVo, backOrder, vo, now);
}
return ResponseDto.success();
} finally {
redisUtils.redisUtil.uLock(lockKey);
}
}
// ======================== 私有辅助方法 ========================
/**
* 判断订单是否使用了优惠券(用户券或店铺券)。
*/
private boolean orderHasUsedCoupon(GoblinStoreOrderVo orderVo) {
boolean hasUserCoupon = orderVo.getUcouponId() != null && !orderVo.getUcouponId().isEmpty();
boolean hasStoreCoupon = orderVo.getStoreCouponId() != null && !orderVo.getStoreCouponId().isEmpty();
return hasUserCoupon || hasStoreCoupon;
}
//退款订单生成
/**
* 构建退款单骨架,填充与SKU无关的基础字段。
* <p>
* 发货前(status=2):状态=自动退款(0),含运费,必须整单。
* 发货后(status=3/4):状态=待审核(1),不含运费,可选整单或指定SKU。
*/
private GoblinBackOrder buildBackOrderSkeleton(GoblinStoreOrderVo orderVo,
GoblinAppOrderRefundParam param,
LocalDateTime now) {
GoblinBackOrder backOrder = GoblinBackOrder.getNew();
backOrder.setBackOrderId(IDGenerator.nextTimeId2());
backOrder.setBackCode(IDGenerator.storeRefundCode(orderVo.getMasterOrderCode()));
......@@ -307,195 +447,251 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
backOrder.setType(GoblinStatusConst.Type.BACK_TYPE_1.getValue());
backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_1.getValue());
backOrder.setCreatedAt(now);
if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {//未发货
backOrder.setRealBackPrice(orderVo.getPriceActual());
backOrder.setBackPriceExpress(orderVo.getPriceExpress());
int orderStatus = orderVo.getStatus();
if (orderStatus == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {
// 未发货:仅支持整单退,含运费,自动退款(整单校验已在主方法完成)
backOrder.setBackPriceExpress(orderVo.getPriceExpress() == null ? BigDecimal.ZERO : orderVo.getPriceExpress());
backOrder.setReason(param.getReason());
backOrder.setPics(param.getPics());
backOrder.setDescribes(param.getDescribes());
backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue());
backOrder.setAuditAt(now);
backOrder.setSkuIdNums(Joiner.on(",").join(orderVo.getOrderSkuVoIds()));
} else if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue()) {//已完成
if (StringUtil.isNotBlank(param.getOrderSkuId())) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(param.getOrderSkuId());
if (orderVo.getPriceRefund().add(orderSkuVo.getSkuPriceActual()).add(orderVo.getPriceExpress()).compareTo(orderVo.getPriceActual()) >= 0) {
backOrder.setRealBackPrice(orderSkuVo.getSkuPriceActual().add(orderVo.getPriceExpress()));
backOrder.setBackPriceExpress(orderVo.getPriceExpress());
} else {
backOrder.setRealBackPrice(orderSkuVo.getSkuPriceActual());
// 已发货/已完成(status=3/4):不含运费,生成待审核退款单
backOrder.setBackPriceExpress(BigDecimal.ZERO);
}
} else {
backOrder.setRealBackPrice(orderVo.getPriceActual());
backOrder.setBackPriceExpress(orderVo.getPriceExpress());
}
backOrder.setReason(param.getReason());
backOrder.setPics(param.getPics());
backOrder.setDescribes(param.getDescribes());
backOrder.setType(param.getRefundType());
backOrder.setType(param.getRefundType() == null ? GoblinStatusConst.Type.BACK_TYPE_1.getValue() : param.getRefundType());
backOrder.setSkuIdNums(param.getOrderSkuId());
} else {
return ResponseDto.failure("不可申请");
}
GoblinBackOrderVo vo = GoblinBackOrderVo.getNew();
BeanUtils.copyProperties(backOrder, vo);
vo.setCreatedAt(nowStr);
List<GoblinBackOrderSkuVo> orderSkuVoList = ObjectUtil.goblinBackOrderSkuVoArrayList();
if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {
//订单状态修改
// orderVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
orderStatus.add(new Object[]{
orderVo.getStatus(), orderVo.getZhengzaiStatus(),now,
orderVo.getOrderId(), now, now
});
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
//订单款式状态修改
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew();
backOrderSkuVo.setOrderSkuId(orderSkuId);
backOrderSkuVo.setSpuId(orderSkuVo.getSpuId());
backOrderSkuVo.setSpuName(orderSkuVo.getSpuName());
backOrderSkuVo.setSkuId(orderSkuVo.getSkuId());
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage());
backOrderSkuVo.setSkuName(orderSkuVo.getSkuName());
backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs());
backOrderSkuVo.setRefundPrice(orderSkuVo.getSkuPriceActual());
backOrderSkuVo.setCreatedAt(nowStr);
orderSkuVoList.add(backOrderSkuVo);
backOrderLog(orderVo, orderSkuVo, now);
//redis
redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
//mongo
mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
orderSkuStatus.add(new Object[]{
orderSkuVo.getStatus(), now,
orderSkuVo.getOrderSkuId(), now, now
});
return backOrder;
}
/**
* 券类商品退款前的校验与状态处理:
* <ul>
* <li>非券类商品(skuType != 2):直接通过</li>
* <li>券状态=5(已使用):拒绝退款</li>
* <li>券状态其他:置为2(无效),防止用户在审核期间消费该券</li>
* </ul>
*
* @return 错误提示,null 表示校验通过
*/
private String validateAndDisableCouponForRefund(GoblinOrderSkuVo orderSkuVo,
String userId,
LocalDateTime now,
LinkedList<Object[]> updateCouponObjs) {
if (!Objects.equals(orderSkuVo.getSkuType(), 2)) {
return null; // 非券类商品,无需校验
}
} else if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue()) {
if (param.getOrderSkuId() != null) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(param.getOrderSkuId());
if (Objects.equals(orderSkuVo.getSkuType(), 2)) {// 券类商品-校验发放的券是否已使用
// 券类商品默认一个商品对应一个券,下单只可购买一张
String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(orderVo.getUserId());
String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(userId);
List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisUtils.get(ucKey);
if (vos == null || vos.isEmpty()) {
log.warn("券类商品订单申请退款,未找到用户券列表[orderSkuId={},uid={}]",
orderSkuVo.getOrderSkuId(), userId);
return null;
}
int idx = IntStream.range(0, vos.size())
.filter(i -> vos.get(i).getUcouponId().equals(orderSkuVo.getOrderSkuId())).findFirst().orElse(-1);
.filter(i -> vos.get(i).getUcouponId().equals(orderSkuVo.getOrderSkuId()))
.findFirst().orElse(-1);
if (-1 != idx) {
CandyUserCouponBasicDto basicDto = vos.get(idx);
if (5 == basicDto.getState()) {// 券状态为'5-已使用',则不可退款
return ResponseDto.failure("券已使用,不可申请");
if (5 == basicDto.getState()) {
return "券已使用,不可申请"; // 券已使用,不可退款
}
basicDto.setState(2);// 置为'2-无效',防止用户在后台审核过程中使用券,造成券已使用,订单也退款
// 置为无效,防止用户在后台审核过程中使用券
basicDto.setState(2);
vos.set(idx, basicDto);
redisUtils.redisUtil.set(ucKey, vos);
updateCandyUserCouponObjs.add(new Object[]{basicDto.getState(), orderVo.getUserId(), now, basicDto.getUcouponId()});
updateCouponObjs.add(new Object[]{basicDto.getState(), userId, now, basicDto.getUcouponId()});
} else {
log.warn("券类商品订单申请退款,未找到对应券[orderSkuId={},uid={}]", orderSkuVo.getOrderSkuId(), orderVo.getUserId());
}
}
//订单款式状态修改
log.warn("券类商品订单申请退款,未找到对应券[orderSkuId={},uid={}]",
orderSkuVo.getOrderSkuId(), userId);
}
return null;
}
/**
* 为单个SKU:计算可退金额、构建 BackOrderSkuVo、更新SKU状态到Redis/Mongo、生成SQL参数。
*
* @param orderSkuId SKU子订单ID
* @param orderSkuVo 对应的SKU VO(调用方已从Redis获取)
* @param setSkuType 是否设置skuType(发货后需要,发货前不需要)
* @return 该SKU的可退金额(remainRefundPrice 结果)
*/
private BigDecimal buildAndPersistRefundSku(String orderSkuId,
GoblinOrderSkuVo orderSkuVo,
boolean setSkuType,
String nowStr,
LocalDateTime now,
List<GoblinBackOrderSkuVo> orderSkuVoList,
LinkedList<Object[]> orderSkuStatusObjs) {
// 计算可退金额 = skuPriceActual - priceRefund - 申请中的退款额
BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(orderSkuId);
BigDecimal refundPrice = GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice);
// 更新SKU状态为"退款中"
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
// 构建 BackOrderSkuVo
GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew();
backOrderSkuVo.setOrderSkuId(param.getOrderSkuId());
backOrderSkuVo.setOrderSkuId(orderSkuId);
backOrderSkuVo.setSpuId(orderSkuVo.getSpuId());
backOrderSkuVo.setSpuName(orderSkuVo.getSpuName());
backOrderSkuVo.setSkuId(orderSkuVo.getSkuId());
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage());
backOrderSkuVo.setSkuName(orderSkuVo.getSkuName());
backOrderSkuVo.setRefundPrice(orderSkuVo.getSkuPriceActual());
backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs());
backOrderSkuVo.setRefundPrice(refundPrice);
backOrderSkuVo.setCreatedAt(nowStr);
if (setSkuType) {
backOrderSkuVo.setSkuType(orderSkuVo.getSkuType());
}
orderSkuVoList.add(backOrderSkuVo);
backOrderLog(orderVo, orderSkuVo, now);
//redis
// 持久化SKU状态:Redis + Mongo
redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
//mongo
mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
orderSkuStatus.add(new Object[]{
// 生成MySQL更新参数
orderSkuStatusObjs.add(new Object[]{
orderSkuVo.getStatus(), now,
orderSkuVo.getOrderSkuId(), now, now
});
} else {
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
//订单款式状态修改
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
if (Objects.equals(orderSkuVo.getSkuType(), 2)) {// 券类商品-校验发放的券是否已使用
// 券类商品默认一个商品对应一个券,下单只可购买一张
String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(orderVo.getUserId());
List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisUtils.get(ucKey);
int idx = IntStream.range(0, vos.size()).filter(i -> vos.get(i).getUcouponId().equals(orderSkuVo.getOrderSkuId())).findFirst().orElse(-1);
if (-1 != idx) {
CandyUserCouponBasicDto basicDto = vos.get(idx);
if (5 == basicDto.getState()) {// 券状态为'5-已使用',则不可退款
return ResponseDto.failure("券已使用,不可申请");
}
basicDto.setState(2);// 置为'2-无效',防止用户在后台审核过程中使用券,造成券已使用,订单也退款
vos.set(idx, basicDto);
redisUtils.redisUtil.set(ucKey, vos);
updateCandyUserCouponObjs.add(new Object[]{basicDto.getState(), orderVo.getUserId(), now, basicDto.getUcouponId()});
} else {
log.warn("券类商品订单申请退款,未找到对应券[orderSkuId={},uid={}]", orderSkuVo.getOrderSkuId(), orderVo.getUserId());
return refundPrice;
}
/**
* 如果 skuIdNums 为空(发货后整单退场景),从 orderSkuVoList 中收集填充。
*/
private void fillSkuIdNumsIfBlank(GoblinBackOrder backOrder, List<GoblinBackOrderSkuVo> orderSkuVoList) {
if (StringUtil.isNotBlank(backOrder.getSkuIdNums())) {
return;
}
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew();
backOrderSkuVo.setOrderSkuId(orderSkuId);
backOrderSkuVo.setSpuId(orderSkuVo.getSpuId());
backOrderSkuVo.setSpuName(orderSkuVo.getSpuName());
backOrderSkuVo.setSkuId(orderSkuVo.getSkuId());
backOrderSkuVo.setSkuName(orderSkuVo.getSkuName());
backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs());
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage());
backOrderSkuVo.setRefundPrice(orderSkuVo.getSkuPriceActual());
backOrderSkuVo.setCreatedAt(nowStr);
backOrderSkuVo.setSkuType(orderSkuVo.getSkuType());
orderSkuVoList.add(backOrderSkuVo);
backOrderLog(orderVo, orderSkuVo, now);
//redis
redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
//mongo
mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
orderSkuStatus.add(new Object[]{
orderSkuVo.getStatus(), now,
orderSkuVo.getOrderSkuId(), now, now
});
List<String> refundOrderSkuIds = new LinkedList<>();
for (GoblinBackOrderSkuVo vo : orderSkuVoList) {
refundOrderSkuIds.add(vo.getOrderSkuId());
}
backOrder.setSkuIdNums(Joiner.on(",").join(refundOrderSkuIds));
}
/**
* 计算并设置最终退款金额。
* 发货前:SKU总额 + 运费;发货后:仅SKU总额。
*/
private void setFinalRefundPrice(GoblinBackOrder backOrder, GoblinStoreOrderVo orderVo, BigDecimal skuRefundPrice) {
if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_2.getValue()) {
backOrder.setRealBackPrice(GoblinRefundHelper.beforeShipRefundPrice(skuRefundPrice, backOrder.getBackPriceExpress()));
} else {
return ResponseDto.failure("不可申请");
backOrder.setRealBackPrice(skuRefundPrice);
backOrder.setBackPriceExpress(BigDecimal.ZERO);
}
}
/**
* 将 backOrder 中的最终字段同步到 vo 中。
*/
private void syncBackOrderVoFields(GoblinBackOrderVo vo, GoblinBackOrder backOrder,
List<GoblinBackOrderSkuVo> orderSkuVoList, String nowStr) {
vo.setRealBackPrice(backOrder.getRealBackPrice());
vo.setBackPriceExpress(backOrder.getBackPriceExpress());
vo.setStatus(backOrder.getStatus());
vo.setAuditAt(backOrder.getAuditAt() == null ? null : nowStr);
vo.setBackOrderSkuVos(orderSkuVoList);
//添加日志
GoblinBackOrderLog backOrderLog = initBackLog(param.getOrderId(), uid, now);
backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_21.getValue());
backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_1.getValue());
backOrderLog.setMessage("用户发起发起:" + JsonUtils.toJson(param));
//redis
vo.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_1.getValue());
}
/**
* 持久化退款单到 Redis + Mongo + MySQL(MQ异步)。
*/
private void persistRefundOrder(GoblinStoreOrderVo orderVo,
GoblinBackOrder backOrder,
GoblinBackOrderVo vo,
LinkedList<String> sqls,
LinkedList<Object[]> applyRefundObjs,
LinkedList<Object[]> orderStatusObjs,
LinkedList<Object[]> orderSkuStatusObjs,
LinkedList<Object[]> refundLogObjs,
LinkedList<Object[]> updateCouponObjs) {
// Redis
redisUtils.setBackOrderVo(backOrder.getBackOrderId(), vo);
redisUtils.setGoblinOrder(orderVo.getOrderId(), orderVo);
redisUtils.addBackOrderByOrderId(orderVo.getOrderId(), backOrder.getBackOrderId());
//mongo
// Mongo
mongoUtils.insertGoblinBackOrderVo(vo);
mongoUtils.updateGoblinStoreOrderVo(orderVo.getOrderId(), orderVo);
//mysql
applyRefund.add(new Object[]{
backOrder.getBackOrderId(), backOrder.getBackCode(), backOrder.getOrderId(),
backOrder.getOrderCode(), backOrder.getStoreId(), backOrder.getUserId(),
backOrder.getSkuIdNums(), backOrder.getType(), backOrder.getReason(),
backOrder.getDescribes(), backOrder.getRealBackPrice(), backOrder.getBackPriceExpress(), backOrder.getStatus(),
backOrder.getLogisCompanyName(), backOrder.getMailNo(), backOrder.getPics(), backOrder.getCreatedAt()
});
//添加日志
refundLog.add(new Object[]{
backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
});
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_USER_ORDER_OPERA.getKey(), SqlMapping.gets(sqls, applyRefund, orderStatus, orderSkuStatus, refundLog, updateCandyUserCouponObjs));
// MySQL (MQ异步)
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_USER_ORDER_OPERA.getKey(),
SqlMapping.gets(sqls, applyRefundObjs, orderStatusObjs, orderSkuStatusObjs, refundLogObjs, updateCouponObjs));
}
/**
* 发货前自动退款:调用支付网关,失败则回滚状态。
*/
private ResponseDto<Boolean> executeAutoRefund(GoblinStoreOrderVo orderVo,
GoblinBackOrder backOrder,
GoblinBackOrderVo vo,
LocalDateTime now) {
String returnString;
try {
returnString = refundHelper.initRefund(orderVo, backOrder.getRealBackPrice(), backOrder.getBackCode());
} catch (Exception e) {
log.error("REFUND EXCEPTION, orderId={}", orderVo.getOrderId(), e);
rollbackAutoRefund(orderVo, backOrder, vo, now, "退款异常:" + e.getMessage());
return ResponseDto.failure("退款失败:退款异常:" + e.getMessage());
}
if (!refundHelper.isRefundSuccess(returnString)) {
// 退款失败,回滚退款单及SKU状态
String message = refundHelper.getRefundMessage(returnString);
log.error("REFUND DATA = " + returnString);
rollbackAutoRefund(orderVo, backOrder, vo, now, "失败原因:" + message);
return ResponseDto.failure("退款失败:失败原因:" + message);
}
// 支付宝micropay需主动查询结果
if (DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY.equals(orderVo.getPaymentType())) {
refundHelper.alipayCallBack(orderVo, backOrder.getBackCode());
}
return ResponseDto.success();
}
/**
* 发货前自动退款失败/异常时,回滚退款单和SKU状态。
*/
private void rollbackAutoRefund(GoblinStoreOrderVo orderVo,
GoblinBackOrder backOrder,
GoblinBackOrderVo vo,
LocalDateTime now,
String errorReason) {
backOrder.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue());
backOrder.setErrorReason(errorReason);
vo.setStatus(backOrder.getStatus());
vo.setErrorReason(backOrder.getErrorReason());
redisUtils.setBackOrderVo(backOrder.getBackOrderId(), vo);
mongoUtils.updateGoblinBackOrderVo(backOrder.getBackOrderId(), vo);
// 回滚SKU状态到订单原始状态
int rollbackStatus = orderVo.getStatus();
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
if (orderSkuVo != null) {
orderSkuVo.setStatus(rollbackStatus);
redisUtils.setGoblinOrderSku(orderSkuId, orderSkuVo);
mongoUtils.updateGoblinOrderSkuVo(orderSkuId, orderSkuVo);
}
}
queueUtils.sendMsgByRedis(
MQConst.GoblinQueue.GOBLIN_USER_ORDER_OPERA.getKey(),
SqlMapping.get("goblin_order.store.applyRefund",
vo.getStatus(), vo.getReason(), vo.getAuditAt(), now,
backOrder.getBackOrderId(), now, now
)
);
}
@Override
public ResponseDto<Boolean> againRefund(GoblinAppOrderRefundParam param) {
LinkedList<String> sqls = CollectionUtil.linkedListString();
......@@ -789,10 +985,11 @@ public class GoblinOrderAppServiceImpl implements IGoblinOrderAppService {
} catch (Exception e) {
e.printStackTrace();
}
} else if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue()) {
} else if (orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_3.getValue()
|| orderVo.getStatus() == GoblinStatusConst.Status.ORDER_STATUS_4.getValue()) {
if (StringUtil.isBlank(orderVo.getDeliveryTime())) {
canRefundTimeDateTime = LocalDateTime.parse(orderVo.getPayTime(), DTF_YMD_HMS).plusDays(7);
}else {
} else {
canRefundTimeDateTime = LocalDateTime.parse(orderVo.getDeliveryTime(), DTF_YMD_HMS).plusDays(7);
}
}
......
package com.liquidnet.service.goblin.service.impl.helper;
import com.liquidnet.commons.lang.util.CollectionUtil;
import com.liquidnet.commons.lang.util.HttpUtil;
import com.liquidnet.commons.lang.util.JsonUtils;
import com.liquidnet.service.goblin.dto.vo.GoblinBackOrderSkuVo;
import com.liquidnet.service.goblin.dto.vo.GoblinOrderSkuVo;
import com.liquidnet.service.goblin.dto.vo.GoblinStoreOrderVo;
import com.liquidnet.service.goblin.util.GoblinRedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import java.math.BigDecimal;
import java.util.HashMap;
@Component
@Slf4j
public class GoblinRefundHelper {
@Autowired
private GoblinRedisUtils redisUtils;
@Value("${liquidnet.service.order.url-pay.goblinRefundUrl}")
private String synUrl;
@Value("${liquidnet.service.dragon.urls.refundApply}")
private String refundApply;
@Value("${liquidnet.service.order.url-pay.goblinRefundUrl}")
private String goblinRefundUrl;
@Value("${liquidnet.service.dragon.urls.refundResult}")
private String refundApplyCallBack;
public static BigDecimal remainRefundPrice(GoblinOrderSkuVo orderSkuVo, BigDecimal applyingRefundPrice) {
BigDecimal priceActual = nullToZero(orderSkuVo.getSkuPriceActual());
BigDecimal priceRefund = nullToZero(orderSkuVo.getPriceRefund());
BigDecimal applying = nullToZero(applyingRefundPrice);
BigDecimal remain = priceActual.subtract(priceRefund).subtract(applying);
return remain.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : remain;
}
public static BigDecimal beforeShipRefundPrice(BigDecimal skuRefundPrice, BigDecimal priceExpress) {
return nullToZero(skuRefundPrice).add(nullToZero(priceExpress));
}
public static GoblinBackOrderSkuVo buildBackOrderSkuVo(GoblinOrderSkuVo orderSkuVo, BigDecimal refundPrice, String nowStr) {
GoblinBackOrderSkuVo backOrderSkuVo = GoblinBackOrderSkuVo.getNew();
backOrderSkuVo.setOrderSkuId(orderSkuVo.getOrderSkuId());
backOrderSkuVo.setSpuId(orderSkuVo.getSpuId());
backOrderSkuVo.setSpuName(orderSkuVo.getSpuName());
backOrderSkuVo.setSkuId(orderSkuVo.getSkuId());
backOrderSkuVo.setSkuName(orderSkuVo.getSkuName());
backOrderSkuVo.setRefundPrice(refundPrice);
backOrderSkuVo.setSkuSpecs(orderSkuVo.getSkuSpecs());
backOrderSkuVo.setCreatedAt(nowStr);
backOrderSkuVo.setSkuPic(orderSkuVo.getSkuImage());
backOrderSkuVo.setSkuType(orderSkuVo.getSkuType());
return backOrderSkuVo;
}
public static boolean isBackOrderFinished(Integer status) {
return status != null && (status == 2 || status == 3 || status == 5 || status == 8 || status == 9 || status == 10 || status == 11);
}
public static boolean isUnshippedSku(GoblinOrderSkuVo orderSkuVo) {
return orderSkuVo != null && orderSkuVo.getStatus() == 2;
}
public static boolean isShippedSku(GoblinOrderSkuVo orderSkuVo) {
if (orderSkuVo == null) {
return false;
}
return orderSkuVo.getStatus() == 3 || orderSkuVo.getStatus() == 4;
}
public String initRefund(GoblinStoreOrderVo orderVo, BigDecimal price, String refundCode) {
MultiValueMap<String, String> params = CollectionUtil.linkedMultiValueMapStringString();
params.add("code", orderVo.getPayCode());
params.add("notifyUrl", synUrl);
params.add("orderCode", orderVo.getMasterOrderCode());
params.add("orderRefundCode", refundCode);
params.add("paymentId", orderVo.getPaymentId());
params.add("paymentType", orderVo.getPaymentType());
params.add("price", String.valueOf(price));
params.add("priceTotal", String.valueOf(getMasterOrderActualPrice(orderVo)));
params.add("reason", "按需退款");
MultiValueMap<String, String> headers = CollectionUtil.linkedMultiValueMapStringString();
headers.add("Accept", "application/json;charset=UTF-8");
String returnString = HttpUtil.post(refundApply, params, headers);
log.debug("REFUND DATA = " + returnString);
return returnString;
}
public String alipayCallBack(GoblinStoreOrderVo orderVo, String refundCode) {
MultiValueMap<String, String> params = CollectionUtil.linkedMultiValueMapStringString();
params.add("callBackUrl", goblinRefundUrl);
params.add("orderCode", orderVo.getMasterOrderCode());
params.add("orderRefundCode", refundCode);
params.add("paymentId", orderVo.getPaymentId());
MultiValueMap<String, String> headers = CollectionUtil.linkedMultiValueMapStringString();
headers.add("Accept", "application/json;charset=UTF-8");
log.debug("REFUND CALLBACK params = " + params);
String returnString = HttpUtil.post(refundApplyCallBack, params, headers);
log.debug("REFUND CALLBACK DATA = " + returnString);
return returnString;
}
public boolean isRefundSuccess(String returnString) {
HashMap hashMapResult = JsonUtils.fromJson(returnString, HashMap.class);
Boolean success = (Boolean) hashMapResult.get("success");
return Boolean.TRUE.equals(success);
}
public String getRefundMessage(String returnString) {
HashMap hashMapResult = JsonUtils.fromJson(returnString, HashMap.class);
String message = (String) hashMapResult.get("message");
return message == null ? "" : message;
}
private BigDecimal getMasterOrderActualPrice(GoblinStoreOrderVo orderVo) {
BigDecimal totalPrice = BigDecimal.ZERO;
String[] orderIds = redisUtils.getMasterCode(orderVo.getMasterOrderCode());
if (orderIds == null || orderIds.length == 0) {
return nullToZero(orderVo.getPriceActual());
}
for (String orderId : orderIds) {
GoblinStoreOrderVo vo = redisUtils.getGoblinOrder(orderId);
if (vo != null) {
totalPrice = totalPrice.add(nullToZero(vo.getPriceActual()));
}
}
return totalPrice.compareTo(BigDecimal.ZERO) > 0 ? totalPrice : nullToZero(orderVo.getPriceActual());
}
private static BigDecimal nullToZero(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
}
......@@ -8,9 +8,13 @@ import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.candy.constant.CandyRedisConst;
import com.liquidnet.service.candy.dto.CandyUserCouponBasicDto;
import com.liquidnet.service.dragon.constant.DragonConstant;
import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.constant.GoblinStatusConst;
import com.liquidnet.service.goblin.dto.vo.*;
import com.liquidnet.service.goblin.entity.GoblinBackOrder;
import com.liquidnet.service.goblin.entity.GoblinBackOrderLog;
import com.liquidnet.service.goblin.param.GoblinStoreOrderRefundParam;
import com.liquidnet.service.goblin.service.impl.helper.GoblinRefundHelper;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreBackOrderService;
import com.liquidnet.service.goblin.util.GoblinMongoUtils;
import com.liquidnet.service.goblin.util.GoblinRedisUtils;
......@@ -19,14 +23,11 @@ import com.liquidnet.service.goblin.util.QueueUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.stream.IntStream;
@Service
......@@ -39,41 +40,32 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
GoblinMongoUtils mongoUtils;
@Autowired
QueueUtils queueUtils;
@Value("${liquidnet.service.order.url-pay.goblinRefundUrl}")
private String synUrl;
@Value("${liquidnet.service.dragon.urls.refundApply}")
private String refundApply;
@Value("${liquidnet.service.order.url-pay.goblinRefundUrl}")
private String goblinRefundUrl;
@Value("${liquidnet.service.dragon.urls.refundResult}")
private String refundApplyCallBack;
@Autowired
GoblinRefundHelper refundHelper;
@Override
public ResponseDto<PageInfo<GoblinStoreBackOrderListVo>> orderBackList(Integer page, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName, Integer status) {
public ResponseDto<GoblinStoreBackOrderListPageVo> orderBackList(Integer page, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName, String status) {
List<GoblinStoreBackOrderListVo> listVos = ObjectUtil.goblinStoreBackOrderListVoArrayList();
String uid = CurrentUtil.getCurrentUid();
GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
if (storeInfoVo == null) {
return ResponseDto.failure("无法查看");
}
HashMap<String, Object> map = mongoUtils.storeBackOrderList(storeInfoVo.getStoreId(), page, orderBackCode, type, cst, cet, orderCode, spuName, status);
String storeId = storeInfoVo.getStoreId();
HashMap<String, Object> map = mongoUtils.storeBackOrderList(storeId, page, orderBackCode, type, cst, cet, orderCode, spuName, status);
long total = (long) map.get("total");
List<GoblinBackOrderVo> voList = (List<GoblinBackOrderVo>) map.get("data");
for (GoblinBackOrderVo item : voList) {
GoblinStoreBackOrderListVo vo = GoblinStoreBackOrderListVo.getNew();
vo.setCreatedAt(item.getCreatedAt());
vo.setBackOrderId(item.getBackOrderId());
vo.setBackCode(item.getBackCode());
vo.setBackOrderSkuVos(item.getBackOrderSkuVos());
vo.setOrderCode(item.getOrderCode());
vo.setRealBackPrice(item.getRealBackPrice());
vo.setStatus(item.getStatus());
vo.setType(item.getType());
listVos.add(vo);
listVos.add(buildStoreBackOrderListVo(item));
}
PageInfo<GoblinStoreBackOrderListVo> pageInfo = new PageInfo(listVos);
pageInfo.setTotal(total);
return ResponseDto.success(pageInfo);
GoblinStoreBackOrderListPageVo result = new GoblinStoreBackOrderListPageVo();
result.setPageInfo(pageInfo);
// 审核 Tab 数量:与列表共用筛选条件,但不带 status 参数
result.setStatusCount(mongoUtils.storeBackOrderStatusCount(storeId, orderBackCode, type, cst, cet, orderCode, spuName));
return ResponseDto.success(result);
}
@Override
......@@ -163,9 +155,19 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
return ResponseDto.failure("无法查看");
}
GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
if(backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue())){
if (backOrderVo == null) {
return ResponseDto.failure("不存在");
}
GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(backOrderVo.getOrderId());
if (orderVo == null || !storeInfoVo.getStoreId().equals(orderVo.getStoreId())) {
return ResponseDto.failure("无法查看");
}
if (backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue())) {
return ResponseDto.failure("已经发起");
}
if (!backOrderVo.getStatus().equals(GoblinStatusConst.Status.ORDER_BACK_STATUS_1.getValue())) {
return ResponseDto.failure("不可操作");
}
backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue());
backOrderVo.setAuditAt(nowStr);
//添加日志
......@@ -173,22 +175,8 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
backOrderLog.setStatus(GoblinStatusConst.Status.ORDER_LOG_STATUS_22.getValue());
backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
backOrderLog.setMessage("商户退款-同意退款:backOrderId=[" + backOrderId + "]");
//调用退款
GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(backOrderVo.getOrderId());
String returnString = initRefund(orderVo, backOrderVo.getRealBackPrice(), backOrderVo.getBackCode());
HashMap hashMapResult = JsonUtils.fromJson(returnString, HashMap.class);
Boolean success = (Boolean) hashMapResult.get("success");
String message = (String) hashMapResult.get("message");
if (!success) {
backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue());
backOrderVo.setErrorReason("失败原因:" + message);
log.error("REFUND DATA = " + returnString);
}
//redis
redisUtils.setBackOrderVo(backOrderId, backOrderVo);
//mongo
mongoUtils.updateGoblinBackOrderVo(backOrderId, backOrderVo);
//mysql
queueUtils.sendMsgByRedis(
MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
SqlMapping.get("goblin_order.store.applyRefund",
......@@ -196,6 +184,15 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
backOrderId, now, now
)
);
String returnString = refundHelper.initRefund(orderVo, backOrderVo.getRealBackPrice(), backOrderVo.getBackCode());
Boolean success = refundHelper.isRefundSuccess(returnString);
String message = refundHelper.getRefundMessage(returnString);
if (!success) {
backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue());
backOrderVo.setErrorReason("失败原因:" + message);
log.error("REFUND DATA = " + returnString);
updateStoreRefundOrderAfterPayment(backOrderVo, now);
}
//添加日志
queueUtils.sendMsgByRedis(
MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
......@@ -205,8 +202,8 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
)
);
if (success) {
if (orderVo.getPaymentType().equals(DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY)) {
alipayCallBack(orderVo, backOrderVo.getBackCode());
if (DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY.equals(orderVo.getPaymentType())) {
refundHelper.alipayCallBack(orderVo, backOrderVo.getBackCode());
}
return ResponseDto.success();
}
......@@ -226,7 +223,7 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
{// 券类商品-拒绝退款后,恢复已发放券状态
List<GoblinBackOrderSkuVo> backOrderSkuVos = backOrderVo.getBackOrderSkuVos();
for (GoblinBackOrderSkuVo backOrderSkuVo : backOrderSkuVos) {
if (2 == backOrderSkuVo.getSkuType()) {
if (null != backOrderSkuVo.getSkuType() && 2 == backOrderSkuVo.getSkuType()) {
// 券类商品默认一个商品对应一个券,下单只可购买一张
String ucKey = CandyRedisConst.BASIC_USER_COUPON.concat(backOrderVo.getUserId());
List<CandyUserCouponBasicDto> vos = (List<CandyUserCouponBasicDto>) redisUtils.get(ucKey);
......@@ -280,6 +277,60 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
return ResponseDto.success();
}
/**
* 管理端新退款入口。
* 发货前(子单全为待发货):整单 + 运费,自动退款;
* 发货后(存在待收货/已完成子单):可选商品,不含运费,生成待审核退款单。
*/
@Override
public ResponseDto<Boolean> refundOrder(GoblinStoreOrderRefundParam param) {
String uid = CurrentUtil.getCurrentUid();
LocalDateTime now = LocalDateTime.now();
String nowStr = DateUtil.getNowTime();
GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
if (storeInfoVo == null) {
return ResponseDto.failure("无法查看");
}
GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(param.getOrderId());
if (orderVo == null || !storeInfoVo.getStoreId().equals(orderVo.getStoreId())) {
return ResponseDto.failure("无法查看");
}
if (!orderVo.getMixId().isEmpty()) {
return ResponseDto.failure("组合购商品暂不支持退款");
}
// 分布式锁防并发重复退款
String lockKey = GoblinRedisConst.REFUND_ORDER_LOCK + param.getOrderId();
if (!redisUtils.redisUtil.lock(lockKey, 1, 30)) {
return ResponseDto.failure("退款处理中,请勿重复操作");
}
try {
if (hasProcessingBackOrder(orderVo.getOrderId())) {
return ResponseDto.failure("已有退款处理中");
}
// 管理端发起退款:根据子单状态决定「发货前整单自动退」还是「发货后部分退待审核」
List<GoblinOrderSkuVo> orderSkuVos = getOrderSkuVos(orderVo);
// 发货前:所有子单均为 status=2(待发货)→ 强制整单退、含运费、自动调支付
boolean beforeShip = orderSkuVos.stream().allMatch(GoblinRefundHelper::isUnshippedSku);
// 是否允许发起:全部待发货,或至少有一个子单已发货/已完成(status=3/4)
// 注意:若存在已退完子单(status=6)与待发货子单混合(如旧接口部分退后),此处会判为不可退,需单独兼容
boolean canRefund = beforeShip || orderSkuVos.stream().anyMatch(GoblinRefundHelper::isShippedSku);
if (!canRefund) {
return ResponseDto.failure("不可退款");
}
// 发货前忽略 orderSkuIds,整单退;发货后按入参选择子单(不传则默认整单剩余可退商品)
List<GoblinOrderSkuVo> refundSkuVos = beforeShip ? orderSkuVos : getSelectedOrderSkuVos(orderVo, orderSkuVos, param.getOrderSkuIds());
if (refundSkuVos.isEmpty()) {
return ResponseDto.failure("请选择退款商品");
}
return createStoreRefundOrder(orderVo, refundSkuVos, beforeShip, param, uid, now, nowStr);
} finally {
redisUtils.redisUtil.uLock(lockKey);
}
}
@Override
public ResponseDto<Boolean> changeSkuRefund(String backOrderId, BigDecimal refundPrice, String orderSkuId) {
String uid = CurrentUtil.getCurrentUid();
......@@ -326,6 +377,227 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
return ResponseDto.success();
}
private boolean hasProcessingBackOrder(String orderId) {
List<String> backOrderIds = redisUtils.getBackOrderByOrderId(orderId);
for (String backOrderId : backOrderIds) {
GoblinBackOrderVo backOrderVo = redisUtils.getBackOrderVo(backOrderId);
if (backOrderVo != null && !GoblinRefundHelper.isBackOrderFinished(backOrderVo.getStatus())) {
return true;
}
}
return false;
}
private List<GoblinOrderSkuVo> getOrderSkuVos(GoblinStoreOrderVo orderVo) {
List<GoblinOrderSkuVo> orderSkuVos = new ArrayList<>();
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
if (orderSkuVo != null) {
orderSkuVos.add(orderSkuVo);
}
}
return orderSkuVos;
}
private List<GoblinOrderSkuVo> getSelectedOrderSkuVos(GoblinStoreOrderVo orderVo, List<GoblinOrderSkuVo> orderSkuVos, List<String> orderSkuIds) {
if (orderSkuIds == null || orderSkuIds.isEmpty()) {
return orderSkuVos;
}
Set<String> orderSkuIdSet = new HashSet<>(orderVo.getOrderSkuVoIds());
Set<String> selectedIdSet = new HashSet<>(orderSkuIds);
List<GoblinOrderSkuVo> selectedSkuVos = new ArrayList<>();
for (String orderSkuId : selectedIdSet) {
if (!orderSkuIdSet.contains(orderSkuId)) {
return new ArrayList<>();
}
}
for (GoblinOrderSkuVo orderSkuVo : orderSkuVos) {
if (selectedIdSet.contains(orderSkuVo.getOrderSkuId())) {
selectedSkuVos.add(orderSkuVo);
}
}
return selectedSkuVos;
}
private ResponseDto<Boolean> createStoreRefundOrder(GoblinStoreOrderVo orderVo, List<GoblinOrderSkuVo> refundSkuVos,
boolean beforeShip, GoblinStoreOrderRefundParam param,
String uid, LocalDateTime now, String nowStr) {
BigDecimal skuRefundPrice = BigDecimal.ZERO;
List<String> orderSkuIds = new ArrayList<>();
List<GoblinBackOrderSkuVo> backOrderSkuVos = ObjectUtil.goblinBackOrderSkuVoArrayList();
boolean selectAll = param.getOrderSkuIds() == null || param.getOrderSkuIds().isEmpty();
for (GoblinOrderSkuVo orderSkuVo : refundSkuVos) {
BigDecimal applyingRefundPrice = mongoUtils.getRefundOrderSkuVoPriceBySkuId(orderSkuVo.getOrderSkuId());
BigDecimal refundPrice = GoblinRefundHelper.remainRefundPrice(orderSkuVo, applyingRefundPrice);
if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
if (!beforeShip && selectAll) {
continue;
}
return ResponseDto.failure("退款价格超过商品可退价格");
}
skuRefundPrice = skuRefundPrice.add(refundPrice);
orderSkuIds.add(orderSkuVo.getOrderSkuId());
backOrderSkuVos.add(GoblinRefundHelper.buildBackOrderSkuVo(orderSkuVo, refundPrice, nowStr));
}
if (backOrderSkuVos.isEmpty()) {
return ResponseDto.failure("退款价格超过商品可退价格");
}
BigDecimal backPriceExpress = beforeShip && orderVo.getPriceExpress() != null ? orderVo.getPriceExpress() : BigDecimal.ZERO;
BigDecimal realBackPrice = beforeShip ? GoblinRefundHelper.beforeShipRefundPrice(skuRefundPrice, backPriceExpress) : skuRefundPrice;
int backOrderStatus = beforeShip ? GoblinStatusConst.Status.ORDER_BACK_STATUS_0.getValue() : GoblinStatusConst.Status.ORDER_BACK_STATUS_1.getValue();
String refundCode = IDGenerator.storeRefundCode(orderVo.getMasterOrderCode());
GoblinBackOrder backOrder = GoblinBackOrder.getNew();
backOrder.setBackOrderId(IDGenerator.nextTimeId2());
backOrder.setBackCode(refundCode);
backOrder.setOrderId(orderVo.getOrderId());
backOrder.setOrderCode(orderVo.getOrderCode());
backOrder.setStoreId(orderVo.getStoreId());
backOrder.setUserId(orderVo.getUserId());
backOrder.setSkuIdNums(String.join(",", orderSkuIds));
backOrder.setType(GoblinStatusConst.Type.BACK_TYPE_1.getValue());
backOrder.setReason(StringUtil.isBlank(param.getReason()) ? GoblinStatusConst.Type.BACK_REASON_TYPE_8.getDesc() : param.getReason());
backOrder.setDescribes(StringUtil.isBlank(param.getDescribes()) ? "店铺退款" : param.getDescribes());
backOrder.setRealBackPrice(realBackPrice);
backOrder.setBackPriceExpress(backPriceExpress);
backOrder.setStatus(backOrderStatus);
backOrder.setCreatedAt(now);
if (beforeShip) {
backOrder.setAuditAt(now);
}
GoblinBackOrderVo backOrderVo = GoblinBackOrderVo.getNew();
BeanUtils.copyProperties(backOrder, backOrderVo);
backOrderVo.setCreatedAt(nowStr);
backOrderVo.setAuditAt(beforeShip ? nowStr : null);
backOrderVo.setBackPriceExpress(backPriceExpress);
backOrderVo.setBackOrderSkuVos(backOrderSkuVos);
backOrderVo.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
GoblinBackOrderLog backOrderLog = initBackLog(backOrder.getBackOrderId(), uid, now);
backOrderLog.setStatus(beforeShip ? GoblinStatusConst.Status.ORDER_LOG_STATUS_22.getValue() : GoblinStatusConst.Status.ORDER_LOG_STATUS_20.getValue());
backOrderLog.setOperationType(GoblinStatusConst.Type.OPERATION_TYPE_2.getValue());
backOrderLog.setMessage("商户发起退款:orderSkuIds=[" + backOrder.getSkuIdNums() + "],refundPrice=[" + realBackPrice + "],[refundCode=" + refundCode + "]");
saveStoreRefundOrder(orderVo, refundSkuVos, backOrder, backOrderVo, backOrderLog, now);
if (beforeShip) {
String returnString = refundHelper.initRefund(orderVo, realBackPrice, refundCode);
if (!refundHelper.isRefundSuccess(returnString)) {
backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue());
backOrderVo.setErrorReason("失败原因:" + refundHelper.getRefundMessage(returnString));
backOrder.setStatus(backOrderVo.getStatus());
backOrder.setErrorReason(backOrderVo.getErrorReason());
log.error("REFUND DATA = " + returnString);
updateStoreRefundOrderAfterPayment(backOrderVo, now);
} else if (DragonConstant.REFUND_TYPE_MICROPAY_ALIPAY.equals(orderVo.getPaymentType())) {
refundHelper.alipayCallBack(orderVo, refundCode);
}
}
return backOrderVo.getStatus() == GoblinStatusConst.Status.ORDER_BACK_STATUS_10.getValue()
? ResponseDto.failure("退款失败:" + backOrderVo.getErrorReason())
: ResponseDto.success();
}
private void saveStoreRefundOrder(GoblinStoreOrderVo orderVo, List<GoblinOrderSkuVo> refundSkuVos,
GoblinBackOrder backOrder, GoblinBackOrderVo backOrderVo,
GoblinBackOrderLog backOrderLog, LocalDateTime now) {
LinkedList<String> sqls = CollectionUtil.linkedListString();
sqls.add(SqlMapping.get("goblin_order.store.backOrderWithExpress"));
sqls.add(SqlMapping.get("goblin_order.store.orderSkuStatus"));
sqls.add(SqlMapping.get("goblin_order.store.refundLog"));
LinkedList<Object[]> backOrderSql = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> orderSkuStatusSql = CollectionUtil.linkedListObjectArr();
LinkedList<Object[]> refundLogSql = CollectionUtil.linkedListObjectArr();
backOrderSql.add(new Object[]{
backOrder.getBackOrderId(), backOrder.getBackCode(), backOrder.getOrderId(),
backOrder.getOrderCode(), backOrder.getStoreId(), backOrder.getUserId(),
backOrder.getSkuIdNums(), backOrder.getType(), backOrder.getReason(),
backOrder.getDescribes(), backOrder.getRealBackPrice(), backOrder.getBackPriceExpress(),
backOrder.getStatus(), backOrder.getPics(), backOrder.getCreatedAt(),
backOrder.getAuditAt(), backOrder.getErrorReason()
});
for (GoblinOrderSkuVo orderSkuVo : refundSkuVos) {
orderSkuVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_61.getValue());
redisUtils.setGoblinOrderSku(orderSkuVo.getOrderSkuId(), orderSkuVo);
mongoUtils.updateGoblinOrderSkuVo(orderSkuVo.getOrderSkuId(), orderSkuVo);
orderSkuStatusSql.add(new Object[]{
orderSkuVo.getStatus(), now,
orderSkuVo.getOrderSkuId(), now, now
});
}
refundLogSql.add(new Object[]{
backOrderLog.getBackOrderLogId(), backOrderLog.getBackOrderId(), backOrderLog.getOperationType(),
backOrderLog.getMessage(), backOrderLog.getOperationName(), backOrderLog.getStatus(), now
});
redisUtils.setBackOrderVo(backOrderVo.getBackOrderId(), backOrderVo);
redisUtils.addBackOrderByOrderId(orderVo.getOrderId(), backOrderVo.getBackOrderId());
mongoUtils.insertGoblinBackOrderVo(backOrderVo);
queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
SqlMapping.gets(sqls, backOrderSql, orderSkuStatusSql, refundLogSql));
}
private void updateStoreRefundOrderAfterPayment(GoblinBackOrderVo backOrderVo, LocalDateTime now) {
redisUtils.setBackOrderVo(backOrderVo.getBackOrderId(), backOrderVo);
mongoUtils.updateGoblinBackOrderVo(backOrderVo.getBackOrderId(), backOrderVo);
queueUtils.sendMsgByRedis(
MQConst.GoblinQueue.GOBLIN_STORE_ORDER_OPERA.getKey(),
SqlMapping.get("goblin_order.store.applyRefund",
backOrderVo.getStatus(), backOrderVo.getReason(), backOrderVo.getAuditAt(), now,
backOrderVo.getBackOrderId(), now, now
)
);
}
private GoblinStoreBackOrderListVo buildStoreBackOrderListVo(GoblinBackOrderVo item) {
GoblinStoreBackOrderListVo vo = GoblinStoreBackOrderListVo.getNew();
vo.setBackOrderId(item.getBackOrderId());
vo.setBackCode(item.getBackCode());
vo.setOrderCode(item.getOrderCode());
vo.setType(item.getType());
vo.setStatus(item.getStatus());
vo.setRealBackPrice(item.getRealBackPrice());
vo.setCreatedAt(item.getCreatedAt());
vo.setAuditAt(item.getAuditAt());
vo.setRefundAt(item.getRefundAt());
vo.setBackOrderSkuVos(item.getBackOrderSkuVos());
if (StringUtil.isNotBlank(item.getRefundAt())) {
vo.setProcessedAt(item.getRefundAt());
} else if (StringUtil.isNotBlank(item.getAuditAt())) {
vo.setProcessedAt(item.getAuditAt());
} else if (StringUtil.isNotBlank(item.getRefuseAt())) {
vo.setProcessedAt(item.getRefuseAt());
}
Integer operationType = resolveBackOrderOperationType(item);
vo.setOperationType(operationType);
if (StringUtil.isNotBlank(item.getOrderId())) {
GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(item.getOrderId());
if (orderVo != null) {
vo.setPriceActual(orderVo.getPriceActual());
int orderSkuCount = orderVo.getOrderSkuVoIds() == null ? 0 : orderVo.getOrderSkuVoIds().size();
int refundSkuCount = item.getBackOrderSkuVos() == null ? 0 : item.getBackOrderSkuVos().size();
boolean fullRefund = orderSkuCount > 0 && refundSkuCount >= orderSkuCount;
vo.setRefundScope(fullRefund ? 1 : 2);
}
}
return vo;
}
private Integer resolveBackOrderOperationType(GoblinBackOrderVo item) {
if (item.getOperationType() != null) {
return item.getOperationType();
}
if ("店铺退款".equals(item.getDescribes())
|| GoblinStatusConst.Type.BACK_REASON_TYPE_8.getDesc().equals(item.getReason())) {
return GoblinStatusConst.Type.OPERATION_TYPE_2.getValue();
}
return GoblinStatusConst.Type.OPERATION_TYPE_1.getValue();
}
private GoblinBackOrderLog initBackLog(String orderId, String uid, LocalDateTime now) {
GoblinBackOrderLog log = GoblinBackOrderLog.getNew();
log.setBackOrderId(orderId);
......@@ -334,42 +606,4 @@ public class GoblinStoreBackOrderServiceImpl implements IGoblinStoreBackOrderSer
log.setCreatedAt(now);
return log;
}
private String initRefund(GoblinStoreOrderVo orderVo, BigDecimal price, String refundCode) {
MultiValueMap<String, String> params = CollectionUtil.linkedMultiValueMapStringString();
params.add("code", orderVo.getPayCode());
params.add("notifyUrl", synUrl);
params.add("orderCode", orderVo.getMasterOrderCode());
params.add("orderRefundCode", refundCode);
params.add("paymentId", orderVo.getPaymentId());
params.add("paymentType", orderVo.getPaymentType());
params.add("price", String.valueOf(price));
BigDecimal totalPrice = BigDecimal.ZERO;
String[] orderIds = redisUtils.getMasterCode(orderVo.getMasterOrderCode());
for (String orderId : orderIds) {
GoblinStoreOrderVo vo = redisUtils.getGoblinOrder(orderId);
totalPrice = totalPrice.add(vo.getPriceActual());
}
params.add("priceTotal", String.valueOf(totalPrice));
params.add("reason", "按需退款");
MultiValueMap<String, String> headers = CollectionUtil.linkedMultiValueMapStringString();
headers.add("Accept", "application/json;charset=UTF-8");
String returnString = HttpUtil.post(refundApply, params, headers);
log.debug("REFUND DATA = " + returnString);
return returnString;
}
private String alipayCallBack(GoblinStoreOrderVo orderVo, String refundCode) {
MultiValueMap<String, String> params = CollectionUtil.linkedMultiValueMapStringString();
params.add("callBackUrl", goblinRefundUrl);
params.add("orderCode", orderVo.getMasterOrderCode());
params.add("orderRefundCode", refundCode);
params.add("paymentId", orderVo.getPaymentId());
MultiValueMap<String, String> headers = CollectionUtil.linkedMultiValueMapStringString();
headers.add("Accept", "application/json;charset=UTF-8");
log.debug("REFUND CALLBACK params = " + params);
String returnString = HttpUtil.post(refundApplyCallBack, params, headers);
log.debug("REFUND CALLBACK DATA = " + returnString);
return returnString;
}
}
package com.liquidnet.service.goblin.service.impl.manage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.github.pagehelper.PageInfo;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.base.ResponseDto;
......@@ -14,29 +15,25 @@ import com.liquidnet.service.goblin.entity.GoblinBackOrder;
import com.liquidnet.service.goblin.entity.GoblinBackOrderLog;
import com.liquidnet.service.goblin.entity.GoblinOrderOperationLog;
import com.liquidnet.service.goblin.param.BackCouponParam;
import com.liquidnet.service.goblin.param.RefundCallbackParam;
import com.liquidnet.service.goblin.service.GoblinCouponService;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreMoneyService;
import com.liquidnet.service.goblin.service.manage.IGoblinStoreOrderService;
import com.liquidnet.service.goblin.util.*;
import com.mongodb.BasicDBObject;
import com.mongodb.client.result.UpdateResult;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Service
......@@ -64,19 +61,73 @@ public class GoblinStoreOrderServiceImpl implements IGoblinStoreOrderService {
@Override
public ResponseDto<PageInfo<GoblinStoreOrderListVo>> orderList(Integer page, String orderCode, Integer type, String cst, String cet, String expressContacts, String phone, Integer status) {
List<GoblinStoreOrderListVo> listVos = ObjectUtil.getGoblinStoreOrderListVoArrayList();
public ResponseDto<GoblinStoreOrderListPageVo> orderList(Integer page, String orderCode, Integer type, String cst, String cet, String expressContacts, String phone, Integer status) {
String uid = CurrentUtil.getCurrentUid();
GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
if (storeInfoVo == null) {
return ResponseDto.failure("无法查看");
}
HashMap<String, Object> map = mongoUtils.storeOrderList(storeInfoVo.getStoreId(), page, orderCode, cst, cet, expressContacts, phone, status);
String storeId = storeInfoVo.getStoreId();
HashMap<String, Object> map = mongoUtils.storeOrderList(storeId, page, orderCode, cst, cet, expressContacts, phone, status);
long total = (long) map.get("total");
List<GoblinStoreOrderVo> voList = (List<GoblinStoreOrderVo>) map.get("data");
List<GoblinStoreOrderListVo> listVos = buildStoreOrderListVos(voList, false);
PageInfo<GoblinStoreOrderListVo> pageInfo = new PageInfo(listVos);
pageInfo.setTotal(total);
GoblinStoreOrderListPageVo result = new GoblinStoreOrderListPageVo();
result.setPageInfo(pageInfo);
// 状态 Tab 数量:与列表共用筛选条件,但不带 status 参数
result.setStatusCount(mongoUtils.storeOrderStatusCount(storeId, orderCode, cst, cet, expressContacts, phone));
return ResponseDto.success(result);
}
@Override
public void exportOrderList(HttpServletResponse response, String orderCode, String cst, String cet, String expressContacts, String phone, Integer status) {
String uid = CurrentUtil.getCurrentUid();
GoblinStoreInfoVo storeInfoVo = redisUtils.getStoreInfoVoByUid(uid);
if (storeInfoVo == null) {
log.warn("店铺-订单:订单列表导出:无法查看[uid:{}]", uid);
return;
}
List<GoblinStoreOrderVo> voList = mongoUtils.storeOrderExportList(storeInfoVo.getStoreId(), orderCode, cst, cet, expressContacts, phone, status);
List<GoblinStoreOrderListVo> listVos = buildStoreOrderListVos(voList, true);
if (org.springframework.util.CollectionUtils.isEmpty(listVos)) {
log.warn("店铺-订单:订单列表导出:无数据[uid:{},storeId:{},orderCode:{},cst:{},cet:{},expressContacts:{},phone:{},status:{}]",
uid, storeInfoVo.getStoreId(), orderCode, cst, cet, expressContacts, phone, status);
return;
}
ServletOutputStream servletOutputStream = null;
try {
String fileName = DateUtil.Formatter.yyyyMMddHHmmssTrim.format(LocalDateTime.now())
.concat(new String(("商城订单信息").getBytes("gb2312"), StandardCharsets.ISO_8859_1));
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=".concat(fileName).concat(ExcelTypeEnum.XLSX.getValue()));
response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
servletOutputStream = response.getOutputStream();
EasyExcel.write(servletOutputStream).head(exportOrderListHead(listVos)).sheet("商城订单信息").doWrite(exportOrderListRows(listVos));
} catch (IOException e) {
log.error("店铺-订单:订单列表导出:异常[uid:{},orderCode:{},cst:{},cet:{},expressContacts:{},phone:{},status:{},ex.msg={}]",
uid, orderCode, cst, cet, expressContacts, phone, status, e.getLocalizedMessage());
} finally {
if (null != servletOutputStream) {
try {
servletOutputStream.close();
} catch (Exception ignored) {
}
}
}
}
private List<GoblinStoreOrderListVo> buildStoreOrderListVos(List<GoblinStoreOrderVo> voList, boolean includeExportFields) {
List<GoblinStoreOrderListVo> listVos = ObjectUtil.getGoblinStoreOrderListVoArrayList();
if (org.springframework.util.CollectionUtils.isEmpty(voList)) {
return listVos;
}
for (GoblinStoreOrderVo item : voList) {
GoblinStoreOrderListVo vo = GoblinStoreOrderListVo.getNew();
vo.setCreatedAt(item.getCreatedAt());
vo.setPayTime(item.getPayTime());
GoblinOrderAttrVo orderAttrVo = item.getOrderAttrVo();
if (orderAttrVo != null) {
vo.setExpressAddressDetail(orderAttrVo.getExpressAddressDetail());
......@@ -96,6 +147,13 @@ public class GoblinStoreOrderServiceImpl implements IGoblinStoreOrderService {
vo.setStatus(item.getStatus());
vo.setMarketType(item.getMarketType());
vo.setPriceExpress(item.getPriceExpress());
vo.setUserId(item.getUserId());
vo.setUserName(item.getUserName());
vo.setUserMobile(item.getUserMobile());
if (includeExportFields) {
vo.setPlatformUcouponPrice(item.getPriceCoupon());
vo.setStoreUcouponPrice(item.getStorePriceCoupon());
}
if (item.getMarketId() == null || item.getMarketId().equals("")) {
vo.setMarketName("");
} else {
......@@ -127,11 +185,129 @@ public class GoblinStoreOrderServiceImpl implements IGoblinStoreOrderService {
orderListSkuVos.add(itemSkuVo);
}
vo.setStoreOrderListSkuVoList(orderListSkuVos);
// 增加物流信息
vo.setGoblinMailVoList(redisUtils.getGoblinMail(item.getOrderId()));
String logisticsCompany = exportLogisticsCompany(vo.getGoblinMailVoList());
if (StringUtils.isBlank(logisticsCompany)) {
logisticsCompany = toExportString(item.getLogisticsCompany());
}
vo.setLogisticsCompany(logisticsCompany);
listVos.add(vo);
}
PageInfo<GoblinStoreOrderListVo> pageInfo = new PageInfo(listVos);
pageInfo.setTotal(total);
return ResponseDto.success(pageInfo);
return listVos;
}
private List<List<String>> exportOrderListHead(List<GoblinStoreOrderListVo> listVos) {
List<String> heads = new ArrayList<>();
heads.add("订单id");
heads.add("订单编号");
heads.add("购买人手机号");
heads.add("快递费");
heads.add("平台券优惠券金额");
heads.add("店铺券优惠金额");
heads.add("收货人");
heads.add("收货人电话");
heads.add("快递地址");
heads.add("支付方式");
heads.add("支付时间");
heads.add("下单时间");
heads.add("订单状态");
heads.add("活动名称");
heads.add("快递公司");
heads.add("物流单号");
int maxSkuCount = maxSkuCount(listVos);
for (int i = 1; i <= maxSkuCount; i++) {
heads.add("商品id" + i);
heads.add("商品名" + i);
heads.add("款式" + i);
heads.add("数量" + i);
heads.add("单价" + i);
heads.add("价格" + i);
heads.add("订单skuId" + i);
}
List<List<String>> headList = new ArrayList<>();
for (String head : heads) {
headList.add(Collections.singletonList(head));
}
return headList;
}
private List<List<String>> exportOrderListRows(List<GoblinStoreOrderListVo> listVos) {
List<List<String>> rows = new ArrayList<>();
for (GoblinStoreOrderListVo order : listVos) {
rows.add(exportOrderListRow(order));
}
return rows;
}
private List<String> exportOrderListRow(GoblinStoreOrderListVo order) {
List<String> row = new ArrayList<>();
row.add(toExportString(order.getOrderId()));
row.add(toExportString(order.getOrderCode()));
row.add(toExportString(order.getUserMobile()));
row.add(toExportString(order.getPriceExpress()));
row.add(toExportString(order.getPlatformUcouponPrice()));
row.add(toExportString(order.getStoreUcouponPrice()));
row.add(toExportString(order.getExpressContacts()));
row.add(toExportString(order.getExpressPhone()));
row.add(toExportString(order.getExpressAddress()) + " " + toExportString(order.getExpressAddressDetail()));
row.add(toExportString(order.getPayType()));
row.add(toExportString(order.getPayTime()));
row.add(toExportString(order.getCreatedAt()));
row.add(toExportString(order.getStatus()));
row.add(toExportString(order.getMarketName()));
row.add(exportLogisticsCompany(order.getGoblinMailVoList()));
row.add(exportMailNo(order.getGoblinMailVoList()));
List<GoblinStoreOrderListSkuVo> skuVos = order.getStoreOrderListSkuVoList();
if (!org.springframework.util.CollectionUtils.isEmpty(skuVos)) {
for (GoblinStoreOrderListSkuVo skuVo : skuVos) {
row.add(toExportString(skuVo.getSkuId()));
row.add(toExportString(skuVo.getSpuName()));
row.add(toExportString(skuVo.getSkuName()));
row.add(toExportString(skuVo.getNum()));
row.add(toExportString(skuVo.getPrice()));
row.add(toExportString(skuVo.getSkuPriceActual()));
row.add(toExportString(skuVo.getOrderSkuId()));
}
}
return row;
}
private int maxSkuCount(List<GoblinStoreOrderListVo> listVos) {
int max = 0;
for (GoblinStoreOrderListVo order : listVos) {
List<GoblinStoreOrderListSkuVo> skuVos = order.getStoreOrderListSkuVoList();
if (!org.springframework.util.CollectionUtils.isEmpty(skuVos)) {
max = Math.max(max, skuVos.size());
}
}
return max;
}
private String exportLogisticsCompany(List<GoblinMailVo> mailVos) {
if (org.springframework.util.CollectionUtils.isEmpty(mailVos)) {
return "";
}
List<String> values = new ArrayList<>();
for (GoblinMailVo mailVo : mailVos) {
values.add(toExportString(mailVo.getLogisticsCompany()));
}
return String.join(",", values);
}
private String exportMailNo(List<GoblinMailVo> mailVos) {
if (org.springframework.util.CollectionUtils.isEmpty(mailVos)) {
return "";
}
List<String> values = new ArrayList<>();
for (GoblinMailVo mailVo : mailVos) {
values.add(toExportString(mailVo.getMailNo()));
}
return String.join(",", values);
}
private String toExportString(Object value) {
return null == value ? "" : String.valueOf(value);
}
@Override
......@@ -148,6 +324,10 @@ public class GoblinStoreOrderServiceImpl implements IGoblinStoreOrderService {
}
GoblinStoreOrderListVo vo = GoblinStoreOrderListVo.getNew();
vo.setCreatedAt(orderVo.getCreatedAt());
// 订单详情增加下单账号信息
vo.setUserId(orderVo.getUserId());
vo.setUserMobile(orderVo.getUserMobile());
vo.setUserName(orderVo.getUserName());
GoblinOrderAttrVo orderAttrVo = orderVo.getOrderAttrVo();
if (orderAttrVo != null) {
vo.setExpressAddressDetail(orderAttrVo.getExpressAddressDetail());
......
......@@ -40,6 +40,7 @@ import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
......@@ -1423,6 +1424,42 @@ public class GoblinMongoUtils {
//查询销量
int size = 20;
int skipCount = ((page - 1) * size);
Criteria criteria = storeOrderListCriteria(storeId, orderCode, cst, cet, expressContacts, phone, status);
Query query = Query.query(criteria);
query.with(Sort.by(Sort.Order.desc("createdAt")));
query.skip(skipCount).limit(size);
storeOrderListFields(query);
List<GoblinStoreOrderVo> dataList = mongoTemplate.find(query, GoblinStoreOrderVo.class, GoblinStoreOrderVo.class.getSimpleName());
//查询总数量
Query countQuery = Query.query(criteria);
countQuery.fields().include("orderCode");
List<GoblinStoreOrderVo> countList = mongoTemplate.find(countQuery, GoblinStoreOrderVo.class, GoblinStoreOrderVo.class.getSimpleName());
long total = countList.size();
HashMap<String, Object> map = CollectionUtil.mapStringObject();
map.put("data", dataList);
map.put("total", total);
return map;
}
//商品订单导出列表
public List<GoblinStoreOrderVo> storeOrderExportList(String storeId, String orderCode, String cst, String cet, String expressContacts, String phone, Integer status) {
Query query = Query.query(storeOrderListCriteria(storeId, orderCode, cst, cet, expressContacts, phone, status));
query.with(Sort.by(Sort.Order.desc("createdAt")));
storeOrderListFields(query);
return mongoTemplate.find(query, GoblinStoreOrderVo.class, GoblinStoreOrderVo.class.getSimpleName());
}
/** 列表查询条件(含 status Tab 筛选) */
private Criteria storeOrderListCriteria(String storeId, String orderCode, String cst, String cet, String expressContacts, String phone, Integer status) {
Criteria criteria = storeOrderListBaseCriteria(storeId, orderCode, cst, cet, expressContacts, phone);
if (status != null) {
criteria = criteria.and("status").is(status);
}
return criteria;
}
/** 列表/统计共用基础条件(不含 status,保证 Tab 数量不受当前 Tab 影响) */
private Criteria storeOrderListBaseCriteria(String storeId, String orderCode, String cst, String cet, String expressContacts, String phone) {
Criteria criteria = Criteria.where("storeId").is(storeId);
if (cst != null && cet != null) {
criteria = criteria.and("createdAt").gte(cst).lt(cet);
......@@ -1433,34 +1470,71 @@ public class GoblinMongoUtils {
if (phone != null) {
criteria = criteria.and("userMobile").is(phone);
}
if (status != null) {
criteria = criteria.and("status").is(status);
}
if (expressContacts != null) {
criteria = criteria.and("orderAttrVo.expressContacts").is(expressContacts);
}
return criteria;
}
/** 统计订单各状态 Tab 数量(沿用列表筛选条件,忽略 status 参数) */
public GoblinStoreOrderStatusCountVo storeOrderStatusCount(String storeId, String orderCode, String cst, String cet, String expressContacts, String phone) {
Criteria criteria = storeOrderListBaseCriteria(storeId, orderCode, cst, cet, expressContacts, phone);
String collection = GoblinStoreOrderVo.class.getSimpleName();
long all = mongoTemplate.count(Query.query(criteria), GoblinStoreOrderVo.class, collection);
return GoblinStoreOrderStatusCountVo.of(all, groupCountByStatus(criteria, collection));
}
private void storeOrderListFields(Query query) {
query.fields()
.include("orderCode").include("createdAt").include("payType").include("payTime").include("status")
.include("orderSkuVoIds").include("orderId").include("storeId")
.include("userId").include("userName").include("userMobile")
.include("priceActual").include("priceTotal").include("priceExpress").include("priceCoupon").include("storePriceCoupon")
.include("marketId").include("marketType").include("orderType").include("source").include("deliveryTime")
.include("ucouponId").include("storeCouponId")
.include("logisticsCompany").include("logisticsCode").include("mailNo")
.include("mixId").include("mixName")
.include("orderAttrVo.expressContacts").include("orderAttrVo.expressAddressDetail")
.include("orderAttrVo.expressAddress").include("orderAttrVo.expressPhone");
}
//商品退款订单列表
public HashMap<String, Object> storeBackOrderList(String storeId, Integer page, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName, String status) {
//查询销量
int size = 20;
int skipCount = ((page - 1) * size);
Criteria criteria = storeBackOrderListCriteria(storeId, orderBackCode, type, cst, cet, orderCode, spuName, status);
Query query = Query.query(criteria);
query.with(Sort.by(Sort.Order.desc("createdAt")));
query.skip(skipCount).limit(size);
query.fields().include("orderCode").include("createdAt").include("payType").include("status").include("orderSkuVoIds").include("orderId").include("priceActual").include("priceExpress").include("marketId")
.include("orderAttrVo.expressContacts").include("orderAttrVo.expressAddressDetail").include("orderAttrVo.expressAddress").include("orderAttrVo.expressPhone");
List<GoblinStoreOrderVo> dataList = mongoTemplate.find(query, GoblinStoreOrderVo.class, GoblinStoreOrderVo.class.getSimpleName());
query.skip(skipCount).limit(size).with(Sort.by(Sort.Order.desc("createdAt")));
List<GoblinBackOrderVo> dataList = mongoTemplate.find(query, GoblinBackOrderVo.class, GoblinBackOrderVo.class.getSimpleName());
//查询总数量
Query countQuery = Query.query(criteria);
countQuery.fields().include("orderCode");
List<GoblinStoreOrderVo> countList = mongoTemplate.find(countQuery, GoblinStoreOrderVo.class, GoblinStoreOrderVo.class.getSimpleName());
long total = countList.size();
long total = mongoTemplate.count(Query.query(criteria), GoblinBackOrderVo.class, GoblinBackOrderVo.class.getSimpleName());
HashMap<String, Object> map = CollectionUtil.mapStringObject();
map.put("data", dataList);
map.put("total", total);
return map;
}
//商品退款订单列表
public HashMap<String, Object> storeBackOrderList(String storeId, Integer page, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName, Integer status) {
//查询销量
int size = 20;
int skipCount = ((page - 1) * size);
/** 列表查询条件(含 status Tab 筛选) */
private Criteria storeBackOrderListCriteria(String storeId, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName, String status) {
Criteria criteria = storeBackOrderListBaseCriteria(storeId, orderBackCode, type, cst, cet, orderCode, spuName);
if (StringUtils.isNotBlank(status)) {
List<Integer> statusList = new ArrayList<>();
for (String part : status.split(",")) {
String trimmed = part.trim();
if (StringUtils.isNotBlank(trimmed)) {
statusList.add(Integer.valueOf(trimmed));
}
}
if (!statusList.isEmpty()) {
criteria = criteria.and("status").in(statusList);
}
}
return criteria;
}
/** 列表/统计共用基础条件(不含 status) */
private Criteria storeBackOrderListBaseCriteria(String storeId, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName) {
Criteria criteria = Criteria.where("storeId").is(storeId);
if (cst != null && cet != null) {
criteria = criteria.and("createdAt").gte(cst).lt(cet);
......@@ -1471,32 +1545,40 @@ public class GoblinMongoUtils {
if (orderBackCode != null) {
criteria = criteria.and("backCode").is(orderBackCode);
}
if (status != null) {
criteria = criteria.and("status").is(status);
}
if (type != null) {
criteria = criteria.and("type").is(type);
}
if (spuName != null) {
criteria = criteria.and("backOrderSkuVos.spuName").regex(".*?\\" + spuName);
}
Query query = Query.query(criteria);
query.skip(skipCount).limit(size).with(Sort.by(Sort.Order.desc("createdAt")));
;
query.fields().include("backCode").include("orderCode").include("backOrderId").include("type").include("status").include("realBackPrice").include("createdAt")
.include("backOrderSkuVos.spuName").include("backOrderSkuVos.skuName").include("backOrderSkuVos.spuPic").include("backOrderSkuVos.skuPic")
.include("backOrderSkuVos.skuSpecs").include("backOrderSkuVos.skuId").include("backOrderSkuVos.spuId").include("backOrderSkuVos.orderSkuId")
.include("backOrderSkuVos.refundPrice");
List<GoblinBackOrderVo> dataList = mongoTemplate.find(query, GoblinBackOrderVo.class, GoblinBackOrderVo.class.getSimpleName());
//查询总数量
Query countQuery = Query.query(criteria);
countQuery.fields().include("orderCode");
List<GoblinBackOrderVo> countList = mongoTemplate.find(countQuery, GoblinBackOrderVo.class, GoblinBackOrderVo.class.getSimpleName());
long total = countList.size();
HashMap<String, Object> map = CollectionUtil.mapStringObject();
map.put("data", dataList);
map.put("total", total);
return map;
return criteria;
}
/** 统计退款各审核 Tab 数量(沿用列表筛选条件,忽略 status 参数) */
public GoblinStoreBackOrderStatusCountVo storeBackOrderStatusCount(String storeId, String orderBackCode, Integer type, String cst, String cet, String orderCode, String spuName) {
Criteria criteria = storeBackOrderListBaseCriteria(storeId, orderBackCode, type, cst, cet, orderCode, spuName);
String collection = GoblinBackOrderVo.class.getSimpleName();
long all = mongoTemplate.count(Query.query(criteria), GoblinBackOrderVo.class, collection);
return GoblinStoreBackOrderStatusCountVo.of(all, groupCountByStatus(criteria, collection));
}
/** 按 status 分组计数,订单/退款列表统计共用 */
private Map<Integer, Long> groupCountByStatus(Criteria criteria, String collection) {
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(criteria),
Aggregation.group("status").count().as("count")
);
AggregationResults<Document> results = mongoTemplate.aggregate(aggregation, collection, Document.class);
Map<Integer, Long> grouped = new HashMap<>();
for (Document doc : results.getMappedResults()) {
Object statusKey = doc.get("_id");
if (statusKey == null) {
continue;
}
Number count = doc.get("count", Number.class);
grouped.put(((Number) statusKey).intValue(), count == null ? 0L : count.longValue());
}
return grouped;
}
//商户 正在下单列表
......@@ -1693,6 +1775,21 @@ public class GoblinMongoUtils {
return refundPrice;
}
// 获取指定子单已成功或进行中的退款金额;新退款链路需要精确到当前 orderSkuId,避免多子单退款单被重复累计。
public BigDecimal getRefundOrderSkuVoPriceBySkuId(String orderSkuId) {
BigDecimal refundPrice = BigDecimal.ZERO;
List<GoblinBackOrderVo> backOrderVos = mongoTemplate.find(Query.query(Criteria.where("backOrderSkuVos.orderSkuId").is(orderSkuId).and("status").nin(3, 5, 9, 10, 11)),
GoblinBackOrderVo.class, GoblinBackOrderVo.class.getSimpleName());
for (GoblinBackOrderVo vo : backOrderVos) {
for (GoblinBackOrderSkuVo orderSkuVo : vo.getBackOrderSkuVos()) {
if (orderSkuId.equals(orderSkuVo.getOrderSkuId())) {
refundPrice = refundPrice.add(orderSkuVo.getRefundPrice());
}
}
}
return refundPrice;
}
//根据spuId查询活动id
public List<String> getMarketIdListBySpuId(String spuId) {
Query query = Query.query(Criteria.where("marketId").ne(null).and("spuId").regex(spuId.concat(GoblinStatusConst.MarketPreStatus.MARKET_PRE_ZHENGZAI.getValue()).concat(".*")));
......
......@@ -129,6 +129,7 @@ goblin_order.store.log=INSERT INTO goblin_order_operation_log (`order_log_id`,`o
goblin_order.store.refundLog=INSERT INTO goblin_back_order_log (`back_order_log_id`,`back_order_id`,`operation_type`,`message`,`operation_name`,`status`,`created_at`) VALUES(?,?,?,?,?,?,?)
goblin_order.store.orderExpressPrice=UPDATE goblin_store_order SET price_total =? , price_modify = ? ,price_voucher = ? , price_actual = ? ,price_express = ? , updated_at = ? WHERE order_id = ? and (updated_at <= ? or created_at = ? or updated_at is null)
goblin_order.store.backOrder=INSERT INTO goblin_back_order (`back_order_id`,`back_code`,`order_id`,`order_code`,`store_id`,`user_id`,`sku_id_nums`,`type`,`reason`,`describes`,`real_back_price`,`status`,`created_at`,`audit_at`,`error_reason`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
goblin_order.store.backOrderWithExpress=INSERT INTO goblin_back_order (`back_order_id`,`back_code`,`order_id`,`order_code`,`store_id`,`user_id`,`sku_id_nums`,`type`,`reason`,`describes`,`real_back_price`,`back_price_express`,`status`,`pics`,`created_at`,`audit_at`,`error_reason`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
goblin_order.store.changeExpress=UPDATE goblin_back_order SET real_back_price = ? ,back_price_express = ? updated_at = ? WHERE back_order_id = ? and (updated_at <= ? or created_at = ? or updated_at is null)
goblin_order.store.changeSku=UPDATE goblin_back_order SET real_back_price = ? , updated_at = ? WHERE back_order_id = ? and (updated_at <= ? or created_at = ? or updated_at is null)
goblin_order.store.backOrderStatus=UPDATE goblin_back_order SET status = ? , refuse_at=?,refuse_size=?,updated_at = ? WHERE back_order_id = ? and (updated_at <= ? or created_at = ? or updated_at is null)
......@@ -137,6 +138,7 @@ goblin_order.store.orderStatus.time=UPDATE goblin_store_order SET status = ? ,zh
goblin_order.store.applyRefund=UPDATE goblin_back_order SET status = ? ,reason=?,audit_at=?, updated_at = ? WHERE back_order_id = ? and (updated_at <= ? or created_at = ? or updated_at is null)
#---- \u7528\u6237\u8BA2\u5355\u64CD\u4F5C
goblin_order.user.applyRefund=INSERT INTO goblin_back_order (`back_order_id`,`back_code`,`order_id`,`order_code`,`store_id`,`user_id`,`sku_id_nums`,`type`,`reason`,`describes`,`real_back_price`,`back_price_express`,`status`,`logis_company_name`,`mail_no`,`pics`,`created_at`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
goblin_order.user.applyRefundAuto=INSERT INTO goblin_back_order (`back_order_id`,`back_code`,`order_id`,`order_code`,`store_id`,`user_id`,`sku_id_nums`,`type`,`reason`,`describes`,`real_back_price`,`back_price_express`,`status`,`logis_company_name`,`mail_no`,`pics`,`created_at`,`audit_at`,`error_reason`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
goblin_order.user.againRefund=UPDATE goblin_back_order SET status = ? , describes = ? , pics = ? , reason = ? , type = ?, updated_at=? where back_order_id=?
#---- \u8D2D\u7269\u8F66\u64CD\u4F5C
goblin_shop.cart.delete=UPDATE goblin_shopping_cart set del_tag=? where user_id=? and sku_id=?
......
package com.liquidnet.service.goblin.test;
import com.liquidnet.service.goblin.dto.vo.GoblinOrderSkuVo;
import com.liquidnet.service.goblin.service.impl.helper.GoblinRefundHelper;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigDecimal;
public class GoblinRefundHelperTest {
@Test
public void remainRefundPriceSubtractsRefundedAndApplyingAmount() {
GoblinOrderSkuVo skuVo = new GoblinOrderSkuVo();
skuVo.setSkuPriceActual(new BigDecimal("100.00"));
skuVo.setPriceRefund(new BigDecimal("30.00"));
BigDecimal remain = GoblinRefundHelper.remainRefundPrice(skuVo, new BigDecimal("20.00"));
Assert.assertEquals(0, new BigDecimal("50.00").compareTo(remain));
}
@Test
public void remainRefundPriceNeverReturnsNegativeAmount() {
GoblinOrderSkuVo skuVo = new GoblinOrderSkuVo();
skuVo.setSkuPriceActual(new BigDecimal("10.00"));
skuVo.setPriceRefund(new BigDecimal("8.00"));
BigDecimal remain = GoblinRefundHelper.remainRefundPrice(skuVo, new BigDecimal("5.00"));
Assert.assertEquals(0, BigDecimal.ZERO.compareTo(remain));
}
@Test
public void beforeShipRefundIncludesExpressFee() {
BigDecimal refundPrice = GoblinRefundHelper.beforeShipRefundPrice(new BigDecimal("80.00"), new BigDecimal("12.00"));
Assert.assertEquals(0, new BigDecimal("92.00").compareTo(refundPrice));
}
}
......@@ -1253,22 +1253,6 @@ public class GoblinOrderServiceImpl implements IGoblinOrderService {
List<GoblinBackOrderSkuVo> backOrderSkuVos = backOrderVo.getBackOrderSkuVos();
GoblinStoreOrderVo orderVo = redisUtils.getGoblinOrder(orderId);
orderVo.setPriceRefund(orderVo.getPriceRefund() == null ? BigDecimal.ZERO : orderVo.getPriceRefund().add(refundCallbackParam.getRefundPrice()));
if (orderVo.getPriceRefund().compareTo(orderVo.getPriceActual()) >= 0) {
//整单退款 退券
orderVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_6.getValue());
if (!(orderVo.getUcouponId() == null || orderVo.getUcouponId().equals(""))) {
orderUtils.backCoupon(orderVo.getUcouponId(), orderVo.getUserId());
}
if (!(orderVo.getStoreCouponId() == null || orderVo.getStoreCouponId().equals(""))) {
List<BackCouponParam> params = ObjectUtil.getBackCouponParam();
BackCouponParam backCouponParam = BackCouponParam.getNew();
backCouponParam.setuCouponIds(orderVo.getStoreCouponId());
backCouponParam.setUid(orderVo.getUserId());
params.add(backCouponParam);
orderUtils.backStoreCoupon(params);
}
}
backOrderVo.setStatus(GoblinStatusConst.Status.ORDER_BACK_STATUS_2.getValue());
backOrderVo.setRefundAt(nowStr);
for (GoblinBackOrderSkuVo backOrderSkuVo : backOrderSkuVos) {
......@@ -1322,6 +1306,20 @@ public class GoblinOrderServiceImpl implements IGoblinOrderService {
logVo.setCreatedAt(now);
mongoUtils.insertGoblinOrderLogVo(logVo);
}
if (isOrderFullyRefunded(orderVo)) {
orderVo.setStatus(GoblinStatusConst.Status.ORDER_STATUS_6.getValue());
if (!(orderVo.getUcouponId() == null || orderVo.getUcouponId().equals(""))) {
orderUtils.backCoupon(orderVo.getUcouponId(), orderVo.getUserId());
}
if (!(orderVo.getStoreCouponId() == null || orderVo.getStoreCouponId().equals(""))) {
List<BackCouponParam> params = ObjectUtil.getBackCouponParam();
BackCouponParam backCouponParam = BackCouponParam.getNew();
backCouponParam.setuCouponIds(orderVo.getStoreCouponId());
backCouponParam.setUid(orderVo.getUserId());
params.add(backCouponParam);
orderUtils.backStoreCoupon(params);
}
}
//redis
redisUtils.setGoblinOrder(orderId, orderVo);
redisUtils.setBackOrderVo(backOrderVo.getBackOrderId(), backOrderVo);
......@@ -1479,4 +1477,26 @@ public class GoblinOrderServiceImpl implements IGoblinOrderService {
return "";
}
/**
* 主单是否已全部退完:以所有子单实退金额为准,不再要求累计退款金额达到主单实付(含运费)。
* 已发货后部分退不含运费时,商品全部退完也应进入退款通过;待发货整单退含运费时,子单同样会全部退完。
*/
private boolean isOrderFullyRefunded(GoblinStoreOrderVo orderVo) {
if (orderVo == null || orderVo.getOrderSkuVoIds() == null || orderVo.getOrderSkuVoIds().isEmpty()) {
return false;
}
for (String orderSkuId : orderVo.getOrderSkuVoIds()) {
GoblinOrderSkuVo orderSkuVo = redisUtils.getGoblinOrderSkuVo(orderSkuId);
if (orderSkuVo == null) {
return false;
}
BigDecimal priceRefund = orderSkuVo.getPriceRefund() == null ? BigDecimal.ZERO : orderSkuVo.getPriceRefund();
BigDecimal skuPriceActual = orderSkuVo.getSkuPriceActual() == null ? BigDecimal.ZERO : orderSkuVo.getSkuPriceActual();
if (priceRefund.compareTo(skuPriceActual) < 0) {
return false;
}
}
return true;
}
}
Markdown is supported
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