浏览代码

库存管理

chenbiao 3 年之前
父节点
当前提交
4beecd61fc
共有 18 个文件被更改,包括 893 次插入9 次删除
  1. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/stock/GoodsDonStockMapper.java
  2. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/stock/GoodsDonStockRecordMapper.java
  3. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/stock/GoodsSkuStockRelateMapper.java
  4. 11 0
      netflix-dao/src/main/java/com/cyksj/mapper/stock/OrderDonStockMapper.java
  5. 36 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonStock.java
  6. 69 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonStockRecord.java
  7. 24 0
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsSkuStockRelate.java
  8. 25 0
      netflix-dao/src/main/java/com/cyksj/model/entity/OrderDonStock.java
  9. 1 1
      netflix-dao/src/main/java/com/cyksj/model/entity/OrderDonWaybill.java
  10. 1 1
      netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqSetLogisticsPost.java
  11. 30 0
      netflix-dao/src/main/java/com/cyksj/model/request/StockUpReq.java
  12. 108 0
      netflix-dao/src/main/java/com/cyksj/model/views/StatisticsStockView.java
  13. 21 0
      netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java
  14. 50 0
      netflix-service/src/main/java/com/cyksj/service/mange/stock/GoodsDonStockService.java
  15. 259 0
      netflix-service/src/main/java/com/cyksj/service/mange/stock/impl/GoodsDonStockServiceImpl.java
  16. 5 0
      netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
  17. 78 7
      netflix-web/src/main/java/com/cyksj/web/controller/manage/statistics/StatisticsDataController.java
  18. 142 0
      netflix-web/src/main/java/com/cyksj/web/controller/manage/stock/StockController.java

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/stock/GoodsDonStockMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper.stock;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.GoodsDonStock;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:12 PM
+ */
+public interface GoodsDonStockMapper extends BaseMapper<GoodsDonStock> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/stock/GoodsDonStockRecordMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper.stock;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.GoodsDonStockRecord;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:13 PM
+ */
+public interface GoodsDonStockRecordMapper extends BaseMapper<GoodsDonStockRecord> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/stock/GoodsSkuStockRelateMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper.stock;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.GoodsSkuStockRelate;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:13 PM
+ */
+public interface GoodsSkuStockRelateMapper extends BaseMapper<GoodsSkuStockRelate> {
+}

+ 11 - 0
netflix-dao/src/main/java/com/cyksj/mapper/stock/OrderDonStockMapper.java

@@ -0,0 +1,11 @@
+package com.cyksj.mapper.stock;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.OrderDonStock;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:13 PM
+ */
+public interface OrderDonStockMapper extends BaseMapper<OrderDonStock> {
+}

+ 36 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonStock.java

@@ -0,0 +1,36 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:06 PM
+ */
+@Getter
+@Setter
+public class GoodsDonStock extends BaseEntity {
+
+    private String title;
+
+    private Integer total;
+
+    private Integer stock;
+
+    private Integer upgrade;
+
+    private Integer unavailable;
+
+    @DbIgnore
+    @TableField(exist = false)
+    private GoodsDonStockRecord record;
+
+    @DbIgnore
+    @TableField(exist = false)
+    private List<GoodsSkuStockRelate> relateList;
+
+}

+ 69 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonStockRecord.java

@@ -0,0 +1,69 @@
+package com.cyksj.model.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.DbIgnore;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:07 PM
+ */
+@Getter
+@Setter
+@SearchBean(tables = "goods_don_stock s, goods_don_stock_record r ",
+            where = "s.id = r.stock_id "
+)
+public class GoodsDonStockRecord extends BaseEntity{
+
+    @DbField("s.id")
+    private Long stockId;
+
+    @DbField("s.title")
+    @TableField(exist = false)
+    private String title;
+
+    @DbField("r.then_total")
+    private Integer thenTotal;
+
+    /**
+     * put
+     * out
+     * clean
+     * refund
+     * transport
+     */
+    @DbField("r.type")
+    private String type;
+
+    @DbField("r.num")
+    private Integer num;
+
+    @DbField("r.available")
+    private Integer available;
+
+    @DbField("r.price")
+    private BigDecimal price;
+
+    @DbField("r.remark")
+    private String remark;
+
+    @DbField("r.voucher")
+    private String voucher;
+
+    /**
+     * 创建时间
+     */
+    @DbField("r.created_time")
+    @TableField(exist = false)
+    private Date recordCreatedTime;
+}

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsSkuStockRelate.java

@@ -0,0 +1,24 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:09 PM
+ */
+@Getter
+@Setter
+public class GoodsSkuStockRelate extends BaseEntity{
+
+    private Long stockId;
+
+    private Long goodsId;
+
+    private String goodsTitle;
+
+    private Long skuId;
+
+    private String skuTitle;
+
+}

