Commit 30de2176a4d547bc754943e40efaa27609146188

Authored by YunaiV
1 parent 94247add

✨ ERP:增加 ERP 销售订单的实现 50%

Showing 14 changed files with 522 additions and 264 deletions
yudao-module-erp/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/enums/sale/ErpSaleOrderStatusEnum.java deleted
1   -package cn.iocoder.yudao.module.erp.enums.sale;
2   -
3   -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
4   -import lombok.AllArgsConstructor;
5   -import lombok.Getter;
6   -
7   -import java.util.Arrays;
8   -
9   -/**
10   - * ERP 销售订单的状态枚举
11   - *
12   - * @author 芋道源码
13   - */
14   -@AllArgsConstructor
15   -@Getter
16   -public enum ErpSaleOrderStatusEnum implements IntArrayValuable {
17   -
18   - AUDIT_NONE(0, "未审核"),
19   - AUDIT_PASS(10, "已审核"),
20   - SALE_PART(20, "部分销售"),
21   - SALE_ALL(21, "完成销售"),
22   - ;
23   -
24   - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ErpSaleOrderStatusEnum::getStatus).toArray();
25   -
26   - /**
27   - * 状态
28   - */
29   - private final Integer status;
30   - /**
31   - * 状态名
32   - */
33   - private final String name;
34   -
35   - @Override
36   - public int[] array() {
37   - return ARRAYS;
38   - }
39   -
40   -}
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
1 1 package cn.iocoder.yudao.module.erp.controller.admin.sale;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
8 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  11 +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO;
9 12 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
10 13 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderRespVO;
11 14 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
  15 +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpCustomerDO;
12 16 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
  17 +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
  18 +import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
  19 +import cn.iocoder.yudao.module.erp.service.sale.ErpCustomerService;
13 20 import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
  21 +import cn.iocoder.yudao.module.erp.service.stock.ErpStockService;
  22 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  23 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
14 24 import io.swagger.v3.oas.annotations.Operation;
15 25 import io.swagger.v3.oas.annotations.Parameter;
16 26 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -22,9 +32,13 @@ import org.springframework.validation.annotation.Validated;
22 32 import org.springframework.web.bind.annotation.*;
23 33  
24 34 import java.io.IOException;
  35 +import java.math.BigDecimal;
25 36 import java.util.List;
  37 +import java.util.Map;
26 38  
27 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  40 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
  41 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 42 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
29 43  
30 44  
... ... @@ -36,65 +50,116 @@ public class ErpSaleOrderController {
36 50  
37 51 @Resource
38 52 private ErpSaleOrderService saleOrderService;
  53 + @Resource
  54 + private ErpStockService stockService;
  55 + @Resource
  56 + private ErpProductService productService;
  57 + @Resource
  58 + private ErpCustomerService customerService;
  59 +
  60 + @Resource
  61 + private AdminUserApi adminUserApi;
39 62  
40   - // TODO 芋艿:待 review
41 63 @PostMapping("/create")
42   - @Operation(summary = "创建ERP 销售订单")
43   - @PreAuthorize("@ss.hasPermission('erp:sale-order:create')")
  64 + @Operation(summary = "创建销售订单")
  65 + @PreAuthorize("@ss.hasPermission('erp:stock-out:create')")
44 66 public CommonResult<Long> createSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO createReqVO) {
45 67 return success(saleOrderService.createSaleOrder(createReqVO));
46 68 }
47 69  
48   - // TODO 芋艿:待 review
49 70 @PutMapping("/update")
50   - @Operation(summary = "更新ERP 销售订单")
51   - @PreAuthorize("@ss.hasPermission('erp:sale-order:update')")
  71 + @Operation(summary = "更新销售订单")
  72 + @PreAuthorize("@ss.hasPermission('erp:stock-out:update')")
52 73 public CommonResult<Boolean> updateSaleOrder(@Valid @RequestBody ErpSaleOrderSaveReqVO updateReqVO) {
53 74 saleOrderService.updateSaleOrder(updateReqVO);
54 75 return success(true);
55 76 }
56 77  
57   - // TODO 芋艿:待 review
  78 + @PutMapping("/update-status")
  79 + @Operation(summary = "更新销售订单的状态")
  80 + @PreAuthorize("@ss.hasPermission('erp:stock-out:update-status')")
  81 + public CommonResult<Boolean> updateSaleOrderStatus(@RequestParam("id") Long id,
  82 + @RequestParam("status") Integer status) {
  83 + saleOrderService.updateSaleOrderStatus(id, status);
  84 + return success(true);
  85 + }
  86 +
58 87 @DeleteMapping("/delete")
59   - @Operation(summary = "删除ERP 销售订单")
60   - @Parameter(name = "id", description = "编号", required = true)
61   - @PreAuthorize("@ss.hasPermission('erp:sale-order:delete')")
62   - public CommonResult<Boolean> deleteSaleOrder(@RequestParam("id") Long id) {
63   - saleOrderService.deleteSaleOrder(id);
  88 + @Operation(summary = "删除销售订单")
  89 + @Parameter(name = "ids", description = "编号数组", required = true)
  90 + @PreAuthorize("@ss.hasPermission('erp:stock-out:delete')")
  91 + public CommonResult<Boolean> deleteSaleOrder(@RequestParam("ids") List<Long> ids) {
  92 + saleOrderService.deleteSaleOrder(ids);
64 93 return success(true);
65 94 }
66 95  
67   - // TODO 芋艿:待 review
68 96 @GetMapping("/get")
69   - @Operation(summary = "获得ERP 销售订单")
  97 + @Operation(summary = "获得销售订单")
70 98 @Parameter(name = "id", description = "编号", required = true, example = "1024")
71   - @PreAuthorize("@ss.hasPermission('erp:sale-order:query')")
  99 + @PreAuthorize("@ss.hasPermission('erp:stock-out:query')")
72 100 public CommonResult<ErpSaleOrderRespVO> getSaleOrder(@RequestParam("id") Long id) {
73 101 ErpSaleOrderDO saleOrder = saleOrderService.getSaleOrder(id);
74   - return success(BeanUtils.toBean(saleOrder, ErpSaleOrderRespVO.class));
  102 + if (saleOrder == null) {
  103 + return success(null);
  104 + }
  105 + List<ErpSaleOrderItemDO> saleOrderItemList = saleOrderService.getSaleOrderItemListByOrderId(id);
  106 + Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
  107 + convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductId));
  108 + return success(BeanUtils.toBean(saleOrder, ErpSaleOrderRespVO.class, saleOrderVO ->
  109 + saleOrderVO.setItems(BeanUtils.toBean(saleOrderItemList, ErpSaleOrderRespVO.Item.class, item -> {
  110 + BigDecimal stockCount = stockService.getStockCount(item.getProductId());
  111 + item.setStockCount(stockCount != null ? stockCount : BigDecimal.ZERO);
  112 + MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
  113 + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()));
  114 + }))));
