소스 검색

产品调库相关

HuRongxin 1 개월 전
부모
커밋
8fb80b52fc
17개의 변경된 파일1197개의 추가작업 그리고 64개의 파일을 삭제
  1. 105 0
      ruoyi-admin/src/main/java/org/dromara/web/controller/ProductDispatchRecordController.java
  2. 6 0
      ruoyi-admin/src/main/java/org/dromara/web/controller/ProductStockController.java
  3. 172 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/ProductDispatchRecord.java
  4. 165 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductDispatchRecordBo.java
  5. 2 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductInOutRecordBo.java
  6. 2 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductStockBo.java
  7. 203 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductDispatchRecordVo.java
  8. 4 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductInOutRecordVo.java
  9. 2 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductStockVo.java
  10. 17 0
      ruoyi-admin/src/main/java/org/dromara/web/mapper/ProductDispatchRecordMapper.java
  11. 8 2
      ruoyi-admin/src/main/java/org/dromara/web/mapper/ProductInOutRecordMapper.java
  12. 68 0
      ruoyi-admin/src/main/java/org/dromara/web/service/IProductDispatchRecordService.java
  13. 3 0
      ruoyi-admin/src/main/java/org/dromara/web/service/IProductStockService.java
  14. 265 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductDispatchRecordServiceImpl.java
  15. 155 62
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductInOutRecordServiceImpl.java
  16. 13 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductStockServiceImpl.java
  17. 7 0
      ruoyi-admin/src/main/resources/mapper/stock/ProductDispatchRecordMapper.xml

+ 105 - 0
ruoyi-admin/src/main/java/org/dromara/web/controller/ProductDispatchRecordController.java

