|
|
# 库存管理
|
|
|
|
|
|
## 1. 仓库列表维护
|
|
|
|
|
|
### 1.1 注册中心配置
|
|
|
|
|
|
首先我们需要把库存服务注册到注册中心中。
|
|
|
|
|
|

|
|
|
|
|
|
然后在nacos中发现注册的服务
|
|
|
|
|
|

|
|
|
|
|
|
### 1.2 网关路由配置
|
|
|
|
|
|
客户端首先访问的都是网关服务,所以需要配置对应的路由规则
|
|
|
|
|
|

|
|
|
|
|
|
就可以完成对仓库列表的处理了
|
|
|
|
|
|

|
|
|
|
|
|
### 1.3 关键字查询
|
|
|
|
|
|
然后实现仓库列表的关键字查询
|
|
|
|
|
|

|
|
|
|
|
|

|
|
|
|
|
|
## 2.商品库存管理
|
|
|
|
|
|
添加对应的检索条件
|
|
|
|
|
|

|
|
|
|
|
|
效果展示
|
|
|
|
|
|

|
|
|
|
|
|
## 3.采购流程
|
|
|
|
|
|
完整的采购流程
|
|
|
|
|
|

|
|
|
|
|
|
### 3.1 采购需求维护
|
|
|
|
|
|

|
|
|
|
|
|
### 3.2 采购需求合并
|
|
|
|
|
|
#### 3.2.1 查询分配的采购单
|
|
|
|
|
|
合并采购需求时我们需要把这些采购需求合并到一个采购单中,所以首先需要创建采购单
|
|
|
|
|
|

|
|
|
|
|
|
然后在整合时我们需要查询出状态为 新建 或者 已分配 的采购单
|
|
|
|
|
|

|
|
|
|
|
|
对应的需要创建接口
|
|
|
|
|
|

|
|
|
|
|
|