75 115 }
76 116  
77   - // TODO 芋艿:待 review
78 117 @GetMapping("/page")
79   - @Operation(summary = "获得ERP 销售订单分页")
80   - @PreAuthorize("@ss.hasPermission('erp:sale-order:query')")
  118 + @Operation(summary = "获得销售订单分页")
  119 + @PreAuthorize("@ss.hasPermission('erp:stock-out:query')")
81 120 public CommonResult<PageResult<ErpSaleOrderRespVO>> getSaleOrderPage(@Valid ErpSaleOrderPageReqVO pageReqVO) {
82 121 PageResult<ErpSaleOrderDO> pageResult = saleOrderService.getSaleOrderPage(pageReqVO);
83   - return success(BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class));
  122 + return success(buildSaleOrderVOPageResult(pageResult));
84 123 }
85 124  
86   - // TODO 芋艿:待 review
87 125 @GetMapping("/export-excel")
88   - @Operation(summary = "导出ERP 销售订单 Excel")
89   - @PreAuthorize("@ss.hasPermission('erp:sale-order:export')")
  126 + @Operation(summary = "导出销售订单 Excel")
  127 + @PreAuthorize("@ss.hasPermission('erp:stock-out:export')")
90 128 @OperateLog(type = EXPORT)
91 129 public void exportSaleOrderExcel(@Valid ErpSaleOrderPageReqVO pageReqVO,
92   - HttpServletResponse response) throws IOException {
  130 + HttpServletResponse response) throws IOException {
93 131 pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
94   - List<ErpSaleOrderDO> list = saleOrderService.getSaleOrderPage(pageReqVO).getList();
  132 + List<ErpSaleOrderRespVO> list = buildSaleOrderVOPageResult(saleOrderService.getSaleOrderPage(pageReqVO)).getList();
95 133 // 导出 Excel
96   - ExcelUtils.write(response, "ERP 销售订单.xls", "数据", ErpSaleOrderRespVO.class,
97   - BeanUtils.toBean(list, ErpSaleOrderRespVO.class));
  134 + ExcelUtils.write(response, "销售订单.xls", "数据", ErpSaleOrderRespVO.class, list);
  135 + }
  136 +
  137 + private PageResult<ErpSaleOrderRespVO> buildSaleOrderVOPageResult(PageResult<ErpSaleOrderDO> pageResult) {
  138 + if (CollUtil.isEmpty(pageResult.getList())) {
  139 + return PageResult.empty(pageResult.getTotal());
  140 + }
  141 + // 1.1 出库项
  142 + List<ErpSaleOrderItemDO> saleOrderItemList = saleOrderService.getSaleOrderItemListByOrderIds(
  143 + convertSet(pageResult.getList(), ErpSaleOrderDO::getId));
  144 + Map<Long, List<ErpSaleOrderItemDO>> saleOrderItemMap = convertMultiMap(saleOrderItemList, ErpSaleOrderItemDO::getOrderId);
  145 + // 1.2 商品信息
  146 + Map<Long, ErpProductRespVO> productMap = productService.getProductVOMap(
  147 + convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductId));
  148 + // 1.3 客户信息
  149 + Map<Long, ErpCustomerDO> customerMap = customerService.getCustomerMap(
  150 + convertSet(pageResult.getList(), ErpSaleOrderDO::getCustomerId));
  151 + // 1.4 管理员信息
  152 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  153 + convertSet(pageResult.getList(), erpStockRecordDO -> Long.parseLong(erpStockRecordDO.getCreator())));
  154 + // 2. 开始拼接
  155 + return BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class, saleOrder -> {
  156 + saleOrder.setItems(BeanUtils.toBean(saleOrderItemMap.get(saleOrder.getId()), ErpSaleOrderRespVO.Item.class,
  157 + item -> MapUtils.findAndThen(productMap, item.getProductId(), product -> item.setProductName(product.getName())
  158 + .setProductBarCode(product.getBarCode()).setProductUnitName(product.getUnitName()))));
  159 + saleOrder.setProductNames(CollUtil.join(saleOrder.getItems(), ",", ErpSaleOrderRespVO.Item::getProductName));
  160 + MapUtils.findAndThen(customerMap, saleOrder.getCustomerId(), supplier -> saleOrder.setCustomerName(supplier.getName()));
  161 + MapUtils.findAndThen(userMap, Long.parseLong(saleOrder.getCreator()), user -> saleOrder.setCreatorName(user.getNickname()));
  162 + });
