feat:批量发货

This commit is contained in:
dzc committed 2025-10-13 13:06:09 +08:00
1 parent c89a979427
commit 203e0d71b5
10 files changed
+149 -31

No files matched your search

@@ -40,7 +40,10 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_035, "交易订单创建失败,原因:用户积分不足");
ErrorCode OMS_EXPRESS_ORDER_CREATE_ERROR = new ErrorCode(1_011_000_036, "快递订单创建失败,原因:{}");
ErrorCode ORDER_DELIVERY_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_037, "交易订单取消发货失败,订单不是【已发货】状态");
ErrorCode OMS_EXPRESS_ORDER_CANCEL_ERROR = new ErrorCode(1_011_000_036, "快递订单取消失败,原因:{}");
ErrorCode OMS_EXPRESS_ORDER_CANCEL_ERROR = new ErrorCode(1_011_000_037, "快递订单取消失败,原因:{}");
ErrorCode OMS_EXPRESS_EXCEL_EMPTY_ERROR = new ErrorCode(1_011_000_038, "商家发货单导入失败,原因:文件为空");
ErrorCode OMS_EXPRESS_CJFH_TEMP_EMPTY_ERROR = new ErrorCode(1_011_000_039, "商家发货物流公司未配置");
ErrorCode OMS_EXPRESS_ORDER_IMPORT_ERROR = new ErrorCode(1_011_000_040, "订单号:{},{}");
// ========== After Sale 模块 1-011-000-100 ==========
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
@@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.trade.controller.admin.order;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.*;
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -18,11 +20,13 @@ import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import com.kuaidi100.sdk.response.PrintBaseResp;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
@@ -210,4 +214,21 @@ public class TradeOrderController {
ExcelUtils.write(response, "订单列表.xls", "数据", TradeOrderListExportVo.class, exportList);
}
@GetMapping("/get-import-template")
@Operation(summary = "发货模版")
public void importTemplate(HttpServletResponse response) throws IOException {
ExcelUtils.write(response, "发货模版.xls", "发货模版", TradeMchDeliveryImportVo.class, new ArrayList<>());
}
@PostMapping("/import")
@Operation(summary = "导入发货模版")
@Parameters({
@Parameter(name = "file", description = "Excel 文件", required = true),
})
public CommonResult<Boolean> importExcel(@RequestParam("file") MultipartFile file) throws Exception {
List<TradeMchDeliveryImportVo> list = ExcelUtils.read(file, TradeMchDeliveryImportVo.class);
tradeOrderUpdateService.deliveryOrderByExcel(list);
return success(true);
}
}
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 商家发货模版
*/
@Schema(description = "管理后台 - 商家发货模版 Response VO")
@Data
@ExcelIgnoreUnannotated
@HeadRowHeight(25)
@ContentRowHeight(25)
@Accessors(chain = false)
public class TradeMchDeliveryImportVo {
@ExcelProperty("订单编号")
private String no;
@ExcelProperty("发货物流单号")
private String logisticsNo;
}
@@ -13,6 +13,8 @@ public class TradeOrderDeliveryReqVO {
@NotNull(message = "订单编号不能为空")
private Long id;
private String no;
@Schema(description = "发货物流公司编号", example = "1")
@NotNull(message = "发货物流公司不能为空")
private Long logisticsId;
@@ -2,6 +2,9 @@ package cn.iocoder.yudao.module.trade.controller.admin.order.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -13,10 +16,12 @@ import java.time.LocalDateTime;
@Schema(description = "管理后台 - 订单导出 Response VO")
@Data
@ExcelIgnoreUnannotated
@HeadRowHeight(25)
@ContentRowHeight(25)
public class TradeOrderListExportVo {
@ExcelProperty("订单编号")
private Long id;
private String no;
@ExcelProperty("订单状态")
private String orderStatus;
@@ -52,9 +57,11 @@ public class TradeOrderListExportVo {
private String logisticsNo;
@ExcelProperty("发货时间")
private LocalDateTime deliveryTime;
@ColumnWidth(50)
private String deliveryTime;
@ExcelProperty("下单时间")
private LocalDateTime createTime;
@ColumnWidth(50)
private String createTime;
}
@@ -1,5 +1,8 @@
package cn.iocoder.yudao.module.trade.convert.order;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -46,6 +49,7 @@ import org.mapstruct.factory.Mappers;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -146,7 +150,7 @@ public interface TradeOrderConvert {
// 用户
MemberUserRespDTO user = memberUserMap.get(order.getUserId());
exportEntity.setId(order.getId());
exportEntity.setNo(order.getNo());
exportEntity.setOrderStatus(TradeOrderStatusEnum.valueOfByStatus(order.getStatus()).getName());
String orderDesc = "";
@@ -165,9 +169,9 @@ public interface TradeOrderConvert {
exportEntity.setReceiverDetailAddress(order.getReceiverDetailAddress());
exportEntity.setLogisticsNo(order.getLogisticsNo());
if (ObjectUtil.isNotEmpty(order.getDeliveryTime())) {
exportEntity.setDeliveryTime(order.getDeliveryTime());
exportEntity.setDeliveryTime(LocalDateTimeUtil.format(order.getDeliveryTime(), DatePattern.NORM_DATETIME_FORMATTER));
}
exportEntity.setCreateTime(order.getCreateTime());
exportEntity.setCreateTime(LocalDateTimeUtil.format(order.getCreateTime(), DatePattern.NORM_DATETIME_FORMATTER));
return exportEntity;
});
}
@@ -47,6 +47,8 @@ public interface DeliveryExpressService {
*/
DeliveryExpressDO getDeliveryExpress(Long id);
DeliveryExpressDO getDeliveryExpress(String code);
/**
* 校验快递公司是否合法
*
@@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.express.Delive
import cn.iocoder.yudao.module.trade.convert.delivery.DeliveryExpressConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryExpressMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -73,6 +74,7 @@ public class DeliveryExpressServiceImpl implements DeliveryExpressService {
throw exception(EXPRESS_CODE_DUPLICATE);
}
}
private void validateDeliveryExpressExists(Long id) {
if (deliveryExpressMapper.selectById(id) == null) {
throw exception(EXPRESS_NOT_EXISTS);
@@ -84,6 +86,13 @@ public class DeliveryExpressServiceImpl implements DeliveryExpressService {
return deliveryExpressMapper.selectById(id);
}
@Override
public DeliveryExpressDO getDeliveryExpress(String code) {
return deliveryExpressMapper.selectOne(new LambdaQueryWrapper<DeliveryExpressDO>()
.eq(DeliveryExpressDO::getCode, code)
);
}
@Override
public DeliveryExpressDO validateDeliveryExpress(Long id) {
DeliveryExpressDO deliveryExpress = deliveryExpressMapper.selectById(id);
@@ -1,9 +1,6 @@
package cn.iocoder.yudao.module.trade.service.order;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
@@ -52,11 +49,11 @@ public interface TradeOrderUpdateService {
/**
* 同步订单的支付状态
*
* <p>
* 1. Quietly 表示,即使同步失败,也不会抛出异常
* 2. 什么时候回出现异常?因为是主动同步,可能和支付模块的回调通知 {@link #updateOrderPaid(Long, Long)} 存在并发冲突,导致抛出异常
*
* @param id 订单编号
* @param id 订单编号
* @param payOrderId 支付订单编号
*/
void syncOrderPayStatusQuietly(Long id, Long payOrderId);
@@ -68,6 +65,11 @@ public interface TradeOrderUpdateService {
*/
void deliveryOrder(TradeOrderDeliveryReqVO deliveryReqVO);
/**
* 导入发货模版
*/
void deliveryOrderByExcel(List<TradeMchDeliveryImportVo> list);
/**
* 【会员】收货交易订单
*
@@ -131,14 +133,14 @@ public interface TradeOrderUpdateService {
* 【管理员】核销订单
*
* @param userId 管理员编号
* @param id 订单编号
* @param id 订单编号
*/
void pickUpOrderByAdmin(Long userId, Long id);
/**
* 【管理员】核销订单
*
* @param userId 管理员编号
* @param userId 管理员编号
* @param pickUpVerifyCode 自提核销码
*/
void pickUpOrderByAdmin(Long userId, String pickUpVerifyCode);
@@ -204,9 +206,9 @@ public interface TradeOrderUpdateService {
/**
* 取消支付订单
*
* @param userId 用户编号
* @param orderId 订单编号
* @param cancelType 取消类型
* @param userId 用户编号
* @param orderId 订单编号
* @param cancelType 取消类型
*/
void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
@@ -28,10 +28,7 @@ import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdatePriceReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.*;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementReqVO;
import cn.iocoder.yudao.module.trade.controller.app.order.vo.AppTradeOrderSettlementRespVO;
@@ -65,6 +62,7 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper;
import cn.iocoder.yudao.module.trade.util.SignatureUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.kuaidi100.sdk.api.LabelV2;
@@ -87,6 +85,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
@@ -98,8 +97,10 @@ import javax.validation.constraints.NotNull;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.minusTime;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -453,6 +454,44 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
getSelf().sendDeliveryOrderMessage(order, deliveryReqVO);
}
/**
* 导入发货模版
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void deliveryOrderByExcel(List<TradeMchDeliveryImportVo> list) {
if (ObjectUtil.isEmpty(list)) throw exception(OMS_EXPRESS_EXCEL_EMPTY_ERROR);
List<String> noList = list.stream().filter(item -> ObjectUtil.isNotEmpty(item.getNo())).map(TradeMchDeliveryImportVo::getNo).distinct().collect(Collectors.toList());
// 查询订单
List<TradeOrderDO> orderList = tradeOrderMapper.selectList(new LambdaUpdateWrapper<TradeOrderDO>().in(TradeOrderDO::getNo, noList));
// 查询商家发货模版-CJFH
DeliveryExpressDO deliveryExpress = deliveryExpressService.getDeliveryExpress("CJFH");
if (ObjectUtil.isEmpty(deliveryExpress)) throw exception(OMS_EXPRESS_CJFH_TEMP_EMPTY_ERROR);
List<TradeOrderDeliveryReqVO> deliverylist = new ArrayList<>();
for (TradeOrderDO v : orderList) {
TradeMchDeliveryImportVo target = list.stream().filter(item -> ObjectUtil.equal(item.getNo(), v.getNo())).findFirst().orElse(null);
if (ObjectUtil.isEmpty(target)) continue;
TradeOrderDeliveryReqVO deliveryReqVO = new TradeOrderDeliveryReqVO();
deliveryReqVO.setId(v.getId());
deliveryReqVO.setNo(v.getNo());
deliveryReqVO.setLogisticsId(deliveryExpress.getId());
deliveryReqVO.setLogisticsNo(target.getLogisticsNo());
deliverylist.add(deliveryReqVO);
}
// 批量发货
deliverylist.forEach(item -> {
try {
getSelf().deliveryOrder(item);
} catch (Exception e) {
log.error("批量发货item:{},异常:{}", JSON.toJSON(item), e.getMessage());
throw exception(OMS_EXPRESS_ORDER_IMPORT_ERROR, item.getNo(), e.getMessage());
}
});
}
@Override
public void deliverySto(Long id) {
// 1.1 校验并获得交易订单(可发货)
@@ -617,7 +656,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
recManInfo.setMobile(order.getReceiverMobile());
recManInfo.setPrintAddr(AreaUtils.format(
order.getReceiverAreaId()).replace(" ", "")
+order.getReceiverDetailAddress());
+ order.getReceiverDetailAddress());
ManInfo sendManInfo = new ManInfo();
sendManInfo.setName("杨林");
@@ -653,9 +692,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
try {
HttpResult httpResult = baseClient.execute(printReq);
System.out.println(httpResult);
if (httpResult.getStatus() == org.apache.http.HttpStatus.SC_OK && StringUtils.isNotBlank(httpResult.getBody())){
Result<OrderResult> result = new Gson().fromJson(httpResult.getBody(),new TypeToken<Result<OrderResult>>(){}.getType());
if (result.getCode() == 200){
if (httpResult.getStatus() == org.apache.http.HttpStatus.SC_OK && StringUtils.isNotBlank(httpResult.getBody())) {
Result<OrderResult> result = new Gson().fromJson(httpResult.getBody(), new TypeToken<Result<OrderResult>>() {
}.getType());
if (result.getCode() == 200) {
// 2. 更新订单为已发货
TradeOrderDO updateOrderObj = new TradeOrderDO();
// 2.1 快递发货
@@ -688,10 +728,10 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
// 4.2 发送订阅消息
getSelf().sendDeliveryOrderMessage(order, deliveryReqVO);
return result.getData().getLabel();
}else {
} else {
throw exception(OMS_EXPRESS_ORDER_CREATE_ERROR, result.getMessage());
}
}else {
} else {
throw exception(OMS_EXPRESS_ORDER_CREATE_ERROR, "接口无返回内容");
}
} catch (Exception e) {
@@ -733,16 +773,16 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
printReq.setT(t);
printReq.setKey(key);
printReq.setMethod(ApiInfoConstant.CANCEL_METHOD);
printReq.setSign(SignUtils.printSign(param,t,key,secret));
printReq.setSign(SignUtils.printSign(param, t, key, secret));
printReq.setParam(param);
IBaseClient baseClient = new LabelV2();
try {
HttpResult httpResult = baseClient.execute(printReq);
System.out.println(httpResult);
if (httpResult.getStatus() == org.apache.http.HttpStatus.SC_OK && StringUtils.isNotBlank(httpResult.getBody())){
if (httpResult.getStatus() == org.apache.http.HttpStatus.SC_OK && StringUtils.isNotBlank(httpResult.getBody())) {
return httpResult.getBody();
}else {
} else {
throw exception(OMS_EXPRESS_ORDER_CANCEL_ERROR, "接口无返回内容");
}
} catch (Exception e) {