chenxiaofei 2 месяцев назад
Родитель
Сommit
1848c86990

+ 5 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/web/constant/HttpStatus.java

@@ -47,6 +47,11 @@ public class HttpStatus {
      */
     public static final int GOODS_PUT_ON_SHELVES_FAIL_CODE = 60667;
 
+    /**
+     * 库存不足需确认状态码
+     */
+    public static final int STOCK_INSUFFICIENT_CONFIRM_CODE = 60668;
+
     /**
      * 全局异常提示信息
      */

+ 9 - 0
sckw-modules-api/sckw-contract-api/src/main/java/com/sckw/contract/api/RemoteContractService.java

@@ -157,4 +157,13 @@ public interface RemoteContractService {
     ContractDetailRespVo signTradeContract(ContractAuditPara contractAuditPara);
 
     LogisticDetailRespVo signLogisticsContract(ContractAuditPara contractAuditPara);
+
+    /**
+     * 更新贸易合同商品已采购数量(累加履行量)
+     *
+     * @param contractId 贸易合同id
+     * @param goodsId    商品id
+     * @param orderAmount 本次下单数量
+     */
+    void updateTradeContractGoodsPerformed(Long contractId, Long goodsId, BigDecimal orderAmount);
 }

+ 15 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/dubbo/RemoteContractServiceImpl.java

@@ -703,5 +703,20 @@ public class RemoteContractServiceImpl implements RemoteContractService {
         throw new BusinessException("未找到合同");
     }
 
+    @Override
+    public void updateTradeContractGoodsPerformed(Long contractId, Long goodsId, BigDecimal orderAmount) {
+        KwcContractTradeGoods tradeGoods = kwcContractTradeGoodsMapper.selectOne(new LambdaQueryWrapper<KwcContractTradeGoods>()
+                .eq(KwcContractTradeGoods::getContractId, contractId)
+                .eq(KwcContractTradeGoods::getGoodsId, goodsId)
+                .eq(KwcContractTradeGoods::getDelFlag, 0)
+        );
+        if (Objects.isNull(tradeGoods)) {
+            throw new BusinessException("贸易合同商品信息不存在");
+        }
+        BigDecimal performed = tradeGoods.getPerformedAmount() != null ? tradeGoods.getPerformedAmount() : BigDecimal.ZERO;
+        tradeGoods.setPerformedAmount(performed.add(orderAmount));
+        tradeGoods.setUpdateTime(new Date());
+        kwcContractTradeGoodsMapper.updateById(tradeGoods);
+    }
 
 }

+ 14 - 1
sckw-modules/sckw-order/src/main/java/com/sckw/order/controller/KwoTradeOrderController.java

