谷粒商城--SPU和SKU(分组与属性关联、发布商品、仓库服务)

67 阅读14分钟

分组与属性关联、发布商品、仓库服务

分组与属性关联

显示属性

这里其实就是一个分布查询,流程如下:

  1. 点击分组属性的时候获取到分组id,
  2. 拿分组id去关联表查分组id对应的attr_id
  3. attr_id去pms_attr表中获取属性

image-20220808231237643

controller

/**
 * 3.获取属性分组的关联的所有属性
 */
@RequestMapping("/{attrgroupId}/attr/relation")
public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId) {
    List<AttrEntity> entities = attrService.getRelationAttr(attrgroupId);
    return R.ok().put("data", entities);
}

service

@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
    //分布查询,第一步去关联表中查出所有的组和属性id
    List<AttrAttrgroupRelationEntity> entities = relationService.list(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id",attrgroupId));

    //第二收集属性id
    List<Long> attrIds = entities.stream().map((attr) -> {
        return attr.getAttrId();
    }).collect(Collectors.toList());

    List<AttrEntity> list = this.listByIds(attrIds);
    return list;
}

测试

属性显示成功

image-20220811124827549

移除属性

这里为了方便,我们直接写一个批量删除的接口

controller

  • /product/attrgroup/attr/relation/delete
  • post请求会带来json数据,要封装成自定义对象vos需要@RequestBody注解
  • 意思就是将请求体中的数据封装成vos
/**
 * 4.移除属性分组和属性的关系
 */
@PostMapping("/attr/relation/delete")
public R deleteRelation(@RequestBody AttrGroupRelationVo[] vos) {
    attrService.deleteRelation(vos);
    return R.ok();
}

service

@Override
public void deleteRelation(AttrGroupRelationVo[] vos) {
    List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
        AttrAttrgroupRelationEntity entity = new AttrAttrgroupRelationEntity();
        BeanUtils.copyProperties(item, entity);
        return entity;
    }).collect(Collectors.toList());
    relation.deleteBatchRelation(entities);
}

mapper

void deleteBatchRelation(@Param("entities") List<AttrAttrgroupRelationEntity> entities);

    <delete id="deleteBatchRelation">
        DELETE FROM `pms_attr_attrgroup_relation` where
        <foreach collection="entities" item="item" separator="OR">
            (attr_id = #{item.attrId} AND attr_group_id = #{item.attrGroupId})
        </foreach>
    </delete>
查询分组未关联的属性

逻辑分析

image-20220811172114595

controller

/**
 * 5.获取属性分组没有关联的所有属性
 * /product/attrgroup/{attrgroupId}/noattr/relation
 */
@RequestMapping("/{attrgroupId}/noattr/relation")
public R attrNoRelation(@RequestParam Map<String, Object> params,
                        @PathVariable("attrgroupId") Long attrgroupId) {
   PageUtils page = attrService.getNoRelationAttr(params,attrgroupId);
    return R.ok().put("page", page);
}

service

认真看注释,认真理解,还是很绕的

查询分组未关联的数据三步!

  1. 获得当前分类下的所有分组
  2. 获得这些分组下所有已添加的属性
  3. 添加新属性时移除这些已添加的属性
@Override
public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {

    /**
     *  1.当前分组只能关联自己所属的分类里面的所有属性
     */
    AttrGroupEntity attrGroupEntity = attrGroupService.getById(attrgroupId);
    Long catelogId = attrGroupEntity.getCatelogId();

    /**
     *  2 .当前分组只能引用别的分组没有引用的属性
     *  2.1 当前分类下的所有分组
     *  2.2 这些分组关联的属性
     *  2.3 从当前分类的所有属性中移除这些属性
     */

    /**
     * 2.1 当前分类下的所有分组。收集到他们的组id
     */
    List<AttrGroupEntity> group = attrGroupService.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));

    List<Long> collectGroupIds = group.stream().map((item) -> {
        return item.getAttrGroupId();
    }).collect(Collectors.toList());

    /**
     *  2.2 收集到分组的所有属性
     *  (1)拿着上一步收集到的组id到关系表中查找关系表实体类对象,
     *  (2)通过关系表实体类对象获得所有分组下的所有属性id
     */
    List<AttrAttrgroupRelationEntity> groupId = relationService.list(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", collectGroupIds));
    List<Long> attrIds = groupId.stream().map((item) -> {
        return item.getAttrId();
    }).collect(Collectors.toList());

    /**
     * 2.3 从当前分类的所有属性中移除这些属性并筛选出基本属性(where attr_type = 1)
     */
    QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).eq("attr_type",ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
    //如果其他分组也没关联属性,那么就不加这个条件
    if (attrIds != null && attrIds.size() > 0){
        wrapper.notIn("attr_id", attrIds);
    }

    /**
     * 分页多条件查询
     * where (`attr_id` = ? or `attr_name` like ?)
     */
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
        wrapper.and((w) -> {
            w.eq("attr_id", key).or().like("attr_name", key);
        });
    }


    /**
     * page方法需要两个参数
     * 1.IPage对象(通过工具类Query获取并通过.getPage(params)封装页面传来分页参数)
     * 2.wrapper(自己生成)
     */
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);
    PageUtils pageUtils = new PageUtils(page);
    return pageUtils;
}