|
|
|
|
|
|
然后就可以实现整合处理了。
|
|
|
|
|
|
#### 3.2.2 采购需求合并
|
|
|
|
|
|
接收传递的信息创建对应的VO对象
|
|
|
|
|
|
```java
|
|
|
@Data
|
|
|
public class MergeVO {
|
|
|
//{ purchaseId: this.purchaseId, items: items }
|
|
|
private Long purchaseId;
|
|
|
private List<Long> items;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
创建对应的枚举类型的常量
|
|
|
|
|
|
```java
|
|
|
package com.msb.common.constant;
|
|
|
|
|
|
/**
|
|
|
* 库存模块的常量
|
|
|
*
|
|
|
*/
|
|
|
public class WareConstant {
|
|
|
|
|
|
/**
|
|
|
* 采购单状态
|
|
|
*/
|
|
|
public enum PurchaseStatusEnum{
|
|
|
CREATED(0,"新建")
|
|
|
,ASSIGED(1,"已分配")
|
|
|
,RECEIVE(2,"已领取")
|
|
|
,FINISH(3,"已完成")
|
|
|
,HASERROR(4,"有异常");
|
|
|
private int code;
|
|
|
private String msg;
|
|
|
PurchaseStatusEnum(int code,String msg){
|
|
|
this.code = code;
|
|
|
this.msg = msg;
|
|
|
}
|
|
|
public int getCode(){
|
|
|
return code;
|
|
|
}
|
|
|
|
|
|
public String getMsg() {
|
|
|
return msg;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 采购需求状态
|
|
|
*/
|
|
|
public enum PurchaseDetailStatusEnum{
|
|
|
CREATED(0,"新建")
|
|
|
,ASSIGED(1,"已分配")
|
|
|
,BUYING(2,"正在采购")
|
|
|
,FINISH(3,"已完成")
|
|
|
,HASERROR(4,"采购失败");
|
|
|
private int code;
|
|
|
private String msg;
|
|
|
PurchaseDetailStatusEnum(int code,String msg){
|
|
|
this.code = code;
|
|
|
this.msg = msg;
|
|
|
}
|
|
|
public int getCode(){
|
|
|
return code;
|
|
|
}
|
|
|
|
|
|
public String getMsg() {
|
|
|
return msg;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
添加对应的接口
|
|
|
|
|
|

|
|
|
|
|
|
然后在service中完成对应的业务处理
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 完成采购需求的合单操作
|
|
|
* @param mergeVO
|
|
|
* @return
|
|
|
*/
|
|
|
@Transactional
|
|
|
@Override
|
|
|
public Integer merge(MergeVO mergeVO) {
|
|
|
Long purchaseId = mergeVO.getPurchaseId();
|
|
|
if(purchaseId == null){
|
|
|
// 新建采购单
|
|
|
PurchaseEntity purchaseEntity = new PurchaseEntity();
|
|
|
purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
|
|
|
purchaseEntity.setCreateTime(new Date());
|
|
|
purchaseEntity.setUpdateTime(new Date());
|
|
|
this.save(purchaseEntity);
|
|
|
purchaseId = purchaseEntity.getId();
|
|
|
}
|
|
|
|
|
|
// 整合菜单需求单
|
|
|
List<Long> items = mergeVO.getItems();
|
|
|
final long finalPurchaseId = purchaseId;
|
|
|
List<PurchaseDetailEntity> list = items.stream().map(i -> {
|
|
|
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
|
|
|
// 更新每一条 需求单的 采购单编号
|
|
|
detailEntity.setId(i);
|
|
|
detailEntity.setPurchaseId(finalPurchaseId);
|
|
|
detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGED.getCode());
|
|
|
return detailEntity;
|
|
|
}).collect(Collectors.toList());
|
|
|
|
|
|
detailService.updateBatchById(list);
|
|
|
// 更新对应的采购单的更新时间
|
|
|
PurchaseEntity entity = new PurchaseEntity();
|
|
|
entity.setId(purchaseId);
|
|
|
entity.setUpdateTime(new Date());
|
|
|
this.updateById(entity);
|
|
|
return null;
|
|
|
}
|
|
|
```
|
|
|
|
|
|

|
|
|
|
|
|
### 3.3 领取采购单
|
|
|
|
|
|
采购单合单完成后,采购人员需要对应的领取采购单来执行后续的流程
|
|
|
|
|
|
先创建对应的领取采购单的接口
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 领取采购单
|
|
|
* [2,3,4]
|
|
|
* @return
|
|
|
*/
|
|
|
@PostMapping("/receive")
|
|
|
public R receive(@RequestBody List<Long> ids){
|
|
|
purchaseService.received(ids);
|
|
|
return R.ok();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
在service中实现领取采购单的业务
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 领取采购单
|
|
|
* @param ids
|
|
|
*/
|
|
|
@Transactional
|
|
|
@Override
|
|
|
public void received(List<Long> ids) {
|
|
|
// 1.领取的采购单的状态只能是新建或者已分配的采购单 其他的是不能领取的
|
|
|
List<PurchaseEntity> list = ids.stream().map(id -> {
|
|
|
return this.getById(id);
|
|
|
}).filter(item -> {
|
|
|
|
|
|
if (item.getStatus() == WareConstant.PurchaseStatusEnum.CREATED.getCode() ||
|
|
|
item.getStatus() == WareConstant.PurchaseStatusEnum.ASSIGED.getCode()) {
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}).map(item->{
|
|
|
item.setUpdateTime(new Date()); // 设置更新时间
|
|
|
// 更新采购单的状态为 已领取
|
|
|
item.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());
|
|
|
return item;
|
|
|
}).collect(Collectors.toList());
|
|
|
// 2.更新采购单的状态为 已领取
|
|
|
this.updateBatchById(list);
|
|
|
// 3.更新采购项的状态为 正在采购
|
|
|
|
|
|
for (Long id : ids) {
|
|
|
// 根据采购单id 找到对应的采购项对象
|
|
|
List<PurchaseDetailEntity> detailEntities = detailService.listDetailByPurchaseId(id);
|
|
|
List<PurchaseDetailEntity> collect = detailEntities.stream().map(item -> {
|
|
|
PurchaseDetailEntity entity = new PurchaseDetailEntity();
|
|
|
entity.setId(item.getId());
|
|
|
entity.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
|
|
|
return entity;
|
|
|
}).collect(Collectors.toList());
|
|
|
// 批量更新采购项
|
|
|
detailService.updateBatchById(collect);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
postman发送请求测试
|
|
|
|
|
|

|
|
|
|
|
|
数据状态更新
|
|
|
|
|
|

|
|
|
|
|
|

|
|
|
|
|
|
搞定
|
|
|
|
|
|
### 3.4 完成采购操作
|
|
|
|
|
|
#### 3.4.1 VO对象
|
|
|
|
|
|
创建对应的VO对象来接收传递的数据
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 采购项的VO数据
|
|
|
*/
|
|
|
@Data
|
|
|
public class PurchaseItemDoneVO {
|
|
|
|
|
|
private Long itemId;
|
|
|
private Integer status;
|
|
|
private String reason;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 采购单的VO数据
|
|
|
*/
|
|
|
@Data
|
|
|
public class PurchaseDoneVO {
|
|
|
|
|
|
private Long id;
|
|
|
|
|
|
private List<PurchaseItemDoneVO> items;
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 3.4.2 OpenFeign配置
|
|
|
|
|
|
OpenFeign的配置-在商品入库的时候我们需要通过OpenFegin来调用商品服务的接口来查询sku的名称
|
|
|
|
|
|
```java
|
|
|
@FeignClient("mall-product")
|
|
|
public interface ProductFeignService {
|
|
|
|
|
|
/**
|
|
|
* 当然我们也可以通过网关来调用商品服务
|
|
|
* @param skuId
|
|
|
* @return
|
|
|
*/
|
|
|
@RequestMapping("/product/skuinfo/info/{skuId}")
|
|
|
public R info(@PathVariable("skuId") Long skuId);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
在启动类中添加Enable注解
|
|
|
|
|
|

|
|
|
|
|
|
不要忘了添加对应的依赖
|
|
|
|
|
|

|
|
|
|
|
|
#### 3.4.3 业务代码实现
|
|
|
|
|
|
首先是Controller接口的创建
|
|
|
|
|
|

|
|
|
|
|
|
然后对应的service逻辑的处理
|
|
|
|
|
|
```java
|
|
|
@Transactional
|
|
|
@Override
|
|
|
public void done(PurchaseDoneVO vo) {
|
|
|
// 获取采购单编号
|
|
|
Long id = vo.getId();
|
|
|
// 2.改变采购项的状态
|
|
|
Boolean flag = true; // 记录采购的状态 默认为 完成
|
|
|
// 获取所有的采购项
|
|
|
List<PurchaseItemDoneVO> items = vo.getItems();
|
|
|
List<PurchaseDetailEntity> list = new ArrayList<>();
|
|
|
for (PurchaseItemDoneVO item : items) {
|
|
|
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
|
|
|
if(item.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()){
|
|
|
// 该采购项采购出现了问题
|
|
|
flag = false;
|
|
|
detailEntity.setStatus(item.getStatus());
|
|
|
}else{
|
|
|
// 采购项采购成功
|
|
|
detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.FINISH.getCode());
|
|
|
// 3.将采购成功的采购项进入库操作
|
|
|
// 跟进采购项编号查询出对应的采购项详情
|
|
|
PurchaseDetailEntity detailEntity1 = detailService.getById(item.getItemId());
|
|
|
wareSkuService.addStock(detailEntity1.getSkuId(),detailEntity1.getWareId(),detailEntity1.getSkuNum());
|
|
|
|
|
|
}
|
|
|
detailEntity.setId(item.getItemId());
|
|
|
//detailService.updateById(detailEntity);
|
|
|
list.add(detailEntity);
|
|
|
}
|
|
|
detailService.updateBatchById(list); // 批量更新 采购项
|
|
|
|
|
|
// 1.改变采购单的状态
|
|
|
PurchaseEntity purchaseEntity = new PurchaseEntity();
|
|
|
purchaseEntity.setId(id);
|
|
|
purchaseEntity.setStatus(flag?WareConstant.PurchaseStatusEnum.FINISH.getCode()
|
|
|
: WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode());
|
|
|
purchaseEntity.setUpdateTime(new Date());
|
|
|
this.updateById(purchaseEntity);
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
然后就是入库的逻辑操作
|
|
|
|
|
|
```java
|
|
|
/**
|
|
|
* 入库操作
|
|
|
* @param skuId 商品编号
|
|
|
* @param wareId 仓库编号
|
|
|
* @param skuNum 采购商品的数量
|
|
|
*/
|
|
|
@Override
|
|
|
public void addStock(Long skuId, Long wareId, Integer skuNum) {
|
|
|
// 判断是否有改商品和仓库的入库记录
|
|
|
List<WareSkuEntity> list = skuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
|
|
|
if(list == null || list.size() == 0){
|
|
|
// 如果没有就新增商品库存记录
|
|
|
WareSkuEntity entity = new WareSkuEntity();
|
|
|
entity.setSkuId(skuId);
|
|
|
entity.setWareId(wareId);
|
|
|
entity.setStock(skuNum);
|
|
|
entity.setStockLocked(0);
|
|
|
try {
|
|
|
// 动态的设置商品的名称
|
|
|
R info = productFeignService.info(skuId); // 通过Feign远程调用商品服务的接口
|
|
|
Map<String,Object> data = (Map<String, Object>) info.get("skuInfo");
|
|
|
if(info.getCode() == 0){
|
|
|
entity.setSkuName((String) data.get("skuName"));
|
|
|
}
|
|
|
}catch (Exception e){
|
|
|
|
|
|
}
|
|
|
skuDao.insert(entity); // 插入商品库存记录
|
|
|
}else{
|
|
|
// 如果有就更新库存
|
|
|
skuDao.addStock(skuId,wareId,skuNum);
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
然后对应的Dao接口和Mapper的SQL代码
|
|
|
|
|
|
```java
|
|
|
@Mapper
|
|
|
public interface WareSkuDao extends BaseMapper<WareSkuEntity> {
|
|
|
|
|
|
void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```xml
|
|
|
<insert id="addStock">
|
|
|
UPDATE wms_ware_sku SET stock=stock+#{skuNum} WHERE sku_id=#{skuId} AND ware_id=#{wareId}
|
|
|
</insert>
|
|
|
```
|
|
|
|
|
|
#### 3.4.4 PostMan测试
|
|
|
|
|
|
最后通过PostMan来测试完成操作
|
|
|
|
|
|

|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"id":3,
|
|
|
"items":[
|
|
|
{"itemId":4,"status":3,"reason":""}
|
|
|
,{"itemId":5,"status":3,"reason":""}
|
|
|
]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
商品库存
|
|
|
|
|
|

|
|
|
|
|
|
采购单
|
|
|
|
|
|

|
|
|
|
|
|
采购项
|
|
|
|
|
|

|
|
|
|
|
|
## 4.阶段性总结
|
|
|
|
|
|
### 4.1 分布式的概念
|
|
|
|
|
|
微服务(SpringCloudAlibaba)
|
|
|
|
|
|
注册中心,配置中心,远程调用,网关,负载均衡,链路追踪...
|
|
|
|
|
|
### 4.2 技术栈
|
|
|
|
|
|
SpringBoot2.4.12 SpringCloud MyBatis-Plus MySQL Vue ElementUI 人人fast,阿里云对象存储
|
|
|
|
|
|
### 4.3 环境
|
|
|
|
|
|
Docker Linux Vagrant MySQL Redis 人人开源
|
|
|
|
|
|
### 4.4 开发规范
|
|
|
|
|
|
数据校验JSR303 全局跨域 R全局统一返回,全局异常处理 枚举状态 业务状态,VO、DTO、PO划分,逻辑删除,Lombok
|