@@ -309,6 +309,12 @@ public class KwoTradeOrderController {
      * @Param params:
      * @return: com.sckw.core.web.response.HttpResult
      */
+    @GetMapping(value = "/countStatistic", produces = MediaType.APPLICATION_JSON_VALUE)
+    @Operation(summary = "统计贸易订单数量")
+    public HttpResult countStatistic() {
+        return HttpResult.ok("统计贸易订单数量成功", kwoTradeOrderService.countStatistic());
+    }
+
     @PostMapping(value = "/tradeOrderExport", produces = MediaType.APPLICATION_JSON_VALUE)
     public void export(@RequestBody @Validated TradeOrderListExportParam params, HttpServletResponse response) {
         List<TradeOrderListExport> list = kwoTradeOrderService.export(params);
@@ -355,6 +361,13 @@ public class KwoTradeOrderController {
         return HttpResult.ok("增补运力查询成功");
     }
 
-
+    @GlobalTransactional(name = "default_tx_group")
+    @RepeatSubmit(interval = 1000, message = "前方拥堵,请稍后尝试")
+    @PostMapping(value = "/cancelOrder", produces = MediaType.APPLICATION_JSON_VALUE)
+    @Operation(summary = "买家撤销贸易订单", description = "买家撤销贸易订单")
+    public HttpResult cancelOrder(@RequestBody @Validated CancelTradeOrderParam param) {
+        kwoTradeOrderService.cancelTradeOrder(param);
+        return HttpResult.ok("订单撤销成功");
+    }
 
 }

+ 1 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/enums/TradeOrderStatusEnum.java

@@ -25,6 +25,7 @@ public enum TradeOrderStatusEnum {
     DEAL(3, "结算中"),
     SUCCESS(4, "已完成"),
     BACK(5, "审核驳回"),
+    CANCEL(6, "已取消"),
     ;
     private final Integer code;
     private final String msg;

+ 24 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/CancelTradeOrderParam.java

@@ -0,0 +1,24 @@
+package com.sckw.order.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.hibernate.validator.constraints.Length;
+
+@Getter
+@Setter
+@ToString
+@Schema(description = "买家撤销贸易订单参数")
+public class CancelTradeOrderParam {
+
+    @NotNull(message = "订单id不能为空")
+    @Schema(description = "贸易订单id")
+    private Long id;
+
+    @Length(max = 200, message = "撤销原因最多200字")
+    @Schema(description = "撤销原因")
+    private String remark;
+
+}

+ 18 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/TradeOrderCountReq.java

@@ -0,0 +1,18 @@
+package com.sckw.order.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * @desc: trade order count request
+ * @author: Codex
+ * @date: 2026-03-11
+ */
+@Getter
+@Setter
+@ToString
+@Schema(description = "trade order count request")
+public class TradeOrderCountReq {
+}

+ 5 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/TradeOrderParam.java

@@ -81,5 +81,10 @@ public class TradeOrderParam {
     @Schema(description = "结束时间")
     private LocalDate endTime;
 
+    /**
+     * 库存不足时是否确认继续下单
+     */
+    @Schema(description = "库存不足确认继续下单")
+    private Boolean confirmInsufficientStock;
 
 }

+ 23 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/TradeOrderReceiveAddressReq.java

@@ -0,0 +1,23 @@
+package com.sckw.order.model.vo.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * @desc: trade order receive address request
+ * @author: cxf
+ * @date: 2026-03-11
+ */
+@Getter
+@Setter
+@ToString
+@Schema(description = "trade order receive address request")
+public class TradeOrderReceiveAddressReq {
+
+    @NotNull(message = "tradeOrderId不能为空")
+    @Schema(description = "订单贸易id")
+    private Long tradeOrderId;
+}

+ 45 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/res/TradeOrderCountResp.java

@@ -0,0 +1,45 @@
+package com.sckw.order.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * @desc: trade order count response
+ * @author: cxf
+ * @date: 2026-03-11
+ */
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+@Schema(description = "查询订单状态数量响应")
+public class TradeOrderCountResp {
+
+    @Schema(description = "状态列表")
+    private List<TradeOrderCountItem> items;
+    @Schema(description = "总数")
+    private Long totalCount;
+    @Getter
+    @Setter
+    @ToString
+    @Accessors(chain = true)
+    @Schema(description = "状态详情")
+    public static class TradeOrderCountItem {
+
+        @Schema(description = "状态")
+        private Integer status;
+
+        @Schema(description = "状态描述")
+        private String statusDesc;
+
+        @Schema(description = "状态数量")
+        private Long statusCount;
+
+
+    }
+}

+ 29 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/res/TradeOrderReceiveAddressResp.java

@@ -0,0 +1,29 @@
+package com.sckw.order.model.vo.res;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+/**
+ * @desc: trade order receive address response
+ * @author: Codex
+ * @date: 2026-03-11
+ */
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+@Schema(description = "查询收货地址响应")
+public class TradeOrderReceiveAddressResp {
+
+    @Schema(description = "详细地址")
+    private String detailAddress;
+
+    @Schema(description = "经度")
+    private String lng;
+
+    @Schema(description = "纬度")
+    private String lat;
+}

+ 11 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/repository/KwoTradeOrderUnitRepository.java

@@ -7,6 +7,7 @@ import com.sckw.order.dao.KwoTradeOrderUnitMapper;
 import com.sckw.order.model.KwoTradeOrderUnit;
 import org.springframework.stereotype.Repository;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 
@@ -35,4 +36,14 @@ public class KwoTradeOrderUnitRepository extends ServiceImpl<KwoTradeOrderUnitMa
                 .eq(BaseModel::getDelFlag,0)
                 .in(KwoTradeOrderUnit::getEntId, entIds));
     }