tips:

注意非空判断

image-20220811173223505

测试

image-20220811183248352

给销售属性绑定分组,把9号属性绑定给1号分组

image-20220811183459861

查询分组未关联的属性

image-20220811183601207

image-20220811183612731

添加属性关联

常规的调用,注意点是saveBatch传的参数是数据对应的实体类

我们想传其他vo时,需要对这个方法进行一个重写

最后也是通过把vo的值赋给对应实体类,在调用相应批量保存

controller

/**
 * 6.添加属性与分组关联关系
 * /product/attrgroup/attr/relation
 */
@PostMapping("/attr/relation")
public R addRelation(@RequestBody List<AttrGroupRelationVo> vos) {
    relationService.saveBatch(vos);
    return R.ok();
}

service

@Override
public void saveBatch(List<AttrGroupRelationVo> vos) {
    List<AttrAttrgroupRelationEntity> collect = vos.stream().map((item) -> {
        AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
        BeanUtils.copyProperties(item, relationEntity);
        return relationEntity;
    }).collect(Collectors.toList());
    this.saveBatch(collect);
}

发布商品

调试会员等级接口

启动会员微服务,添加网关,添加前端页面…

添加如下会员:

image-20220812111110133

获取分类关联的品牌

controller

/**
 * 1.获取分类关联的品牌
 * /product/categorybrandrelation/brands/list
 */
@GetMapping("/brands/list")
public R relationBrandList(@RequestParam(value = "catId", required = true) Long catId) {
    List<BrandEntity> vos = categoryBrandRelationService.getBrandsByCatId(catId);
    //品牌对象集合在进行筛选,赋予品牌对象id和name,返回封装的vo给前端
    List<BrandVo> collect = vos.stream().map(item -> {
        BrandVo brandVo = new BrandVo();
        brandVo.setBrandId(item.getBrandId());
        brandVo.setBrandName(item.getName());
        return brandVo;
    }).collect(Collectors.toList());
    return R.ok().put("data",collect);
}

service

@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {
    //获得CategoryBrandRelationEntity集合对象
    List<CategoryBrandRelationEntity> catelogId = relationDao.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));
    //获得所有集合对象中brandid,通过brandService查询所有品牌,封装成品牌对象集合
    List<BrandEntity> collect = catelogId.stream().map(item -> {
        Long brandId = item.getBrandId();
        BrandEntity entity = brandService.getById(brandId);
        return entity;
    }).collect(Collectors.toList());
    //返回品牌对象集合
    return collect;
}