+ 25 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDonStock.java

@@ -0,0 +1,25 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:10 PM
+ */
+@Getter
+@Setter
+public class OrderDonStock extends BaseEntity{
+
+    private Long orderId;
+
+    private Long recordId;
+
+    /**
+     * hasPayment("已付款"),
+     * transport("已发货"),
+     *  refund("已退款")
+     */
+    private String status;
+
+}

+ 1 - 1
netflix-dao/src/main/java/com/cyksj/model/entity/OrderDonWaybill.java

@@ -13,7 +13,7 @@ public class OrderDonWaybill extends BaseEntity{
     /**
      * 订单id
      */
-    private Integer orderId;
+    private Long orderId;
 
     /**
      * 物流公司简码

+ 1 - 1
netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqSetLogisticsPost.java

@@ -17,7 +17,7 @@ public class ReqSetLogisticsPost {
     /**
      * 订单id
      */
-    private Integer orderId;
+    private Long orderId;
 
     /**
      * 物流公司简码

+ 30 - 0
netflix-dao/src/main/java/com/cyksj/model/request/StockUpReq.java

@@ -0,0 +1,30 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+/**
+ * @author chan
+ * @date 2022/12/7 5:35 PM
+ */
+@Getter
+@Setter
+public class StockUpReq {
+
+
+    @NotNull(message = "缺少库存id")
+    private Long id;
+
+    @NotNull(message = "缺少数量")
+    private Integer num;
+
+    private BigDecimal price;
+
+    private String voucher;
+
+    private String remark;
+}

+ 108 - 0
netflix-dao/src/main/java/com/cyksj/model/views/StatisticsStockView.java

@@ -0,0 +1,108 @@
+package com.cyksj.model.views;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * @author chan
+ * @date 2022/12/8 2:55 PM
+ */
+@Getter
+@Setter
+public class StatisticsStockView {
+
+    /**
+     * 商品
+     */
+    @ExcelProperty("商品名称")
+    private String title;
+
+    /**
+     * 当前总数
+     */
+    @ExcelProperty("当前库存数量")
+    private Integer thenTotal = 0;
+
+    /**
+     * 当前价值
+     */
+    @ExcelProperty("当前库存价值")
+    private BigDecimal thenPrice = BigDecimal.ZERO;
+
+    /**
+     * 入库总数
+     */
+    @ExcelProperty("入库")
+    private Integer putTotal = 0;
+
+    /**
+     * 入库总额
+     */
+    @ExcelProperty("入库价值")
+    private BigDecimal putPrice = BigDecimal.ZERO;
+
+    /**
+     * 出库总数
+     */
+    @ExcelProperty("出库")
+    private Integer outTotal = 0;
+
+    /**
+     * 出库金额
+     */
+    @ExcelProperty("出库价值")
+    private BigDecimal outPrice = BigDecimal.ZERO;
+
+    /**
+     * 不可用总数
+     */
+    @ExcelProperty("不可用总数")
+    private Integer unavailable;
+
+    /**
+     * 不可用金额
+     */
+    @ExcelIgnore
+    private BigDecimal unavailablePrice = BigDecimal.ZERO;
+
+    /**
+     * 退货总数
+     */
+    @ExcelProperty("退货库存")
+    private Integer refundTotal = 0;
+
+    /**
+     * 退货金额
+     */
+    @ExcelProperty("退货库存价值")
+    private BigDecimal refundPrice = BigDecimal.ZERO;
+
+    /**
+     * put
+     * out
+     * clean
+     * refund
+     * transport
+     */
+    public void setTotalWithPrice(String type, Integer num, BigDecimal price) {
+        switch (type){
+            case "put":
+                this.putTotal += num;
+                this.putPrice = this.putPrice.add(price);
+                break;
+            case "transport":
+                this.outTotal += num;
+                this.outPrice = this.outPrice.add(price);
+                break;
+            case "refund":
+                this.refundTotal += num;
+                this.refundPrice = this.refundPrice.add(price);
+                break;
+            default:
+        }
+    }
+}

+ 21 - 0
netflix-service/src/main/java/com/cyksj/service/mange/impl/CmsOrderDonServiceImpl.java

@@ -8,16 +8,20 @@ import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
 import com.cyksj.common.util.Jsons;
 import com.cyksj.mapper.*;
+import com.cyksj.mapper.stock.GoodsSkuStockRelateMapper;
+import com.cyksj.mapper.stock.OrderDonStockMapper;
 import com.cyksj.model.dto.WxMpTemplateData;
 import com.cyksj.model.dto.WxMpTemplateMessage;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.excel.ExcelOrderDonInfo;
 import com.cyksj.model.manage.request.ReqSetLogisticsPost;
 import com.cyksj.service.mange.CmsOrderDonService;
+import com.cyksj.service.mange.stock.GoodsDonStockService;
 import com.cyksj.service.wechat.WeChatService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 import java.util.Optional;
@@ -43,6 +47,12 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
 
     private final WeChatService weChatService;
 
+    private final GoodsSkuStockRelateMapper skuStockRelateMapper;
+
+    private final OrderDonStockMapper orderDonStockMapper;
+
+    private final GoodsDonStockService goodsDonStockService;
+
     private static final GlobalThreadPoolTaskExecutor TASK_POOL = GlobalThreadPoolTaskExecutor.getInstance();
 
     @Override
@@ -55,6 +65,7 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         return transportMapper.selectOne(Wrappers.lambdaQuery(OrderDonTransport.class).eq(OrderDonTransport::getOrderId, orderId));
     }
 
