准备工作以及功能分析
仓库管理对应的是 gulimall-ware 模块,把后端项目启动起来,添加到注册中心以及 gateway 中的网关配置,这个参考下之前的实现即可
把前端的ware代码导入
![image-20220525162058100](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525162058100.png)
功能列表
![image-20220525170403883](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525170403883.png)
![image-20220525162335208](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525162335208.png)
总体还是比较简单的,01,02,03,05,都是查询,04,06,07是功能操作后面重点说下业务逻辑
前几个查询的实现
整体思路就是重写 list,然后看后端打印的 sql,第一个我重点说下,后面就直接贴代码了
01 仓库列表查询
public PageUtils queryPage(Map<String, Object> params) {
QueryWrapper<WareInfoEntity> queryWrapper = new QueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.eq("id", key).or()
.like("name", key)
.or().like("address", key)
.or().like("areacode", key);
}
IPage<WareInfoEntity> page = this.page(
new Query<WareInfoEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
![image-20220525162641710](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525162641710.png)
02 查询商品库存
@Override
public PageUtils queryPage(Map<String, Object> params) {
QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();
String skuId = (String) params.get("skuId");
if (!StringUtils.isEmpty(skuId)) {
queryWrapper.eq("sku_id", skuId);
}
System.out.println(skuId);
String wareId = (String) params.get("wareId");
if (!StringUtils.isEmpty(wareId)) {
queryWrapper.eq("ware_id", wareId);
}
IPage<WareSkuEntity> page = this.page(
new Query<WareSkuEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
03 查询采购需求
@Override
public PageUtils queryPage(Map<String, Object> params) {
QueryWrapper<PurchaseDetailEntity> queryWrapper = new QueryWrapper<>();
String key = (String) params.get("key");
if (!StringUtils.isEmpty(key)) {
queryWrapper.and(w->{
w.eq("purchase_id", key).or().eq("sku_id", key);
});
}
String status = (String) params.get("status");
if (!StringUtils.isEmpty(status)) {
queryWrapper.eq("status", status);
}
String wareId = (String) params.get("wareId");
if (!StringUtils.isEmpty(wareId)) {
queryWrapper.eq("ware_id", wareId);
}
IPage<PurchaseDetailEntity> page = this.page(
new Query<PurchaseDetailEntity>().getPage(params),
queryWrapper
);
return new PageUtils(page);
}
![image-20220525162935080](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525162935080.png)
05 查询未领取的采购单
/**
* 查询未领取的采购单
*/
@RequestMapping("/unreceive/list")
public R unreceive(@RequestParam Map<String, Object> params){
PageUtils page = purchaseService.queryPageUnreceive(params);
return R.ok().put("page", page);
}
@Override
public PageUtils queryPageUnreceive(Map<String, Object> params) {
IPage<PurchaseEntity> page = this.page(
new Query<PurchaseEntity>().getPage(params),
new QueryWrapper<PurchaseEntity>().eq("status", 0).or().eq("status", 1)
);
return new PageUtils(page);
}
![image-20220525163128580](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525163128580.png)
这个接口是用在合并整单的时候,下面来讲解合并采购需求的业务逻辑
合并采购需求
![image-20220525163240767](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525163240767.png)
上传合并到的 单子的id,如果没有选择则会新建一条,如下是没有选择合并整单的目标单据时出现的页面,这里就存在 purchaseId 可传可不传的情况,后面需要做分支处理,可在 浏览器的 network 中验证,下面的前端对应js的地方(其实这里的前端我感觉都是可以看一下的,😄,感觉里面的vue写的比我好很多)
![image-20220525163951646](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525163951646.png)
![image-20220525163319242](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525163319242.png)
先创建 vo
@Data
public class MergeVo {
private Long purchaseId; //整单id
private List<Long> items; //合并项集合
}
api
/**
* 合并采购需求
*/
@PostMapping("/merge")
public R merge(@RequestBody MergeVo mergeVo){
purchaseService.mergePurchase(mergeVo);
return R.ok();
}
service
@Transactional
@Override
public void mergePurchase(MergeVo mergeVo) {
Long purchaseId = mergeVo.getPurchaseId();
// 1. 没传采购单 id,新建一张采购单
if (purchaseId == null) {
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
purchaseEntity.setCreateTime(new Date());
purchaseEntity.setUpdateTime(new Date());
this.save(purchaseEntity);
// 拿到创建出来的 id
purchaseId = purchaseEntity.getId();
}
// TODO 校验状态 不满足抛出异常
// 2.更新采购需求状态 写入关联的 采购单id(新创建出来的,或者 vo 中给出的关联id)
List<Long> items = mergeVo.getItems();
Long finalPurchaseId = purchaseId;
List<PurchaseDetailEntity> collect = items.stream().map(i -> {
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
detailEntity.setId(i);
detailEntity.setPurchaseId(finalPurchaseId);
detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode());
return detailEntity;
}).collect(Collectors.toList());
detailService.updateBatchById(collect);
// 3. 设置采购单更新时间(小声bb,因为这里没设置 mbp 自动填充)
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(purchaseId);
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
}
这里在common创建了一个枚举类取标识状态,学到了学到了,这里的 msg和前端筛选的状态是对应的
![image-20220525164922991](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525164922991.png)
package com.example.common.constant;
public class WareConstant {
public enum PurchaseStatusEnum {
CREATED(0, "新建"), ASSIGNED(1, "已分配"),
RECEIVED(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 void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
public enum PurchaseDetailStatusEnum {
CREATED(0, "新建"), ASSIGNED(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 void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
}
我们来测试下这个接口,两种情况:
- 提前创建号采购单,关联选中采购单
- 不选中采购单
创建两条采购需求单,然后合并,这里的 sku_id 需要是真实的,后面会去使用 feign 远程调用
领取采购单
还是接口开发的步骤,先看接口文档,然后对应下前端代码,理解业务场景,具体业务编写,场景测试
![image-20220525165920120](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525165920120.png)
这里对应是提供的第三方接口,领取采购单就无法再被修改状态了
/**
* @param ids 采购单 id
*/
@Override
public void received(List<Long> ids) {
// 1.确认当前采购单是新建或者已分配状态
List<PurchaseEntity> collect = ids.stream().map(id -> {
PurchaseEntity byId = this.getById(id);
return byId;
}).filter(item -> {
if (item.getStatus() == WareConstant.PurchaseStatusEnum.CREATED.getCode() ||
item.getStatus() == WareConstant.PurchaseStatusEnum.ASSIGNED.getCode()) {
return true;
}
return false;
}).map(item -> {
item.setStatus(WareConstant.PurchaseStatusEnum.RECEIVED.getCode());
item.setUpdateTime(new Date());
return item;
}).collect(Collectors.toList());
// 2.改变采购单的状态
this.updateBatchById(collect);
// 3.改变采购项的状态
collect.forEach(item -> {
List<PurchaseDetailEntity> entities = detailService.listDetailByPurchaseId(item.getId());
List<PurchaseDetailEntity> detailEntities = entities.stream().map(entity -> {
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
detailEntity.setId(entity.getId());
detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
return detailEntity;
}).collect(Collectors.toList());
detailService.updateBatchById(detailEntities);
});
}
完成采购
![image-20220525222146833](https://yixinglian.oss-cn-hangzhou.aliyuncs.com/blogimage-20220525222146833.png)
完成采购后,需要添加 sku 库存
// 两个 vo
@Data
public class PurchaseDoneVo {
@NotNull
private Long id; // 采购单 id
private List<PurchaseItemDoneVo> items;
}
@Data
public class PurchaseItemDoneVo {
private Long itemId;
private Integer status;
private String reason;
}
@Override
public void done(PurchaseDoneVo purchaseDoneVo) {
// 1. 改变采购单状态
Long id = purchaseDoneVo.getId();
List<PurchaseItemDoneVo> items = purchaseDoneVo.getItems();
// 2. 改变采购项状态
Boolean flag = true;
List<PurchaseDetailEntity> updates = new ArrayList<>();
for (PurchaseItemDoneVo item : items) {
PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
if (item.getStatus() == WareConstant.PurchaseStatusEnum.HASERROR.getCode()) {
flag = false;
detailEntity.setStatus(item.getStatus());
} else {
detailEntity.setStatus(WareConstant.PurchaseStatusEnum.FINISH.getCode());
// 3. 将成功采购的进行入库
PurchaseDetailEntity entity = detailService.getById(item.getItemId());
wareSkuService.addStock(entity.getSkuId(), entity.getWareId(), entity.getSkuNum());
}
detailEntity.setId(item.getItemId());
updates.add(detailEntity);
}
detailService.updateBatchById(updates);
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(id);
purchaseEntity.setStatus(flag ? WareConstant.PurchaseStatusEnum.FINISH.getCode() : WareConstant.PurchaseStatusEnum.HASERROR.getCode());
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
}
@Transactional
@Override
public void addStock(Long skuId, Long wareId, Integer skuNum) {
// 1. 判断如果没有这个库存记录则新增
List<WareSkuEntity> list = baseMapper.selectList(new QueryWrapper<WareSkuEntity>()
.eq("sku_id", skuId).eq("ware_id", wareId));
if (list == null || list.size() == 0) {
WareSkuEntity skuEntity = new WareSkuEntity();
skuEntity.setSkuId(skuId);
skuEntity.setStock(skuNum);
skuEntity.setWareId(wareId);
skuEntity.setStockLocked(0);
// TODO 远程调用 查询 sku name,如果失败整个事物不回滚
try {
R info = productFeignService.info(skuId);
Map<String, Object> skuInfo = (Map<String, Object>) info.get("skuInfo");
if ((Integer) info.get("code") == 0) {
skuEntity.setSkuName((String) skuInfo.get("skuName"));
}
} catch (Exception e) {
}
baseMapper.insert(skuEntity);
}
baseMapper.addStock(skuId, wareId, skuNum);
}