测试

开发规范

  1. Controller:处理请求,接受和校验数据
  2. Service接受controlLer传来的数据,进行业务处理
  3. Controller接受service处理完的数据,封装页面指定的vo

image-20220812115152211

获取分类下所有分组&关联属性

接口功能如下

也就是说当我们选择手机分类时,那就查出手机相关的分组信息,并查出每个分组相应属性信息

image-20220812180531794

vo

@Data
public class AttrGroupWithAttrsVo {
    /**
     * 分组id
     */
    @TableId
    private Long attrGroupId;
    /**
     * 组名
     */
    private String attrGroupName;
    /**
     * 排序
     */
    private Integer sort;
    /**
     * 描述
     */
    private String descript;
    /**
     * 组图标
     */
    private String icon;
    /**
     * 所属分类id
     */
    private Long catelogId;
    
    private List<AttrEntity> attrs;
}

controller

/**
 * 7.获取分类下所有分组&关联属性
 * /product/attrgroup/{catelogId}/withattr
 */
@GetMapping("/{catelogId}/withattr")
public R getAttrGroupWithAttrs(@PathVariable("catelogId") Long catelogId) {
    List<AttrGroupWithAttrsVo> vos = attrGroupService.getAttrGroupWithAttrsByCatelogId(catelogId);
    return R.ok().put("data",vos);
}

service

vo的重要性:

  1. vo(value object)当相应数据需要自定义时,用vo是最好的选择,不需要对实体类字段进行修改

image-20220812181322056

/**
     * 获取分类下的所有分组及属性
     * @param catelogId
     * @return
     */
    @Override
    public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
        /** 1.获取分类下的所有分组,封装成集合
         *  分类和组的关系在pms_group表中,所以(where catelog_id = ?)即可查出分类对应的组
         *  由于这是mp,它会得出所有的这种关系,并把结果封装成集合
         */
        List<AttrGroupEntity> list = this.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));

        /** 2.获得分组下的属性
         *  要第三张关联表,直接调用关联表的service即查询分组对应的属性id
         *  获得属性id在去调用属性表的service即可查询属性名
         *  以上两步前面已经写好逻辑了直接调用即可attrService.getRelationAttr(groupId)
         */
        List<AttrGroupWithAttrsVo> collect = list.stream().map((item) -> {
            AttrGroupWithAttrsVo attrGroupWithAttrsVo = new AttrGroupWithAttrsVo();
            BeanUtils.copyProperties(item, attrGroupWithAttrsVo);
            List<AttrEntity> attrs = attrService.getRelationAttr(attrGroupWithAttrsVo.getAttrGroupId());
            if (attrs != null) {
                attrGroupWithAttrsVo.setAttrs(attrs);
            }
            return attrGroupWithAttrsVo;
        }).filter((attrvo) -> {
            return attrvo.getAttrs() != null && attrvo.getAttrs().size() > 0;
        }).collect(Collectors.toList());
        return collect;
    }

测试

image-20220812180358798

商品新增vo抽取

设置完属性,点击保存之后取消保存,复制控制台输出

image-20220812210242531

