文章目录
当前笔记包含以下内容:
- 97-仓储服务-API-仓库管理-合并采购需求
- 98-仓储服务-API-仓库管理-领取采购单
- 99-仓储服务-API-仓库管理-完成采购
一,简化的采购流程概述
图片展示了一个简化的采购流程。从左到右,流程可以分为以下几个步骤:
-
人工创建采购需求:这个阶段通常由内部用户或部门提出购买某种商品或服务的需求。
-
库存预警创建采购需求:如果库存量低于预定的阈值,系统会自动触发一个采购需求。
-
人工合并:在这一环节中,所有的采购需求被汇总在一起,可能通过手动的方式进行整合。
-
系统定时合并:此外,系统也会定期地将采购需求合并成一张采购单。
-
分配给采购人员:一旦生成了采购单,它会被分配给专门负责采购的人员。
-
通知供应商或自主采购:接下来,采购人员要么联系供应商并下单,要么自行完成采购任务。
-
采购单入库:当物品到达后,它们需要被记录和录入到库存管理系统中。
-
添加库存:最后一步是更新库存数量,准确反映当前的库存。
二,采购流程
1,新建采购单
点击按钮,新建采购单,这步操作非常简单。
2,给采购单分配负责人
点击采购单右侧分配
按钮,给采购单分配负责人。
注意,负责人来自管理员列表,可以在这个功能模块新增一个管理员。
然后点击分配按钮,给采购单分配采购负责人。
3,合并采购需求到采购单
在采购需求中,可以选择讲采购需求合并到未完成采购的采购单中。
点击合并整单后,会调用一个接口查询unreceive状态的采购单。
选中要合并的需求后,点击合并整单,在下拉列表中选择一个采购单,点击确定。
4,采购负责人领取采购单
领取采购单在谷粒商城系统仅仅提供了接口,未有前端实现。从业务上来说,采购负责人可以在移动端操作,领取分配给自己的采购单。
这里用postman调用接口,模拟前端操作领取采购单。
领取完成后,采购单状态变更,如下。
5,完成采购
采购负责人在完成采购任务后,会在采购管理系统中确认完成,特别说明,谷粒商城中不包含采购管理系统,我们需要用Postman模拟请求。
完成采购有2种可能情况:
- 所有采购需求都完成的情况下,采购单的状态为
已完成
,采购需求的状态为已完成
- 部分采购需求完成,采购单的状态为
有异常
,成功的采购需求状态为已完成
,不成功的采购需求状态为采购失败
完成采购的后台业务逻辑:
- 更新采购需求状态,可能是
完成
,可能是采购失败
- 完成采购需求需要更新库存
- 根据所有采购需求的状态更新采购单状态,可能是
完成
,可能是有异常
localhost:88/api/ware/purchase/done
{
"id":1,
"items":[
{
"itemId":1,
"status":3,
"reason":""
},
{
"itemId":2,
"status":4,
"reason":"无货"
}
]
}
三,代码总结
1,查询unreceive状态的采购单接口
Controller:
@RequestMapping("/unreceive/list")
public R unreceivelist(@RequestParam Map<String, Object> params){
PageUtils page = purchaseService.queryPageUnreceivePurchase(params);
return R.ok().put("page", page);
}
Service:
public PageUtils queryPageUnreceivePurchase(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);
}
2 新增将采购需求合并到采购单的接口
Controller:
@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();
if(purchaseId == null){
//1、新建一个
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
purchaseEntity.setCreateTime(new Date());
purchaseEntity.setUpdateTime(new Date());
this.save(purchaseEntity);
purchaseId = purchaseEntity.getId();
}
//TODO 确认采购单状态是0,1才可以合并
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);
PurchaseEntity purchaseEntity = new PurchaseEntity();
purchaseEntity.setId(purchaseId);
purchaseEntity.setUpdateTime(new Date());
this.updateById(purchaseEntity);
}
3,领取采购单
Controller:
/**
* 领取采购单
* @return
*/
@PostMapping("/received")
public R received(@RequestBody List<Long> ids){
purchaseService.received(ids);
return R.ok();
}
Service:
@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.RECEIVE.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 entity1 = new PurchaseDetailEntity();
entity1.setId(entity.getId());
entity1.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
return entity1;
}).collect(Collectors.toList());
detailService.updateBatchById(detailEntities);
});
}
5, 完成采购
5.1 分页插件配置
增加MybatisPlus分页配置,以便前端的分页功能能正常使用。
5.2 服务间接口调用的不同方式
在微服务架构中,Feign 是一个声明式的 HTTP 客户端,它简化了 HTTP 请求的发送。当使用 Feign 进行服务间通信时,有两种常见的配置方式:一种是直接与目标服务通信(绕过网关),另一种是通过 API 网关进行通信。
直接访问服务
配置步骤:
-
定义 Feign Client:
@FeignClient(name = "gulimall-product") public interface SkuInfoFeignService { @GetMapping("/skuinfo/info/{skuId}") SkuInfoEntity getSkuInfo(@PathVariable("skuId") Long skuId); }
-
服务发现:
- 确保
gulimall-product
服务已经在 Eureka 注册中心注册。 - 在 Feign 客户端所在的服务中配置 Eureka 和 Ribbon 以支持服务发现和服务实例选择。
- 确保
-
负载均衡:
- Ribbon 自动为 Feign 提供客户端负载均衡能力,无需额外配置。
优劣分析:
-
优点:
- 性能更高: 减少了网关转发的延迟。
- 简单直接: 服务间的调用更加直接,减少了中间环节。
-
缺点:
- 服务地址变更困难: 如果服务地址发生变化,需要修改 Feign 客户端的配置。
- 安全性较低: 缺少统一的安全认证机制。
- 管理不便: 每个服务都需要单独维护路由信息。
通过网关访问服务
配置步骤:
-
定义 Feign Client:
@FeignClient(name = "gulimall-gateway") public interface SkuInfoFeignService { @GetMapping("/api/product/skuinfo/info/{skuId}") SkuInfoEntity getSkuInfo(@PathVariable("skuId") Long skuId); }
-
网关配置:
- 配置网关路由规则将请求转发到相应的服务。
- 需要在网关服务中添加
/api/product/skuinfo/info/{skuId}
路由规则,指向gulimall-product
服务。
-
服务发现:
- 确保
gulimall-gateway
和gulimall-product
都已在 Eureka 注册中心注册。 - 在 Feign 客户端所在的服务中配置 Eureka 和 Ribbon 以支持服务发现和服务实例选择。
- 确保
优劣分析:
-
优点:
- 统一入口: 所有外部请求都通过网关进入,便于统一管理。
- 增强安全性: 可以在网关层实现认证授权等安全措施。
- 易于扩展: 网关可以提供缓存、监控等功能,方便扩展。
-
缺点:
- 性能影响: 增加了网关转发的延迟。
- 复杂度增加: 需要维护网关路由规则,增加了系统的复杂度。
小小结
选择哪种方式取决于您的具体需求和场景。如果追求高性能和简单性,可以选择直接访问服务;如果希望有更好的可管理性和安全性,则应考虑通过网关访问服务。