98 163 }
99 164  
100 165 }
101 166 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java
... ... @@ -17,6 +17,19 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
17 17 @ToString(callSuper = true)
18 18 public class ErpSaleOrderPageReqVO extends PageParam {
19 19  
  20 + /**
  21 + * 入库状态 - 无
  22 + */
  23 + public static final Integer IN_STATUS_NONE = 0;
  24 + /**
  25 + * 入库状态 - 部分
  26 + */
  27 + public static final Integer IN_STATUS_PART = 1;
  28 + /**
  29 + * 入库状态 - 全部
  30 + */
  31 + public static final Integer IN_STATUS_ALL = 2;
  32 +
20 33 @Schema(description = "销售单编号", example = "XS001")
21 34 private String no;
22 35  
... ... @@ -28,7 +41,7 @@ public class ErpSaleOrderPageReqVO extends PageParam {
28 41 private LocalDateTime[] orderTime;
29 42  
30 43 @Schema(description = "备注", example = "你猜")
31   - private String description;
  44 + private String remark;
32 45  
33 46 @Schema(description = "销售状态", example = "2")
34 47 private Integer status;
... ... @@ -36,4 +49,10 @@ public class ErpSaleOrderPageReqVO extends PageParam {
36 49 @Schema(description = "创建者")
37 50 private String creator;
38 51  
  52 + @Schema(description = "产品编号", example = "1")
  53 + private Long productId;
  54 +
  55 + @Schema(description = "入库状态", example = "2")
  56 + private Integer inStatus;
  57 +
39 58 }
40 59 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
... ... @@ -3,12 +3,13 @@ package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
3 3 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 4 import com.alibaba.excel.annotation.ExcelProperty;
5 5 import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
6 7 import lombok.Data;
7 8  
8 9 import java.math.BigDecimal;
9 10 import java.time.LocalDateTime;
  11 +import java.util.List;
10 12  
11   -// TODO 芋艿:导出最后搞
12 13 @Schema(description = "管理后台 - ERP 销售订单 Response VO")
13 14 @Data
14 15 @ExcelIgnoreUnannotated
... ... @@ -23,56 +24,103 @@ public class ErpSaleOrderRespVO {
23 24 private String no;
24 25  
25 26 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724")
26   - @ExcelProperty("客户编号")
27 27 private Long customerId;
  28 + @Schema(description = "客户名称", example = "芋道")
  29 + @ExcelProperty("客户名称")
  30 + private String customerName;
28 31  
29 32 @Schema(description = "下单时间", requiredMode = Schema.RequiredMode.REQUIRED)
30 33 @ExcelProperty("下单时间")
31 34 private LocalDateTime orderTime;
32 35  
33   - // TODO 芋艿:example 后面
34   - @Schema(description = "销售员编号数组")
35   - @ExcelProperty("销售员编号数组")
36   - private String salePersonIds;
  36 + @Schema(description = "销售员编号", example = "1888")
  37 + private Long saleUserId;
37 38  
38   - @Schema(description = "合计价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "26094")
39   - @ExcelProperty("合计价格,单位:元")
40   - private BigDecimal totalPrice;
  39 + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89")
  40 + @ExcelProperty("结算账户编号")
  41 + private Long accountId;
  42 +
  43 + @Schema(description = "合计产品价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
  44 + private BigDecimal totalProductPrice;
  45 +
  46 + @Schema(description = "合计税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
  47 + private BigDecimal totalTaxPrice;
41 48  
42 49 @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
43   - @ExcelProperty("优惠率,百分比")
44 50 private BigDecimal discountPercent;
45 51  
46   - @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "44.52")
47   - @ExcelProperty("优惠金额,单位:元")
  52 + @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
48 53 private BigDecimal discountPrice;
49 54  
50   - @Schema(description = "支付金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "322.40")
51   - @ExcelProperty("支付金额,单位:元")
52   - private BigDecimal payPrice;
53   -
54   - @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "71.27")
55   - @ExcelProperty("定金金额,单位:元")
  55 + @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
  56 + @NotNull(message = "定金金额,单位:元不能为空")
56 57 private BigDecimal depositPrice;
57 58  
  59 + @Schema(description = "销售状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  60 + @ExcelProperty("销售状态")
  61 + private Integer status;
  62 +
58 63 @Schema(description = "附件地址", example = "https://www.iocoder.cn")
59 64 @ExcelProperty("附件地址")
60 65 private String fileUrl;
61 66  
62   - @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "311.89")
63   - @ExcelProperty("结算账户编号")
64   - private Long accountId;
65   -
66 67 @Schema(description = "备注", example = "你猜")
67 68 @ExcelProperty("备注")
68   - private String description;
  69 + private String remark;
69 70  
70   - @Schema(description = "销售状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
71   - @ExcelProperty("销售状态")
72   - private Integer status;
  71 + @Schema(description = "审核人", example = "芋道")
  72 + private String creator;
  73 + @Schema(description = "审核人名称", example = "芋道")
  74 + private String creatorName;
73 75  
74 76 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
75 77 @ExcelProperty("创建时间")
76 78 private LocalDateTime createTime;
77 79  
  80 + @Schema(description = "订单项列表", requiredMode = Schema.RequiredMode.REQUIRED)
  81 + private List<Item> items;
  82 +
  83 + @Schema(description = "产品信息", requiredMode = Schema.RequiredMode.REQUIRED)
  84 + @ExcelProperty("产品信息")
  85 + private String productNames;
  86 +
  87 + @Data
  88 + public static class Item {
  89 +
  90 + @Schema(description = "订单项编号", example = "11756")
  91 + private Long id;
  92 +
  93 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
  94 + private Long productId;
  95 +
  96 + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
  97 + private Long productUnitId;
  98 +
  99 + @Schema(description = "产品单价", example = "100.00")
  100 + private BigDecimal productPrice;
  101 +
  102 + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  103 + @NotNull(message = "产品数量不能为空")
  104 + private BigDecimal count;
  105 +
  106 + @Schema(description = "税率,百分比", example = "99.88")
  107 + private BigDecimal taxPercent;
  108 +
  109 + @Schema(description = "备注", example = "随便")
  110 + private String remark;
  111 +
  112 + // ========== 关联字段 ==========
  113 +
  114 + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "巧克力")
  115 + private String productName;
  116 + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "A9985")
  117 + private String productBarCode;
  118 + @Schema(description = "产品单位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "盒")
  119 + private String productUnitName;
  120 +
  121 + @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  122 + private BigDecimal stockCount; // 该字段仅仅在“详情”和“编辑”时使用
  123 +
  124 + }
  125 +
78 126 }
79 127 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
... ... @@ -28,26 +28,16 @@ public class ErpSaleOrderSaveReqVO {
28 28 @NotNull(message = "下单时间不能为空")
29 29 private LocalDateTime orderTime;
30 30  
31   - @Schema(description = "销售员编号数组")
32   - private List<Long> salePersonIds;
  31 + @Schema(description = "销售员编号", example = "1888")
  32 + private Long saleUserId;
33 33  
34   - @Schema(description = "合计价格,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "26094")
35   - @NotNull(message = "合计价格,单位:元不能为空")
36   - private BigDecimal totalPrice;
  34 + @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31189")
  35 + @NotNull(message = "结算账户编号不能为空")
  36 + private Long accountId;
37 37  
38 38 @Schema(description = "优惠率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "99.88")
39   - @NotNull(message = "优惠率,百分比不能为空")
40 39 private BigDecimal discountPercent;
41 40  
42   - @Schema(description = "优惠金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "4452")
43   - @NotNull(message = "优惠金额,单位:元不能为空")
44   - private BigDecimal discountPrice;
45   -
46   - // TODO 芋艿:后面删除
47   -// @Schema(description = "支付金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "32240")
48   -// @NotNull(message = "支付金额,单位:元不能为空")
49   -// private BigDecimal payPrice;
50   -
51 41 @Schema(description = "定金金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
52 42 @NotNull(message = "定金金额,单位:元不能为空")
53 43 private BigDecimal depositPrice;
... ... @@ -55,73 +45,38 @@ public class ErpSaleOrderSaveReqVO {
55 45 @Schema(description = "附件地址", example = "https://www.iocoder.cn")
56 46 private String fileUrl;
57 47  
58   - @Schema(description = "结算账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31189")
59   - @NotNull(message = "结算账户编号不能为空")
60   - private Long accountId;
61   -
62 48 @Schema(description = "备注", example = "你猜")
63   - private String description;
64   -
65   - // TODO 芋艿:后面删除
66   -// @Schema(description = "销售状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
67   -// @NotNull(message = "销售状态不能为空")
68   -// private Integer status;
  49 + private String remark;
69 50  
70   - @Schema(description = "ERP 销售订单明细列表")
71   - private List<Item> salesOrderItems;
  51 + @Schema(description = "订单清单列表")
  52 + private List<Item> items;
72 53  
73   - @Schema(description = "管理后台 - ERP 销售订单明细新增/修改 Request VO")
74 54 @Data
75   - public class Item {
  55 + public static class Item {
76 56  
77   - @Schema(description = "编号", example = "20704")
  57 + @Schema(description = "订单项编号", example = "11756")
78 58 private Long id;
79 59  
80   - // TODO 芋艿:后面删除
81   -// @Schema(description = "销售订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30765")
82   -// @NotNull(message = "销售订单编号不能为空")
83   -// private Long orderId;
84   -
85   -// @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5574")
86   -// @NotNull(message = "商品 SPU 编号不能为空")
87   -// private Long productSpuId;
88   -
89   - @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21273")
90   - @NotNull(message = "商品 SKU 编号不能为空")
91   - private Long productSkuId;
  60 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
  61 + @NotNull(message = "产品编号不能为空")
  62 + private Long productId;
92 63  
93   - @Schema(description = "商品单位", requiredMode = Schema.RequiredMode.REQUIRED)
94   - @NotEmpty(message = "商品单位不能为空")
95   - private String productUnit;
  64 + @Schema(description = "产品单位单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "3113")
  65 + @NotNull(message = "产品单位单位不能为空")
  66 + private Long productUnitId;
96 67  
97   - @Schema(description = "商品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "6897")
98   - @NotNull(message = "商品单价不能为空")
  68 + @Schema(description = "产品单价", example = "100.00")
99 69 private BigDecimal productPrice;
100 70  
101   - @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "22100")
102   - @NotNull(message = "数量不能为空")
103   - private Integer count;
  71 + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
  72 + @NotNull(message = "产品数量不能为空")
  73 + private BigDecimal count;
104 74  
105   - // TODO 芋艿:后面删除
106   -// @Schema(description = "总价", requiredMode = Schema.RequiredMode.REQUIRED, example = "26868")
107   -// @NotNull(message = "总价不能为空")
108   -// private BigDecimal totalPrice;
109   -
110   - @Schema(description = "备注", example = "你说的对")
111   - private String description;
112   -
113   - @Schema(description = "税率,百分比", requiredMode = Schema.RequiredMode.REQUIRED)
114   - @NotNull(message = "税率,百分比不能为空")
  75 + @Schema(description = "税率,百分比", example = "99.88")
115 76 private BigDecimal taxPercent;
116 77  
117   - @Schema(description = "税额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "15791")
118   - @NotNull(message = "税额,单位:元不能为空")
119   - private BigDecimal taxPrice;
120   -
121   - // TODO 芋艿:后面删除
122   -// @Schema(description = "支付金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "21930")
123   -// @NotNull(message = "支付金额,单位:元不能为空")
124   -// private BigDecimal payPrice;
  78 + @Schema(description = "备注", example = "随便")
  79 + private String remark;
125 80  
126 81 }
127 82  
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
1 1 package cn.iocoder.yudao.module.erp.dal.dataobject.sale;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4   -import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
5   -import cn.iocoder.yudao.module.erp.enums.sale.ErpSaleOrderStatusEnum;
6 4 import com.baomidou.mybatisplus.annotation.KeySequence;
7   -import com.baomidou.mybatisplus.annotation.TableField;
8 5 import com.baomidou.mybatisplus.annotation.TableId;
9 6 import com.baomidou.mybatisplus.annotation.TableName;
10 7 import lombok.*;
11 8  
12 9 import java.math.BigDecimal;
13 10 import java.time.LocalDateTime;
14   -import java.util.List;
15 11  
16 12 /**
17 13 * ERP 销售订单 DO
18 14 *
19 15 * @author 芋道源码
20 16 */
21   -@TableName(value = "erp_sale_order", autoResultMap = true)
  17 +@TableName(value = "erp_sale_order")
22 18 @KeySequence("erp_sale_order_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
23 19 @Data
24 20 @EqualsAndHashCode(callSuper = true)
... ... @@ -40,13 +36,13 @@ public class ErpSaleOrderDO extends BaseDO {
40 36 /**
41 37 * 销售状态
42 38 *
43   - * 枚举 {@link ErpSaleOrderStatusEnum}
  39 + * 枚举 {@link cn.iocoder.yudao.module.erp.enums.ErpAuditStatus}
44 40 */
45 41 private Integer status;
46 42 /**
47 43 * 客户编号
48 44 *
49   - * TODO 芋艿:关联
  45 + * 关联 {@link ErpCustomerDO#getId()}
50 46 */
51 47 private Long customerId;
52 48 /**
... ... @@ -56,34 +52,46 @@ public class ErpSaleOrderDO extends BaseDO {
56 52 */
57 53 private Long accountId;
58 54 /**
59   - * 销售员编号数组
  55 + * 销售员编号
60 56 *
61   - * TODO 芋艿:关联
  57 + * 关联 AdminUserDO 的 id 字段
62 58 */
63   - @TableField(typeHandler = LongListTypeHandler.class)
64   - private List<Long> salePersonIds;
  59 + private Long saleUserId;
65 60 /**
66 61 * 下单时间
67 62 */
68 63 private LocalDateTime orderTime;
69 64  
70 65 /**
71   - * 合计价格,单位:元
  66 + * 合计数量
  67 + */
  68 + private BigDecimal totalCount;
  69 + /**
  70 + * 最终合计价格,单位:元
  71 + *
  72 + * totalPrice = totalProductPrice + totalTaxPrice - discountPrice
72 73 */
73 74 private BigDecimal totalPrice;
  75 +
  76 + /**
  77 + * 合计产品价格,单位:元
  78 + */
  79 + private BigDecimal totalProductPrice;
  80 + /**
  81 + * 合计税额,单位:元
  82 + */
  83 + private BigDecimal totalTaxPrice;
74 84 /**
75 85 * 优惠率,百分比
76 86 */
77 87 private BigDecimal discountPercent;
78 88 /**
79 89 * 优惠金额,单位:元
  90 + *
  91 + * discountPrice = (totalProductPrice + totalTaxPrice) * discountPercent
80 92 */
81 93 private BigDecimal discountPrice;
82 94 /**
83   - * 支付金额,单位:元
84   - */
85   - private BigDecimal payPrice;
86   - /**
87 95 * 定金金额,单位:元
88 96 */
89 97 private BigDecimal depositPrice;
... ... @@ -95,6 +103,18 @@ public class ErpSaleOrderDO extends BaseDO {
95 103 /**
96 104 * 备注
97 105 */
98   - private String description;
  106 + private String remark;
  107 +
  108 + // ========== 销售入库 ==========
  109 + /**
  110 + * 销售入库数量
  111 + */
  112 + private BigDecimal inCount;
  113 +
  114 + // ========== 销售退货(出库)) ==========
  115 + /**
  116 + * 销售退货数量
  117 + */
  118 + private BigDecimal returnCount;
99 119  
100 120 }
101 121 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSalesOrderItemDO.java renamed to yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderItemDO.java
1 1 package cn.iocoder.yudao.module.erp.dal.dataobject.sale;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
4 5 import com.baomidou.mybatisplus.annotation.KeySequence;
5 6 import com.baomidou.mybatisplus.annotation.TableId;
6 7 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -13,15 +14,15 @@ import java.math.BigDecimal;
13 14 *
14 15 * @author 芋道源码
15 16 */
16   -@TableName("erp_sales_order_items")
17   -@KeySequence("erp_sales_order_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  17 +@TableName("erp_sale_order_items")
  18 +@KeySequence("erp_sale_order_items_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
18 19 @Data
19 20 @EqualsAndHashCode(callSuper = true)
20 21 @ToString(callSuper = true)
21 22 @Builder
22 23 @NoArgsConstructor
23 24 @AllArgsConstructor
24   -public class ErpSalesOrderItemDO extends BaseDO {
  25 +public class ErpSaleOrderItemDO extends BaseDO {
25 26  
26 27 /**
27 28 * 编号
... ... @@ -36,53 +37,58 @@ public class ErpSalesOrderItemDO extends BaseDO {
36 37 private Long orderId;
37 38  
38 39 /**
39   - * 商品 SPU 编号
  40 + * 产品编号
40 41 *
41   - * TODO 芋艿 关联
  42 + * 关联 {@link ErpProductDO#getId()}
42 43 */
43   - private Long productSpuId;
  44 + private Long productId;
44 45 /**
45   - * 商品 SKU 编号
  46 + * 产品单位单位
46 47 *
47   - * TODO 芋艿 关联
  48 + * 冗余 {@link ErpProductDO#getUnitId()}
48 49 */
49   - private Long productSkuId;
50   - /**
51   - * 商品单位
52   - *
53   - * TODO 芋艿 冗余
54   - */
55   - private String productUnit;
  50 + private Long productUnitId;
  51 +
56 52 /**
57   - * 商品单价
58   - *
59   - * TODO 芋艿 冗余
  53 + * 产品单位单价,单位:元
60 54 */
61 55 private BigDecimal productPrice;
62   -
63 56 /**
64 57 * 数量
65 58 */
66   - private Integer count;
  59 + private BigDecimal count;
67 60 /**
68   - * 总价
  61 + * 总价,单位:元
  62 + *
  63 + * totalPrice = productPrice * count
69 64 */
70 65 private BigDecimal totalPrice;
71 66 /**
72   - * 备注
73   - */
74   - private String description;
75   - /**
76 67 * 税率,百分比
77 68 */
78 69 private BigDecimal taxPercent;
79 70 /**
80 71 * 税额,单位:元
  72 + *
  73 + * taxPrice = totalPrice * taxPercent
81 74 */
82 75 private BigDecimal taxPrice;
  76 +
  77 + /**
  78 + * 备注
  79 + */
  80 + private String remark;
  81 +
  82 + // ========== 销售入库 ==========
  83 + /**
  84 + * 销售入库数量
  85 + */
  86 + private BigDecimal inCount;
  87 +
  88 + // ========== 销售退货(出库)) ==========
83 89 /**
84   - * 支付金额,单位:元
  90 + * 销售退货数量
85 91 */
86   - private BigDecimal payPrice;
  92 + private BigDecimal returnCount;
87 93  
88 94 }
89 95 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSalesOrderItemMapper.java renamed to yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderItemMapper.java
1 1 package cn.iocoder.yudao.module.erp.dal.mysql.sale;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4   -import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSalesOrderItemDO;
  4 +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
5 5 import org.apache.ibatis.annotations.Mapper;
6 6  
  7 +import java.util.Collection;
7 8 import java.util.List;
8 9  
9 10 /**
... ... @@ -12,14 +13,18 @@ import java.util.List;
12 13 * @author 芋道源码
13 14 */
14 15 @Mapper
15   -public interface ErpSalesOrderItemMapper extends BaseMapperX<ErpSalesOrderItemDO> {
  16 +public interface ErpSaleOrderItemMapper extends BaseMapperX<ErpSaleOrderItemDO> {
16 17  
17   - default List<ErpSalesOrderItemDO> selectListById(Long id) {
18   - return selectList(ErpSalesOrderItemDO::getId, id);
  18 + default List<ErpSaleOrderItemDO> selectListByOrderId(Long orderId) {
  19 + return selectList(ErpSaleOrderItemDO::getOrderId, orderId);
19 20 }
20 21  
21   - default int deleteById(Long id) {
22   - return delete(ErpSalesOrderItemDO::getId, id);
  22 + default List<ErpSaleOrderItemDO> selectListByOrderIds(Collection<Long> orderIds) {
  23 + return selectList(ErpSaleOrderItemDO::getOrderId, orderIds);
  24 + }
  25 +
  26 + default int deleteByOrderId(Long orderId) {
  27 + return delete(ErpSaleOrderItemDO::getOrderId, orderId);
23 28 }
24 29  
25 30 }
26 31 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
... ... @@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.erp.dal.mysql.sale;
3 3  
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
7 7 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
8 8 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
  9 +import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockOutItemDO;
  10 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 11 import org.apache.ibatis.annotations.Mapper;
10 12  
11 13 /**
... ... @@ -17,14 +19,29 @@ import org.apache.ibatis.annotations.Mapper;
17 19 public interface ErpSaleOrderMapper extends BaseMapperX<ErpSaleOrderDO> {
18 20  
19 21 default PageResult<ErpSaleOrderDO> selectPage(ErpSaleOrderPageReqVO reqVO) {
20   - return selectPage(reqVO, new LambdaQueryWrapperX<ErpSaleOrderDO>()
21   - .likeIfPresent(ErpSaleOrderDO::getNo, reqVO.getNo())
  22 + MPJLambdaWrapperX<ErpSaleOrderDO> query = new MPJLambdaWrapperX<ErpSaleOrderDO>()
  23 + .eqIfPresent(ErpSaleOrderDO::getNo, reqVO.getNo())
22 24 .eqIfPresent(ErpSaleOrderDO::getCustomerId, reqVO.getCustomerId())
23 25 .betweenIfPresent(ErpSaleOrderDO::getOrderTime, reqVO.getOrderTime())
24   - .eqIfPresent(ErpSaleOrderDO::getDescription, reqVO.getDescription())
25 26 .eqIfPresent(ErpSaleOrderDO::getStatus, reqVO.getStatus())
  27 + .likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark())
26 28 .eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator())
27   - .orderByDesc(ErpSaleOrderDO::getId));
  29 + .orderByDesc(ErpSaleOrderDO::getId);
  30 + if (reqVO.getProductId() != null) {
  31 + query.leftJoin(ErpStockOutItemDO.class, ErpStockOutItemDO::getOutId, ErpSaleOrderDO::getId)
  32 + .eq(reqVO.getProductId() != null, ErpStockOutItemDO::getProductId, reqVO.getProductId())
  33 + .groupBy(ErpSaleOrderDO::getId); // 避免 1 对多查询,产生相同的 1
  34 + }
  35 + return selectJoinPage(reqVO, ErpSaleOrderDO.class, query);
  36 + }
  37 +
  38 + default int updateByIdAndStatus(Long id, Integer status, ErpSaleOrderDO updateObj) {
  39 + return update(updateObj, new LambdaUpdateWrapper<ErpSaleOrderDO>()
  40 + .eq(ErpSaleOrderDO::getId, id).eq(ErpSaleOrderDO::getStatus, status));
  41 + }
  42 +
  43 + default ErpSaleOrderDO selectByNo(String no) {
  44 + return selectOne(ErpSaleOrderDO::getNo, no);
28 45 }
29 46  
30 47 }
31 48 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/stock/ErpStockMapper.java
1 1 package cn.iocoder.yudao.module.erp.dal.mysql.stock;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.map.MapUtil;
3 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 6 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 7 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 8 import cn.iocoder.yudao.module.erp.controller.admin.stock.vo.stock.ErpStockPageReqVO;
7 9 import cn.iocoder.yudao.module.erp.dal.dataobject.stock.ErpStockDO;
  10 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
8 11 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 12 import org.apache.ibatis.annotations.Mapper;
10 13  
11 14 import java.math.BigDecimal;
  15 +import java.util.List;
  16 +import java.util.Map;
12 17  
13 18 /**
14 19 * ERP 产品库存 Mapper
... ... @@ -37,11 +42,23 @@ public interface ErpStockMapper extends BaseMapperX&lt;ErpStockDO&gt; {
37 42 updateWrapper.setSql("count = count + " + count);
38 43 } else if (count.compareTo(BigDecimal.ZERO) < 0) {
39 44 if (!negativeEnable) {
40   - updateWrapper.gt(ErpStockDO::getCount, count.abs());
  45 + updateWrapper.ge(ErpStockDO::getCount, count.abs());
41 46 }
42 47 updateWrapper.setSql("count = count - " + count.abs());
43 48 }
44 49 return update(null, updateWrapper);
45 50 }
46 51  
  52 + default BigDecimal selectSumByProductId(Long productId) {
  53 + // SQL sum 查询
  54 + List<Map<String, Object>> result = selectMaps(new QueryWrapper<ErpStockDO>()
  55 + .select("SUM(count) AS sumCount")
  56 + .eq("product_id", productId));
  57 + // 获得数量
  58 + if (CollUtil.isEmpty(result)) {
  59 + return BigDecimal.ZERO;
  60 + }
  61 + return BigDecimal.valueOf(MapUtil.getDouble(result.get(0), "sumCount", 0D));
  62 + }
  63 +
47 64 }
48 65 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
... ... @@ -4,8 +4,12 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
5 5 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
6 6 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
  7 +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
7 8 import jakarta.validation.Valid;
8 9  
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +
9 13 /**
10 14 * ERP 销售订单 Service 接口
11 15 *
... ... @@ -14,7 +18,7 @@ import jakarta.validation.Valid;
14 18 public interface ErpSaleOrderService {
15 19  
16 20 /**
17   - * 创建ERP 销售订单
  21 + * 创建销售订单
18 22 *
19 23 * @param createReqVO 创建信息
20 24 * @return 编号
... ... @@ -22,33 +26,59 @@ public interface ErpSaleOrderService {
22 26 Long createSaleOrder(@Valid ErpSaleOrderSaveReqVO createReqVO);
23 27  
24 28 /**
25   - * 更新ERP 销售订单
  29 + * 更新销售订单
26 30 *
27 31 * @param updateReqVO 更新信息
28 32 */
29 33 void updateSaleOrder(@Valid ErpSaleOrderSaveReqVO updateReqVO);
30 34  
31 35 /**
32   - * 删除ERP 销售订单
  36 + * 更新销售订单的状态
33 37 *
34 38 * @param id 编号
  39 + * @param status 状态
35 40 */
36   - void deleteSaleOrder(Long id);
  41 + void updateSaleOrderStatus(Long id, Integer status);
37 42  
38 43 /**
39   - * 获得ERP 销售订单
  44 + * 删除销售订单
  45 + *
  46 + * @param ids 编号数组
  47 + */
  48 + void deleteSaleOrder(List<Long> ids);
  49 +
  50 + /**
  51 + * 获得销售订单
40 52 *
41 53 * @param id 编号
42   - * @return ERP 销售订单
  54 + * @return 销售订单
43 55 */
44 56 ErpSaleOrderDO getSaleOrder(Long id);
45 57  
46 58 /**
47   - * 获得ERP 销售订单分页
  59 + * 获得销售订单分页
48 60 *
49 61 * @param pageReqVO 分页查询
50   - * @return ERP 销售订单分页
  62 + * @return 销售订单分页
51 63 */
52 64 PageResult<ErpSaleOrderDO> getSaleOrderPage(ErpSaleOrderPageReqVO pageReqVO);
53 65  
  66 + // ==================== 销售订单项 ====================
  67 +
  68 + /**
  69 + * 获得销售订单项列表
  70 + *
  71 + * @param orderId 销售订单编号
  72 + * @return 销售订单项列表
  73 + */
  74 + List<ErpSaleOrderItemDO> getSaleOrderItemListByOrderId(Long orderId);
  75 +
  76 + /**
  77 + * 获得销售订单项 List
  78 + *
  79 + * @param orderIds 销售订单编号数组
  80 + * @return 销售订单项 List
  81 + */
  82 + List<ErpSaleOrderItemDO> getSaleOrderItemListByOrderIds(Collection<Long> orderIds);
  83 +
54 84 }
55 85 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
1 1 package cn.iocoder.yudao.module.erp.service.sale;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
4 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 7 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
6 8 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
  9 +import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
7 10 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
8   -import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSalesOrderItemDO;
  11 +import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
  12 +import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderItemMapper;
9 13 import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderMapper;
10   -import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSalesOrderItemMapper;
  14 +import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
  15 +import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
  16 +import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
11 17 import jakarta.annotation.Resource;
12 18 import org.springframework.stereotype.Service;
13 19 import org.springframework.transaction.annotation.Transactional;
14 20 import org.springframework.validation.annotation.Validated;
15 21  
  22 +import java.math.BigDecimal;
  23 +import java.util.Collection;
  24 +import java.util.Collections;
16 25 import java.util.List;
  26 +import java.util.Map;
17 27  
18 28 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
19 30 import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
20 31  
  32 +// TODO 芋艿:记录操作日志
  33 +
21 34 /**
22 35 * ERP 销售订单 Service 实现类
23 36 *
... ... @@ -30,61 +43,142 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
30 43 @Resource
31 44 private ErpSaleOrderMapper saleOrderMapper;
32 45 @Resource
33   - private ErpSalesOrderItemMapper salesOrderItemMapper;
  46 + private ErpSaleOrderItemMapper saleOrderItemMapper;
  47 +
  48 + @Resource
  49 + private ErpNoRedisDAO noRedisDAO;
  50 +
  51 + @Resource
  52 + private ErpProductService productService;
  53 + @Resource
  54 + private ErpCustomerService customerService;
34 55  
35 56 @Override
36 57 @Transactional(rollbackFor = Exception.class)
37 58 public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) {
38   - // 插入
39   - ErpSaleOrderDO saleOrder = BeanUtils.toBean(createReqVO, ErpSaleOrderDO.class);
40   - saleOrderMapper.insert(saleOrder);
  59 + // 1.1 校验订单项的有效性
  60 + List<ErpSaleOrderItemDO> saleOrderItems = validateSaleOrderItems(createReqVO.getItems());
  61 + // 1.2 校验客户
  62 + customerService.validateCustomer(createReqVO.getCustomerId());
  63 + // 1.3 生成调拨单号,并校验唯一性
  64 + String no = noRedisDAO.generate(ErpNoRedisDAO.STOCK_MOVE_NO_PREFIX);
  65 + if (saleOrderMapper.selectByNo(no) != null) {
  66 + throw exception(STOCK_MOVE_NO_EXISTS);
  67 + }
41 68  
42   - // 插入子表
43   -// createSalesOrderItemsList(saleOrder.getId(), createReqVO.getSalesOrderItems());
44   - // 返回
  69 + // 2.1 插入订单
  70 + ErpSaleOrderDO saleOrder = BeanUtils.toBean(createReqVO, ErpSaleOrderDO.class, in -> in
  71 + .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
  72 + .setTotalCount(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getCount, BigDecimal::add))
  73 + .setTotalPrice(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)));
  74 + saleOrderMapper.insert(saleOrder);
  75 + // 2.2 插入订单项
  76 + saleOrderItems.forEach(o -> o.setOrderId(saleOrder.getId()));
  77 + saleOrderItemMapper.insertBatch(saleOrderItems);
45 78 return saleOrder.getId();
46 79 }
47 80  
48   - private void createSalesOrderItemsList(Long id, List<ErpSalesOrderItemDO> list) {
49   - list.forEach(o -> o.setId(id));
50   - salesOrderItemMapper.insertBatch(list);
51   - }
52   -
53 81 @Override
54 82 @Transactional(rollbackFor = Exception.class)
55 83 public void updateSaleOrder(ErpSaleOrderSaveReqVO updateReqVO) {
56   - // 校验存在
57   - validateSaleOrderExists(updateReqVO.getId());
58   - // 更新
59   - ErpSaleOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOrderDO.class);
  84 + // 1.1 校验存在
  85 + ErpSaleOrderDO saleOrder = validateSaleOrderExists(updateReqVO.getId());
  86 + if (ErpAuditStatus.APPROVE.getStatus().equals(saleOrder.getStatus())) {
  87 + throw exception(STOCK_MOVE_UPDATE_FAIL_APPROVE, saleOrder.getNo());
  88 + }
  89 + // 1.2 校验客户
  90 + customerService.validateCustomer(updateReqVO.getCustomerId());
  91 + // 1.3 校验订单项的有效性
  92 + List<ErpSaleOrderItemDO> saleOrderItems = validateSaleOrderItems(updateReqVO.getItems());
  93 +
  94 + // 2.1 更新订单
  95 + ErpSaleOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOrderDO.class, in -> in
  96 + .setTotalCount(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getCount, BigDecimal::add))
  97 + .setTotalPrice(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getTotalPrice, BigDecimal::add)));
60 98 saleOrderMapper.updateById(updateObj);
  99 + // 2.2 更新订单项
  100 + updateSaleOrderItemList(updateReqVO.getId(), saleOrderItems);
  101 + }
  102 +
  103 + @Override
  104 + @Transactional(rollbackFor = Exception.class)
  105 + public void updateSaleOrderStatus(Long id, Integer status) {
  106 + boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
  107 + // 1.1 校验存在
  108 + ErpSaleOrderDO saleOrder = validateSaleOrderExists(id);
  109 + // 1.2 校验状态
  110 + if (saleOrder.getStatus().equals(status)) {
  111 + throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
  112 + }
  113 + // TODO @芋艿:需要校验是不是有入库、有退货
61 114  
62   - // 更新子表
63   -// updateSalesOrderItemsList(updateReqVO.getId(), updateReqVO.getSalesOrderItems());
  115 + // 2. 更新状态
  116 + int updateCount = saleOrderMapper.updateByIdAndStatus(id, saleOrder.getStatus(),
  117 + new ErpSaleOrderDO().setStatus(status));
  118 + if (updateCount == 0) {
  119 + throw exception(approve ? STOCK_MOVE_APPROVE_FAIL : STOCK_MOVE_PROCESS_FAIL);
  120 + }
64 121 }
65 122  
66   - private void updateSalesOrderItemsList(Long id, List<ErpSalesOrderItemDO> list) {
67   - deleteSalesOrderItemsById(id);
68   - list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
69   - createSalesOrderItemsList(id, list);
  123 + private List<ErpSaleOrderItemDO> validateSaleOrderItems(List<ErpSaleOrderSaveReqVO.Item> list) {
  124 + // 1. 校验产品存在
  125 + List<ErpProductDO> productList = productService.validProductList(
  126 + convertSet(list, ErpSaleOrderSaveReqVO.Item::getProductId));
  127 + Map<Long, ErpProductDO> productMap = convertMap(productList, ErpProductDO::getId);
  128 + // 2. 转化为 ErpSaleOrderItemDO 列表
  129 + return convertList(list, o -> BeanUtils.toBean(o, ErpSaleOrderItemDO.class, item -> item
  130 + .setProductUnitId(productMap.get(item.getProductId()).getUnitId())
  131 + .setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()))));
  132 + }
  133 +
  134 + private void updateSaleOrderItemList(Long id, List<ErpSaleOrderItemDO> newList) {
  135 + // 第一步,对比新老数据,获得添加、修改、删除的列表
  136 + List<ErpSaleOrderItemDO> oldList = saleOrderItemMapper.selectListByOrderId(id);
  137 + List<List<ErpSaleOrderItemDO>> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录
  138 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
  139 +
  140 + // 第二步,批量添加、修改、删除
  141 + if (CollUtil.isNotEmpty(diffList.get(0))) {
  142 + diffList.get(0).forEach(o -> o.setOrderId(id));
  143 + saleOrderItemMapper.insertBatch(diffList.get(0));
  144 + }
  145 + if (CollUtil.isNotEmpty(diffList.get(1))) {
  146 + saleOrderItemMapper.updateBatch(diffList.get(1));
  147 + }
  148 + if (CollUtil.isNotEmpty(diffList.get(2))) {
  149 + saleOrderItemMapper.deleteBatchIds(convertList(diffList.get(2), ErpSaleOrderItemDO::getId));
  150 + }
70 151 }
71 152  
72 153 @Override
73 154 @Transactional(rollbackFor = Exception.class)
74   - public void deleteSaleOrder(Long id) {
75   - // 校验存在
76   - validateSaleOrderExists(id);
77   - // 删除
78   - saleOrderMapper.deleteById(id);
79   -
80   - // 删除子表
81   - deleteSalesOrderItemsById(id);
  155 + public void deleteSaleOrder(List<Long> ids) {
  156 + // 1. 校验不处于已审批
  157 + List<ErpSaleOrderDO> saleOrders = saleOrderMapper.selectBatchIds(ids);
  158 + if (CollUtil.isEmpty(saleOrders)) {
  159 + return;
  160 + }
  161 + saleOrders.forEach(saleOrder -> {
  162 + if (ErpAuditStatus.APPROVE.getStatus().equals(saleOrder.getStatus())) {
  163 + throw exception(STOCK_MOVE_DELETE_FAIL_APPROVE, saleOrder.getNo());
  164 + }
  165 + });
  166 +
  167 + // 2. 遍历删除,并记录操作日志
  168 + saleOrders.forEach(saleOrder -> {
  169 + // 2.1 删除订单
  170 + saleOrderMapper.deleteById(saleOrder.getId());
  171 + // 2.2 删除订单项
  172 + saleOrderItemMapper.deleteByOrderId(saleOrder.getId());
  173 + });
82 174 }
83 175  
84   - private void validateSaleOrderExists(Long id) {
85   - if (saleOrderMapper.selectById(id) == null) {
86   - throw exception(SALE_ORDER_NOT_EXISTS);
  176 + private ErpSaleOrderDO validateSaleOrderExists(Long id) {
  177 + ErpSaleOrderDO saleOrder = saleOrderMapper.selectById(id);
  178 + if (saleOrder == null) {
  179 + throw exception(STOCK_MOVE_NOT_EXISTS);
87 180 }
  181 + return saleOrder;
88 182 }
89 183  
90 184 @Override
... ... @@ -97,10 +191,19 @@ public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
97 191 return saleOrderMapper.selectPage(pageReqVO);
98 192 }
99 193  
100   - // ==================== 子表(ERP 销售订单明细) ====================
  194 + // ==================== 订单项 ====================
101 195  
102   - private void deleteSalesOrderItemsById(Long id) {
103   - salesOrderItemMapper.deleteById(id);
  196 + @Override
  197 + public List<ErpSaleOrderItemDO> getSaleOrderItemListByOrderId(Long moveId) {
  198 + return saleOrderItemMapper.selectListByOrderId(moveId);
  199 + }
  200 +
  201 + @Override
  202 + public List<ErpSaleOrderItemDO> getSaleOrderItemListByOrderIds(Collection<Long> moveIds) {
  203 + if (CollUtil.isEmpty(moveIds)) {
  204 + return Collections.emptyList();
  205 + }
  206 + return saleOrderItemMapper.selectListByOrderIds(moveIds);
104 207 }
105 208  
106 209 }
107 210 \ No newline at end of file
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockService.java
... ... @@ -31,6 +31,14 @@ public interface ErpStockService {
31 31 ErpStockDO getStock(Long productId, Long warehouseId);
32 32  
33 33 /**
  34 + * 获得产品库存数量
  35 + *
  36 + * @param productId 产品编号
  37 + * @return 产品库存数量
  38 + */
  39 + BigDecimal getStockCount(Long productId);
  40 +
  41 + /**
34 42 * 获得产品库存分页
35 43 *
36 44 * @param pageReqVO 分页查询
... ...
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/stock/ErpStockServiceImpl.java
... ... @@ -50,6 +50,11 @@ public class ErpStockServiceImpl implements ErpStockService {
50 50 }
51 51  
52 52 @Override
  53 + public BigDecimal getStockCount(Long productId) {
  54 + return stockMapper.selectSumByProductId(productId);
  55 + }
  56 +
  57 + @Override
53 58 public PageResult<ErpStockDO> getStockPage(ErpStockPageReqVO pageReqVO) {
54 59 return stockMapper.selectPage(pageReqVO);
55 60 }
... ...