[在线JSON字符串转Java实体类(JavaBean、Entity)-BeJSON.com](https://www.bejson.com/json2javapojo/new/)

直接解析json数据封装成实体类

这里我简单截取一个主要的Vo

此Vo包括每个步骤所携带的数据,有的是单个字段有的是一个集合

逻辑不难,难点是要理清逻辑,注意细节!

@Data
public class SpuSaveVo {

    @NotEmpty(groups = {AddGroup.class})
    private String spuName;
    private String spuDescription;
    @NotEmpty(groups = {AddGroup.class})
    private Long catalogId;
    @NotEmpty(groups = {AddGroup.class})
    private Long brandId;
    private double weight;
    private int publishStatus;
    private List<String> decript;
    private List<String> images;
    private Bounds bounds;
    @NotEmpty(groups = {AddGroup.class})
    private List<BaseAttrs> baseAttrs;
    @NotEmpty(groups = {AddGroup.class})
    private List<Skus> skus;

}
商品新增业务流程分析

逻辑很简单那,就是把数据保存到多张表

因为这个Vo收集的数据很多,包括每个步骤你所选择的数据

1.保存spu基本信息 pms_spu_info

因为所有传来的信息都在vo里,所以我们把信息拷贝到对应的实体类中,如果vo没有的那就可以自己赋值

表结构如下:

image-20220813192926303

这里的infoEntity.setCreateTime(new Date());infoEntity.setUpdateTime(new Date());是因为前端传入的是没有这两个字段的,我们自己赋值即可

SpuInfoEntity infoEntity = new SpuInfoEntity();
BeanUtils.copyProperties(vo, infoEntity);
infoEntity.setCreateTime(new Date());
infoEntity.setUpdateTime(new Date());
this.saveBaseInfo(infoEntity);

2.保存spu的描述图片 pms_spu_info_desc
保存哪个数据到哪个表,就注入那个service

String.join()的作用是把集合中的元素通过","分割形成一个一个的字符串

List<String> decript = vo.getDecript();
SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
descEntity.setSpuId(infoEntity.getId());
descEntity.setDecript(String.join(",", decript));
spuInfoDescService.saveSpuInfoDesc(descEntity);

3.保存spu的图片集 pms_spu_images
从vo中获取所有图片集合
调用图片service进行保存,保存只需要两个点
图片id和url地址,传入对象即可

List<String> images = vo.getImages();
imagesService.saveImages(infoEntity.getId(), images);

4.保存spu的规格参数 pms_product_attr_value
从vo中获取所有规格参数集合
对规格参数集合进行遍历,设置每项的属性

List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
List<ProductAttrValueEntity> collect = baseAttrs.stream().map((attr) -> {
    ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
    valueEntity.setAttrId(attr.getAttrId());
    AttrEntity id = attrService.getById(attr.getAttrId());
    valueEntity.setAttrName(id.getAttrName());
    valueEntity.setAttrValue(attr.getAttrValues());
    valueEntity.setQuickShow(attr.getShowDesc());
    valueEntity.setSpuId(infoEntity.getId());
    return valueEntity;
}).collect(Collectors.toList());
attrValueService.saveProductAttr(collect);

5.保存spu的积分信息 mall_sms -> sms_spu_bounds

Bounds bounds = vo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
BeanUtils.copyProperties(bounds, spuBoundTo);
spuBoundTo.setSpuId(infoEntity.getId());
R r0 = couponFeignService.saveSpuBounds(spuBoundTo);
if (r0.getCode() != 0) {
    log.error("远程保存spu积分信息异常");
}
couponFeignService.saveSpuBounds(spuBoundTo);

6.保存当前spu对应的所有sku信息;

//6.1sku的基本信息;pms_sku_info
List<Skus> skus = vo.getSkus();
if (skus != null && skus.size() > 0) {
    skus.forEach(item -> {
        String defalutImg = "";
        for (Images image : item.getImages()) {
            if (image.getDefaultImg() == 1) {
                defalutImg = image.getImgUrl();
            }
        }
        SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
        BeanUtils.copyProperties(item, skuInfoEntity);
        //添加vo中没有的信息
        skuInfoEntity.setBrandId(infoEntity.getBrandId());
        skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
        skuInfoEntity.setSaleCount(0L);
        skuInfoEntity.setSpuId(infoEntity.getId());
        skuInfoEntity.setSkuDefaultImg(defalutImg);
        skuInfoService.saveSkuInfo(skuInfoEntity);

        //6.2sku图片信息;pms_sku_images
        //没有图片路径的无需保存
        Long skuId = skuInfoEntity.getSkuId();
        List<SkuImagesEntity> imageEntities = item.getImages().stream().map(img -> {
            SkuImagesEntity skuImagesEntity = new SkuImagesEntity();

            skuImagesEntity.setSkuId(skuId);
            skuImagesEntity.setImgUrl(img.getImgUrl());
            skuImagesEntity.setDefaultImg(img.getDefaultImg());

            return skuImagesEntity;
        }).filter(entity -> {
            return !StringUtils.isEmpty(entity.getImgUrl());
        }).collect(Collectors.toList());
        skuImagesService.saveBatch(imageEntities);

        //6.3sku的销售属性;pms_sku_sale_attr_value
        List<Attr> attr = item.getAttr();
        List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
            SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
            BeanUtils.copyProperties(a, attrValueEntity);
            attrValueEntity.setSkuId(skuId);

            return attrValueEntity;
        }).collect(Collectors.toList());
        skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

        //6.4sku的优惠满减信息(跨服务);
        SkuReductionTo skuReductionTo = new SkuReductionTo();
        BeanUtils.copyProperties(item, skuReductionTo);
        skuReductionTo.setSkuId(skuId);
        if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1) {
            R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
            if (r1.getCode() != 0) {
                log.error("远程保存spu积分信息异常");
            }
        }

    });
}