+
+    public List<KwoTradeOrderUnit> queryByEntIdAndUnitType(Long entId, String unitType) {
+        if (entId == null) {
+            return Collections.emptyList();
+        }
+        return list(Wrappers.<KwoTradeOrderUnit>lambdaQuery()
+                .eq(BaseModel::getDelFlag, 0)
+                .eq(KwoTradeOrderUnit::getEntId, entId)
+                .eq(KwoTradeOrderUnit::getUnitType, unitType));
+    }
 }

+ 149 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java

@@ -8,6 +8,7 @@ import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.github.pagehelper.PageHelper;
@@ -52,6 +53,7 @@ import com.sckw.order.model.vo.res.*;
 import com.sckw.order.model.vo.res.GoodsInfoDetailRes;
 import com.sckw.order.model.vo.res.OrderDetailRes;
 import com.sckw.order.model.vo.res.UnitInfoDetailRes;
+import com.sckw.order.repository.KwoTradeOrderUnitRepository;
 import com.sckw.payment.api.dubbo.PayCenterDubboService;
 import com.sckw.payment.api.dubbo.PaymentDubboService;
 import com.sckw.payment.api.model.WalletFreeze;
@@ -142,6 +144,7 @@ public class KwoTradeOrderService {
     private final KwoTradeOrderGoodsUnitService kwoTradeOrderGoodsUnitService;
     private final KwoTradeOrderAmountService tradeOrderAmountService;
     private final KwoTradeOrderTransportService kwoTradeOrderTransportService;
+    private final KwoTradeOrderUnitRepository kwoTradeOrderUnitRepository;
     @Value("${url.order.list.valet.pc}")
     private String pcValetListUrl;
 
@@ -1811,6 +1814,66 @@ public class KwoTradeOrderService {
      * @Param params:
      * @return: java.util.List<com.sckw.report.service.param.TradeOrderListExport>
      */
+
+
+    public TradeOrderCountResp countStatistic() {
+        Long entId = LoginUserHolder.getEntId();
+        Set<Long> tradeOrderIds = kwoTradeOrderUnitRepository
+                .queryByEntIdAndUnitType(entId, OrderUnitTypeEnum.PURCHASE.getType())
+                .stream()
+                .map(KwoTradeOrderUnit::getTOrderId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        Long totalCount = 0L;
+        Map<Integer, Long> statusCountMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
+        if (CollUtil.isNotEmpty(tradeOrderIds)) {
+            QueryWrapper<KwoTradeOrder> queryWrapper = new QueryWrapper<>();
+            queryWrapper.select("status", "COUNT(1) AS statusCount")
+                    .eq("del_flag", Global.NO)
+                    .in("id", tradeOrderIds)
+                    .groupBy("status")
+                    .orderByAsc("status");
+
+            List<Map<String, Object>> statisticMaps = kwoTradeOrderMapper.selectMaps(queryWrapper);
+            if (CollUtil.isNotEmpty(statisticMaps)) {
+                statusCountMap = statisticMaps.stream()
+                        .filter(item -> Objects.nonNull(item.get("status")))
+                        .collect(Collectors.toMap(item -> Integer.parseInt(String.valueOf(item.get("status"))),
+                                item -> Long.parseLong(String.valueOf(item.get("statusCount"))),
+                                Long::sum));
+            }
+
+            totalCount = Optional.ofNullable(kwoTradeOrderMapper.selectCount(
+                    new LambdaQueryWrapper<KwoTradeOrder>()
+                            .eq(KwoTradeOrder::getDelFlag, Global.NO)
+                            .in(KwoTradeOrder::getId, tradeOrderIds))).orElse(0L);
+        }
+
+        Set<Integer> statuses = new TreeSet<>();
+        TradeOrderStatusEnum.getSortList().stream().map(TradeOrderStatusEnum::getCode).forEach(statuses::add);
+        statuses.addAll(statusCountMap.keySet());
+
+        List<TradeOrderCountResp.TradeOrderCountItem> items = new ArrayList<>(statuses.size());
+        for (Integer status : statuses) {
+            TradeOrderCountResp.TradeOrderCountItem item = new TradeOrderCountResp.TradeOrderCountItem();
+            item.setStatus(status)
+                    .setStatusDesc(getTradeOrderStatusDesc(status))
+                    .setStatusCount(statusCountMap.getOrDefault(status, 0L));
+            items.add(item);
+        }
+        return new TradeOrderCountResp().setTotalCount(totalCount).setItems(items);
+    }
+
+    private String getTradeOrderStatusDesc(Integer status) {
+        String statusDesc = TradeOrderStatusEnum.getMsg(status);
+        if (StrUtil.isNotBlank(statusDesc)) {
+            return statusDesc;
+        }
+        statusDesc = OrderStatusEnum.getMsg(status);
+        return StrUtil.isNotBlank(statusDesc) ? statusDesc : "unknown";
+    }
+
     public List<TradeOrderListExport> export(TradeOrderListExportParam params) {
         TradeOrderListSelectDTO dto = new TradeOrderListSelectDTO();
         List<Long> ids = new ArrayList<>();
@@ -1948,6 +2011,39 @@ public class KwoTradeOrderService {
         KwpGoods goodsById = goodsInfoService.getGoodsById(tradeOrderParam.getGoodsId());
 
         TradeContractResDto tradeContractResDto = checkPara(tradeOrderParam, order, goodsById);
+
+        // ====== 下单前校验 start ======
+        // 1. 验证线下钱包预付余额是否足够,不足则提示"线下钱包预付余额不足"
+        // TODO: 后续对接外部系统,验证线下钱包预付余额
+        // validateOfflineWalletBalance(buyEntId, order.getPrice());
+
+        // 2. 验证商品是否开启库存(amount不为null代表已开启库存,null代表无限库存无需验证)
+        if (goodsById.getAmount() != null && goodsById.getAmount().compareTo(tradeOrderParam.getAmount()) < 0) {
+            if (!Boolean.TRUE.equals(tradeOrderParam.getConfirmInsufficientStock())) {
+                throw new CustomPromptException(HttpStatus.STOCK_INSUFFICIENT_CONFIRM_CODE, "当前库存可能不足,是否依旧下单?");
+            }
+        }
+
+        // 3. 线下钱包扣减预付余额、增加冻结金额
+        // TODO: 后续对接外部系统,执行线下钱包扣减预付余额、增加冻结金额
+        // deductOfflineWalletAndFreeze(buyEntId, order.getPrice());
+
+        // 4. 记录钱包金额账数据,类型为冻结,备注为"贸易订单号:xxx 下单冻结"
+        // TODO: 后续对接外部系统,记录钱包流水
+        // recordWalletTransaction(buyEntId, order.getPrice(), "冻结", "贸易订单号:" + order.getTOrderNo() + " 下单冻结");
+
+        // 5. 验证贸易合同限量采购,若为限量采购则校验剩余可采购数量并扣减本次下单量
+        GoodsInfoDto contractGoodsInfo = tradeContractResDto.getGoodsInfoDto();
+        if (contractGoodsInfo.getAmount() != null && contractGoodsInfo.getAmount().compareTo(BigDecimal.ZERO) > 0) {
+            BigDecimal performed = contractGoodsInfo.getPerformedAmount() != null ? contractGoodsInfo.getPerformedAmount() : BigDecimal.ZERO;
+            BigDecimal remaining = contractGoodsInfo.getAmount().subtract(performed);
+            if (tradeOrderParam.getAmount().compareTo(remaining) > 0) {
+                throw new BusinessException("当前贸易合同该商品为限量采购,剩余可采购数量为" + remaining.stripTrailingZeros().toPlainString() + ",本次下单数量超出限额");
+            }
+            remoteContractService.updateTradeContractGoodsPerformed(tradeOrderParam.getTradeContractId(), tradeOrderParam.getGoodsId(), tradeOrderParam.getAmount());
+        }
+        // ====== 下单前校验 end ======
+
         WalletFreeze walletFreeze = new WalletFreeze();
         walletFreeze.setOrderNo(order.getTOrderNo());
         walletFreeze.setMoney(order.getPrice());
@@ -2086,6 +2182,59 @@ public class KwoTradeOrderService {
 
     }
 
+    /**
+     * 买家撤销贸易订单
+     */
+    public void cancelTradeOrder(CancelTradeOrderParam param) {
+        KwoTradeOrder order = kwoTradeOrderMapper.selectOne(
+                new LambdaQueryWrapper<KwoTradeOrder>()
+                        .eq(KwoTradeOrder::getId, param.getId())
+                        .eq(KwoTradeOrder::getDelFlag, 0)
+        );
+        if (Objects.isNull(order)) {
+            throw new BusinessException("贸易订单不存在");
+        }
+        if (!Objects.equals(order.getStatus(), TradeOrderStatusEnum.AUDIT.getCode())) {
+            throw new BusinessException("当前订单状态不允许撤销");
+        }
+
+        // 1. 线下钱包加回预付余额、减冻结金额
+        // TODO: 后续对接外部系统,执行线下钱包加回预付余额、减冻结金额
+        // refundOfflineWalletPrepaid(buyEntId, order.getPrice());
+
+        // 2. 记录钱包金额账数据,类型为解冻,备注为"贸易订单号:xxx 撤销订单"
+        // TODO: 后续对接外部系统,记录钱包流水
+        // recordWalletTransaction(buyEntId, order.getPrice(), "解冻", "贸易订单号:" + order.getTOrderNo() + " 撤销订单");
+
+        // 线上钱包解冻
+        WalletFreeze walletFreeze = new WalletFreeze();
+        walletFreeze.setTTradeOrderId(order.getId());
+        walletFreeze.setOrderNo(order.getTOrderNo());
+        BaseResult<Boolean> unfreezeResult = paymentDubboService.unfreezeMoney(walletFreeze);
+        if (unfreezeResult.getCode() != 60200) {
+            throw new BusinessException(unfreezeResult.getMessage());
+        }
+
+        // 3. 贸易合同该商品有采购数量,则更新采购数量(加回本单总量)
+        KwoTradeOrderGoods orderGoods = kwoTradeOrderGoodsService.getByOrderId(order.getId());
+        KwoTradeOrderContract orderContract = kwoTradeOrderContractService.getByOrderId(order.getId());
+        if (Objects.nonNull(orderGoods) && Objects.nonNull(orderContract)) {
+            TradeContractResDto contractRes = remoteContractService.queryTradeContract(orderContract.getContractId(), orderGoods.getGoodsId());
+            if (Objects.nonNull(contractRes) && Objects.nonNull(contractRes.getGoodsInfoDto())) {
+                GoodsInfoDto goodsInfo = contractRes.getGoodsInfoDto();
+                if (goodsInfo.getAmount() != null && goodsInfo.getAmount().compareTo(BigDecimal.ZERO) > 0) {
+                    remoteContractService.updateTradeContractGoodsPerformed(
+                            orderContract.getContractId(), orderGoods.getGoodsId(), order.getAmount().negate()
+                    );
+                }
+            }
+        }
+
+        // 4. 更新贸易订单状态为"取消"
+        order.setStatus(TradeOrderStatusEnum.CANCEL.getCode());
+        kwoTradeOrderMapper.updateById(order);
+    }
+
     private TradeContractResDto checkPara(TradeOrderParam tradeOrderParam, KwoTradeOrder order, KwpGoods goodsById) {
         if (Objects.isNull(goodsById) || Objects.equals(goodsById.getDelFlag(), Global.YES)) {
             throw new BusinessException("商品信息不存在");