+    @Transactional(rollbackFor = Throwable.class)
     @Override
     public OrderDonWaybill setWaybill(ReqSetLogisticsPost reqSetLogisticsPost) {
         OrderDonWaybill orderDonWaybill = Optional.ofNullable(orderDonWaybillMapper.selectOne(Wrappers.lambdaQuery(OrderDonWaybill.class).eq(OrderDonWaybill::getOrderId, reqSetLogisticsPost.getOrderId()))).orElse(new OrderDonWaybill());
@@ -66,6 +77,15 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
             OrderDon orderDon = getById(orderDonWaybill.getOrderId());
             orderDon.setStatus(OrderDon.Status.transport);
             updateById(orderDon);
+
+            //库存扣减逻辑
+            List<GoodsSkuStockRelate> relateList = skuStockRelateMapper.selectList(Wrappers.lambdaQuery(GoodsSkuStockRelate.class).eq(GoodsSkuStockRelate::getSkuId,orderDon.getSkuId()));
+            if(!relateList.isEmpty()){
+                relateList.forEach((relate)->{
+                    goodsDonStockService.upStockRelate(relate.getStockId(),orderDon.getId(),"transport");
+                });
+            }
+
             //发送微信模板消息
             TASK_POOL.execute(()->{
                 try {
@@ -114,4 +134,5 @@ public class CmsOrderDonServiceImpl extends ServiceImpl<OrderDonMapper,OrderDon>
         data.setRemark(new WxMpTemplateData("可复制运单编号查询物流信息"));
         weChatService.sendTemplateMessage(weChatService.getAccessToken(), Jsons.toJson(templateMessage));
     }
+
 }

+ 50 - 0
netflix-service/src/main/java/com/cyksj/service/mange/stock/GoodsDonStockService.java

@@ -0,0 +1,50 @@
+package com.cyksj.service.mange.stock;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.cyksj.model.entity.GoodsDonStock;
+import com.cyksj.model.entity.GoodsDonStockRecord;
+import com.cyksj.model.request.StockUpReq;
+
+/**
+ * @author chan
+ * @date 2022/12/7 5:54 PM
+ */
+public interface GoodsDonStockService {
+
+    void upStockRelate(Long stockId, Long orderId, String status);
+
+    /**
+     * 增加库存
+     */
+    void incr(StockUpReq stock);
+
+    /**
+     * 减少库存
+     */
+    void sub(StockUpReq stock);
+
+    /**
+     * 修改不可用库存
+     */
+    void upUnavailable(StockUpReq stock);
+
+    /**
+     * 修改可刷机
+     */
+    void upgrade(StockUpReq stock);
+
+    /**
+     * 新建库存商品
+     */
+    void save(GoodsDonStock stock);
+
+    /**
+     * 编辑备注
+     */
+    void upRecord(GoodsDonStockRecord record);
+
+    /**
+     * 删除库存
+     */
+    void del(Long stockId);
+}

+ 259 - 0
netflix-service/src/main/java/com/cyksj/service/mange/stock/impl/GoodsDonStockServiceImpl.java

@@ -0,0 +1,259 @@
+package com.cyksj.service.mange.stock.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.math.MathUtil;
+import cn.hutool.core.util.NumberUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.mapper.stock.GoodsDonStockMapper;
+import com.cyksj.mapper.stock.GoodsDonStockRecordMapper;
+import com.cyksj.mapper.stock.GoodsSkuStockRelateMapper;
+import com.cyksj.mapper.stock.OrderDonStockMapper;
+import com.cyksj.model.entity.GoodsDonStock;
+import com.cyksj.model.entity.GoodsDonStockRecord;
+import com.cyksj.model.entity.GoodsSkuStockRelate;
+import com.cyksj.model.entity.OrderDonStock;
+import com.cyksj.model.request.StockUpReq;
+import com.cyksj.service.mange.stock.GoodsDonStockService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.List;
+
+/**
+ * @author chan
+ * @date 2022/12/7 5:55 PM
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class GoodsDonStockServiceImpl implements GoodsDonStockService {
+
+    private final GoodsDonStockMapper stockMapper;
+
+    private final GoodsDonStockRecordMapper stockRecordMapper;
+
+    private final OrderDonStockMapper orderDonStockMapper;
+
+    private final GoodsSkuStockRelateMapper skuStockRelateMapper;
+
+    @Transactional(rollbackFor = Throwable.class)
+    @Override
+    public void upStockRelate(Long stockId, Long orderId, String status) {
+        if ("refund".equals(status)) {
+            List<OrderDonStock> orderDonStocks = orderDonStockMapper.selectList(Wrappers.lambdaQuery(OrderDonStock.class).eq(OrderDonStock::getOrderId, orderId));
+
+            //退款
+            orderDonStocks.forEach((orderDonStock)->{
+                orderDonStock.setStatus("refund");
+                orderDonStockMapper.updateById(orderDonStock);
+                GoodsDonStockRecord putRecord = stockRecordMapper.selectById(orderDonStock.getRecordId());
+
+                GoodsDonStock stock = stockMapper.selectById(putRecord.getStockId());
+                //新增库存记录
+                GoodsDonStockRecord record = new GoodsDonStockRecord();
+                record.setNum(1);
+                record.setStockId(putRecord.getStockId());
+                record.setThenTotal(stock.getTotal());
+                record.setType("refund");
+                record.setPrice(putRecord.getPrice());
+                stockRecordMapper.insert(record);
+
+                //修改批次可用库存数
+                putRecord.setAvailable(putRecord.getAvailable() + 1);
+                stockRecordMapper.updateById(putRecord);
+                //修改库存商品可用数
+                stock.setTotal(stock.getTotal() + 1);
+                stock.setStock(stock.getStock() + 1);
+                stock.setUpgrade(stock.getUpgrade() + 1);
+                stockMapper.updateById(stock);
+            });
+            //1.修改退款状态
+            //2.退回库存
+
+        }else {
+            //发货
+            GoodsDonStockRecord putRecord = stockRecordMapper.selectOne(Wrappers.lambdaQuery(GoodsDonStockRecord.class)
+                    .eq(GoodsDonStockRecord::getStockId, stockId)
+                    .eq(GoodsDonStockRecord::getType, "put")
+                    .gt(GoodsDonStockRecord::getAvailable, 0)
+                    .orderByAsc(GoodsDonStockRecord::getCreatedTime)
+                    .last(" limit 1")
+            );
+            if (putRecord == null) {
+                throw BusinessRuntimeException.getInstance("该库存商品不足.");
+            }
+
+            //储存发货库存信息
+            OrderDonStock orderDonStock = new OrderDonStock();
+            orderDonStock.setOrderId(orderId);
+            orderDonStock.setRecordId(putRecord.getId());
+            orderDonStock.setStatus(status);
+            orderDonStockMapper.insert(orderDonStock);
+
+            GoodsDonStock stock = stockMapper.selectById(putRecord.getStockId());
+            //新增库存记录
+            GoodsDonStockRecord record = new GoodsDonStockRecord();
+            record.setNum(-1);
+            record.setStockId(stockId);
+            record.setThenTotal(stock.getTotal());
+            record.setType("transport");
+            record.setPrice(putRecord.getPrice());
+            stockRecordMapper.insert(record);
+
+            //修改批次可用库存数
+            putRecord.setAvailable(putRecord.getAvailable() - 1);
+            stockRecordMapper.updateById(putRecord);
+            //修改库存商品可用数
+            stock.setTotal(stock.getTotal() - 1);
+            stock.setStock(stock.getStock() - 1);
+            stock.setUpgrade(stock.getUpgrade() - 1);
+            stockMapper.updateById(stock);
+        }
+    }
+
+    @Transactional(rollbackFor = Throwable.class)
+    @Override
+    public void incr(StockUpReq stock) {
+        GoodsDonStock goodsDonStock = stockMapper.selectById(stock.getId());
+
+        //新增库存明细
+        GoodsDonStockRecord record = new GoodsDonStockRecord();
+        record.setVoucher(stock.getVoucher());
+        record.setNum(stock.getNum());
+        record.setPrice(stock.getPrice());
+        record.setAvailable(stock.getNum());
+        record.setStockId(stock.getId());
+        record.setThenTotal(goodsDonStock.getTotal());
+        record.setType("put");
+        stockRecordMapper.insert(record);
+
+        //增加总库存数,可用库存数
+        goodsDonStock.setTotal(goodsDonStock.getTotal() + stock.getNum());
+        goodsDonStock.setStock(goodsDonStock.getStock() + stock.getNum());
+        stockMapper.updateById(goodsDonStock);
+
+    }
+
+    @Transactional(rollbackFor = Throwable.class)
+    @Override
+    public void sub(StockUpReq stock) {
+        GoodsDonStock goodsDonStock = stockMapper.selectById(stock.getId());
+        GoodsDonStockRecord putRecord = stockRecordMapper.selectOne(
+                Wrappers.lambdaQuery(GoodsDonStockRecord.class)
+                        .eq(GoodsDonStockRecord::getStockId,stock.getId())
+                        .eq(GoodsDonStockRecord::getType,"put")
+                        .gt(GoodsDonStockRecord::getAvailable,0)
+                        .orderByAsc(GoodsDonStockRecord::getId)
+                        .last(" limit 1")
+                );
+        if(putRecord == null){
+            throw BusinessRuntimeException.getInstance("没有库存>0的批次");
+        }
+        if(putRecord.getAvailable() - stock.getNum() < 0){
+            throw BusinessRuntimeException.getInstance("该批次货物仅可扣减" + putRecord.getAvailable() + "数");
+        }
+        putRecord.setAvailable(putRecord.getAvailable() - stock.getNum());
+        stockRecordMapper.updateById(putRecord);
+        //扣减库存明细
+        GoodsDonStockRecord  record = new GoodsDonStockRecord();
+        record.setNum(stock.getNum());
+        record.setPrice(putRecord.getPrice());
+        record.setStockId(stock.getId());
+        record.setThenTotal(goodsDonStock.getTotal());
+        record.setType("out");
+        record.setRemark(stock.getRemark());
+        stockRecordMapper.insert(record);
+
+        //增加总库存数,可用库存数
+        goodsDonStock.setTotal(goodsDonStock.getTotal() - stock.getNum());
+        goodsDonStock.setStock(goodsDonStock.getStock() - stock.getNum());
+        stockMapper.updateById(goodsDonStock);
+
+    }
+
+    @Transactional(rollbackFor = Throwable.class)
+    @Override
+    public void upUnavailable(StockUpReq stock) {
+        GoodsDonStock goodsDonStock = stockMapper.selectById(stock.getId());
+        if (stock.getNum() > 0) {
+            //增加
+            if (goodsDonStock.getStock() <= stock.getNum()) {
+                throw BusinessRuntimeException.getInstance("新增数量不可超过总库存数");
+            }
+            GoodsDonStockRecord putRecord = stockRecordMapper.selectOne(
+                    Wrappers.lambdaQuery(GoodsDonStockRecord.class)
+                            .eq(GoodsDonStockRecord::getStockId,stock.getId())
+                            .eq(GoodsDonStockRecord::getType,"put")
+                            .gt(GoodsDonStockRecord::getAvailable,0)
+                            .orderByAsc(GoodsDonStockRecord::getStockId)
+                            .last(" limit 1")
+            );
+            if(putRecord == null){
+                throw BusinessRuntimeException.getInstance("没有库存>0的批次");
+            }
+            putRecord.setAvailable(putRecord.getAvailable() - stock.getNum());
+            stockRecordMapper.updateById(putRecord);
+            goodsDonStock.setUnavailable(goodsDonStock.getUnavailable() + Math.abs(stock.getNum()));
+            goodsDonStock.setStock(goodsDonStock.getStock() - stock.getNum());
+            stockMapper.updateById(goodsDonStock);
+            //新增库存明细
+            GoodsDonStockRecord  record = new GoodsDonStockRecord();
+            record.setNum(stock.getNum());
+            record.setPrice(stock.getPrice());
+            record.setStockId(stock.getId());
+            record.setThenTotal(goodsDonStock.getTotal());
+            record.setType("out");
+            record.setRemark(stock.getRemark());
+
+        }else if(stock.getNum() < 0){
+            //扣减
+            if (goodsDonStock.getUnavailable() <= stock.getNum()) {
+                throw BusinessRuntimeException.getInstance("扣减数量不可超过目前不可用数量");
+            }
+
+        }else {
+            //清库
+
+        }
+    }
+
+    @Transactional(rollbackFor = Throwable.class)
+    @Override
+    public void upgrade(StockUpReq stock) {
+        GoodsDonStock goodsDonStock = stockMapper.selectById(stock.getId());
+        goodsDonStock.setUpgrade(goodsDonStock.getUpgrade() + stock.getNum());
+        stockMapper.updateById(goodsDonStock);
+    }
+
+    @Transactional(rollbackFor = Throwable.class)
+    @Override
+    public void save(GoodsDonStock stock) {
+        stockMapper.insert(stock);
+        GoodsDonStockRecord record = stock.getRecord();
+        record.setStockId(stock.getId());
+        record.setType("put");
+        record.setAvailable(record.getNum());
+        stockRecordMapper.insert(record);
+
+        stock.getRelateList().forEach((relate)->{
+            relate.setStockId(stock.getId());
+            skuStockRelateMapper.insert(relate);
+        });
+    }
+
+    @Override
+    public void upRecord(GoodsDonStockRecord record) {
+        stockRecordMapper.update(null,Wrappers.lambdaUpdate(GoodsDonStockRecord.class).eq(GoodsDonStockRecord::getId,record.getId()).set(GoodsDonStockRecord::getRemark,record.getRemark()));
+    }
+
+    @Override
+    public void del(Long stockId) {
+        stockMapper.deleteById(stockId);
+        skuStockRelateMapper.delete(Wrappers.lambdaQuery(GoodsSkuStockRelate.class).eq(GoodsSkuStockRelate::getStockId,stockId));
+    }
+}

+ 5 - 0
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -54,6 +54,7 @@ import com.cyksj.model.views.CouponNewUserView;
 import com.cyksj.model.views.CouponUserView;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.coupon.CouponFontService;
+import com.cyksj.service.mange.stock.GoodsDonStockService;
 import com.cyksj.service.order.OrderDonService;
 import com.cyksj.service.wechat.WeChatService;
 import com.cyksj.task.JobManager;
@@ -146,6 +147,8 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 
 	private final CouponSkuMapper couponSkuMapper;
 
+	private final GoodsDonStockService goodsDonStockService;
+
 	@Transactional(rollbackFor = Throwable.class)
 	@Override
 	public OrderDon submit(OrderPayRequest payRequest) throws Exception {
@@ -730,6 +733,7 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		final String outNo = outRefundNo;
 		THREAD_POOL_TASK_EXECUTOR.execute(()->{
 			refundRecord(orderDon, goodsDon, sku, outNo);
+			goodsDonStockService.upStockRelate(null, orderDon.getId(),"refund");
 		});
 		//虚物订单退款,清出车队,车票变为已过期
 		clearUnrealGoodsDetail(orderDon, goodsDon);
@@ -802,6 +806,7 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			//记录退款信息
 			THREAD_POOL_TASK_EXECUTOR.execute(()->{
 				refundRecord(orderDon, goodsDon, sku, outNo);
+				goodsDonStockService.upStockRelate(null, orderDon.getId(),"refund");
 			});
 			//虚物订单退款,清出车队,车票变为已过期
 			clearUnrealGoodsDetail(orderDon, goodsDon);

+ 78 - 7
netflix-web/src/main/java/com/cyksj/web/controller/manage/statistics/StatisticsDataController.java

@@ -4,9 +4,12 @@ import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
 import com.alibaba.excel.support.ExcelTypeEnum;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.exception.BusinessRuntimeException;
 import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.stock.GoodsDonStockRecordMapper;
+import com.cyksj.model.entity.*;
 import com.cyksj.model.excel.ExcelAccountData;
 import com.cyksj.model.views.BackgroundDataView;
 import com.cyksj.model.views.ExpiredAccountDataView;
@@ -16,10 +19,6 @@ import com.cyksj.mapper.manage.CustomerServiceEveryMonthDetailMapper;
 import com.cyksj.mapper.manage.RealGoodsOrderHandleMapper;
 import com.cyksj.mapper.manage.statistics.IncrExpiryTimeRecordMapper;
 import com.cyksj.mapper.manage.statistics.StatisticsUserDataMapper;
-import com.cyksj.model.entity.GroupsRelation;
-import com.cyksj.model.entity.IncrExpiryTimeRecord;
-import com.cyksj.model.entity.StatisticsCustomerServiceEveryMonth;
-import com.cyksj.model.entity.StatisticsUserData;
 import com.cyksj.model.request.RenewTimeUpReq;
 import com.cyksj.model.views.*;
 import com.cyksj.service.mange.statistics.StatisticsDataService;
@@ -35,12 +34,12 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletResponse;
-import java.util.List;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Collectors;
 
 /*
@@ -71,6 +70,8 @@ public class StatisticsDataController {
 
 	private final RealGoodsOrderHandleMapper realGoodsOrderHandleMapper;
 
+	private final GoodsDonStockRecordMapper stockRecordMapper;
+
 	/**
 	 * 查询统计数据
 	 */
@@ -208,4 +209,74 @@ public class StatisticsDataController {
 		customerDataView.setMonthData(search);
 		return GatewayResponse.SUCCESS.newBuilder().toResult(customerDataView);
 	}
+
+	@GetMapping("/get/stock")
+	public Result<List<StatisticsStockView>> getStock(HttpServletRequest request, @RequestParam(defaultValue = "0") Long startTime,@RequestParam(defaultValue = "0") Long endTime){
+		List<GoodsDonStock> goodsDonStocks = beanSearcher.searchAll(GoodsDonStock.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		List<StatisticsStockView> res = new ArrayList<>(goodsDonStocks.size() + 1);
+
+		goodsDonStocks.forEach((stock)->{
+			List<GoodsDonStockRecord> goodsDonStockRecords = stockRecordMapper.selectList(Wrappers.lambdaQuery(GoodsDonStockRecord.class)
+					.eq(GoodsDonStockRecord::getStockId, stock.getId())
+					.ge(startTime != 0 , GoodsDonStockRecord::getCreatedTime, DateTime.of(startTime).toString())
+					.le(endTime != 0, GoodsDonStockRecord::getCreatedTime, DateTime.of(endTime).toString())
+			);
+			Map<String, List<GoodsDonStockRecord>> typeRecordsMap = goodsDonStockRecords.stream().collect(Collectors.groupingBy(GoodsDonStockRecord::getType));
+			StatisticsStockView statisticsStockView = new StatisticsStockView();
+			List<GoodsDonStockRecord> put = typeRecordsMap.get("put");
+			if (put != null) {
+				int sum = put.stream().mapToInt(GoodsDonStockRecord::getAvailable).sum();
+				BigDecimal price = put.stream().map((record) -> record.getPrice().multiply(BigDecimal.valueOf(record.getAvailable())))
+						.reduce(BigDecimal.ZERO, BigDecimal::add);
+				statisticsStockView.setThenTotal(sum);
+				statisticsStockView.setThenPrice(price);
+			}
+			statisticsStockView.setUnavailable(stock.getUnavailable());
+			statisticsStockView.setTitle(stock.getTitle());
+			typeRecordsMap.forEach((type,records)->{
+				int sum = records.stream().mapToInt(GoodsDonStockRecord::getNum).sum();
+				BigDecimal price = records.stream().map((record) -> record.getPrice().multiply(BigDecimal.valueOf(record.getNum())))
+						.reduce(BigDecimal.ZERO, BigDecimal::add);
+				statisticsStockView.setTotalWithPrice(type,sum,price);
+			});
+			res.add(statisticsStockView);
+		});
+
+		return GatewayResponse.SUCCESS.newBuilder().toResult(res);
+	}
+
+	@GetMapping("/export/stock")
+	public void exportStock(HttpServletRequest request, HttpServletResponse response, @RequestParam(defaultValue = "0") Long startTime,@RequestParam(defaultValue = "0") Long endTime){
+		List<GoodsDonStock> goodsDonStocks = beanSearcher.searchAll(GoodsDonStock.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		List<StatisticsStockView> res = new ArrayList<>(goodsDonStocks.size() + 1);
+
+		goodsDonStocks.forEach((stock)->{
+			List<GoodsDonStockRecord> goodsDonStockRecords = stockRecordMapper.selectList(Wrappers.lambdaQuery(GoodsDonStockRecord.class)
+					.eq(GoodsDonStockRecord::getStockId, stock.getId())
+					.ge(startTime != 0 , GoodsDonStockRecord::getCreatedTime, DateTime.of(startTime).toString())
+					.le(endTime != 0, GoodsDonStockRecord::getCreatedTime, DateTime.of(endTime).toString())
+			);
+			Map<String, List<GoodsDonStockRecord>> typeRecordsMap = goodsDonStockRecords.stream().collect(Collectors.groupingBy(GoodsDonStockRecord::getType));
+			StatisticsStockView statisticsStockView = new StatisticsStockView();
+			List<GoodsDonStockRecord> put = typeRecordsMap.get("put");
+			if (put != null) {
+				int sum = put.stream().mapToInt(GoodsDonStockRecord::getAvailable).sum();
+				BigDecimal price = put.stream().map((record) -> record.getPrice().multiply(BigDecimal.valueOf(record.getAvailable())))
+						.reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(100),2, RoundingMode.HALF_EVEN);
+				statisticsStockView.setThenTotal(sum);
+				statisticsStockView.setThenPrice(price);
+			}
+			statisticsStockView.setUnavailable(stock.getUnavailable());
+			statisticsStockView.setTitle(stock.getTitle());
+			typeRecordsMap.forEach((type,records)->{
+				int sum = records.stream().mapToInt(GoodsDonStockRecord::getNum).sum();
+				BigDecimal price = records.stream().map((record) -> record.getPrice().multiply(BigDecimal.valueOf(record.getNum())))
+						.reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(100),2, RoundingMode.HALF_EVEN);
+				statisticsStockView.setTotalWithPrice(type,sum,price);
+			});
+			res.add(statisticsStockView);
+		});
+		EasyExcelUtils.createExcelStreamMutilByEasyExcel(response, StatisticsStockView.class, res, "库存统计", "库存统计", ExcelTypeEnum.XLSX, null);
+
+	}
 }

+ 142 - 0
netflix-web/src/main/java/com/cyksj/web/controller/manage/stock/StockController.java

@@ -0,0 +1,142 @@
+package com.cyksj.web.controller.manage.stock;
+
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.dto.Result;
+import com.cyksj.enums.GatewayResponse;
+import com.cyksj.model.entity.GoodsDonStock;
+import com.cyksj.model.entity.GoodsDonStockRecord;
+import com.cyksj.model.request.StockUpReq;
+import com.cyksj.service.mange.stock.GoodsDonStockService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.SearchResult;
+import com.ejlchina.searcher.util.MapUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
+
+/**
+ * @author chan
+ * @date 2022/12/6 4:14 PM
+ */
+@RequestMapping("/manage/stock")
+@RestController
+@RequiredArgsConstructor
+@Slf4j
+public class StockController {
+
+    private final HttpServletRequest request;
+
+    private final BeanSearcher beanSearcher;
+
+    private final GoodsDonStockService stockService;
+
+    @GetMapping
+    public Result<SearchResult<GoodsDonStock>> get(){
+        SearchResult<GoodsDonStock> search = beanSearcher.search(GoodsDonStock.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * 新增库存
+     */
+    @PostMapping("/incr")
+    public Result<String> incr(@RequestBody StockUpReq stock){
+        if (stock.getNum() <= 0) {
+            throw BusinessRuntimeException.getInstance("新增库存数量不能为负数");
+        }
+        if (stock.getPrice() == null) {
+            throw BusinessRuntimeException.getInstance("请填写进价");
+        }
+        if(StringUtils.isBlank(stock.getVoucher())){
+            throw BusinessRuntimeException.getInstance("请添加凭证");
+        }
+        stockService.incr(stock);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 扣减库存
+     */
+    @PostMapping("/sub")
+    public Result<String> sub(@RequestBody StockUpReq stock){
+        stockService.sub(stock);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 编辑不可用
+     */
+    @PutMapping("/put/unavailable")
+    public Result<String> upUnavailable(@RequestBody StockUpReq stock){
+        if(StringUtils.isBlank(stock.getRemark())){
+            throw BusinessRuntimeException.getInstance("请填写备注");
+        }
+        stockService.upUnavailable(stock);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 编辑已刷机
+     */
+    @PutMapping("/put/upgrade")
+    public Result<String> upgrade(@RequestBody StockUpReq stock){
+        stockService.upgrade(stock);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * 新建库存
+     */
+    @PostMapping
+    public Result<String> save(@RequestBody GoodsDonStock stock){
+        if (stock.getRelateList().size() == 0) {
+            throw BusinessRuntimeException.getInstance("缺少关联商品");
+        }
+        if (stock.getRecord() == null) {
+            throw BusinessRuntimeException.getInstance("缺少明细凭证等信息");
+        }
+        stockService.save(stock);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /*
+    库存明细
+    ------------------------------------------------------------------
+    */
+
+    /**
+     * 库存列表
+     */
+    @GetMapping("/get/record")
+    public Result<SearchResult<GoodsDonStockRecord>> getRecord(){
+        SearchResult<GoodsDonStockRecord> search = beanSearcher.search(GoodsDonStockRecord.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+        search.getDataList().forEach((record)->{
+            record.setPrice(record.getPrice().multiply(BigDecimal.valueOf(record.getNum())));
+        });
+        return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+    }
+
+    /**
+     * 编辑备注
+     */
+    @PutMapping("/put/record")
+    public Result<String> upRecord(@RequestBody GoodsDonStockRecord record){
+        if (record.getId() == null) {
+            throw BusinessRuntimeException.getInstance("缺少必填参数.");
+        }
+        stockService.upRecord(record);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    @DeleteMapping("/del/{stockId}")
+    public Result<String> del(@PathVariable Long stockId){
+        stockService.del(stockId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+
+}