测试

检索功能

也就是多条件分页查询,很常见的功能!

spu检索

controller

/**
 * 列表
 */
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
    PageUtils page = spuInfoService.queryPageByCondition(params);

    return R.ok().put("page", page);
}

service

@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
    QueryWrapper<SpuInfoEntity> queryWrapper = new QueryWrapper<>();
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
        //等价sql: status=1 and (id=1 or spu_name like xxx)
        queryWrapper.and((w) -> {
            w.eq("id", key).or().like("spu_name", key);
        });
    }
    String status = (String) params.get("status");
    if (!StringUtils.isEmpty(status)) {
        queryWrapper.eq("publish_status", status);
    }
    String brandId = (String) params.get("brandId");
    if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {
        queryWrapper.eq("brand_id", brandId);
    }
    String catelogId = (String) params.get("catelogId");
    if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {
        queryWrapper.eq("catalog_id", catelogId);
    }
    IPage<SpuInfoEntity> page = this.page(
            new Query<SpuInfoEntity>().getPage(params),
            queryWrapper
    );
    return new PageUtils(page);
}

sku检索

controller

/**
 * 列表
 */
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){
    PageUtils page = skuInfoService.queryPageByParams(params);

    return R.ok().put("page", page);
}

service

@Override
public PageUtils queryPageByParams(Map<String, Object> params) {
    QueryWrapper<SkuInfoEntity> queryWrapper = new QueryWrapper<>();
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
        queryWrapper.and((w) -> {
            w.eq("sku_id", key).or().like("sku_name", key);
        });
    }
    String catelogId = (String) params.get("catelogId");
    if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {
        queryWrapper.eq("catalog_id", catelogId);
    }
    String brandId = (String) params.get("brandId");
    if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {
        queryWrapper.eq("brand_id", brandId);
    }
    String max = (String) params.get("max");
    if (!StringUtils.isEmpty(max)) {
        try {
            BigDecimal bigDecimal = new BigDecimal(max);
            if (bigDecimal.compareTo(new BigDecimal("0")) == 1) {
                queryWrapper.le("price", max);
            }
        } catch (Exception e) {
        }
    }
    String min = (String) params.get("min");
    if (!StringUtils.isEmpty(min)) {
        queryWrapper.ge("price", min);
    }

    IPage<SkuInfoEntity> page = this.page(
            new Query<SkuInfoEntity>().getPage(params),
            queryWrapper
    );
    return new PageUtils((page));
}

仓库服务

整合ware服务&获取仓库列表
  1. 加入微服务注册中心
  2. 加入网关

获取仓库列表就是对仓库表的简单查询,逆向生成代码以帮我们生成好,只要配置好网关就可以直接显示

image-20220906101248116

