feat: 出库改造

This commit is contained in:
DataCall 2024-08-09 14:30:00 +08:00
parent 2d3edd7818
commit bd28191462
11 changed files with 170 additions and 101 deletions

View file

@ -37,6 +37,12 @@ public class InventoryHistoryBo extends BaseEntity {
@NotNull(message = "操作id出库、入库、库存移动表单id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long formId;
/**
* 操作单号入库出库移库盘库单号
*/
@NotNull(message = "操作单号(入库、出库、移库、盘库单号)不能为空", groups = { AddGroup.class, EditGroup.class })
private String formNo;
/**
* 操作类型
*/

View file

@ -1,5 +1,6 @@
package com.ruoyi.wms.domain.bo;
import com.ruoyi.common.mybatis.core.domain.PlaceAndItem;
import com.ruoyi.wms.domain.entity.ShipmentOrderDetail;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
@ -11,6 +12,7 @@ import jakarta.validation.constraints.*;
import io.github.linpeilie.annotations.AutoMapper;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 出库单详情业务对象 wms_shipment_order_detail
@ -25,7 +27,7 @@ import java.math.BigDecimal;
@AutoMapper(target = ShipmentOrderDetail.class, reverseConvertGenerate = false),
@AutoMapper(target = InventoryBo.class, reverseConvertGenerate = false)
})
public class ShipmentOrderDetailBo extends BaseEntity {
public class ShipmentOrderDetailBo extends BaseEntity implements PlaceAndItem {
/**
*
@ -69,6 +71,27 @@ public class ShipmentOrderDetailBo extends BaseEntity {
@NotNull(message = "所属库区不能为空", groups = { AddGroup.class, EditGroup.class })
private Long areaId;
/**
* 批号
*/
private String batchNumber;
/**
* 生产日期
*/
private LocalDateTime productionDate;
/**
* 过期时间
*/
private LocalDateTime expirationTime;
/**
* 入库记录id
*/
@NotNull(message = "入库记录id不能为空", groups = { AddGroup.class, EditGroup.class })
private Long inventoryDetailId;
/**
* 备注
*/

View file

@ -33,6 +33,10 @@ public class InventoryHistory extends BaseEntity {
* 操作id出库入库库存移动表单id
*/
private Long formId;
/**
* 操作单号入库出库移库盘库单号
*/
private String formNo;
/**
* 操作类型
*/

View file

@ -8,6 +8,7 @@ import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 出库单详情对象 wms_shipment_order_detail
@ -52,6 +53,22 @@ public class ShipmentOrderDetail extends BaseEntity {
* 所属库区
*/
private Long areaId;
/**
* 批号
*/
private String batchNumber;
/**
* 生产日期
*/
private LocalDateTime productionDate;
/**
* 过期时间
*/
private LocalDateTime expirationTime;
/**
* 入库记录id
*/
private Long inventoryDetailId;
/**
* 备注
*/

View file

@ -41,6 +41,12 @@ public class InventoryHistoryVo extends BaseEntity implements Serializable {
@ExcelDictFormat(readConverterExp = "出=库、入库、库存移动表单id")
private Long formId;
/**
* 操作单号入库出库移库盘库单号
*/
@ExcelProperty(value = "操作单号")
private String formNo;
/**
* 操作类型
*/

View file

@ -6,13 +6,12 @@ import com.ruoyi.common.mybatis.core.domain.PlaceAndItem;
import com.ruoyi.wms.domain.entity.ShipmentOrderDetail;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.ruoyi.common.excel.annotation.ExcelDictFormat;
import com.ruoyi.common.excel.convert.ExcelDictConvert;
import lombok.Data;
import io.github.linpeilie.annotations.AutoMapper;
import java.io.Serializable;
import java.io.Serial;
import java.time.LocalDateTime;
/**
* 出库单详情视图对象 wms_shipment_order_detail
@ -70,6 +69,30 @@ public class ShipmentOrderDetailVo implements Serializable, PlaceAndItem {
@ExcelProperty(value = "所属库区")
private Long areaId;
/**
* 批号
*/
@ExcelProperty(value = "批号")
private String batchNumber;
/**
* 生产日期
*/
@ExcelProperty(value = "生产日期")
private LocalDateTime productionDate;
/**
* 过期时间
*/
@ExcelProperty(value = "过期时间")
private LocalDateTime expirationTime;
/**
* 入库记录id
*/
@ExcelProperty(value = "入库记录id")
private Long inventoryDetailId;
/**
* 备注
*/
@ -78,5 +101,5 @@ public class ShipmentOrderDetailVo implements Serializable, PlaceAndItem {
private ItemSkuVo itemSku;
private BigDecimal maxQuantity;
private BigDecimal remainQuantity;
}

View file

@ -102,66 +102,28 @@ public class InventoryDetailService extends ServiceImpl<InventoryDetailMapper, I
}
/**
* 计算出库数据
* @param warehouseId
* @param inventoryBoList
* 校验入库记录剩余数
* @param inventoryDetailBoList
*/
public List<InventoryDetailBo> calcShipmentInventoryDetailData(@NotNull Long warehouseId, @NotEmpty List<InventoryBo> inventoryBoList) {
LambdaQueryWrapper<InventoryDetail> inventoryDetailLambdaQueryWrapper = Wrappers.lambdaQuery();
inventoryDetailLambdaQueryWrapper.eq(InventoryDetail::getWarehouseId, warehouseId);
inventoryDetailLambdaQueryWrapper.in(InventoryDetail::getAreaId, inventoryBoList.stream().map(InventoryBo::getAreaId).toList());
inventoryDetailLambdaQueryWrapper.gt(InventoryDetail::getRemainQuantity, 0);
inventoryDetailLambdaQueryWrapper.orderByAsc(InventoryDetail::getSkuId, InventoryDetail::getWarehouseId, InventoryDetail::getAreaId, BaseEntity::getCreateTime);
List<InventoryDetail> inventoryDetailList = inventoryDetailMapper.selectList(inventoryDetailLambdaQueryWrapper);
public void validateRemainQuantity(List<InventoryDetailBo> inventoryDetailBoList) {
if (CollUtil.isEmpty(inventoryDetailBoList)) {
return;
}
List<InventoryDetail> inventoryDetailList = inventoryDetailMapper.selectBatchIds(inventoryDetailBoList.stream().map(InventoryDetailBo::getId).toList());
if (CollUtil.isEmpty(inventoryDetailList)) {
throw new BaseException("库存不足");
}
Set<String> inventoryDetailSizeGroupByKey = inventoryDetailList.stream().map(PlaceAndItem::getKey).collect(Collectors.toSet());
if (inventoryDetailSizeGroupByKey.size() < inventoryBoList.size()) {
Map<Long, BigDecimal> remainQuantityMap = inventoryDetailList
.stream()
.collect(Collectors.toMap(InventoryDetail::getId, InventoryDetail::getRemainQuantity));
boolean validResult = inventoryDetailBoList
.stream()
.anyMatch(inventoryDetailBo ->
!remainQuantityMap.containsKey(inventoryDetailBo.getId())
|| remainQuantityMap.get(inventoryDetailBo.getId()).compareTo(inventoryDetailBo.getShipmentQuantity()) < 0
);
if (validResult) {
throw new BaseException("库存不足");
}
// 计算得到的出库数据集合
List<InventoryDetailBo> shipmentList = new LinkedList<>();
// 本次需出库数map
Map<String, BigDecimal> needMap = inventoryBoList.stream().collect(Collectors.toMap(PlaceAndItem::getKey, InventoryBo::getQuantity));
String lastKey = "";
for (InventoryDetail detail : inventoryDetailList) {
String currentKey = detail.getKey();
if (!needMap.containsKey(currentKey)) {
continue;
}
BigDecimal needQuantity = needMap.get(currentKey);
if (lastKey.equals(currentKey) && needQuantity.compareTo(BigDecimal.ZERO) > 0) {
// 该位置下商品还有入库明细看下需要出库的数量是否为0大于0继续出小于等于0直接跳过
allocateShipmentData(needMap, needQuantity, currentKey, MapstructUtils.convert(detail, InventoryDetailBo.class), shipmentList);
} else if (!lastKey.equals(currentKey)) {
// 如果不一样看下上一个位置下的商品剩余数是否为0不为0则库存不足了
if (!"".equals(lastKey) && needMap.get(lastKey).compareTo(BigDecimal.ZERO) > 0) {
throw new BaseException("库存不足");
}
// 上一个足够继续下一个
allocateShipmentData(needMap, needQuantity, currentKey, MapstructUtils.convert(detail, InventoryDetailBo.class), shipmentList);
}
lastKey = currentKey;
}
return shipmentList;
}
/**
* 分配出库的inventoryDetail
* @param needMap 所需出库数据map
* @param needQuantity 所需出库数
* @param currentKey 当前key
* @param detail 当前入库记录
* @param shipmentInventoryDetailDataBoList 最终分配数据集
*/
private void allocateShipmentData(Map<String, BigDecimal> needMap, BigDecimal needQuantity, String currentKey, InventoryDetailBo detail, List<InventoryDetailBo> shipmentInventoryDetailDataBoList) {
// 计算这条入库明细可出多少数量
BigDecimal shipmentQuantity = needQuantity.compareTo(detail.getRemainQuantity()) > 0 ? detail.getRemainQuantity() : needQuantity;
// 更新所需出库数据map
needMap.put(currentKey, needQuantity.subtract(shipmentQuantity));
// 放入最终出库数据集
detail.setShipmentQuantity(shipmentQuantity);
shipmentInventoryDetailDataBoList.add(detail);
}
}

View file

@ -15,6 +15,7 @@ import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.wms.domain.bo.InventoryBo;
import com.ruoyi.wms.domain.bo.InventoryDetailBo;
import com.ruoyi.wms.domain.bo.ShipmentDataBo;
import com.ruoyi.wms.domain.bo.ShipmentOrderDetailBo;
import com.ruoyi.wms.domain.entity.Inventory;
import com.ruoyi.wms.domain.vo.InventoryVo;
import com.ruoyi.wms.domain.vo.ItemSkuVo;
@ -147,19 +148,7 @@ public class InventoryService extends ServiceImpl<InventoryMapper, Inventory> {
return inventoryMapper.exists(lqw);
}
/**
* 校验库存并计算出库数据
* @param warehouseId
* @param inventoryBoList
*/
public ShipmentDataBo validateInventoryAndCalcShipmentData(@NotNull Long warehouseId, @NotEmpty List<InventoryBo> inventoryBoList) {
List<InventoryBo> shipmentInventoryList = validateInventoryAndCalcShipmentInventoryData(warehouseId, inventoryBoList);
List<InventoryDetailBo> shipmentInventoryDetailList = inventoryDetailService.calcShipmentInventoryDetailData(warehouseId, inventoryBoList);
ShipmentDataBo shipmentDataBo = new ShipmentDataBo(shipmentInventoryDetailList, shipmentInventoryList);
return shipmentDataBo;
}
public List<InventoryBo> validateInventoryAndCalcShipmentInventoryData(@NotNull Long warehouseId, @NotEmpty List<InventoryBo> inventoryBoList) {
public void validateInventory(@NotNull Long warehouseId, @NotEmpty List<InventoryBo> inventoryBoList) {
LambdaQueryWrapper<Inventory> inventoryLambdaQueryWrapper = Wrappers.lambdaQuery();
inventoryLambdaQueryWrapper.eq(Inventory::getWarehouseId, warehouseId);
inventoryLambdaQueryWrapper.in(Inventory::getAreaId, inventoryBoList.stream().map(InventoryBo::getAreaId).toList());
@ -169,18 +158,12 @@ public class InventoryService extends ServiceImpl<InventoryMapper, Inventory> {
throw new BaseException("库存不足");
}
Map<String, Inventory> inventoryMap = inventoryList.stream().collect(Collectors.toMap(PlaceAndItem::getKey, Function.identity()));
List<InventoryBo> shipmentData = new LinkedList<>();
for (InventoryBo bo : inventoryBoList) {
Inventory inventory = inventoryMap.get(bo.getKey());
if (inventory == null || inventory.getQuantity().compareTo(bo.getQuantity()) < 0) {
throw new BaseException("库存不足");
}
InventoryBo addData = new InventoryBo();
BeanUtils.copyProperties(bo, addData);
addData.setQuantity(bo.getQuantity().negate());
shipmentData.add(addData);
}
return shipmentData;
}
public TableDataInfo<InventoryVo> queryWarehouseBoardList(InventoryBo bo, PageQuery pageQuery) {

View file

@ -153,6 +153,7 @@ public class ReceiptOrderService {
bo.getDetails().forEach(detail -> {
InventoryHistory inventoryHistory = new InventoryHistory();
inventoryHistory.setFormId(bo.getId());
inventoryHistory.setFormNo(bo.getReceiptOrderNo());
inventoryHistory.setFormType(ServiceConstants.InventoryHistoryFormType.RECEIPT);
inventoryHistory.setSkuId(detail.getSkuId());
inventoryHistory.setQuantity(detail.getQuantity());

View file

@ -3,16 +3,14 @@ package com.ruoyi.wms.service;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.utils.MapstructUtils;
import com.ruoyi.common.mybatis.core.domain.PlaceAndItem;
import com.ruoyi.common.mybatis.core.page.TableDataInfo;
import com.ruoyi.common.mybatis.core.page.PageQuery;
import com.ruoyi.common.core.utils.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.wms.domain.entity.Inventory;
import com.ruoyi.wms.domain.entity.InventoryDetail;
import com.ruoyi.wms.domain.vo.ItemSkuVo;
import com.ruoyi.wms.domain.vo.ReceiptOrderDetailVo;
import com.ruoyi.wms.mapper.InventoryDetailMapper;
import com.ruoyi.wms.mapper.InventoryMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -40,6 +38,7 @@ public class ShipmentOrderDetailService extends ServiceImpl<ShipmentOrderDetailM
private final ShipmentOrderDetailMapper shipmentOrderDetailMapper;
private final ItemSkuService itemSkuService;
private final InventoryMapper inventoryMapper;
private final InventoryDetailMapper inventoryDetailMapper;
/**
* 查询出库单详情
@ -124,16 +123,16 @@ public class ShipmentOrderDetailService extends ServiceImpl<ShipmentOrderDetailM
.stream()
.collect(Collectors.toMap(ItemSkuVo::getId, Function.identity()));
// 查剩余库存
LambdaQueryWrapper<Inventory> inventoryLambdaQueryWrapper = Wrappers.lambdaQuery();
inventoryLambdaQueryWrapper.eq(Inventory::getWarehouseId, details.get(0).getWarehouseId());
inventoryLambdaQueryWrapper.in(Inventory::getAreaId, details.stream().map(ShipmentOrderDetailVo::getAreaId).toList());
inventoryLambdaQueryWrapper.in(Inventory::getSkuId, details.stream().map(ShipmentOrderDetailVo::getSkuId).toList());
Map<String, BigDecimal> maxQuantityMap = inventoryMapper.selectList(inventoryLambdaQueryWrapper)
List<Long> inventoryDetailIds = details
.stream()
.collect(Collectors.toMap(PlaceAndItem::getKey, Inventory::getQuantity));
.map(ShipmentOrderDetailVo::getInventoryDetailId)
.toList();
Map<Long, BigDecimal> remainQuantityMap = inventoryDetailMapper.selectBatchIds(inventoryDetailIds)
.stream()
.collect(Collectors.toMap(InventoryDetail::getId, InventoryDetail::getRemainQuantity));
details.forEach(detail -> {
detail.setItemSku(itemSkuMap.get(detail.getSkuId()));
detail.setMaxQuantity(maxQuantityMap.get(detail.getKey()));
detail.setRemainQuantity(remainQuantityMap.get(detail.getInventoryDetailId()));
});
return details;
}

View file

@ -21,6 +21,7 @@ import com.ruoyi.wms.domain.entity.ShipmentOrderDetail;
import com.ruoyi.wms.domain.vo.ShipmentOrderVo;
import com.ruoyi.wms.mapper.InventoryDetailMapper;
import com.ruoyi.wms.mapper.ShipmentOrderMapper;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@ -44,6 +45,7 @@ public class ShipmentOrderService {
private final InventoryService inventoryService;
private final InventoryDetailMapper inventoryDetailMapper;
private final InventoryHistoryService inventoryHistoryService;
private final InventoryDetailService inventoryDetailService;
/**
* 查询出库单
@ -153,30 +155,73 @@ public class ShipmentOrderService {
public void shipment(ShipmentOrderBo bo) {
// 1.校验
validateBeforeShipment(bo);
// 2. 保存入库单和入库单明细
// 2.按仓库库区规格合并商品明细数量
List<InventoryBo> mergedInventoryBoList = mergeShipmentOrderDetailByPlaceAndItem(bo.getDetails());
// 3.校验库存
inventoryService.validateInventory(bo.getWarehouseId(), mergedInventoryBoList);
// 4.校验库存记录
List<InventoryDetailBo> inventoryDetailBoList = convertShipmentOrderDetailToInventoryDetail(bo.getDetails());
inventoryDetailService.validateRemainQuantity(inventoryDetailBoList);
// 5. 保存入库单和入库单明细
if (Objects.isNull(bo.getId())) {
insertByBo(bo);
} else {
updateByBo(bo);
}
// 3.计算出库数据
ShipmentDataBo shipmentDataBo = inventoryService.validateInventoryAndCalcShipmentData(bo.getWarehouseId(), MapstructUtils.convert(bo.getDetails(), InventoryBo.class));
// 4.更新库存
inventoryService.updateInventoryQuantity(shipmentDataBo.getShipmentInventoryList());
// 5.更新入库记录剩余数
inventoryDetailMapper.updateRemainQuantity(shipmentDataBo.getShipmentInventoryDetailList(), LoginHelper.getUsername(), LocalDateTime.now());
// 6.创建出库记录
saveInventoryHistory(shipmentDataBo.getShipmentInventoryDetailList(), bo);
// 6.更新库存
mergedInventoryBoList.forEach(mergedInventoryBo -> mergedInventoryBo.setQuantity(mergedInventoryBo.getQuantity().negate()));
inventoryService.updateInventoryQuantity(mergedInventoryBoList);
// 7.更新入库记录剩余数
inventoryDetailMapper.updateRemainQuantity(inventoryDetailBoList, LoginHelper.getUsername(), LocalDateTime.now());
// 8.创建库存记录
saveInventoryHistory(bo);
}
private void saveInventoryHistory(List<InventoryDetailBo> list, ShipmentOrderBo bo){
/**
* 按仓库库区规格合并商品明细的数量
* @param shipmentOrderDetailBoList
* @return
*/
public List<InventoryBo> mergeShipmentOrderDetailByPlaceAndItem(@NotEmpty List<ShipmentOrderDetailBo> shipmentOrderDetailBoList) {
Map<String, InventoryBo> mergedMap = new HashMap<>();
shipmentOrderDetailBoList.forEach(detail -> {
String mergedKey = detail.getKey();
if (mergedMap.containsKey(mergedKey)) {
InventoryBo mergedInventoryBo = mergedMap.get(mergedKey);
mergedInventoryBo.setQuantity(mergedInventoryBo.getQuantity().add(detail.getQuantity()));
return;
}
InventoryBo mergedInventoryBo = new InventoryBo();
mergedInventoryBo.setWarehouseId(detail.getWarehouseId());
mergedInventoryBo.setAreaId(detail.getAreaId());
mergedInventoryBo.setSkuId(detail.getSkuId());
mergedInventoryBo.setQuantity(detail.getQuantity());
mergedMap.put(mergedKey, mergedInventoryBo);
});
return new ArrayList<>(mergedMap.values());
}
public List<InventoryDetailBo> convertShipmentOrderDetailToInventoryDetail(List<ShipmentOrderDetailBo> shipmentOrderDetailBoList) {
return shipmentOrderDetailBoList
.stream()
.map(detail -> {
InventoryDetailBo inventoryDetailBo = new InventoryDetailBo();
inventoryDetailBo.setId(detail.getInventoryDetailId());
inventoryDetailBo.setShipmentQuantity(detail.getQuantity());
return inventoryDetailBo;
}).toList();
}
private void saveInventoryHistory(ShipmentOrderBo bo){
List<InventoryHistory> inventoryHistoryList = new LinkedList<>();
list.forEach(detail -> {
bo.getDetails().forEach(detail -> {
InventoryHistory inventoryHistory = new InventoryHistory();
inventoryHistory.setFormId(bo.getId());
inventoryHistory.setFormNo(bo.getShipmentOrderNo());
inventoryHistory.setFormType(ServiceConstants.InventoryHistoryFormType.SHIPMENT);
inventoryHistory.setSkuId(detail.getSkuId());
inventoryHistory.setQuantity(detail.getShipmentQuantity().negate());
inventoryHistory.setQuantity(detail.getQuantity().negate());
inventoryHistory.setWarehouseId(detail.getWarehouseId());
inventoryHistory.setAreaId(detail.getAreaId());
inventoryHistory.setBatchNumber(detail.getBatchNumber());