diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/detail/WmsInventoryDetailPageReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/detail/WmsInventoryDetailPageReqVO.java index 05294c7..f3931d8 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/detail/WmsInventoryDetailPageReqVO.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/inventory/vo/detail/WmsInventoryDetailPageReqVO.java @@ -7,6 +7,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import org.springframework.format.annotation.DateTimeFormat; +import java.math.BigDecimal; import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @@ -53,6 +54,9 @@ public class WmsInventoryDetailPageReqVO extends PageParam { @Schema(description = "批号", example = "BATCH001") private String batchNo; + @Schema(description = "最小剩余库存数量", example = "0.01") + private BigDecimal minRemainQuantity; + @Schema(description = "距离过期天数", example = "30") private Integer daysToExpires; diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/WmsShipmentOrderController.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/WmsShipmentOrderController.java new file mode 100644 index 0000000..0bdbde8 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/WmsShipmentOrderController.java @@ -0,0 +1,220 @@ +package cn.iocoder.yudao.module.wms.controller.admin.order.shipment; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.NumberUtil; +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail.WmsShipmentOrderDetailRespVO; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderPageReqVO; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderRespVO; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderSaveReqVO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.merchant.WmsMerchantDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDetailDO; +import cn.iocoder.yudao.module.wms.framework.config.WmsProperties; +import cn.iocoder.yudao.module.wms.service.md.item.WmsItemService; +import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService; +import cn.iocoder.yudao.module.wms.service.md.merchant.WmsMerchantService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseAreaService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService; +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderDetailService; +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; + +@Tag(name = "管理后台 - WMS 出库单") +@RestController +@RequestMapping("/wms/shipment-order") +@Validated +public class WmsShipmentOrderController { + + @Resource + private WmsShipmentOrderService shipmentOrderService; + @Resource + private WmsShipmentOrderDetailService shipmentOrderDetailService; + @Resource + private WmsMerchantService merchantService; + @Resource + private WmsWarehouseService warehouseService; + @Resource + private WmsWarehouseAreaService warehouseAreaService; + @Resource + private WmsItemService itemService; + @Resource + private WmsItemSkuService itemSkuService; + + @Resource + private WmsProperties wmsProperties; + + @Resource + private AdminUserApi adminUserApi; + + @PostMapping("/create") + @Operation(summary = "创建出库单") + @PreAuthorize("@ss.hasPermission('wms:shipment-order:create')") + public CommonResult createShipmentOrder(@Valid @RequestBody WmsShipmentOrderSaveReqVO createReqVO) { + return success(shipmentOrderService.createShipmentOrder(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新出库单") + @PreAuthorize("@ss.hasPermission('wms:shipment-order:update')") + public CommonResult updateShipmentOrder(@Valid @RequestBody WmsShipmentOrderSaveReqVO updateReqVO) { + shipmentOrderService.updateShipmentOrder(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除出库单") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('wms:shipment-order:delete')") + public CommonResult deleteShipmentOrder(@RequestParam("id") Long id) { + shipmentOrderService.deleteShipmentOrder(id); + return success(true); + } + + @PutMapping("/complete") + @Operation(summary = "完成出库") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('wms:shipment-order:complete')") + public CommonResult completeShipmentOrder(@RequestParam("id") Long id) { + shipmentOrderService.completeShipmentOrder(id); + return success(true); + } + + @PutMapping("/cancel") + @Operation(summary = "作废出库单") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('wms:shipment-order:cancel')") + public CommonResult cancelShipmentOrder(@RequestParam("id") Long id) { + shipmentOrderService.cancelShipmentOrder(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得出库单") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('wms:shipment-order:query')") + public CommonResult getShipmentOrder(@RequestParam("id") Long id) { + WmsShipmentOrderDO order = shipmentOrderService.getShipmentOrder(id); + if (order == null) { + return success(null); + } + // 获得出库单的明细列表 + List detailList = shipmentOrderDetailService.getShipmentOrderDetailList(id); + // 拼接结果返回 + WmsShipmentOrderRespVO respVO = buildShipmentOrderRespVO(order) + .setDetails(buildShipmentOrderDetailRespVOList(detailList)); + return success(respVO); + } + + @GetMapping("/page") + @Operation(summary = "获得出库单分页") + @PreAuthorize("@ss.hasPermission('wms:shipment-order:query')") + public CommonResult> getShipmentOrderPage(@Valid WmsShipmentOrderPageReqVO pageReqVO) { + PageResult pageResult = shipmentOrderService.getShipmentOrderPage(pageReqVO); + return success(new PageResult<>(buildShipmentOrderRespVOList(pageResult.getList()), pageResult.getTotal())); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出出库单 Excel") + @PreAuthorize("@ss.hasPermission('wms:shipment-order:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportShipmentOrderExcel(@Valid WmsShipmentOrderPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = shipmentOrderService.getShipmentOrderPage(pageReqVO).getList(); + ExcelUtils.write(response, "出库单.xls", "数据", WmsShipmentOrderRespVO.class, + buildShipmentOrderRespVOList(list)); + } + + // ==================== 拼接 VO ==================== + + private WmsShipmentOrderRespVO buildShipmentOrderRespVO(WmsShipmentOrderDO order) { + if (order == null) { + return null; + } + List list = buildShipmentOrderRespVOList(List.of(order)); + return CollUtil.getFirst(list); + } + + private List buildShipmentOrderRespVOList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + // 获取相关的商户、仓库、库区、用户等数据 + Map merchantMap = merchantService.getMerchantMap(convertSet(list, WmsShipmentOrderDO::getMerchantId)); + Map warehouseMap = warehouseService.getWarehouseMap(convertSet(list, WmsShipmentOrderDO::getWarehouseId)); + Map areaMap = wmsProperties.isAreaEnabled() + ? warehouseAreaService.getWarehouseAreaMap(convertSet(list, WmsShipmentOrderDO::getAreaId)) + : Collections.emptyMap(); + Map userMap = adminUserApi.getUserMap(convertSetByFlatMap(list, + order -> Stream.of(parseUserId(order.getCreator()), parseUserId(order.getUpdater())))); + // 拼接数据 + return BeanUtils.toBean(list, WmsShipmentOrderRespVO.class, vo -> { + MapUtils.findAndThen(merchantMap, vo.getMerchantId(), merchant -> vo.setMerchantName(merchant.getName())); + MapUtils.findAndThen(warehouseMap, vo.getWarehouseId(), warehouse -> vo.setWarehouseName(warehouse.getName())); + MapUtils.findAndThen(areaMap, vo.getAreaId(), area -> vo.setAreaName(area.getName())); + MapUtils.findAndThen(userMap, parseUserId(vo.getCreator()), user -> vo.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, parseUserId(vo.getUpdater()), user -> vo.setUpdaterName(user.getNickname())); + }); + } + + private Long parseUserId(String userId) { + return NumberUtil.parseLong(userId, null); + } + + private List buildShipmentOrderDetailRespVOList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + // 获取相关的商品、SKU、仓库、库区等数据 + Map skuMap = itemSkuService.getItemSkuMap(convertSet(list, WmsShipmentOrderDetailDO::getSkuId)); + Map itemMap = itemService.getItemMap(convertSet(skuMap.values(), WmsItemSkuDO::getItemId)); + Map warehouseMap = warehouseService.getWarehouseMap( + convertSet(list, WmsShipmentOrderDetailDO::getWarehouseId)); + Map areaMap = wmsProperties.isAreaEnabled() + ? warehouseAreaService.getWarehouseAreaMap(convertSet(list, WmsShipmentOrderDetailDO::getAreaId)) + : Collections.emptyMap(); + // 拼接数据 + return BeanUtils.toBean(list, WmsShipmentOrderDetailRespVO.class, vo -> { + MapUtils.findAndThen(skuMap, vo.getSkuId(), sku -> { + vo.setSkuCode(sku.getCode()).setSkuName(sku.getName()).setItemId(sku.getItemId()); + MapUtils.findAndThen(itemMap, sku.getItemId(), item -> vo.setItemCode(item.getCode()) + .setItemName(item.getName()).setUnit(item.getUnit())); + }); + MapUtils.findAndThen(warehouseMap, vo.getWarehouseId(), warehouse -> vo.setWarehouseName(warehouse.getName())); + MapUtils.findAndThen(areaMap, vo.getAreaId(), area -> vo.setAreaName(area.getName())); + }); + } + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/WmsShipmentOrderDetailController.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/WmsShipmentOrderDetailController.java new file mode 100644 index 0000000..087b948 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/WmsShipmentOrderDetailController.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.wms.controller.admin.order.shipment; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail.WmsShipmentOrderDetailRespVO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDetailDO; +import cn.iocoder.yudao.module.wms.framework.config.WmsProperties; +import cn.iocoder.yudao.module.wms.service.md.item.WmsItemService; +import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseAreaService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService; +import cn.iocoder.yudao.module.wms.service.order.shipment.WmsShipmentOrderDetailService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +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 java.util.Collections; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; + +@Tag(name = "管理后台 - WMS 出库单明细") +@RestController +@RequestMapping("/wms/shipment-order-detail") +@Validated +public class WmsShipmentOrderDetailController { + + @Resource + private WmsShipmentOrderDetailService shipmentOrderDetailService; + @Resource + private WmsItemService itemService; + @Resource + private WmsItemSkuService itemSkuService; + @Resource + private WmsWarehouseService warehouseService; + @Resource + private WmsWarehouseAreaService warehouseAreaService; + + @Resource + private WmsProperties wmsProperties; + + @GetMapping("/list-by-order-id") + @Operation(summary = "获得出库单明细列表") + @Parameter(name = "orderId", description = "出库单编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('wms:shipment-order:query')") + public CommonResult> getShipmentOrderDetailListByOrderId( + @RequestParam("orderId") Long orderId) { + List list = shipmentOrderDetailService.getShipmentOrderDetailList(orderId); + return success(buildShipmentOrderDetailRespVOList(list)); + } + + // ==================== 拼接 VO ==================== + + private List buildShipmentOrderDetailRespVOList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + // 查询关联数据 + Map skuMap = itemSkuService.getItemSkuMap(convertSet(list, WmsShipmentOrderDetailDO::getSkuId)); + Map itemMap = itemService.getItemMap(convertSet(skuMap.values(), WmsItemSkuDO::getItemId)); + Map warehouseMap = warehouseService.getWarehouseMap( + convertSet(list, WmsShipmentOrderDetailDO::getWarehouseId)); + Map areaMap = wmsProperties.isAreaEnabled() + ? warehouseAreaService.getWarehouseAreaMap(convertSet(list, WmsShipmentOrderDetailDO::getAreaId)) + : Collections.emptyMap(); + // 拼接数据 + return BeanUtils.toBean(list, WmsShipmentOrderDetailRespVO.class, vo -> { + MapUtils.findAndThen(skuMap, vo.getSkuId(), sku -> { + vo.setSkuCode(sku.getCode()).setSkuName(sku.getName()).setItemId(sku.getItemId()); + MapUtils.findAndThen(itemMap, sku.getItemId(), item -> vo.setItemCode(item.getCode()) + .setItemName(item.getName()).setUnit(item.getUnit())); + }); + MapUtils.findAndThen(warehouseMap, vo.getWarehouseId(), warehouse -> vo.setWarehouseName(warehouse.getName())); + MapUtils.findAndThen(areaMap, vo.getAreaId(), area -> vo.setAreaName(area.getName())); + }); + } + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/detail/WmsShipmentOrderDetailRespVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/detail/WmsShipmentOrderDetailRespVO.java new file mode 100644 index 0000000..8672783 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/detail/WmsShipmentOrderDetailRespVO.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - WMS 出库单明细 Response VO") +@Data +public class WmsShipmentOrderDetailRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "出库单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long orderId; + + @Schema(description = "商品编号", example = "2048") + private Long itemId; + + @Schema(description = "商品编号", example = "SPU-APPLE") + private String itemCode; + + @Schema(description = "商品名称", example = "红富士苹果") + private String itemName; + + @Schema(description = "商品单位", example = "箱") + private String unit; + + @Schema(description = "SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long skuId; + + @Schema(description = "规格编号", example = "SKU-APPLE-10KG") + private String skuCode; + + @Schema(description = "规格名称", example = "10kg 箱装") + private String skuName; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long warehouseId; + + @Schema(description = "仓库名称", example = "北京仓") + private String warehouseName; + + @Schema(description = "库区编号", example = "1024") + private Long areaId; + + @Schema(description = "库区名称", example = "北京库区01") + private String areaName; + + @Schema(description = "库存明细编号", example = "1024") + private Long inventoryDetailId; + + @Schema(description = "批号", example = "B202605110001") + private String batchNo; + + @Schema(description = "生产日期") + private LocalDateTime productionDate; + + @Schema(description = "过期日期") + private LocalDateTime expirationDate; + + @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + private BigDecimal quantity; + + @Schema(description = "金额", example = "1000.00") + private BigDecimal amount; + + @Schema(description = "备注", example = "备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/detail/WmsShipmentOrderDetailSaveReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/detail/WmsShipmentOrderDetailSaveReqVO.java new file mode 100644 index 0000000..b594a75 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/detail/WmsShipmentOrderDetailSaveReqVO.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - WMS 出库单明细保存 Request VO") +@Data +public class WmsShipmentOrderDetailSaveReqVO { + + @Schema(description = "编号", example = "1024") + private Long id; + + @Schema(description = "SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "SKU 不能为空") + private Long skuId; + + @Schema(description = "库区编号", example = "1024") + private Long areaId; + + @Schema(description = "库存明细编号", example = "1024") + private Long inventoryDetailId; + + @Schema(description = "批号", example = "B202605110001") + @Size(max = 64, message = "批号长度不能超过 64 个字符") + private String batchNo; + + @Schema(description = "生产日期") + private LocalDateTime productionDate; + + @Schema(description = "过期日期") + private LocalDateTime expirationDate; + + @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "出库数量不能为空") + @DecimalMin(value = "0", inclusive = false, message = "出库数量必须大于 0") + private BigDecimal quantity; + + @Schema(description = "金额", example = "1000.00") + @DecimalMin(value = "0", message = "金额不能小于 0") + private BigDecimal amount; + + @Schema(description = "备注", example = "备注") + @Size(max = 255, message = "备注长度不能超过 255 个字符") + private String remark; + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderPageReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderPageReqVO.java new file mode 100644 index 0000000..551be8d --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderPageReqVO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.wms.enums.order.WmsOrderStatusEnum; +import cn.iocoder.yudao.module.wms.enums.order.WmsShipmentOrderTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - WMS 出库单分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WmsShipmentOrderPageReqVO extends PageParam { + + @Schema(description = "出库单号", example = "CK202605110001") + private String no; + + @Schema(description = "单据状态", example = "0") + @InEnum(WmsOrderStatusEnum.class) + private Integer status; + + @Schema(description = "仓库编号", example = "1024") + private Long warehouseId; + + @Schema(description = "客户编号", example = "1024") + private Long merchantId; + + @Schema(description = "单据日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] orderDate; + + @Schema(description = "最小数量", example = "1.00") + private BigDecimal totalQuantityMin; + + @Schema(description = "最大数量", example = "100.00") + private BigDecimal totalQuantityMax; + + @Schema(description = "最小总金额", example = "1.00") + private BigDecimal totalAmountMin; + + @Schema(description = "最大总金额", example = "1000.00") + private BigDecimal totalAmountMax; + + @Schema(description = "出库类型", example = "201") + @InEnum(WmsShipmentOrderTypeEnum.class) + private Integer type; + + @Schema(description = "业务单号", example = "SO202605110001") + private String bizOrderNo; + + @Schema(description = "创建用户", example = "1") + private String creator; + + @Schema(description = "更新用户", example = "1") + private String updater; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + @Schema(description = "更新时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] updateTime; + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderRespVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderRespVO.java new file mode 100644 index 0000000..837e660 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderRespVO.java @@ -0,0 +1,93 @@ +package cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail.WmsShipmentOrderDetailRespVO; +import cn.iocoder.yudao.module.wms.enums.DictTypeConstants; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - WMS 出库单 Response VO") +@Data +@ExcelIgnoreUnannotated +public class WmsShipmentOrderRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "出库单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "CK202605110001") + @ExcelProperty("出库单号") + private String no; + + @Schema(description = "出库类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "201") + @ExcelProperty(value = "出库类型", converter = DictConvert.class) + @DictFormat(DictTypeConstants.SHIPMENT_ORDER_TYPE) + private Integer type; + + @Schema(description = "出库状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @ExcelProperty(value = "出库状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.ORDER_STATUS) + private Integer status; + + @Schema(description = "业务单号", example = "SO202605110001") + @ExcelProperty("业务单号") + private String bizOrderNo; + + @Schema(description = "客户编号", example = "1024") + private Long merchantId; + + @Schema(description = "客户名称", example = "某某公司") + @ExcelProperty("客户") + private String merchantName; + + @Schema(description = "备注", example = "备注") + private String remark; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long warehouseId; + + @Schema(description = "仓库名称", example = "北京仓") + @ExcelProperty("仓库") + private String warehouseName; + + @Schema(description = "库区编号", example = "1024") + private Long areaId; + + @Schema(description = "库区名称", example = "北京库区01") + private String areaName; + + @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @ExcelProperty("出库数量") + private BigDecimal totalQuantity; + + @Schema(description = "总金额", example = "1000.00") + @ExcelProperty("总金额") + private BigDecimal totalAmount; + + @Schema(description = "出库明细") + private List details; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "创建者", example = "1") + private String creator; + @Schema(description = "创建者名称", example = "芋道") + private String creatorName; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; + + @Schema(description = "更新者", example = "1") + private String updater; + @Schema(description = "更新者名称", example = "芋道") + private String updaterName; + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderSaveReqVO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderSaveReqVO.java new file mode 100644 index 0000000..9e8bf70 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/controller/admin/order/shipment/vo/order/WmsShipmentOrderSaveReqVO.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.detail.WmsShipmentOrderDetailSaveReqVO; +import cn.iocoder.yudao.module.wms.enums.order.WmsShipmentOrderTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - WMS 出库单保存 Request VO") +@Data +public class WmsShipmentOrderSaveReqVO { + + @Schema(description = "编号", example = "1024") + private Long id; + + @Schema(description = "出库单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "CK202605110001") + @NotBlank(message = "出库单号不能为空") + @Size(max = 64, message = "出库单号长度不能超过 64 个字符") + private String no; + + @Schema(description = "出库类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "201") + @NotNull(message = "出库类型不能为空") + @InEnum(WmsShipmentOrderTypeEnum.class) + private Integer type; + + @Schema(description = "业务单号", example = "SO202605110001") + @Size(max = 64, message = "业务单号长度不能超过 64 个字符") + private String bizOrderNo; + + @Schema(description = "客户编号", example = "1024") + private Long merchantId; + + @Schema(description = "备注", example = "备注") + @Size(max = 255, message = "备注长度不能超过 255 个字符") + private String remark; + + @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "仓库不能为空") + private Long warehouseId; + + @Schema(description = "库区编号", example = "1024") + private Long areaId; + + @Schema(description = "出库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00") + @NotNull(message = "出库数量不能为空") + private BigDecimal totalQuantity; + + @Schema(description = "总金额", example = "1000.00") + private BigDecimal totalAmount; + + @Schema(description = "出库明细") + @Valid + private List details; + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/order/shipment/WmsShipmentOrderDO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/order/shipment/WmsShipmentOrderDO.java new file mode 100644 index 0000000..8d89187 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/order/shipment/WmsShipmentOrderDO.java @@ -0,0 +1,94 @@ +package cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.merchant.WmsMerchantDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO; +import cn.iocoder.yudao.module.wms.enums.DictTypeConstants; +import cn.iocoder.yudao.module.wms.enums.order.WmsShipmentOrderTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; + +/** + * WMS 出库单 DO + * + * @author 芋道源码 + */ +@TableName("wms_shipment_order") +@KeySequence("wms_shipment_order_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WmsShipmentOrderDO extends BaseDO { + + /** + * 主键编号 + */ + @TableId + private Long id; + /** + * 出库单号 + */ + private String no; + /** + * 出库类型 + * + * 枚举 {@link WmsShipmentOrderTypeEnum} + * 字典 {@link DictTypeConstants#SHIPMENT_ORDER_TYPE} + */ + private Integer type; + /** + * 出库状态 + * + * 字典 {@link DictTypeConstants#ORDER_STATUS} + */ + private Integer status; + /** + * 业务订单号 + */ + private String bizOrderNo; + /** + * 客户编号 + * + * 关联 {@link WmsMerchantDO#getId()} + */ + private Long merchantId; + /** + * 备注 + */ + private String remark; + + // ========= 仓库库区字段 ========= + + /** + * 仓库编号 + * + * 关联 {@link WmsWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 库区编号 + * + * 关联 {@link WmsWarehouseAreaDO#getId()} + */ + private Long areaId; + + // ========= 汇总金额字段 ========= + + /** + * 总数量 + */ + private BigDecimal totalQuantity; + /** + * 总金额 + */ + private BigDecimal totalAmount; + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/order/shipment/WmsShipmentOrderDetailDO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/order/shipment/WmsShipmentOrderDetailDO.java new file mode 100644 index 0000000..ae062bc --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/dataobject/order/shipment/WmsShipmentOrderDetailDO.java @@ -0,0 +1,103 @@ +package cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * WMS 出库单明细 DO + * + * @author 芋道源码 + */ +@TableName("wms_shipment_order_detail") +@KeySequence("wms_shipment_order_detail_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WmsShipmentOrderDetailDO extends BaseDO { + + /** + * 主键编号 + */ + @TableId + private Long id; + /** + * 备注 + */ + private String remark; + + // ========= 单据商品字段 ========= + + /** + * 出库单编号 + * + * 关联 {@link WmsShipmentOrderDO#getId()} + */ + private Long orderId; + /** + * 商品 SKU 编号 + * + * 关联 {@link WmsItemSkuDO#getId()} + */ + private Long skuId; + + // ========= 仓库库区字段 ========= + + /** + * 仓库编号 + * + * 关联 {@link WmsWarehouseDO#getId()} + */ + private Long warehouseId; + /** + * 库区编号 + * + * 关联 {@link WmsWarehouseAreaDO#getId()} + */ + private Long areaId; + /** + * 库存明细编号 + * + * 关联 {@link WmsInventoryDetailDO#getId()} + */ + private Long inventoryDetailId; + + // ========= 批次效期字段 ========= + + /** + * 批号 + */ + private String batchNo; + /** + * 生产日期 + */ + private LocalDateTime productionDate; + /** + * 过期日期 + */ + private LocalDateTime expirationDate; + + // ========= 汇总金额字段 ========= + + /** + * 出库数量 + */ + private BigDecimal quantity; + /** + * 金额(小计、非单价) + */ + private BigDecimal amount; + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryDetailMapper.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryDetailMapper.java index a5973cf..51f55db 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryDetailMapper.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/inventory/WmsInventoryDetailMapper.java @@ -8,8 +8,10 @@ import cn.iocoder.yudao.module.wms.controller.admin.inventory.vo.detail.WmsInven import cn.iocoder.yudao.module.wms.dal.dataobject.inventory.WmsInventoryDetailDO; import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemDO; import cn.iocoder.yudao.module.wms.dal.dataobject.md.item.WmsItemSkuDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -35,6 +37,7 @@ public interface WmsInventoryDetailMapper extends BaseMapperX { + + default List selectListByOrderId(Long orderId) { + return selectList(WmsShipmentOrderDetailDO::getOrderId, orderId); + } + + default List selectListByOrderIds(Collection orderIds) { + return selectList(new LambdaQueryWrapperX() + .inIfPresent(WmsShipmentOrderDetailDO::getOrderId, orderIds) + .orderByAsc(WmsShipmentOrderDetailDO::getOrderId) + .orderByAsc(WmsShipmentOrderDetailDO::getId)); + } + + default void deleteByOrderId(Long orderId) { + delete(WmsShipmentOrderDetailDO::getOrderId, orderId); + } + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/shipment/WmsShipmentOrderMapper.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/shipment/WmsShipmentOrderMapper.java new file mode 100644 index 0000000..3a882d9 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/dal/mysql/order/shipment/WmsShipmentOrderMapper.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.wms.dal.mysql.order.shipment; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderPageReqVO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * WMS 出库单 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface WmsShipmentOrderMapper extends BaseMapperX { + + default PageResult selectPage(WmsShipmentOrderPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(WmsShipmentOrderDO::getNo, reqVO.getNo()) + .eqIfPresent(WmsShipmentOrderDO::getStatus, reqVO.getStatus()) + .eqIfPresent(WmsShipmentOrderDO::getWarehouseId, reqVO.getWarehouseId()) + .eqIfPresent(WmsShipmentOrderDO::getMerchantId, reqVO.getMerchantId()) + .betweenIfPresent(WmsShipmentOrderDO::getCreateTime, reqVO.getOrderDate()) + .geIfPresent(WmsShipmentOrderDO::getTotalQuantity, reqVO.getTotalQuantityMin()) + .leIfPresent(WmsShipmentOrderDO::getTotalQuantity, reqVO.getTotalQuantityMax()) + .geIfPresent(WmsShipmentOrderDO::getTotalAmount, reqVO.getTotalAmountMin()) + .leIfPresent(WmsShipmentOrderDO::getTotalAmount, reqVO.getTotalAmountMax()) + .eqIfPresent(WmsShipmentOrderDO::getType, reqVO.getType()) + .likeIfPresent(WmsShipmentOrderDO::getBizOrderNo, reqVO.getBizOrderNo()) + .eqIfPresent(WmsShipmentOrderDO::getCreator, reqVO.getCreator()) + .eqIfPresent(WmsShipmentOrderDO::getUpdater, reqVO.getUpdater()) + .betweenIfPresent(WmsShipmentOrderDO::getCreateTime, reqVO.getCreateTime()) + .betweenIfPresent(WmsShipmentOrderDO::getUpdateTime, reqVO.getUpdateTime()) + .orderByDesc(WmsShipmentOrderDO::getId)); + } + + default WmsShipmentOrderDO selectByNo(String no) { + return selectOne(WmsShipmentOrderDO::getNo, no); + } + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java index 8fe51bb..e8f2756 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/DictTypeConstants.java @@ -10,8 +10,7 @@ public interface DictTypeConstants { String MERCHANT_TYPE = "merchant_type"; String ORDER_STATUS = "wms_order_status"; String RECEIPT_ORDER_TYPE = "wms_receipt_order_type"; - String SHIPMENT_STATUS = "wms_shipment_status"; - String SHIPMENT_TYPE = "wms_shipment_type"; + String SHIPMENT_ORDER_TYPE = "wms_shipment_order_type"; String INVENTORY_HISTORY_TYPE = "wms_inventory_history_type"; String MOVEMENT_STATUS = "wms_movement_status"; String CHECK_STATUS = "wms_check_status"; diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java index cbcd7fb..ad6a87d 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/ErrorCodeConstants.java @@ -48,6 +48,7 @@ public interface ErrorCodeConstants { // ========== WMS 基础数据-往来企业 1-060-105-000 ========== ErrorCode MERCHANT_NOT_EXISTS = new ErrorCode(1_060_105_000, "往来企业不存在"); ErrorCode MERCHANT_NOT_SUPPLIER = new ErrorCode(1_060_105_001, "往来企业必须是供应商或客户/供应商类型"); + ErrorCode MERCHANT_NOT_CUSTOMER = new ErrorCode(1_060_105_002, "往来企业必须是客户或客户/供应商类型"); // ========== WMS 入库单 1-060-200-000 ========== ErrorCode RECEIPT_ORDER_NOT_EXISTS = new ErrorCode(1_060_200_000, "入库单不存在"); @@ -58,4 +59,18 @@ public interface ErrorCodeConstants { ErrorCode RECEIPT_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_200_005, "入库单状态不是草稿或已作废,不能删除"); ErrorCode RECEIPT_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_200_007, "入库单明细不存在"); + // ========== WMS 出库单 1-060-201-000 ========== + ErrorCode SHIPMENT_ORDER_NOT_EXISTS = new ErrorCode(1_060_201_000, "出库单不存在"); + ErrorCode SHIPMENT_ORDER_NO_DUPLICATE = new ErrorCode(1_060_201_001, "出库单号重复"); + ErrorCode SHIPMENT_ORDER_STATUS_NOT_PREPARE = new ErrorCode(1_060_201_002, "出库单状态不是草稿,不能操作"); + ErrorCode SHIPMENT_ORDER_DETAIL_REQUIRED = new ErrorCode(1_060_201_003, "出库单至少包含一条明细"); + ErrorCode SHIPMENT_ORDER_AREA_REQUIRED = new ErrorCode(1_060_201_004, "库区模式下,出库单库区不能为空"); + ErrorCode SHIPMENT_ORDER_STATUS_NOT_DELETABLE = new ErrorCode(1_060_201_005, "出库单状态不是草稿或已作废,不能删除"); + ErrorCode SHIPMENT_ORDER_DETAIL_NOT_EXISTS = new ErrorCode(1_060_201_007, "出库单明细不存在"); + + // ========== WMS 库存 1-060-300-000 ========== + ErrorCode INVENTORY_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_000, "库存不足,当前库存:{},变更数量:{}"); + ErrorCode INVENTORY_DETAIL_NOT_EXISTS = new ErrorCode(1_060_300_001, "库存明细不存在"); + ErrorCode INVENTORY_DETAIL_REMAIN_QUANTITY_NOT_ENOUGH = new ErrorCode(1_060_300_002, "库存明细剩余数量不足,当前剩余:{},出库数量:{}"); + } diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsOrderTypeConstants.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsOrderTypeConstants.java index 4bd1604..52a89a5 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsOrderTypeConstants.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsOrderTypeConstants.java @@ -19,4 +19,10 @@ public final class WmsOrderTypeConstants { public static final int RECEIPT_RETURN = 102; // 退货入库:WmsReceiptOrderDO public static final int RECEIPT_GIVE_BACK = 103; // 归还入库:WmsReceiptOrderDO + // ========== 出库单类型 [200, 300) ========== + + public static final int SHIPMENT_RETURN = 200; // 退货出库:WmsShipmentOrderDO + public static final int SHIPMENT_SALE = 201; // 销售出库:WmsShipmentOrderDO + public static final int SHIPMENT_PRODUCTION = 202; // 生产出库:WmsShipmentOrderDO + } diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsShipmentOrderTypeEnum.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsShipmentOrderTypeEnum.java new file mode 100644 index 0000000..79545f1 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/enums/order/WmsShipmentOrderTypeEnum.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.wms.enums.order; + +import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * WMS 出库单类型枚举 + * + * @author 芋道源码 + */ +@Getter +@AllArgsConstructor +public enum WmsShipmentOrderTypeEnum implements ArrayValuable { + + RETURN(WmsOrderTypeConstants.SHIPMENT_RETURN, "退货出库"), + SALE(WmsOrderTypeConstants.SHIPMENT_SALE, "销售出库"), + PRODUCTION(WmsOrderTypeConstants.SHIPMENT_PRODUCTION, "生产出库"); + + public static final Integer[] ARRAYS = Arrays.stream(values()).map(WmsShipmentOrderTypeEnum::getType).toArray(Integer[]::new); + + /** + * 类型 + */ + private final Integer type; + /** + * 名称 + */ + private final String name; + + @Override + public Integer[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/dto/WmsInventoryChangeReqDTO.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/dto/WmsInventoryChangeReqDTO.java index 339a2d3..aeb22b4 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/dto/WmsInventoryChangeReqDTO.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/inventory/dto/WmsInventoryChangeReqDTO.java @@ -54,6 +54,10 @@ public class WmsInventoryChangeReqDTO { */ private Long areaId; /** + * 库存明细编号 + */ + private Long inventoryDetailId; + /** * 变更数量 */ private BigDecimal quantity; diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantService.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantService.java index 5c087d3..2bafaec 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantService.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantService.java @@ -55,9 +55,19 @@ public interface WmsMerchantService { * @param id 编号 * @return 往来企业 */ + @SuppressWarnings("UnusedReturnValue") WmsMerchantDO validateSupplierMerchantExists(Long id); /** + * 校验客户存在 + * + * @param id 编号 + * @return 往来企业 + */ + @SuppressWarnings("UnusedReturnValue") + WmsMerchantDO validateCustomerMerchantExists(Long id); + + /** * 获得往来企业 * * @param id 编号 diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantServiceImpl.java index 5f555af..cff2439 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantServiceImpl.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/md/merchant/WmsMerchantServiceImpl.java @@ -79,6 +79,16 @@ public class WmsMerchantServiceImpl implements WmsMerchantService { } @Override + public WmsMerchantDO validateCustomerMerchantExists(Long id) { + WmsMerchantDO merchant = validateMerchantExists(id); + if (ObjectUtil.notEqual(merchant.getType(), WmsMerchantTypeEnum.CUSTOMER.getType()) + && ObjectUtil.notEqual(merchant.getType(), WmsMerchantTypeEnum.CUSTOMER_SUPPLIER.getType())) { + throw exception(MERCHANT_NOT_CUSTOMER); + } + return merchant; + } + + @Override public WmsMerchantDO getMerchant(Long id) { return merchantMapper.selectById(id); } diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderServiceImpl.java index 33e10af..2d1f334 100644 --- a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderServiceImpl.java +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/receipt/WmsReceiptOrderServiceImpl.java @@ -171,6 +171,14 @@ public class WmsReceiptOrderServiceImpl implements WmsReceiptOrderService { return order; } + /** + * 增加入库单对应库存。 + * + * 入库在库存变更模型中使用正数数量;批次模式下,会根据入库明细生成新的库存明细。 + * + * @param order 入库单 + * @param details 入库单明细列表 + */ private void createInventory(WmsReceiptOrderDO order, List details) { List items = convertList(details, detail -> BeanUtils.toBean(detail, WmsInventoryChangeReqDTO.Item.class)); diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailService.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailService.java new file mode 100644 index 0000000..0ef6623 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailService.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.wms.service.order.shipment; + +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderSaveReqVO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDetailDO; + +import java.util.Collection; +import java.util.List; + +/** + * WMS 出库单明细 Service 接口 + * + * @author 芋道源码 + */ +public interface WmsShipmentOrderDetailService { + + /** + * 创建出库单明细列表 + * + * @param orderId 出库单编号 + * @param reqVO 出库单保存信息 + */ + void createShipmentOrderDetailList(Long orderId, WmsShipmentOrderSaveReqVO reqVO); + + /** + * 更新出库单明细列表 + * + * @param orderId 出库单编号 + * @param reqVO 出库单保存信息 + */ + void updateShipmentOrderDetailList(Long orderId, WmsShipmentOrderSaveReqVO reqVO); + + /** + * 按出库单编号删除明细列表 + * + * @param orderId 出库单编号 + */ + void deleteShipmentOrderDetailListByOrderId(Long orderId); + + /** + * 按出库单编号获得明细列表 + * + * @param orderId 出库单编号 + * @return 明细列表 + */ + List getShipmentOrderDetailList(Long orderId); + + /** + * 按出库单编号集合获得明细列表 + * + * @param orderIds 出库单编号集合 + * @return 明细列表 + */ + List getShipmentOrderDetailList(Collection orderIds); + + /** + * 校验出库单明细列表存在 + * + * @param orderId 出库单编号 + * @return 明细列表 + */ + List validateShipmentOrderDetailListExists(Long orderId); + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailServiceImpl.java new file mode 100644 index 0000000..4c2b39a --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderDetailServiceImpl.java @@ -0,0 +1,130 @@ +package cn.iocoder.yudao.module.wms.service.order.shipment; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderSaveReqVO; +import cn.iocoder.yudao.module.wms.dal.dataobject.md.warehouse.WmsWarehouseAreaDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDetailDO; +import cn.iocoder.yudao.module.wms.dal.mysql.order.shipment.WmsShipmentOrderDetailMapper; +import cn.iocoder.yudao.module.wms.framework.config.WmsProperties; +import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseAreaService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*; + +/** + * WMS 出库单明细 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class WmsShipmentOrderDetailServiceImpl implements WmsShipmentOrderDetailService { + + @Resource + private WmsShipmentOrderDetailMapper shipmentOrderDetailMapper; + @Resource + private WmsItemSkuService itemSkuService; + @Resource + private WmsWarehouseAreaService warehouseAreaService; + @Resource + private WmsProperties wmsProperties; + + @Override + @Transactional(rollbackFor = Exception.class) + public void createShipmentOrderDetailList(Long orderId, WmsShipmentOrderSaveReqVO reqVO) { + List list = buildShipmentOrderDetailList(reqVO); + if (CollUtil.isEmpty(list)) { + return; + } + list.forEach(detail -> detail.setId(null).setOrderId(orderId)); + shipmentOrderDetailMapper.insertBatch(list); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateShipmentOrderDetailList(Long orderId, WmsShipmentOrderSaveReqVO reqVO) { + // 第一步,对比新老数据,获得添加、修改、删除的列表 + List oldList = shipmentOrderDetailMapper.selectListByOrderId(orderId); + List list = buildShipmentOrderDetailList(reqVO); + List newList = CollUtil.isEmpty(list) ? ListUtil.of() : list; + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + + // 第二步,批量添加、修改、删除 + if (CollUtil.isNotEmpty(diffList.get(0))) { + if (CollUtil.isNotEmpty(convertList(diffList.get(0), WmsShipmentOrderDetailDO::getId))) { + throw exception(SHIPMENT_ORDER_DETAIL_NOT_EXISTS); + } + diffList.get(0).forEach(detail -> detail.setOrderId(orderId)); + shipmentOrderDetailMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + diffList.get(1).forEach(detail -> detail.setOrderId(orderId)); + shipmentOrderDetailMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + shipmentOrderDetailMapper.deleteByIds(convertList(diffList.get(2), WmsShipmentOrderDetailDO::getId)); + } + } + + @Override + public void deleteShipmentOrderDetailListByOrderId(Long orderId) { + shipmentOrderDetailMapper.deleteByOrderId(orderId); + } + + @Override + public List getShipmentOrderDetailList(Long orderId) { + return shipmentOrderDetailMapper.selectListByOrderId(orderId); + } + + @Override + public List getShipmentOrderDetailList(Collection orderIds) { + if (CollUtil.isEmpty(orderIds)) { + return ListUtil.of(); + } + return shipmentOrderDetailMapper.selectListByOrderIds(orderIds); + } + + @Override + public List validateShipmentOrderDetailListExists(Long orderId) { + List details = shipmentOrderDetailMapper.selectListByOrderId(orderId); + if (CollUtil.isEmpty(details)) { + throw exception(SHIPMENT_ORDER_DETAIL_REQUIRED); + } + if (wmsProperties.isAreaEnabled() && details.stream() + .anyMatch(detail -> ObjectUtils.equalsAny(detail.getAreaId(), null, WmsWarehouseAreaDO.ID_EMPTY))) { + throw exception(SHIPMENT_ORDER_AREA_REQUIRED); + } + return details; + } + + private List buildShipmentOrderDetailList(WmsShipmentOrderSaveReqVO reqVO) { + if (CollUtil.isEmpty(reqVO.getDetails())) { + return ListUtil.of(); + } + return convertList(reqVO.getDetails(), detail -> { + // 校验 SKU 存在 + itemSkuService.validateItemSkuExists(detail.getSkuId()); + // 校验仓库区域存在,并且属于当前仓库 + Long areaId = warehouseAreaService.validateAndNormalizeWarehouseAreaId( + detail.getAreaId() == null ? reqVO.getAreaId() : detail.getAreaId(), reqVO.getWarehouseId()); + // 构建对象 + return BeanUtils.toBean(detail, WmsShipmentOrderDetailDO.class) + .setWarehouseId(reqVO.getWarehouseId()).setAreaId(areaId); + }); + } + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderService.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderService.java new file mode 100644 index 0000000..2f8f5a2 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderService.java @@ -0,0 +1,68 @@ +package cn.iocoder.yudao.module.wms.service.order.shipment; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderPageReqVO; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderSaveReqVO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDO; +import jakarta.validation.Valid; + +/** + * WMS 出库单 Service 接口 + * + * @author 芋道源码 + */ +public interface WmsShipmentOrderService { + + /** + * 创建出库单 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createShipmentOrder(@Valid WmsShipmentOrderSaveReqVO createReqVO); + + /** + * 更新出库单 + * + * @param updateReqVO 更新信息 + */ + void updateShipmentOrder(@Valid WmsShipmentOrderSaveReqVO updateReqVO); + + /** + * 删除出库单 + * + * @param id 编号 + */ + void deleteShipmentOrder(Long id); + + /** + * 完成出库 + * + * @param id 编号 + */ + void completeShipmentOrder(Long id); + + /** + * 作废出库单 + * + * @param id 编号 + */ + void cancelShipmentOrder(Long id); + + /** + * 获得出库单 + * + * @param id 编号 + * @return 出库单 + */ + WmsShipmentOrderDO getShipmentOrder(Long id); + + /** + * 获得出库单分页 + * + * @param pageReqVO 分页查询 + * @return 出库单分页 + */ + PageResult getShipmentOrderPage(WmsShipmentOrderPageReqVO pageReqVO); + +} diff --git a/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderServiceImpl.java b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderServiceImpl.java new file mode 100644 index 0000000..9cd7253 --- /dev/null +++ b/yudao-module-wms/src/main/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderServiceImpl.java @@ -0,0 +1,192 @@ +package cn.iocoder.yudao.module.wms.service.order.shipment; + +import cn.hutool.core.util.ObjectUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderPageReqVO; +import cn.iocoder.yudao.module.wms.controller.admin.order.shipment.vo.order.WmsShipmentOrderSaveReqVO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDetailDO; +import cn.iocoder.yudao.module.wms.dal.mysql.order.shipment.WmsShipmentOrderMapper; +import cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryOrderTypeEnum; +import cn.iocoder.yudao.module.wms.enums.order.WmsOrderStatusEnum; +import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService; +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; +import cn.iocoder.yudao.module.wms.service.md.merchant.WmsMerchantService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.math.BigDecimal; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.*; + +/** + * WMS 出库单 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class WmsShipmentOrderServiceImpl implements WmsShipmentOrderService { + + @Resource + private WmsShipmentOrderMapper shipmentOrderMapper; + @Resource + private WmsShipmentOrderDetailService shipmentOrderDetailService; + @Resource + private WmsWarehouseService warehouseService; + @Resource + private WmsMerchantService merchantService; + @Resource + private WmsInventoryService inventoryService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createShipmentOrder(WmsShipmentOrderSaveReqVO createReqVO) { + // 1. 校验出库单保存数据 + validateShipmentOrderSaveData(createReqVO); + + // 2.1 插入出库单 + WmsShipmentOrderDO order = BeanUtils.toBean(createReqVO, WmsShipmentOrderDO.class); + order.setStatus(WmsOrderStatusEnum.PREPARE.getStatus()); + shipmentOrderMapper.insert(order); + // 2.2 插入出库单明细 + shipmentOrderDetailService.createShipmentOrderDetailList(order.getId(), createReqVO); + return order.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateShipmentOrder(WmsShipmentOrderSaveReqVO updateReqVO) { + // 1. 校验出库单保存数据 + validateShipmentOrderPrepare(updateReqVO.getId()); + validateShipmentOrderSaveData(updateReqVO); + + // 2.1 更新出库单 + WmsShipmentOrderDO updateObj = BeanUtils.toBean(updateReqVO, WmsShipmentOrderDO.class) + .setStatus(WmsOrderStatusEnum.PREPARE.getStatus()); + shipmentOrderMapper.updateById(updateObj); + // 2.2 更新出库单明细 + shipmentOrderDetailService.updateShipmentOrderDetailList(updateReqVO.getId(), updateReqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteShipmentOrder(Long id) { + // 1. 校验存在,且可删除 + WmsShipmentOrderDO order = validateShipmentOrderExists(id); + if (ObjectUtil.notEqual(order.getStatus(), WmsOrderStatusEnum.PREPARE.getStatus()) + && ObjectUtil.notEqual(order.getStatus(), WmsOrderStatusEnum.CANCELED.getStatus())) { + throw exception(SHIPMENT_ORDER_STATUS_NOT_DELETABLE); + } + + // 2.1 删除出库单 + shipmentOrderMapper.deleteById(id); + // 2.2 删除出库单明细 + shipmentOrderDetailService.deleteShipmentOrderDetailListByOrderId(id); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void completeShipmentOrder(Long id) { + // 1.1 校验存在,且草稿 + WmsShipmentOrderDO order = validateShipmentOrderPrepare(id); + // 1.2 校验出库单明细存在 + List details = shipmentOrderDetailService.validateShipmentOrderDetailListExists(id); + + // 2. 完成出库单 + shipmentOrderMapper.updateById(new WmsShipmentOrderDO().setId(id) + .setStatus(WmsOrderStatusEnum.FINISHED.getStatus())); + + // 3. 扣减库存 + changeInventory(order, details); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelShipmentOrder(Long id) { + // 1. 校验存在,且草稿 + validateShipmentOrderPrepare(id); + + // 2. 作废出库单 + shipmentOrderMapper.updateById(new WmsShipmentOrderDO().setId(id) + .setStatus(WmsOrderStatusEnum.CANCELED.getStatus())); + } + + @Override + public WmsShipmentOrderDO getShipmentOrder(Long id) { + return shipmentOrderMapper.selectById(id); + } + + @Override + public PageResult getShipmentOrderPage(WmsShipmentOrderPageReqVO pageReqVO) { + return shipmentOrderMapper.selectPage(pageReqVO); + } + + private void validateShipmentOrderSaveData(WmsShipmentOrderSaveReqVO reqVO) { + // 校验出库单号唯一 + validateShipmentOrderNoUnique(reqVO.getId(), reqVO.getNo()); + // 校验仓库存在 + warehouseService.validateWarehouseExists(reqVO.getWarehouseId()); + // 校验客户类型 + if (reqVO.getMerchantId() != null) { + merchantService.validateCustomerMerchantExists(reqVO.getMerchantId()); + } + } + + private void validateShipmentOrderNoUnique(Long id, String no) { + WmsShipmentOrderDO order = shipmentOrderMapper.selectByNo(no); + if (order == null) { + return; + } + if (id == null || ObjectUtil.notEqual(order.getId(), id)) { + throw exception(SHIPMENT_ORDER_NO_DUPLICATE); + } + } + + private WmsShipmentOrderDO validateShipmentOrderExists(Long id) { + WmsShipmentOrderDO order = id == null ? null : shipmentOrderMapper.selectById(id); + if (order == null) { + throw exception(SHIPMENT_ORDER_NOT_EXISTS); + } + return order; + } + + /** + * 校验出库单存在且为草稿状态 + * + * @param id 出库单编号 + * @return 出库单 + */ + private WmsShipmentOrderDO validateShipmentOrderPrepare(Long id) { + WmsShipmentOrderDO order = validateShipmentOrderExists(id); + if (ObjectUtil.notEqual(order.getStatus(), WmsOrderStatusEnum.PREPARE.getStatus())) { + throw exception(SHIPMENT_ORDER_STATUS_NOT_PREPARE); + } + return order; + } + + /** + * 扣减出库单对应库存。 + * + * 出库在库存变更模型中使用负数数量;批次模式下,明细里的 inventoryDetailId 会用于扣减对应库存明细的剩余数量。 + * + * @param order 出库单 + * @param details 出库单明细列表 + */ + private void changeInventory(WmsShipmentOrderDO order, List details) { + List items = convertList(details, + detail -> BeanUtils.toBean(detail, WmsInventoryChangeReqDTO.Item.class) + .setQuantity(detail.getQuantity().multiply(BigDecimal.valueOf(-1L)))); + inventoryService.changeInventory(new WmsInventoryChangeReqDTO() + .setOrderId(order.getId()).setOrderNo(order.getNo()) + .setOrderType(WmsInventoryOrderTypeEnum.SHIPMENT.getType()).setItems(items)); + } + +} diff --git a/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderServiceImplTest.java b/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderServiceImplTest.java new file mode 100644 index 0000000..4e92bdd --- /dev/null +++ b/yudao-module-wms/src/test/java/cn/iocoder/yudao/module/wms/service/order/shipment/WmsShipmentOrderServiceImplTest.java @@ -0,0 +1,182 @@ +package cn.iocoder.yudao.module.wms.service.order.shipment; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDO; +import cn.iocoder.yudao.module.wms.dal.dataobject.order.shipment.WmsShipmentOrderDetailDO; +import cn.iocoder.yudao.module.wms.dal.mysql.order.shipment.WmsShipmentOrderDetailMapper; +import cn.iocoder.yudao.module.wms.dal.mysql.order.shipment.WmsShipmentOrderMapper; +import cn.iocoder.yudao.module.wms.enums.inventory.WmsInventoryOrderTypeEnum; +import cn.iocoder.yudao.module.wms.enums.order.WmsOrderStatusEnum; +import cn.iocoder.yudao.module.wms.enums.order.WmsShipmentOrderTypeEnum; +import cn.iocoder.yudao.module.wms.framework.config.WmsProperties; +import cn.iocoder.yudao.module.wms.service.inventory.WmsInventoryService; +import cn.iocoder.yudao.module.wms.service.inventory.dto.WmsInventoryChangeReqDTO; +import cn.iocoder.yudao.module.wms.service.md.item.WmsItemSkuService; +import cn.iocoder.yudao.module.wms.service.md.merchant.WmsMerchantService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseAreaService; +import cn.iocoder.yudao.module.wms.service.md.warehouse.WmsWarehouseService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.math.BigDecimal; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.SHIPMENT_ORDER_AREA_REQUIRED; +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.SHIPMENT_ORDER_DETAIL_REQUIRED; +import static cn.iocoder.yudao.module.wms.enums.ErrorCodeConstants.SHIPMENT_ORDER_STATUS_NOT_DELETABLE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@Import({WmsShipmentOrderServiceImpl.class, WmsShipmentOrderDetailServiceImpl.class, WmsProperties.class}) +public class WmsShipmentOrderServiceImplTest extends BaseDbUnitTest { + + @Resource + private WmsShipmentOrderServiceImpl shipmentOrderService; + + @Resource + private WmsShipmentOrderMapper shipmentOrderMapper; + @Resource + private WmsShipmentOrderDetailMapper shipmentOrderDetailMapper; + + @MockitoBean + private WmsWarehouseService warehouseService; + @MockitoBean + private WmsWarehouseAreaService warehouseAreaService; + @MockitoBean + private WmsMerchantService merchantService; + @MockitoBean + private WmsItemSkuService itemSkuService; + @MockitoBean + private WmsInventoryService inventoryService; + + @Test + public void testCompleteShipmentOrder_success() { + // mock 数据 + Long warehouseId = 100L; + Long skuId = 200L; + Long inventoryDetailId = 300L; + WmsShipmentOrderDO order = createShipmentOrder(warehouseId); + shipmentOrderMapper.insert(order); + shipmentOrderDetailMapper.insert(createShipmentOrderDetail(order.getId(), skuId, warehouseId, 400L, + inventoryDetailId)); + + // 调用 + shipmentOrderService.completeShipmentOrder(order.getId()); + + // 断言:出库单 + WmsShipmentOrderDO dbOrder = shipmentOrderMapper.selectById(order.getId()); + assertNotNull(dbOrder); + assertEquals(WmsOrderStatusEnum.FINISHED.getStatus(), dbOrder.getStatus()); + // 断言:库存变更 + ArgumentCaptor captor = ArgumentCaptor.forClass(WmsInventoryChangeReqDTO.class); + verify(inventoryService).changeInventory(captor.capture()); + WmsInventoryChangeReqDTO inventoryReqDTO = captor.getValue(); + assertEquals(order.getId(), inventoryReqDTO.getOrderId()); + assertEquals(order.getNo(), inventoryReqDTO.getOrderNo()); + assertEquals(WmsInventoryOrderTypeEnum.SHIPMENT.getType(), inventoryReqDTO.getOrderType()); + assertEquals(1, inventoryReqDTO.getItems().size()); + assertEquals(skuId, inventoryReqDTO.getItems().get(0).getSkuId()); + assertEquals(warehouseId, inventoryReqDTO.getItems().get(0).getWarehouseId()); + assertEquals(inventoryDetailId, inventoryReqDTO.getItems().get(0).getInventoryDetailId()); + assertEquals(0, new BigDecimal("-2.00").compareTo(inventoryReqDTO.getItems().get(0).getQuantity())); + } + + @Test + public void testCompleteShipmentOrder_detailRequired() { + // mock 数据 + WmsShipmentOrderDO order = createShipmentOrder(100L); + shipmentOrderMapper.insert(order); + + // 调用,并断言 + assertServiceException(() -> shipmentOrderService.completeShipmentOrder(order.getId()), + SHIPMENT_ORDER_DETAIL_REQUIRED); + verify(inventoryService, never()).changeInventory(any()); + } + + @Test + public void testCompleteShipmentOrder_areaRequired() { + // mock 数据 + WmsShipmentOrderDO order = createShipmentOrder(100L); + shipmentOrderMapper.insert(order); + shipmentOrderDetailMapper.insert(createShipmentOrderDetail(order.getId(), 200L, 100L, 0L, null)); + + // 调用,并断言 + assertServiceException(() -> shipmentOrderService.completeShipmentOrder(order.getId()), + SHIPMENT_ORDER_AREA_REQUIRED); + verify(inventoryService, never()).changeInventory(any()); + } + + @Test + public void testCancelShipmentOrder_success() { + // mock 数据 + WmsShipmentOrderDO order = createShipmentOrder(100L); + shipmentOrderMapper.insert(order); + + // 调用 + shipmentOrderService.cancelShipmentOrder(order.getId()); + + // 断言 + WmsShipmentOrderDO dbOrder = shipmentOrderMapper.selectById(order.getId()); + assertNotNull(dbOrder); + assertEquals(WmsOrderStatusEnum.CANCELED.getStatus(), dbOrder.getStatus()); + verify(inventoryService, never()).changeInventory(any()); + } + + @Test + public void testDeleteShipmentOrder_canceled() { + // mock 数据 + WmsShipmentOrderDO order = createShipmentOrder(100L).setStatus(WmsOrderStatusEnum.CANCELED.getStatus()); + shipmentOrderMapper.insert(order); + shipmentOrderDetailMapper.insert(createShipmentOrderDetail(order.getId(), 200L, 100L, 300L, null)); + + // 调用 + shipmentOrderService.deleteShipmentOrder(order.getId()); + + // 断言 + assertNull(shipmentOrderMapper.selectById(order.getId())); + assertEquals(0, shipmentOrderDetailMapper.selectListByOrderId(order.getId()).size()); + } + + @Test + public void testDeleteShipmentOrder_finished() { + // mock 数据 + WmsShipmentOrderDO order = createShipmentOrder(100L).setStatus(WmsOrderStatusEnum.FINISHED.getStatus()); + shipmentOrderMapper.insert(order); + + // 调用,并断言 + assertServiceException(() -> shipmentOrderService.deleteShipmentOrder(order.getId()), + SHIPMENT_ORDER_STATUS_NOT_DELETABLE); + } + + private static WmsShipmentOrderDO createShipmentOrder(Long warehouseId) { + return new WmsShipmentOrderDO() + .setNo("CK202605120001") + .setType(WmsShipmentOrderTypeEnum.SALE.getType()) + .setStatus(WmsOrderStatusEnum.PREPARE.getStatus()) + .setWarehouseId(warehouseId) + .setAreaId(0L) + .setTotalQuantity(new BigDecimal("2.00")) + .setTotalAmount(new BigDecimal("20.00")); + } + + private static WmsShipmentOrderDetailDO createShipmentOrderDetail(Long orderId, Long skuId, Long warehouseId, + Long areaId, Long inventoryDetailId) { + return WmsShipmentOrderDetailDO.builder() + .orderId(orderId) + .skuId(skuId) + .warehouseId(warehouseId) + .areaId(areaId) + .inventoryDetailId(inventoryDetailId) + .quantity(new BigDecimal("2.00")) + .amount(new BigDecimal("20.00")) + .build(); + } + +} diff --git a/yudao-module-wms/src/test/resources/sql/clean.sql b/yudao-module-wms/src/test/resources/sql/clean.sql index eea18af..675414e 100644 --- a/yudao-module-wms/src/test/resources/sql/clean.sql +++ b/yudao-module-wms/src/test/resources/sql/clean.sql @@ -1,3 +1,5 @@ +DELETE FROM "wms_shipment_order_detail"; +DELETE FROM "wms_shipment_order"; DELETE FROM "wms_receipt_order_detail"; DELETE FROM "wms_receipt_order"; DELETE FROM "wms_inventory_history"; diff --git a/yudao-module-wms/src/test/resources/sql/create_tables.sql b/yudao-module-wms/src/test/resources/sql/create_tables.sql index 6c8e2ca..14c349c 100644 --- a/yudao-module-wms/src/test/resources/sql/create_tables.sql +++ b/yudao-module-wms/src/test/resources/sql/create_tables.sql @@ -106,8 +106,8 @@ CREATE TABLE IF NOT EXISTS "wms_inventory_history" ( CREATE TABLE IF NOT EXISTS "wms_receipt_order" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "no" varchar(64) NOT NULL, - "type" tinyint NOT NULL, - "status" tinyint NOT NULL DEFAULT '0', + "type" int NOT NULL, + "status" int NOT NULL DEFAULT '0', "biz_order_no" varchar(64) DEFAULT NULL, "merchant_id" bigint DEFAULT NULL, "remark" varchar(255) DEFAULT NULL, @@ -144,3 +144,46 @@ CREATE TABLE IF NOT EXISTS "wms_receipt_order_detail" ( "tenant_id" bigint NOT NULL DEFAULT '0', PRIMARY KEY ("id") ) COMMENT 'WMS 入库单明细表'; + +CREATE TABLE IF NOT EXISTS "wms_shipment_order" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "no" varchar(64) NOT NULL, + "type" int NOT NULL, + "status" int NOT NULL DEFAULT '0', + "biz_order_no" varchar(64) DEFAULT NULL, + "merchant_id" bigint DEFAULT NULL, + "remark" varchar(255) DEFAULT NULL, + "warehouse_id" bigint NOT NULL, + "area_id" bigint NOT NULL DEFAULT '0', + "total_quantity" decimal(20, 2) NOT NULL DEFAULT '0', + "total_amount" decimal(10, 2) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT '0', + PRIMARY KEY ("id") +) COMMENT 'WMS 出库单表'; + +CREATE TABLE IF NOT EXISTS "wms_shipment_order_detail" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "remark" varchar(255) DEFAULT NULL, + "order_id" bigint NOT NULL, + "sku_id" bigint NOT NULL, + "warehouse_id" bigint NOT NULL, + "area_id" bigint NOT NULL DEFAULT '0', + "inventory_detail_id" bigint DEFAULT NULL, + "batch_no" varchar(64) DEFAULT NULL, + "production_date" timestamp DEFAULT NULL, + "expiration_date" timestamp DEFAULT NULL, + "quantity" decimal(20, 2) NOT NULL DEFAULT '0', + "amount" decimal(10, 2) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT '0', + PRIMARY KEY ("id") +) COMMENT 'WMS 出库单明细表';