我们只要记住,反是单表操作的逆向生成以帮我们生成好了,我们能拿来直接用,就像增加仓库、删除、修改都是可以直接用的!

多条件分页查询

@Override
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);
}

多条件查询都是一样的套路,获得你搜索的key,然后拿这个key去模糊匹配多个字段

比如这里拿你输入的key会在name、address、areacode做模糊查询,条件直接通过or来拼接

查询库存&创建采购需求

查询库存

查询库存也是单表操作,CRUD都帮我们做好了,我们就在分页的基础上加上多条件查询即可

//多条件分页查询
@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);
    }
    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);
}

创建采购需求

同上都是单表操作,我们只需要做采购需求的多条件分页查询

@Override
public PageUtils queryPage(Map<String, Object> params) {
    QueryWrapper<PurchaseDetailEntity> queryWrapper = new QueryWrapper<PurchaseDetailEntity>();
    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-20220906105205032

1.创建采购单

image-20220906105356726

2.合并请求接口

这里有两种情况如下:

  • 如果没有选中采购单,那么会自动创建采购单进行合并
  • 有的话,就用采购单id

controller

/**
 * 合并采购单
 */
@PostMapping("/merge")
public R merge(@RequestBody MergeVo mergeVo) {
    boolean flag = purchaseService.mergePurchase(mergeVo);
    if(flag){
        return R.ok();
    }else {
        return R.error().put("msg","请选择新建或已分配的采购需求");
    }
}

VO如下:
    
@Data
public class MergeVo {

    private Long purchaseId;

    private List<Long> items;
}

impl

实际上就是创建完采购需求对象和采购单对象后,点击合并,这两个对象信息会发生变化,整体就是做这些操作

具体的看注释,这里还用到了一些枚举类的写法,通过枚举类获得状态信息,了解即可,这里就不写了,可以去看老师的源码

@Transactional
@Override
public boolean mergePurchase(MergeVo mergeVo) {
    //一、获取Vo中的信息
    //如果指定了采购单,那就获取采购单的id
    Long purchaseId = mergeVo.getPurchaseId();
    //获得采购需求的id
    List<Long> items = mergeVo.getItems();

    //二、过滤采购需求
    //对采购需求id进行过滤,如果采购需求处于新建或者已分配的收集成新的集合
    //这样做的目的是为了进行筛选,如果你选中正在采购的是不会被合并的
    List<Long> collect = items.stream()
            .filter(i -> {
                //通过采购需求的id获取采购需求实体类
                        PurchaseDetailEntity purchaseDetailEntity = purchaseDetailService.getById(i);
                        if (purchaseDetailEntity.getStatus() == WareConstant.PurchaseDetailStatusEnum.CREATED.getCode()
                                || purchaseDetailEntity.getStatus() == WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode()) {
                            return true;
                        } else {
                            return false;
                        }
                    }
            ).collect(Collectors.toList());

    //三、没有指定采购单逻辑和指定了的逻辑
    if (collect != null && collect.size() > 0) {
        //3.1如果没有指定采购单,那就自动创建一个
        if (purchaseId == null) {
            PurchaseEntity purchaseEntity = new PurchaseEntity();
            //如果是新创建的采购单,创建时间更新时间,状态都是没有默认值的所以这默认值我们自己来赋值
            purchaseEntity.setCreateTime(new Date());
            purchaseEntity.setUpdateTime(new Date());
            //这里设置采购单的状态采用的是枚举类的形式获取
            purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
            this.save(purchaseEntity);
            //获得自动创建的采购单id
            purchaseId = purchaseEntity.getId();
        }

        /** 3.2指定采购单了,逻辑如下
         * 1.采购单id为Vo中获取的指定id
         * 2.设置所有的采购需求对象并收集成对象
         */
        Long finalPurchaseId = purchaseId;
        List<PurchaseDetailEntity> collect1 = collect.stream().map(i -> {
            //获取所有的采购需求对象
            //更新采购需求的状态,一共需要该两个点,一个是采购状态,一个是采购单id。设置采购需求的id是为了区分是哪一个进行了更改
            PurchaseDetailEntity purchaseDetailEntity = purchaseDetailService.getById(i);
            purchaseDetailEntity.setPurchaseId(finalPurchaseId);
            purchaseDetailEntity.setId(i);
            purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode());
            return purchaseDetailEntity;
        }).collect(Collectors.toList());

        //批量更改采购需求,这里是MP里的接口,可直接传入对象,MP会自动读取里面的ID
        purchaseDetailService.updateBatchById(collect1);

        //四、优化时间更新,为了显示的时间符合我们的样式
        PurchaseEntity purchaseEntity = new PurchaseEntity();
        purchaseEntity.setId(purchaseId);
        purchaseEntity.setUpdateTime(new Date());

        //五、更新采购单
        return this.updateById(purchaseEntity);
    } else {
        return false;
    }
}
领取采购单