@@ -0,0 +1,105 @@
+package org.dromara.web.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.web.domain.vo.ProductDispatchRecordVo;
+import org.dromara.web.domain.bo.ProductDispatchRecordBo;
+import org.dromara.web.service.IProductDispatchRecordService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 产品调库记录
+ *
+ * @author Lion Li
+ * @date 2025-08-25
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stock/dispatchRecord")
+public class ProductDispatchRecordController extends BaseController {
+
+    private final IProductDispatchRecordService productDispatchRecordService;
+
+    /**
+     * 查询产品调库记录列表
+     */
+    @SaCheckPermission("stock:dispatchRecord:list")
+    @GetMapping("/list")
+    public TableDataInfo<ProductDispatchRecordVo> list(ProductDispatchRecordBo bo, PageQuery pageQuery) {
+        return productDispatchRecordService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出产品调库记录列表
+     */
+    @SaCheckPermission("stock:dispatchRecord:export")
+    @Log(title = "产品调库记录", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ProductDispatchRecordBo bo, HttpServletResponse response) {
+        List<ProductDispatchRecordVo> list = productDispatchRecordService.queryList(bo);
+        ExcelUtil.exportExcel(list, "产品调库记录", ProductDispatchRecordVo.class, response);
+    }
+
+    /**
+     * 获取产品调库记录详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("stock:dispatchRecord:query")
+    @GetMapping("/{id}")
+    public R<ProductDispatchRecordVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long id) {
+        return R.ok(productDispatchRecordService.queryById(id));
+    }
+
+    /**
+     * 新增产品调库记录
+     */
+    @SaCheckPermission("stock:dispatchRecord:add")
+    @Log(title = "产品调库记录", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody List<ProductDispatchRecordBo> boList) {
+        return toAjax(productDispatchRecordService.insertByBo(boList));
+    }
+
+    /**
+     * 修改产品调库记录
+     */
+    @SaCheckPermission("stock:dispatchRecord:edit")
+    @Log(title = "产品调库记录", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ProductDispatchRecordBo bo) {
+        return toAjax(productDispatchRecordService.updateByBo(bo));
+    }
+
+    /**
+     * 删除产品调库记录
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("stock:dispatchRecord:remove")
+    @Log(title = "产品调库记录", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(productDispatchRecordService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 6 - 0
ruoyi-admin/src/main/java/org/dromara/web/controller/ProductStockController.java

@@ -16,6 +16,7 @@ import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.web.domain.bo.ProductStockBo;
+import org.dromara.web.domain.vo.HospitalMealRecipeVo;
 import org.dromara.web.domain.vo.ProductStockVo;
 import org.dromara.web.service.IProductStockService;
 import org.springframework.validation.annotation.Validated;
@@ -80,6 +81,11 @@ public class ProductStockController extends BaseController {
         return toAjax(productStockService.insertByBo(bo));
     }
 
+    @PostMapping("/getProductStock")
+    public R<Long> getBySettlementId(@RequestBody ProductStockBo bo) {
+        return R.ok(productStockService.getProductStock(bo.getProductId(),bo.getLocationId()));
+    }
+
     /**
      * 修改产品库存
      */

+ 172 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/ProductDispatchRecord.java

@@ -0,0 +1,172 @@
+package org.dromara.web.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 产品调库记录对象 product_dispatch_record
+ *
+ * @author Lion Li
+ * @date 2025-08-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("product_dispatch_record")
+public class ProductDispatchRecord extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 调库操作类型
+     */
+    private String dispatchType;
+
+    /**
+     * 调库批次
+     */
+    private String serialNo;
+
+    /**
+     * 调出科室id
+     */
+    private Long outDeptId;
+
+    /**
+     * 调出科室名称
+     */
+    private String outDeptName;
+
+    /**
+     * 调入科室id
+     */
+    private Long inDeptId;
+
+    /**
+     * 调入科室名称
+     */
+    private String inDeptName;
+
+    /**
+     * 调出库位Id
+     */
+    private Long outLocationId;
+
+    /**
+     * 调出库位名称
+     */
+    private String outLocationName;
+
+    /**
+     * 调入库位Id
+     */
+    private Long inLocationId;
+
+    /**
+     * 调入库位名称
+     */
+    private String inLocationName;
+
+    /**
+     * 预包装单位
+     */
+    private String preUnit;
+
+    /**
+     * 调入数量
+     */
+    private Long num;
+
+    /**
+     * 货道号
+     */
+    private String lineNo;
+
+    /**
+     * 警告数量
+     */
+    private Long alertNum;
+
+    /**
+     * 单位
+     */
+    private String unit;
+
+    /**
+     * 调入库存
+     */
+    private Long stockNum;
+
+    /**
+     * 产品Id
+     */
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     * 商品类型
+     */
+    private String productType;
+
+    /**
+     * 商品编号
+     */
+    private String productNo;
+
+    /**
+     * 规格
+     */
+    private String specsName;
+
+    /**
+     * 商品批号
+     */
+    private String productBatchNo;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 凭证
+     */
+    private String attachment;
+
+    /**
+     * 规格数量
+     */
+    private Long specs;
+
+    /**
+     * 规格单位
+     */
+    private String specsUnit;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 165 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductDispatchRecordBo.java

@@ -0,0 +1,165 @@
+package org.dromara.web.domain.bo;
+
+import org.dromara.web.domain.ProductDispatchRecord;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 产品调库记录业务对象 product_dispatch_record
+ *
+ * @author Lion Li
+ * @date 2025-08-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProductDispatchRecord.class, reverseConvertGenerate = false)
+public class ProductDispatchRecordBo extends BaseEntity {
+
+    /**
+     * id
+     */
+    @NotNull(message = "id不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 调库操作类型
+     */
+    private String dispatchType;
+
+    /**
+     * 调库批次
+     */
+    private String serialNo;
+
+    /**
+     * 调出科室id
+     */
+    private Long outDeptId;
+
+    /**
+     * 调出科室名称
+     */
+    private String outDeptName;
+
+    /**
+     * 调入科室id
+     */
+    private Long inDeptId;
+
+    /**
+     * 调入科室名称
+     */
+    private String inDeptName;
+
+    /**
+     * 调出库位Id
+     */
+    private Long outLocationId;
+
+    /**
+     * 调出库位名称
+     */
+    private String outLocationName;
+
+    /**
+     * 调入库位Id
+     */
+    private Long inLocationId;
+
+    /**
+     * 调入库位名称
+     */
+    private String inLocationName;
+
+    /**
+     * 预包装单位
+     */
+    private String preUnit;
+
+    /**
+     * 调入数量
+     */
+    private Long num;
+
+    /**
+     * 货道号
+     */
+    private String lineNo;
+
+    /**
+     * 警告数量
+     */
+    private Long alertNum;
+
+    /**
+     * 单位
+     */
+    private String unit;
+
+    /**
+     * 调入库存
+     */
+    private Long stockNum;
+
+    /**
+     * 产品Id
+     */
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     * 商品类型
+     */
+    private String productType;
+
+    /**
+     * 商品编号
+     */
+    private String productNo;
+
+    /**
+     * 规格
+     */
+    private String specsName;
+
+    /**
+     * 商品批号
+     */
+    private String productBatchNo;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 凭证
+     */
+    private String attachment;
+
+    /**
+     * 规格数量
+     */
+    private Long specs;
+
+    /**
+     * 规格单位
+     */
+    private String specsUnit;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+
+}

+ 2 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductInOutRecordBo.java

@@ -163,6 +163,8 @@ public class ProductInOutRecordBo extends BaseEntity {
 
     private String specsName;
 
+    private Long inLocationId;
+
 
 
 

+ 2 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductStockBo.java

@@ -143,5 +143,7 @@ public class ProductStockBo extends BaseEntity {
      */
     private String status;
 
+    private Long inLocationId;
+
 
 }

+ 203 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductDispatchRecordVo.java

@@ -0,0 +1,203 @@
+package org.dromara.web.domain.vo;
+
+import org.dromara.web.domain.ProductDispatchRecord;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 产品调库记录视图对象 product_dispatch_record
+ *
+ * @author Lion Li
+ * @date 2025-08-25
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProductDispatchRecord.class)
+public class ProductDispatchRecordVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @ExcelProperty(value = "id")
+    private Long id;
+
+    /**
+     * 调库操作类型
+     */
+    @ExcelProperty(value = "调库操作类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "dispatch_type")
+    private String dispatchType;
+
+    /**
+     * 调库批次
+     */
+    @ExcelProperty(value = "调库批次")
+    private String serialNo;
+
+    /**
+     * 调出科室id
+     */
+    @ExcelProperty(value = "调出科室id")
+    private Long outDeptId;
+
+    /**
+     * 调出科室名称
+     */
+    @ExcelProperty(value = "调出科室名称")
+    private String outDeptName;
+
+    /**
+     * 调入科室id
+     */
+    @ExcelProperty(value = "调入科室id")
+    private Long inDeptId;
+
+    /**
+     * 调入科室名称
+     */
+    @ExcelProperty(value = "调入科室名称")
+    private String inDeptName;
+
+    /**
+     * 调出库位Id
+     */
+    @ExcelProperty(value = "调出库位Id")
+    private Long outLocationId;
+
+    /**
+     * 调出库位名称
+     */
+    @ExcelProperty(value = "调出库位名称")
+    private String outLocationName;
+
+    /**
+     * 调入库位Id
+     */
+    @ExcelProperty(value = "调入库位Id")
+    private Long inLocationId;
+
+    /**
+     * 调入库位名称
+     */
+    @ExcelProperty(value = "调入库位名称")
+    private String inLocationName;
+
+    /**
+     * 预包装单位
+     */
+    @ExcelProperty(value = "预包装单位", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "product_package_unit")
+    private String preUnit;
+
+    /**
+     * 调入数量
+     */
+    @ExcelProperty(value = "调入数量")
+    private Long num;
+
+    /**
+     * 货道号
+     */
+    private String lineNo;
+
+    /**
+     * 警告数量
+     */
+    private Long alertNum;
+
+    /**
+     * 单位
+     */
+    @ExcelProperty(value = "单位", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "product_package_unit")
+    private String unit;
+
+    /**
+     * 调入库存
+     */
+    @ExcelProperty(value = "调入库存")
+    private Long stockNum;
+
+    /**
+     * 产品Id
+     */
+    @ExcelProperty(value = "产品Id")
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    @ExcelProperty(value = "产品名称")
+    private String productName;
+
+    /**
+     * 商品类型
+     */
+    @ExcelProperty(value = "商品类型")
+    private String productType;
+
+    /**
+     * 商品编号
+     */
+    @ExcelProperty(value = "商品编号")
+    private String productNo;
+
+    /**
+     * 规格
+     */
+    @ExcelProperty(value = "规格")
+    private String specsName;
+
+    /**
+     * 商品批号
+     */
+    @ExcelProperty(value = "商品批号")
+    private String productBatchNo;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 凭证
+     */
+    @ExcelProperty(value = "凭证")
+    private String attachment;
+
+    /**
+     * 规格数量
+     */
+    @ExcelProperty(value = "规格数量")
+    private Long specs;
+
+    /**
+     * 规格单位
+     */
+    @ExcelProperty(value = "规格单位", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "product_spec_unit")
+    private String specsUnit;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+
+}

+ 4 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductInOutRecordVo.java

@@ -201,6 +201,10 @@ public class ProductInOutRecordVo implements Serializable {
 
     private Long stockNum;
 
+    private Long totalStockNum;
+
+    private Long oldInStockNum;
+
     /**
      * 状态(0正常 1停用)
      */

+ 2 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductStockVo.java

@@ -175,4 +175,6 @@ public class ProductStockVo implements Serializable {
     private String status;
 
 
+
+
 }

+ 17 - 0
ruoyi-admin/src/main/java/org/dromara/web/mapper/ProductDispatchRecordMapper.java

@@ -0,0 +1,17 @@
+package org.dromara.web.mapper;
+
+import org.dromara.web.domain.ProductDispatchRecord;
+import org.dromara.web.domain.vo.ProductDispatchRecordVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 产品调库记录Mapper接口
+ *
+ * @author Lion Li
+ * @date 2025-08-25
+ */
+@Repository
+public interface ProductDispatchRecordMapper extends BaseMapperPlus<ProductDispatchRecord, ProductDispatchRecordVo> {
+
+}

+ 8 - 2
ruoyi-admin/src/main/java/org/dromara/web/mapper/ProductInOutRecordMapper.java

@@ -24,17 +24,23 @@ public interface ProductInOutRecordMapper extends BaseMapperPlus<ProductInOutRec
         "ANY_VALUE(pr.warning_month) as warningMonth,ANY_VALUE(pr.manufacture_date) as manufactureDate,ANY_VALUE(pr.expiration_date) as expirationDate  " +
         "FROM product_in_out_record pr " +
         "WHERE 1 = 1" +
+        "<if test='locationId != null '> and (pr.location_id = #{locationId}) </if>" +
+        "<if test='categoryId != null '> and (pr.category_id = #{categoryId}) </if>" +
+        "<if test='productType != null and productType != \"\"'> and (pr.product_type = #{productType}) </if>" +
         "<if test='searchValue != null and searchValue != \"\"'> and (pr.product_name like concat('%', #{searchValue}, '%')) </if>" +
         "GROUP BY pr.product_id,pr.category_id,pr.location_id,pr.serial_no,pr.product_name,pr.category_name,pr.location_name,pr.product_no,pr.product_batch_no,pr.unit,pr.product_type,pr.price  " +
         "<if test='offset != null and rows != null'>LIMIT #{offset}, #{rows}</if>" +
         "</script>" )
-    List<ProductInOutRecordVo> queryStockBalance( @Param("searchValue") String searchValue, @Param("offset") Integer offset, @Param("rows") Integer rows);
+    List<ProductInOutRecordVo> queryStockBalance( @Param("productType") String productType,@Param("locationId") Long locationId, @Param("categoryId") Long categoryId, @Param("searchValue") String searchValue, @Param("offset") Integer offset, @Param("rows") Integer rows);
 
     @Select(" <script> SELECT COUNT(*)  FROM ( SELECT 1  FROM product_in_out_record pr WHERE 1 = 1 " +
+        "<if test='locationId != null '> and (pr.location_id = #{locationId}) </if>" +
+        "<if test='categoryId != null '> and (pr.category_id = #{categoryId}) </if>" +
+        "<if test='productType != null and productType != \"\"'> and (pr.product_type = #{productType}) </if>" +
         "    <if test='searchValue != null and searchValue != \"\"'> " +
         "        AND (pr.product_name LIKE CONCAT('%', #{searchValue}, '%') ) </if> " +
         "    GROUP BY pr.product_id,pr.category_id,pr.location_id,pr.serial_no,pr.product_name,pr.category_name,pr.location_name,pr.product_no,pr.product_batch_no,pr.unit " +
         ") AS grouped_data " +
         "</script>" )
-    Integer queryStockBalanceCount( @Param("searchValue") String searchValue);
+    Integer queryStockBalanceCount(@Param("productType") String productType, @Param("locationId") Long locationId, @Param("categoryId") Long categoryId,@Param("searchValue") String searchValue);
 }

+ 68 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/IProductDispatchRecordService.java

@@ -0,0 +1,68 @@
+package org.dromara.web.service;
+
+import org.dromara.web.domain.vo.ProductDispatchRecordVo;
+import org.dromara.web.domain.bo.ProductDispatchRecordBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 产品调库记录Service接口
+ *
+ * @author Lion Li
+ * @date 2025-08-25
+ */
+public interface IProductDispatchRecordService {
+
+    /**
+     * 查询产品调库记录
+     *
+     * @param id 主键
+     * @return 产品调库记录
+     */
+    ProductDispatchRecordVo queryById(Long id);
+
+    /**
+     * 分页查询产品调库记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品调库记录分页列表
+     */
+    TableDataInfo<ProductDispatchRecordVo> queryPageList(ProductDispatchRecordBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的产品调库记录列表
+     *
+     * @param bo 查询条件
+     * @return 产品调库记录列表
+     */
+    List<ProductDispatchRecordVo> queryList(ProductDispatchRecordBo bo);
+
+    /**
+     * 新增产品调库记录
+     *
+     * @param boList 产品调库记录
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(List<ProductDispatchRecordBo> boList);
+
+    /**
+     * 修改产品调库记录
+     *
+     * @param bo 产品调库记录
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ProductDispatchRecordBo bo);
+
+    /**
+     * 校验并批量删除产品调库记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 3 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/IProductStockService.java

@@ -49,6 +49,9 @@ public interface IProductStockService {
      */
     Boolean insertByBo(ProductStockBo bo);
 
+    //根据产品id与库位 查询库存
+    Long getProductStock(Long productId, Long locationId);
+
     /**
      * 修改产品库存
      *

+ 265 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductDispatchRecordServiceImpl.java

@@ -0,0 +1,265 @@
+package org.dromara.web.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import org.dromara.common.core.enums.biz.ProductType;
+import org.dromara.common.core.enums.biz.StockOperType;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.web.domain.*;
+import org.dromara.web.domain.bo.ProductInOutRecordBo;
+import org.dromara.web.mapper.ProductStockMapper;
+import org.springframework.stereotype.Service;
+import org.dromara.web.domain.bo.ProductDispatchRecordBo;
+import org.dromara.web.domain.vo.ProductDispatchRecordVo;
+import org.dromara.web.mapper.ProductDispatchRecordMapper;
+import org.dromara.web.service.IProductDispatchRecordService;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/**
+ * 产品调库记录Service业务层处理
+ *
+ * @author Lion Li
+ * @date 2025-08-25
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProductDispatchRecordServiceImpl implements IProductDispatchRecordService {
+
+    private final ProductDispatchRecordMapper baseMapper;
+
+    private final ProductStockMapper productStockMapper;
+
+    /**
+     * 查询产品调库记录
+     *
+     * @param id 主键
+     * @return 产品调库记录
+     */
+    @Override
+    public ProductDispatchRecordVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询产品调库记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品调库记录分页列表
+     */
+    @Override
+    public TableDataInfo<ProductDispatchRecordVo> queryPageList(ProductDispatchRecordBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ProductDispatchRecord> lqw = buildQueryWrapper(bo);
+        Page<ProductDispatchRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的产品调库记录列表
+     *
+     * @param bo 查询条件
+     * @return 产品调库记录列表
+     */
+    @Override
+    public List<ProductDispatchRecordVo> queryList(ProductDispatchRecordBo bo) {
+        LambdaQueryWrapper<ProductDispatchRecord> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ProductDispatchRecord> buildQueryWrapper(ProductDispatchRecordBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProductDispatchRecord> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProductDispatchRecord::getId);
+        lqw.eq(StringUtils.isNotBlank(bo.getDispatchType()), ProductDispatchRecord::getDispatchType, bo.getDispatchType());
+        lqw.eq(StringUtils.isNotBlank(bo.getSerialNo()), ProductDispatchRecord::getSerialNo, bo.getSerialNo());
+        lqw.eq(bo.getOutDeptId() != null, ProductDispatchRecord::getOutDeptId, bo.getOutDeptId());
+        lqw.like(StringUtils.isNotBlank(bo.getOutDeptName()), ProductDispatchRecord::getOutDeptName, bo.getOutDeptName());
+        lqw.eq(bo.getInDeptId() != null, ProductDispatchRecord::getInDeptId, bo.getInDeptId());
+        lqw.like(StringUtils.isNotBlank(bo.getInDeptName()), ProductDispatchRecord::getInDeptName, bo.getInDeptName());
+        lqw.eq(bo.getOutLocationId() != null, ProductDispatchRecord::getOutLocationId, bo.getOutLocationId());
+        lqw.like(StringUtils.isNotBlank(bo.getOutLocationName()), ProductDispatchRecord::getOutLocationName, bo.getOutLocationName());
+        lqw.eq(bo.getInLocationId() != null, ProductDispatchRecord::getInLocationId, bo.getInLocationId());
+        lqw.like(StringUtils.isNotBlank(bo.getInLocationName()), ProductDispatchRecord::getInLocationName, bo.getInLocationName());
+        lqw.eq(StringUtils.isNotBlank(bo.getPreUnit()), ProductDispatchRecord::getPreUnit, bo.getPreUnit());
+        lqw.eq(bo.getNum() != null, ProductDispatchRecord::getNum, bo.getNum());
+        lqw.eq(StringUtils.isNotBlank(bo.getUnit()), ProductDispatchRecord::getUnit, bo.getUnit());
+        lqw.eq(bo.getStockNum() != null, ProductDispatchRecord::getStockNum, bo.getStockNum());
+        lqw.eq(bo.getProductId() != null, ProductDispatchRecord::getProductId, bo.getProductId());
+        lqw.like(StringUtils.isNotBlank(bo.getProductName()), ProductDispatchRecord::getProductName, bo.getProductName());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductType()), ProductDispatchRecord::getProductType, bo.getProductType());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductNo()), ProductDispatchRecord::getProductNo, bo.getProductNo());
+        lqw.like(StringUtils.isNotBlank(bo.getSpecsName()), ProductDispatchRecord::getSpecsName, bo.getSpecsName());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductBatchNo()), ProductDispatchRecord::getProductBatchNo, bo.getProductBatchNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getAttachment()), ProductDispatchRecord::getAttachment, bo.getAttachment());
+        lqw.eq(bo.getSpecs() != null, ProductDispatchRecord::getSpecs, bo.getSpecs());
+        lqw.eq(StringUtils.isNotBlank(bo.getSpecsUnit()), ProductDispatchRecord::getSpecsUnit, bo.getSpecsUnit());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProductDispatchRecord::getStatus, bo.getStatus());
+        return lqw;
+    }
+
+    /**
+     * 新增产品调库记录
+     *
+     * @param boList 产品调库记录
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(List<ProductDispatchRecordBo> boList) {
+        if (CollUtil.isEmpty(boList)) {
+            return false;
+        }
+
+        boolean overallSuccess = true;
+        Map<Long, ProductStock> outStockCache = new HashMap<>();
+        Map<Long, ProductStock> inStockCache = new HashMap<>();
+
+        for (ProductDispatchRecordBo bo : boList) {
+            try {
+                // 转换并验证
+                ProductDispatchRecord record = MapstructUtils.convert(bo, ProductDispatchRecord.class);
+                validEntityBeforeSave(record);
+
+                // 插入调库记录
+                boolean insertSuccess = baseMapper.insert(record) > 0;
+                if (!insertSuccess) {
+                    overallSuccess = false;
+                    continue;
+                }
+
+                bo.setId(record.getId());
+                boolean stockUpdateSuccess = updateStockQuantities(bo, record, outStockCache, inStockCache);
+
+                if (!stockUpdateSuccess) {
+                    overallSuccess = false;
+                }
+            } catch (Exception e) {
+                overallSuccess = false;
+                log.error("调库操作失败: {}", bo, e);
+            }
+        }
+
+        return overallSuccess;
+    }
+
+    private boolean updateStockQuantities(ProductDispatchRecordBo bo, ProductDispatchRecord record,
+                                          Map<Long, ProductStock> outStockCache,
+                                          Map<Long, ProductStock> inStockCache) {
+        // 获取或查询出库位置库存
+        Long outKey = generateStockKey(bo.getProductId(), bo.getOutLocationId());
+        ProductStock outStock = outStockCache.computeIfAbsent(outKey, k ->
+            productStockMapper.selectOne(new LambdaQueryWrapper<ProductStock>()
+                .eq(ProductStock::getProductId, bo.getProductId())
+                .eq(ProductStock::getLocationId, bo.getOutLocationId())
+                .last("LIMIT 1")));
+
+        if (outStock == null) {
+            log.warn("出库位置库存不存在: productId={}, locationId={}",
+                bo.getProductId(), bo.getOutLocationId());
+            return false;
+        }
+
+        // 检查出库库存是否充足
+        if (outStock.getStockNum() < bo.getNum()) {
+            log.warn("库存不足: 需要{}, 实际{}", bo.getNum(), outStock.getStockNum());
+            return false;
+        }
+
+        // 更新出库库存
+        outStock.setStockNum(outStock.getStockNum() - bo.getNum());
+        outStock.setTotalStockNum(outStock.getTotalStockNum() - bo.getNum());
+        productStockMapper.updateById(outStock);
+
+        // 获取或查询入库位置库存
+        Long inKey = generateStockKey(bo.getProductId(), bo.getInLocationId());
+        ProductStock inStock = inStockCache.computeIfAbsent(inKey, k ->
+            productStockMapper.selectOne(new LambdaQueryWrapper<ProductStock>()
+                .eq(ProductStock::getProductId, bo.getProductId())
+                .eq(ProductStock::getLocationId, bo.getInLocationId())
+                .last("LIMIT 1")));
+
+        // 更新或创建入库库存
+        if (inStock != null) {
+            inStock.setStockNum(inStock.getStockNum() + bo.getNum());
+            inStock.setTotalStockNum(inStock.getTotalStockNum() + bo.getNum());
+            productStockMapper.updateById(inStock);
+        } else {
+            inStock = createNewStock(record, outStock, bo.getInLocationId(), bo.getInLocationName());
+            productStockMapper.insert(inStock);
+            inStockCache.put(inKey, inStock);
+        }
+
+        return true;
+    }
+
+    private Long generateStockKey(Long productId, Long locationId) {
+        return productId * 100000L + locationId;
+    }
+
+    private ProductStock createNewStock(ProductDispatchRecord record, ProductStock templateStock,
+                                        Long locationId, String locationName) {
+        ProductStock stock = new ProductStock();
+        stock.setProductType(record.getProductType());
+        stock.setProductNo(record.getProductNo());
+        stock.setCategoryId(templateStock.getCategoryId());
+        stock.setCategoryName(templateStock.getCategoryName());
+        stock.setProductId(record.getProductId());
+        stock.setProductName(record.getProductName());
+        stock.setSpecsName(record.getSpecsName());
+        stock.setLocationId(locationId);
+        stock.setLocationName(locationName);
+        stock.setLineNo(record.getLineNo());
+        stock.setTotalStockNum(record.getNum());
+        stock.setStockNum(record.getNum());
+        stock.setExpirationNum(0L);
+        stock.setStockUnit(record.getUnit());
+        return stock;
+    }
+
+    /**
+     * 修改产品调库记录
+     *
+     * @param bo 产品调库记录
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProductDispatchRecordBo bo) {
+        ProductDispatchRecord update = MapstructUtils.convert(bo, ProductDispatchRecord.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProductDispatchRecord entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除产品调库记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 155 - 62
ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductInOutRecordServiceImpl.java

@@ -6,15 +6,19 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.BizConst;
 import org.dromara.common.core.enums.biz.ProductType;
 import org.dromara.common.core.enums.biz.StockOperType;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.service.ISysDictDataService;
 import org.dromara.web.domain.*;
 import org.dromara.web.domain.bo.ProductInOutRecordBo;
 import org.dromara.web.domain.vo.ProductInOutRecordVo;
+import org.dromara.web.domain.vo.ProductStockVo;
 import org.dromara.web.mapper.*;
 import org.dromara.web.service.IProductInOutRecordService;
 import org.springframework.stereotype.Service;
@@ -37,6 +41,8 @@ public class ProductInOutRecordServiceImpl implements IProductInOutRecordService
 
     private final ProductInOutRecordMapper baseMapper;
 
+    private final ISysDictDataService dataService;
+
     private final ProductStockMapper productStockMapper;
 
     private final StorageLocationMapper locationMapper;
@@ -107,68 +113,139 @@ public class ProductInOutRecordServiceImpl implements IProductInOutRecordService
      */
     @Override
     public Boolean insertByBo(List<ProductInOutRecordBo> boList) {
-        boolean flag = false;
-        if (CollUtil.isNotEmpty(boList)) {
-            ProductInOutRecordBo bo = null;
-            ProductInOutRecord add = null;
-            List<ProductStock> stockList = null;
-            ProductStock stock = null;
-            List<StorageLocation> locationList = locationMapper.selectList(Wrappers.lambdaQuery(StorageLocation.class).select(StorageLocation::getId, StorageLocation::getStorageName));
-            Map<Long, String> locationMap = locationList.stream().collect(Collectors.toMap(k1 -> k1.getId(), k2 -> String.valueOf(k2.getStorageName()), (k1, k2) -> k1));
-
-            List<ProductCategory> productCategoryList = productCategoryMapper.selectList(Wrappers.lambdaQuery(ProductCategory.class).select(ProductCategory::getCategoryId, ProductCategory::getCategoryName));
-            Map<Long, String> productCategoryMap = productCategoryList.stream().collect(Collectors.toMap(k1 -> k1.getCategoryId(), k2 -> String.valueOf(k2.getCategoryName()), (k1, k2) -> k1));
-
-            List<SuppliesCategory> suppliesCategoryList = suppliesCategoryMapper.selectList(Wrappers.lambdaQuery(SuppliesCategory.class).select(SuppliesCategory::getCategoryId, SuppliesCategory::getCategoryName));
-            Map<Long, String> suppliesCategoryMap = suppliesCategoryList.stream().collect(Collectors.toMap(k1 -> k1.getCategoryId(), k2 -> String.valueOf(k2.getCategoryName()), (k1, k2) -> k1));
-            for (int i = 0; i < boList.size(); i++) {
-                bo = boList.get(i);
-                bo.setLocationName(locationMap.get(bo.getLocationId()));
-                if (bo.getProductType().equals(ProductType.TYPE_ONE.getCode())) {
-                    bo.setCategoryName(productCategoryMap.get(bo.getCategoryId()));
-                } else {
-                    bo.setCategoryName(suppliesCategoryMap.get(bo.getCategoryId()));
+        if (CollUtil.isEmpty(boList)) {
+            return false;
+        }
 
-                }
-                add = MapstructUtils.convert(bo, ProductInOutRecord.class);
-                validEntityBeforeSave(add);
-
-                flag = baseMapper.insert(add) > 0;
-                if (flag) {
-                    bo.setId(add.getId());
-                    stockList = productStockMapper.selectList(new LambdaQueryWrapper<ProductStock>().eq(ProductStock::getProductId, bo.getProductId()).eq(ProductStock::getLocationId, bo.getLocationId()));
-                    if (CollUtil.isNotEmpty(stockList)) {
-                        stock = stockList.get(0);
-                        if (bo.getType().equals(StockOperType.OPER_TYPE_ZERO.getCode())) {
-                            stock.setStockNum(stock.getStockNum() + bo.getNum());
-                            stock.setTotalStockNum(stock.getTotalStockNum() + bo.getNum());
-                        } else {
-                            stock.setStockNum(stock.getStockNum() - bo.getNum());
-//                            stock.setTotalStockNum(stock.getTotalStockNum() - bo.getNum());  出库时总库存应保持不变  只修改可用库存
-                        }
-                        productStockMapper.updateById(stock);
-                    } else {
-                        stock = new ProductStock();
-                        stock.setProductType(add.getProductType());
-                        stock.setProductNo(add.getProductNo());
-                        stock.setCategoryId(add.getCategoryId());
-                        stock.setCategoryName(add.getCategoryName());
-                        stock.setProductId(add.getProductId());
-                        stock.setProductName(add.getProductName());
-                        stock.setSpecsName(add.getSpecsName());
-                        stock.setLocationId(add.getLocationId());
-                        stock.setLocationName(add.getLocationName());
-                        stock.setLineNo(add.getLineNo());
-                        stock.setTotalStockNum(add.getNum());
-                        stock.setStockNum(add.getNum());
-                        stock.setExpirationNum(0L);
-                        stock.setStockUnit(add.getUnit());
-                        productStockMapper.insert(stock);
-                    }
-                }
-            }
+        // 提前查询并缓存数据,避免在循环中重复查询
+        Map<Long, String> locationMap = getLocationMap();
+        Map<Long, String> productCategoryMap = getProductCategoryMap();
+        Map<Long, String> suppliesCategoryMap = getSuppliesCategoryMap();
+
+        boolean overallSuccess = false;
+
+        for (ProductInOutRecordBo bo : boList) {
+            boolean success = processSingleRecord(bo, locationMap, productCategoryMap, suppliesCategoryMap);
+            overallSuccess = overallSuccess || success;
         }
-        return flag;
+
+        return overallSuccess;
+    }
+
+    private Map<Long, String> getLocationMap() {
+        List<StorageLocation> locationList = locationMapper.selectList(
+            Wrappers.lambdaQuery(StorageLocation.class)
+                .select(StorageLocation::getId, StorageLocation::getStorageName));
+        return locationList.stream()
+            .collect(Collectors.toMap(StorageLocation::getId,
+                StorageLocation::getStorageName,
+                (k1, k2) -> k1));
+    }
+
+    private Map<Long, String> getProductCategoryMap() {
+        List<ProductCategory> productCategoryList = productCategoryMapper.selectList(
+            Wrappers.lambdaQuery(ProductCategory.class)
+                .select(ProductCategory::getCategoryId, ProductCategory::getCategoryName));
+        return productCategoryList.stream()
+            .collect(Collectors.toMap(ProductCategory::getCategoryId,
+                ProductCategory::getCategoryName,
+                (k1, k2) -> k1));
+    }
+
+    private Map<Long, String> getSuppliesCategoryMap() {
+        List<SuppliesCategory> suppliesCategoryList = suppliesCategoryMapper.selectList(
+            Wrappers.lambdaQuery(SuppliesCategory.class)
+                .select(SuppliesCategory::getCategoryId, SuppliesCategory::getCategoryName));
+        return suppliesCategoryList.stream()
+            .collect(Collectors.toMap(SuppliesCategory::getCategoryId,
+                SuppliesCategory::getCategoryName,
+                (k1, k2) -> k1));
+    }
+
+    private boolean processSingleRecord(ProductInOutRecordBo bo,
+                                        Map<Long, String> locationMap,
+                                        Map<Long, String> productCategoryMap,
+                                        Map<Long, String> suppliesCategoryMap) {
+        Map<String, List<SysDictDataVo>> dictMap = dataService.selectGroupByType(List.of(BizConst.PRODUCT_SPEC_UNIT, BizConst.PRODUCT_PACKAGE_UNIT)).getData();
+
+        Map<String, String> specMap = dictMap.get(BizConst.PRODUCT_SPEC_UNIT).stream().collect(Collectors.toMap(k1 -> k1.getDictLabel(), k2 -> k2.getDictValue(), (k1, k2) -> k1));
+        Map<String, String> packgeMap = dictMap.get(BizConst.PRODUCT_PACKAGE_UNIT).stream().collect(Collectors.toMap(k1 -> k1.getDictLabel(), k2 -> k2.getDictValue(), (k1, k2) -> k1));
+
+        // 设置位置名称
+        bo.setLocationName(locationMap.get(bo.getLocationId()));
+
+        // 设置分类名称
+        if (ProductType.TYPE_ONE.getCode().equals(bo.getProductType())) {
+            bo.setCategoryName(productCategoryMap.get(bo.getCategoryId()));
+        } else {
+            bo.setCategoryName(suppliesCategoryMap.get(bo.getCategoryId()));
+        }
+        bo.setUnit(specMap.get(bo.getUnit()));
+        bo.setPreUnit(packgeMap.get(bo.getPreUnit()));
+        // 转换并验证实体
+        ProductInOutRecord record = MapstructUtils.convert(bo, ProductInOutRecord.class);
+        validEntityBeforeSave(record);
+
+        // 插入记录
+        boolean insertSuccess = baseMapper.insert(record) > 0;
+        if (!insertSuccess) {
+            return false;
+        }
+
+        bo.setId(record.getId());
+
+        // 处理库存
+        processStock(bo, record);
+        return true;
+    }
+
+    private void processStock(ProductInOutRecordBo bo, ProductInOutRecord record) {
+        // 查询现有库存
+        List<ProductStock> stockList = productStockMapper.selectList(
+            new LambdaQueryWrapper<ProductStock>()
+                .eq(ProductStock::getProductId, bo.getProductId())
+                .eq(ProductStock::getLocationId, bo.getLocationId()));
+
+        if (CollUtil.isNotEmpty(stockList)) {
+            // 更新现有库存
+            updateExistingStock(stockList.get(0), bo);
+        } else {
+            // 创建新库存记录
+            createNewStock(record, bo);
+        }
+    }
+
+    private void updateExistingStock(ProductStock stock, ProductInOutRecordBo bo) {
+        if (StockOperType.OPER_TYPE_ZERO.getCode().equals(bo.getType())) {
+            // 入库操作
+            stock.setStockNum(stock.getStockNum() + bo.getNum());
+            stock.setTotalStockNum(stock.getTotalStockNum() + bo.getNum());
+        } else {
+            // 出库操作
+            stock.setStockNum(stock.getStockNum() - bo.getNum());
+            // 出库时总库存保持不变,只修改可用库存
+        }
+        productStockMapper.updateById(stock);
+    }
+
+    private void createNewStock(ProductInOutRecord record, ProductInOutRecordBo bo) {
+        ProductStock stock = new ProductStock();
+        stock.setProductType(record.getProductType());
+        stock.setProductNo(record.getProductNo());
+        stock.setCategoryId(record.getCategoryId());
+        stock.setCategoryName(record.getCategoryName());
+        stock.setProductId(record.getProductId());
+        stock.setProductName(record.getProductName());
+        stock.setSpecsName(record.getSpecsName());
+        stock.setLocationId(record.getLocationId());
+        stock.setLocationName(record.getLocationName());
+        stock.setLineNo(record.getLineNo());
+        stock.setTotalStockNum(record.getNum());
+        stock.setStockNum(record.getNum());
+        stock.setExpirationNum(0L);
+        stock.setStockUnit(record.getUnit());
+
+        productStockMapper.insert(stock);
     }
 
 
@@ -191,8 +268,24 @@ public class ProductInOutRecordServiceImpl implements IProductInOutRecordService
 
         // 计算正确的offset
         int offset = (pageQuery.getPageNum() - 1) * pageQuery.getPageSize();
-        Integer count = baseMapper.queryStockBalanceCount(bo.getSearchValue());
-        List<ProductInOutRecordVo> balanceVoList = baseMapper.queryStockBalance(bo.getSearchValue(), offset, pageQuery.getPageSize());
+        Integer count = baseMapper.queryStockBalanceCount(bo.getProductType(), bo.getLocationId(), bo.getCategoryId(), bo.getSearchValue());
+        List<ProductInOutRecordVo> balanceVoList = baseMapper.queryStockBalance(bo.getProductType(), bo.getLocationId(), bo.getCategoryId(), bo.getSearchValue(), offset, pageQuery.getPageSize());
+        List<ProductStockVo> stockVoList =null;
+        List<ProductStockVo> oldInStockVoList =null;
+        if (CollUtil.isNotEmpty(balanceVoList)){
+            for (ProductInOutRecordVo recordVo : balanceVoList) {
+                stockVoList = productStockMapper.selectVoList(new LambdaQueryWrapper<ProductStock>().eq(ProductStock::getProductId, recordVo.getProductId()).eq(ProductStock::getLocationId, recordVo.getLocationId()));
+                if (CollUtil.isNotEmpty(stockVoList)) {
+                    recordVo.setTotalStockNum(stockVoList.get(0).getStockNum());
+                }
+               if (null!=bo.getInLocationId()){
+                   oldInStockVoList = productStockMapper.selectVoList(new LambdaQueryWrapper<ProductStock>().eq(ProductStock::getProductId, recordVo.getProductId()).eq(ProductStock::getLocationId, bo.getInLocationId()));
+                   if (CollUtil.isNotEmpty(stockVoList)) {
+                       recordVo.setOldInStockNum(oldInStockVoList.get(0).getStockNum());
+                   }
+               }
+            }
+        }
         TableDataInfo tableDataInfo = new TableDataInfo();
         tableDataInfo.setRows(balanceVoList);
         tableDataInfo.setTotal(count);

+ 13 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductStockServiceImpl.java

@@ -1,5 +1,6 @@
 package org.dromara.web.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -76,6 +77,7 @@ public class ProductStockServiceImpl implements IProductStockService {
         lqw.orderByAsc(ProductStock::getId);
         lqw.eq(bo.getLocationId() != null, ProductStock::getLocationId, bo.getLocationId());
         lqw.eq(bo.getCategoryId() != null, ProductStock::getCategoryId, bo.getCategoryId());
+        lqw.eq(bo.getProductType() != null, ProductStock::getProductType, bo.getProductType());
         if (bo.getSearchValue() != null) {
             lqw.and(wrapper -> wrapper.eq(ProductStock::getProductNo, bo.getSearchValue()).or().eq(ProductStock::getProductName, bo.getSearchValue()));
         }
@@ -100,6 +102,17 @@ public class ProductStockServiceImpl implements IProductStockService {
         return flag;
     }
 
+    @Override
+    public Long getProductStock(Long productId, Long locationId) {
+        Long stockNum=0L;
+        List<ProductStockVo> stockVoList = baseMapper.selectVoList(new LambdaQueryWrapper<ProductStock>().eq(ProductStock::getProductId, productId).eq(ProductStock::getLocationId, locationId));
+        if (CollUtil.isNotEmpty(stockVoList)
+        ){
+            stockNum=stockVoList.get(0).getStockNum();
+        }
+        return stockNum;
+    }
+
     /**
      * 修改产品库存
      *

+ 7 - 0
ruoyi-admin/src/main/resources/mapper/stock/ProductDispatchRecordMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.web.mapper.ProductDispatchRecordMapper">
+
+</mapper>