这里我们只用写好接口的功能,这个请求一般是由app来进行发送

controller

/**
 * 领取采购单
 */
@PostMapping("/received")
public R received(@RequestBody List<Long> ids){
    purchaseService.received(ids);
    return R.ok();
}

impl

  1. 领取采购单,通过接口测试工具完成请求

  2. 领取玩采购单后,更改采购单状态和对应采购需求状态

    1. 采购单状态改为已领取
    2. 采购需求状态改为正在采购
@Override
    public void received(List<Long> ids) {
        //1.确认当前采购单状态
        List<PurchaseEntity> collect = ids.stream().map(item -> {
            //通过采购单id获取采购单对象
            PurchaseEntity purchaseEntity = this.getById(item);
            return purchaseEntity;
        }).filter(id -> {
            //对采购单对象进行过滤,如果状态为新建或者已分配的留下
            if (id.getStatus() == WareConstant.PurchaseStatusEnum.CREATED.getCode() ||
                    id.getStatus() == WareConstant.PurchaseStatusEnum.ASSIGNED.getCode()) {
                return true;
            } else {
                return false;
            }
        }).map(item -> {
            //对上面收集好的在进行过滤,改变采购单状态为已领取(RECEIVE)
            item.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());
            //对上面收集好的在进行过滤,改变采购单更新时间
            item.setUpdateTime(new Date());
            return item;
        }).collect(Collectors.toList());

        //2.批量修改改变采购单状态
        this.updateBatchById(collect);

        //3.改变采购需求中的状态
        if (collect != null && collect.size() > 0) {
            collect.forEach(item -> {
                List<PurchaseDetailEntity> entities = purchaseDetailService.listDetailByPurchaseId(item.getId());
                List<PurchaseDetailEntity> detailEntities = entities.stream().map(entity -> {
                    PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
                    purchaseDetailEntity.setId(entity.getId());
                    //将采购需求中的状态改为正在采购
                    purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
                    return purchaseDetailEntity;
                }).collect(Collectors.toList());
                purchaseDetailService.updateBatchById(detailEntities);
            });
        }
    }

image-20220814004750176

image-20220814004807416

完成采购

controller

这里我们只用写好接口的功能,这个请求一般是由app来进行发送

/**
 * 完成采购单
 */
@PostMapping("/done")
public R finished(@RequestBody PurchaseDoneVo doneVo){
    purchaseService.done(doneVo);
    return R.ok();
}

VO如下:
@Data
public class PurchaseDoneVo {
    @NonNull
    private Long id;

    private List<PurchaseItemDoneVo> items;

    public PurchaseDoneVo(){}
}

@Data
public class PurchaseItemDoneVo {

    private Long itemId;

    private Integer status;

    private String reason;
}

impl

完成采购主要注意有几个地方发生了变化,做好逻辑的判断即可

代码如下:

/**
     * 采购完成一共三地方会发生变化
     *  1.采购单状态
     *  2.库存增加
     *  3.采购需求状态发生变化
     * @param doneVo
     */
    @Override
    public void done(PurchaseDoneVo doneVo) {
        //获取完成的是哪一个采购单
        Long id = doneVo.getId();
        //一、初始化
        Boolean flag = true;
        //获取采购单id集合
        List<PurchaseItemDoneVo> items = doneVo.getItems();
        //收集结果
        List<PurchaseDetailEntity> updates = new ArrayList<>();
        
        for (PurchaseItemDoneVo item : items) {
            PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
            if (item.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()) {
                flag = false;
                purchaseDetailEntity.setStatus(item.getStatus());
            } else {
                //二、采购需求状态发生变化
                purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.FINISH.getCode());
                //由采购单的id获取采购需求对象,有什么用呢?是用来给增加库存时赋值用的
                PurchaseDetailEntity entity = purchaseDetailService.getById(item.getItemId());
                //三、库存增加
                wareSkuService.addStock(entity.getSkuId(), entity.getWareId(), entity.getSkuNum());
            }
            //采购完成,采购需求中的状态也会发生变化,给实体类对象指明id,从而修改对象的状态
            purchaseDetailEntity.setId(item.getItemId());
            //把要修改的采购需求对象放到集合里
            updates.add(purchaseDetailEntity);
        }
        //因为一个采购单里有多个采购需求合并的,所以批量修改采购需求对象
        purchaseDetailService.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);
    }

测试数据如下:

这里id = 6是对6号采购单发起操作,里面的item9和10是采购单对应的采购需求

{
    "id":16,"items":[
        {
            "itemId":17,"status":3,"reason":""
        },
        {
            "itemId":18,"status":4,"reason":"无货"
        }
    ]
}

image-20220907215915451

采购单状态如下:

有异常是因为我们有一个采购单没有采购完成

image-20220907215937701

采购需求如下:

没有完成的采购需求会显示采购失败

image-20220907220051000

库存如下:

image-20220907220103041

显示商品库存中的sku_name

image-20220907222230675

怎么显示呢?锁定库存就是本表库存表相关的可以直接设置,而sku_name是mall-product微服务里才能查询的到的

那就写Feign接口呗,这里介绍两种feign接口的写法:

1.给远程调用的微服务发请求

  •  @FeignClient("mall-product") 指定微服务
    
  •  /product/skuinfo/info/{skuId}
    

2.给网关发请求

  • @FeignClient(“mall-gateway”)
  • /api/product/skuinfo/info/{skuId}
@FeignClient("mall-gateway")
public interface ProductFeignService {
    @RequestMapping("/api/product/skuinfo/info/{skuId}")
    public R info(@PathVariable("skuId") Long skuId);

}

增加库存的时候注入FeignService接口即可实现远程调用

这里采取了trycatch的形式来捕获异常,可以防止远程调用失败时,事务回滚

@Transactional
@Override
public void addStock(Long skuId, Long wareId, Integer skuNum) {
    //判断如果没有此库存记录,则为新增操作;如果有则为更改操作
    List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
    if (wareSkuEntities == null || wareSkuEntities.size() == 0) {
        WareSkuEntity wareSkuEntity = new WareSkuEntity();
        wareSkuEntity.setSkuId(skuId);
        wareSkuEntity.setStock(skuNum);
        wareSkuEntity.setWareId(wareId);
        wareSkuEntity.setStockLocked(0);
        //TODO 远程查询sku的名字
        //如果查询名字查询失败了,事务回滚有点不值得,所以用trycatch来捕捉一下
        try {
            R info = productFeignService.info(skuId);
            Map<String,Object> skuInfo = (Map<String, Object>) info.get("skuInfo");
            if (info.getCode() == 0){
                wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        wareSkuDao.insert(wareSkuEntity);
    } else {
        wareSkuDao.addStock(skuId, wareId, skuNum);
    }
}