HuRongxin 3 сар өмнө
parent
commit
c2bca4ed49
36 өөрчлөгдсөн 3066 нэмэгдсэн , 23 устгасан
  1. 135 0
      ruoyi-admin/src/main/java/org/dromara/web/controller/FoodIngredientStockController.java
  2. 115 0
      ruoyi-admin/src/main/java/org/dromara/web/controller/ProductInOutRecordController.java
  3. 106 0
      ruoyi-admin/src/main/java/org/dromara/web/controller/ProductStockController.java
  4. 171 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/FoodIngredientStock.java
  5. 174 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/ProductInOutRecord.java
  6. 153 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/ProductStock.java
  7. 168 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/bo/FoodIngredientStockBo.java
  8. 169 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductInOutRecordBo.java
  9. 147 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/bo/ProductStockBo.java
  10. 202 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/FoodIngredientStockVo.java
  11. 38 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/FoodStockBalanceVo.java
  12. 212 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductInOutRecordVo.java
  13. 2 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductNutritionVo.java
  14. 178 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/ProductStockVo.java
  15. 2 0
      ruoyi-admin/src/main/java/org/dromara/web/domain/vo/SuppliesManageVo.java
  16. 41 0
      ruoyi-admin/src/main/java/org/dromara/web/mapper/FoodIngredientStockMapper.java
  17. 40 0
      ruoyi-admin/src/main/java/org/dromara/web/mapper/ProductInOutRecordMapper.java
  18. 17 0
      ruoyi-admin/src/main/java/org/dromara/web/mapper/ProductStockMapper.java
  19. 74 0
      ruoyi-admin/src/main/java/org/dromara/web/service/IFoodIngredientStockService.java
  20. 74 0
      ruoyi-admin/src/main/java/org/dromara/web/service/IProductInOutRecordService.java
  21. 68 0
      ruoyi-admin/src/main/java/org/dromara/web/service/IProductStockService.java
  22. 205 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/FoodIngredientStockServiceImpl.java
  23. 235 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductInOutRecordServiceImpl.java
  24. 40 9
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductNutritionServiceImpl.java
  25. 137 0
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductStockServiceImpl.java
  26. 29 14
      ruoyi-admin/src/main/java/org/dromara/web/service/impl/SuppliesManageServiceImpl.java
  27. 7 0
      ruoyi-admin/src/main/resources/mapper/stock/FoodIngredientStockMapper.xml
  28. 7 0
      ruoyi-admin/src/main/resources/mapper/stock/ProductInOutRecordMapper.xml
  29. 7 0
      ruoyi-admin/src/main/resources/mapper/stock/ProductStockMapper.xml
  30. 2 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/BizConst.java
  31. 20 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/biz/ProductType.java
  32. 20 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/biz/StockOperType.java
  33. 2 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysFoodIngredientVo.java
  34. 39 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysFoodStockBalanceVo.java
  35. 20 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysFoodIngredientMapper.java
  36. 10 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysFoodIngredientServiceImpl.java

+ 135 - 0
ruoyi-admin/src/main/java/org/dromara/web/controller/FoodIngredientStockController.java

@@ -0,0 +1,135 @@
+package org.dromara.web.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.RequiredArgsConstructor;
+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.excel.utils.ExcelUtil;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+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.system.config.RedisUtil;
+import org.dromara.web.domain.bo.FoodIngredientStockBo;
+import org.dromara.web.domain.vo.FoodIngredientStockVo;
+import org.dromara.web.domain.vo.FoodStockBalanceVo;
+import org.dromara.web.service.IFoodIngredientStockService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 食材出入库
+ *
+ * @author Lion Li
+ * @date 2025-08-18
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/stock/ingredientStock")
+public class FoodIngredientStockController extends BaseController {
+
+    private final IFoodIngredientStockService foodIngredientStockService;
+
+    /**
+     * 查询食材出入库列表
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:list")
+    @GetMapping("/list")
+    public TableDataInfo<FoodIngredientStockVo> list(FoodIngredientStockBo bo, PageQuery pageQuery) {
+        return foodIngredientStockService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出食材出入库列表
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:export")
+    @Log(title = "食材出入库", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(FoodIngredientStockBo bo, HttpServletResponse response) {
+        List<FoodIngredientStockVo> list = foodIngredientStockService.queryList(bo);
+        ExcelUtil.exportExcel(list, "食材出入库", FoodIngredientStockVo.class, response);
+    }
+
+    /**
+     * 获取食材出入库详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:query")
+    @GetMapping("/{id}")
+    public R<FoodIngredientStockVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long id) {
+        return R.ok(foodIngredientStockService.queryById(id));
+    }
+
+    /**
+     * 新增食材出入库
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:add")
+    @Log(title = "食材出入库", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody List<FoodIngredientStockBo> boList) {
+        return toAjax(foodIngredientStockService.insertByBo(boList));
+    }
+
+    /**
+     * 修改食材出入库
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:edit")
+    @Log(title = "食材出入库", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody FoodIngredientStockBo bo) {
+        return toAjax(foodIngredientStockService.updateByBo(bo));
+    }
+
+    /**
+     * 删除食材出入库
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:remove")
+    @Log(title = "食材出入库", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(foodIngredientStockService.deleteWithValidByIds(List.of(ids), true));
+    }
+    /**获取入库批次号
+     */
+    @GetMapping("/getBatchNumber")
+    public R<String> getBatchNumber(@RequestParam String prefix) {
+        return R.ok(RedisUtil.generatePatientNumber(prefix));
+    }
+
+    /**
+     * 查询食材库存
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:list")
+    @GetMapping("/stockList")
+    public TableDataInfo<FoodStockBalanceVo> stockList(FoodIngredientStockBo bo, PageQuery pageQuery) {
+        return foodIngredientStockService.queryStockBalancePage(bo, pageQuery);
+    }
+
+    /**
+     * 食材库存导出
+     */
+    @SaCheckPermission("stock:foodIngredientInOut:export")
+    @Log(title = "食材库存导出", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportStock")
+    public void exportStock(FoodIngredientStockBo bo, HttpServletResponse response) {
+        TableDataInfo<FoodStockBalanceVo> tableDataInfo = foodIngredientStockService.queryStockBalance(bo);
+        ExcelUtil.exportExcel(tableDataInfo.getRows(), "食材出入库", FoodStockBalanceVo.class, response);
+    }
+
+}

+ 115 - 0
ruoyi-admin/src/main/java/org/dromara/web/controller/ProductInOutRecordController.java

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

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

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

+ 171 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/FoodIngredientStock.java

@@ -0,0 +1,171 @@
+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.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 食材出入库对象 food_ingredient_stock
+ *
+ * @author Lion Li
+ * @date 2025-08-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("food_ingredient_stock")
+public class FoodIngredientStock extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 食材id
+     */
+    private Long foodId;
+
+    /**
+     * 食材名称
+     */
+    private String foodName;
+
+    /**
+     * 食材大类Id
+     */
+    private Long mainClass;
+
+    /**
+     * 食材大类名称
+     */
+    private String mainClassName;
+
+    /**
+     * 食材亚类Id
+     */
+    private Long subClass;
+
+    /**
+     * 食材大类名称
+     */
+    private String subClassName;
+
+    /**
+     *
+     */
+    private String foodNo;
+
+    /**
+     * 数量
+     */
+    private BigDecimal num;
+
+    /**
+     *
+     */
+    private BigDecimal quantity;
+
+    /**
+     * 合计金额
+     */
+    private BigDecimal amount;
+
+    /**
+     * 单价
+     */
+    private BigDecimal unitPrice;
+
+    /**
+     * 单价
+     */
+    private BigDecimal usedQuantity;
+
+    /**
+     * 操作类型
+     */
+    private String type;
+
+    /**
+     * 处方来源
+     */
+    private String visitType;
+
+    /**
+     * 库存单位
+     */
+    private String stockUnit;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 入库批次
+     */
+    private String batch;
+
+    /**
+     * 凭证
+     */
+    private String attachment;
+
+    /**
+     *
+     */
+    private Long configDetailId;
+
+    /**
+     *
+     */
+    private Long configId;
+
+    /**
+     * 生产日期
+     */
+    private Date dateInProduced;
+
+    /**
+     * 失效日期
+     */
+    private Date expiryDate;
+
+    /**
+     * 发票号
+     */
+    private String invoiceNumber;
+
+    /**
+     * 科室id
+     */
+    private Long deptId;
+
+    /**
+     * 科室名称
+     */
+    private String deptName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 174 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/ProductInOutRecord.java

@@ -0,0 +1,174 @@
+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.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.io.Serial;
+
+/**
+ * 产品出入库记录对象 product_in_out_record
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("product_in_out_record")
+public class ProductInOutRecord extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 操作类型
+     */
+    private String type;
+
+    /**
+     * 处方来源
+     */
+    private String visitType;
+
+    /**
+     * 预包装单位
+     */
+    private String preUnit;
+
+    /**
+     * 商品批号
+     */
+    private String productBatchNo;
+
+    /**
+     * 凭证
+     */
+    private String attachment;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 生产日期
+     */
+    private Date manufactureDate;
+
+    /**
+     * 失效日期
+     */
+    private Date expirationDate;
+
+    private Integer warningMonth;
+
+    /**
+     * 科室id
+     */
+    private Long deptId;
+
+    /**
+     * 科室名称
+     */
+    private String deptName;
+
+    /**
+     * 分类Id
+     */
+    private Long categoryId;
+
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+
+    /**
+     * 警告数量
+     */
+    private Long alertNum;
+
+    /**
+     * 货道号
+     */
+    private String lineNo;
+
+    /**
+     * 库位Id
+     */
+    private Long locationId;
+
+    /**
+     * 库位名称
+     */
+    private String locationName;
+
+    /**
+     * 产品Id
+     */
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     * 货道号
+     */
+    private String invoiceNo;
+
+    /**
+     * 数量
+     */
+    private Long num;
+
+    /**
+     * 单价
+     */
+    private BigDecimal price;
+
+    /**
+     * 合计
+     */
+    private BigDecimal totalPrice;
+
+    /**
+     * 单位
+     */
+    private String unit;
+
+    /**
+     * 入库批次
+     */
+    private String serialNo;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    private String productType;
+
+    private String productNo;
+
+    private String specsName;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 153 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/ProductStock.java

@@ -0,0 +1,153 @@
+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;
+import java.math.BigDecimal;
+
+/**
+ * 产品库存对象 product_stock
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("product_stock")
+public class ProductStock extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 分类Id
+     */
+    private Long categoryId;
+
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+
+    /**
+     * 警告数量
+     */
+    private Long alertNum;
+
+    /**
+     * 科室ID
+     */
+    private String deptId;
+
+    /**
+     * 过期库存
+     */
+    private Long expirationNum;
+
+    /**
+     * 货道号
+     */
+    private String lineNo;
+
+    /**
+     * 库位Id
+     */
+    private Long locationId;
+
+    /**
+     * 库位名称
+     */
+    private String locationName;
+
+    /**
+     * 产品类型
+     */
+    private String productType;
+
+    /**
+     * 产品Id
+     */
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     *
+     */
+    private Long num;
+
+    /**
+     * 预包装单位名称
+     */
+    private String preUnitName;
+
+    /**
+     * 商品编码
+     */
+    private String productNo;
+
+    /**
+     * 入货价格
+     */
+    private BigDecimal purchasePrice;
+
+    /**
+     * 有效期限制
+     */
+    private Long quGuaraPeriodLimit;
+
+    /**
+     * 可用库存
+     */
+    private Long stockNum;
+
+    /**
+     * 规格
+     */
+    private String specsName;
+
+    /**
+     * 总库存数量
+     */
+    private Long totalStockNum;
+
+    /**
+     * 库存单位
+     */
+    private String stockUnit;
+
+    /**
+     * 库存单位名称
+     */
+    private String stockUnitName;
+
+    /**
+     * 单位名称
+     */
+    private String unitName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 168 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/bo/FoodIngredientStockBo.java

@@ -0,0 +1,168 @@
+package org.dromara.web.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.web.domain.FoodIngredientStock;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 食材出入库业务对象 food_ingredient_stock
+ *
+ * @author Lion Li
+ * @date 2025-08-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = FoodIngredientStock.class, reverseConvertGenerate = false)
+public class FoodIngredientStockBo extends BaseEntity {
+
+    /**
+     * id
+     */
+    @NotNull(message = "id不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 食材id
+     */
+    private Long foodId;
+
+    /**
+     * 食材名称
+     */
+    private String foodName;
+
+    /**
+     * 食材大类Id
+     */
+    private Long mainClass;
+
+    /**
+     * 食材大类名称
+     */
+    private String mainClassName;
+
+    /**
+     * 食材亚类Id
+     */
+    private Long subClass;
+
+    /**
+     * 食材大类名称
+     */
+    private String subClassName;
+
+    /**
+     *
+     */
+    private String foodNo;
+
+    /**
+     * 数量
+     */
+    private BigDecimal num;
+
+    /**
+     *
+     */
+    private BigDecimal quantity;
+
+    /**
+     * 合计金额
+     */
+    private BigDecimal amount;
+
+    /**
+     * 单价
+     */
+    private BigDecimal unitPrice;
+
+    /**
+     * 单价
+     */
+    private BigDecimal usedQuantity;
+
+    /**
+     * 操作类型
+     */
+    private String type;
+
+    /**
+     * 处方来源
+     */
+    private String visitType;
+
+    /**
+     * 库存单位
+     */
+    private String stockUnit;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 入库批次
+     */
+    private String batch;
+
+    /**
+     * 凭证
+     */
+    private String attachment;
+
+    /**
+     *
+     */
+    private Long configDetailId;
+
+    /**
+     *
+     */
+    private Long configId;
+
+    /**
+     * 生产日期
+     */
+    private Date dateInProduced;
+
+    /**
+     * 失效日期
+     */
+    private Date expiryDate;
+
+    /**
+     * 发票号
+     */
+    private String invoiceNumber;
+
+    /**
+     * 科室id
+     */
+    private Long deptId;
+
+    /**
+     * 科室名称
+     */
+    @NotBlank(message = "科室名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String deptName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    List<Date> dateRange;
+
+
+}

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

@@ -0,0 +1,169 @@
+package org.dromara.web.domain.bo;
+
+import org.dromara.web.domain.ProductInOutRecord;
+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.*;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * 产品出入库记录业务对象 product_in_out_record
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProductInOutRecord.class, reverseConvertGenerate = false)
+public class ProductInOutRecordBo extends BaseEntity {
+
+    /**
+     * id
+     */
+    @NotNull(message = "id不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 操作类型
+     */
+    private String type;
+
+    /**
+     * 处方来源
+     */
+    private String visitType;
+
+    /**
+     * 预包装单位
+     */
+    private String preUnit;
+
+    /**
+     * 商品批号
+     */
+    private String productBatchNo;
+
+    /**
+     * 凭证
+     */
+    private String attachment;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 生产日期
+     */
+    private Date manufactureDate;
+
+    /**
+     * 失效日期
+     */
+    private Date expirationDate;
+
+    /**
+     * 科室id
+     */
+    private Long deptId;
+
+    /**
+     * 科室名称
+     */
+    private String deptName;
+
+    /**
+     * 分类Id
+     */
+    private Long categoryId;
+
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+
+    /**
+     * 警告数量
+     */
+    private Long alertNum;
+
+    /**
+     * 货道号
+     */
+    private String lineNo;
+
+    /**
+     * 库位Id
+     */
+    private Long locationId;
+
+    /**
+     * 库位名称
+     */
+    private String locationName;
+
+    /**
+     * 产品Id
+     */
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     * 货道号
+     */
+    private String invoiceNo;
+
+    /**
+     * 数量
+     */
+    private Long num;
+
+    /**
+     * 单价
+     */
+    private BigDecimal price;
+
+    /**
+     * 合计
+     */
+    private BigDecimal totalPrice;
+
+    private Integer warningMonth;
+
+    /**
+     * 单位
+     */
+    private String unit;
+
+    /**
+     * 入库批次
+     */
+    private String serialNo;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    private String productType;
+
+    private String productNo;
+
+    private String specsName;
+
+
+
+
+}

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

@@ -0,0 +1,147 @@
+package org.dromara.web.domain.bo;
+
+import org.dromara.web.domain.ProductStock;
+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.*;
+
+import java.math.BigDecimal;
+
+/**
+ * 产品库存业务对象 product_stock
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProductStock.class, reverseConvertGenerate = false)
+public class ProductStockBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    @NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 分类Id
+     */
+    private Long categoryId;
+
+    /**
+     * 分类名称
+     */
+    private String categoryName;
+
+    /**
+     * 警告数量
+     */
+    private Long alertNum;
+
+    /**
+     * 科室ID
+     */
+    private String deptId;
+
+    /**
+     * 过期库存
+     */
+    private Long expirationNum;
+
+    /**
+     * 货道号
+     */
+    private String lineNo;
+
+    /**
+     * 库位Id
+     */
+    private Long locationId;
+
+    /**
+     * 库位名称
+     */
+    private String locationName;
+
+    /**
+     * 产品类型
+     */
+    private String productType;
+
+    /**
+     * 产品Id
+     */
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    private String productName;
+
+    /**
+     *
+     */
+    private Long num;
+
+    /**
+     * 预包装单位名称
+     */
+    private String preUnitName;
+
+    /**
+     * 商品编码
+     */
+    private String productNo;
+
+    /**
+     * 入货价格
+     */
+    private BigDecimal purchasePrice;
+
+    /**
+     * 有效期限制
+     */
+    private Long quGuaraPeriodLimit;
+
+    /**
+     * 可用库存
+     */
+    private Long stockNum;
+
+    /**
+     * 规格
+     */
+    private String specsName;
+
+    /**
+     * 总库存数量
+     */
+    private Long totalStockNum;
+
+    /**
+     * 库存单位
+     */
+    private String stockUnit;
+
+    /**
+     * 库存单位名称
+     */
+    private String stockUnitName;
+
+    /**
+     * 单位名称
+     */
+    private String unitName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+
+}

+ 202 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/FoodIngredientStockVo.java

@@ -0,0 +1,202 @@
+package org.dromara.web.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.web.domain.FoodIngredientStock;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+
+
+/**
+ * 食材出入库视图对象 food_ingredient_stock
+ *
+ * @author Lion Li
+ * @date 2025-08-18
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = FoodIngredientStock.class)
+public class FoodIngredientStockVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    private Long id;
+
+    /**
+     * 食材id
+     */
+    private Long foodId;
+
+    /**
+     * 食材名称
+     */
+    @ExcelProperty(value = "食材名称")
+    private String foodName;
+
+    /**
+     * 食材大类Id
+     */
+    private Long mainClass;
+
+    /**
+     * 食材大类名称
+     */
+    @ExcelProperty(value = "食材大类")
+    private String mainClassName;
+
+    /**
+     * 食材亚类Id
+     */
+    private Long subClass;
+
+    /**
+     * 食材大类名称
+     */
+    @ExcelProperty(value = "食材亚类")
+    private String subClassName;
+
+    /**
+     *
+     */
+    @ExcelProperty(value = "")
+    private String foodNo;
+
+    /**
+     * 数量
+     */
+    @ExcelProperty(value = "数量")
+    private BigDecimal num;
+
+    /**
+     *
+     */
+    @ExcelProperty(value = "")
+    private BigDecimal quantity;
+
+    /**
+     * 合计金额
+     */
+    @ExcelProperty(value = "合计金额")
+    private BigDecimal amount;
+
+    /**
+     * 单价
+     */
+    @ExcelProperty(value = "单价")
+    private BigDecimal unitPrice;
+
+    /**
+     * 单价
+     */
+    @ExcelProperty(value = "单价")
+    private BigDecimal usedQuantity;
+
+    /**
+     * 操作类型
+     */
+    @ExcelProperty(value = "操作类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "stock_oper_type")
+    private String type;
+
+    /**
+     * 处方来源
+     */
+    @ExcelProperty(value = "处方来源", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "treatment_user_type")
+    private String visitType;
+
+    /**
+     * 库存单位
+     */
+    @ExcelProperty(value = "库存单位")
+    private String stockUnit;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 入库批次
+     */
+    @ExcelProperty(value = "入库批次")
+    private String batch;
+
+    /**
+     * 凭证
+     */
+    @ExcelProperty(value = "凭证")
+    private String attachment;
+
+    /**
+     *
+     */
+    @ExcelProperty(value = "")
+    private Long configDetailId;
+
+    /**
+     *
+     */
+    @ExcelProperty(value = "")
+    private Long configId;
+
+    /**
+     * 生产日期
+     */
+    @ExcelProperty(value = "生产日期")
+    private Date dateInProduced;
+
+    /**
+     * 失效日期
+     */
+    @ExcelProperty(value = "失效日期")
+    private Date expiryDate;
+
+    /**
+     * 发票号
+     */
+    @ExcelProperty(value = "发票号")
+    private String invoiceNumber;
+
+    /**
+     * 科室id
+     */
+    @ExcelProperty(value = "科室id")
+    private Long deptId;
+
+    /**
+     * 科室名称
+     */
+    @ExcelProperty(value = "科室名称")
+    private String deptName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    @ExcelProperty(value = "操作日期")
+    private Date createTime;
+
+    private Long createBy;
+
+    @ExcelProperty(value = "操作人")
+    private String createByName;
+
+
+}

+ 38 - 0
ruoyi-admin/src/main/java/org/dromara/web/domain/vo/FoodStockBalanceVo.java

@@ -0,0 +1,38 @@
+package org.dromara.web.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.web.domain.FoodIngredientStock;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = FoodIngredientStock.class)
+public class FoodStockBalanceVo implements Serializable {
+    private Long foodId;
+
+    @ExcelProperty(value = "食材名称")
+    private String foodName;
+
+    private Long mainClass;
+
+    @ExcelProperty(value = "食材大类")
+    private String mainClassName;
+
+    private Long subClass;
+
+    @ExcelProperty(value = "食材亚类")
+    private String subClassName;
+
+    @ExcelProperty(value = "库存数量")
+    private BigDecimal stockNum;
+
+    @ExcelProperty(value = "库存单位")
+    private String stockUnit;
+
+
+}

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

@@ -0,0 +1,212 @@
+package org.dromara.web.domain.vo;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.dromara.web.domain.ProductInOutRecord;
+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_in_out_record
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProductInOutRecord.class)
+public class ProductInOutRecordVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @ExcelProperty(value = "id")
+    private Long id;
+
+    /**
+     * 操作类型
+     */
+    @ExcelProperty(value = "操作类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "stock_oper_type")
+    private String type;
+
+    /**
+     * 处方来源
+     */
+    @ExcelProperty(value = "处方来源", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "treatment_user_type")
+    private String visitType;
+
+    /**
+     * 预包装单位
+     */
+    @ExcelProperty(value = "预包装单位", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "product_package_unit")
+    private String preUnit;
+
+    /**
+     * 商品批号
+     */
+    @ExcelProperty(value = "商品批号")
+    private String productBatchNo;
+
+    @ExcelProperty(value = "商品类型", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "product_type")
+    private String productType;
+
+    @ExcelProperty(value = "商品编码")
+    private String productNo;
+
+    @ExcelProperty(value = "规格")
+    private String specsName;
+
+    /**
+     * 凭证
+     */
+    @ExcelProperty(value = "凭证")
+    private String attachment;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+    /**
+     * 生产日期
+     */
+    @ExcelProperty(value = "生产日期")
+    private Date manufactureDate;
+
+    /**
+     * 失效日期
+     */
+    @ExcelProperty(value = "失效日期")
+    private Date expirationDate;
+
+    /**
+     * 科室id
+     */
+    @ExcelProperty(value = "科室id")
+    private Long deptId;
+
+    /**
+     * 科室名称
+     */
+    @ExcelProperty(value = "科室名称")
+    private String deptName;
+
+    /**
+     * 分类Id
+     */
+    private Long categoryId;
+
+    /**
+     * 分类名称
+     */
+    @ExcelProperty(value = "分类名称")
+    private String categoryName;
+
+    /**
+     * 警告数量
+     */
+    @ExcelProperty(value = "警告数量")
+    private Long alertNum;
+
+    /**
+     * 货道号
+     */
+    @ExcelProperty(value = "货道号")
+    private String lineNo;
+
+    /**
+     * 库位Id
+     */
+    @ExcelProperty(value = "库位Id")
+    private Long locationId;
+
+    /**
+     * 库位名称
+     */
+    @ExcelProperty(value = "库位名称")
+    private String locationName;
+
+    /**
+     * 产品Id
+     */
+    @ExcelProperty(value = "产品Id")
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    @ExcelProperty(value = "产品名称")
+    private String productName;
+
+    /**
+     * 货道号
+     */
+    @ExcelProperty(value = "货道号")
+    private String invoiceNo;
+
+    @ExcelProperty(value = "临期提醒期")
+    private Integer warningMonth;
+
+    /**
+     * 数量
+     */
+    @ExcelProperty(value = "数量")
+    private Long num;
+
+    /**
+     * 单价
+     */
+    @ExcelProperty(value = "单价")
+    private BigDecimal price;
+
+    /**
+     * 合计
+     */
+    @ExcelProperty(value = "合计")
+    private BigDecimal totalPrice;
+
+    /**
+     * 单位
+     */
+    @ExcelProperty(value = "单位", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "product_package_unit")
+    private String unit;
+
+    /**
+     * 入库批次
+     */
+    @ExcelProperty(value = "入库批次")
+    private String serialNo;
+
+    private Date createTime;
+
+    private Long stockNum;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+
+}

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

@@ -71,6 +71,8 @@ public class ProductNutritionVo implements Serializable {
     @ExcelProperty(value = "品牌")
     private String brand;
 
+    private Long stockNum;
+
     /**
      * 产品所属标签
      */

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

@@ -0,0 +1,178 @@
+package org.dromara.web.domain.vo;
+
+import org.dromara.web.domain.ProductStock;
+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.math.BigDecimal;
+import java.util.Date;
+
+
+
+/**
+ * 产品库存视图对象 product_stock
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProductStock.class)
+public class ProductStockVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 分类Id
+     */
+    @ExcelProperty(value = "分类Id")
+    private Long categoryId;
+
+    /**
+     * 分类名称
+     */
+    @ExcelProperty(value = "分类名称")
+    private String categoryName;
+
+    /**
+     * 警告数量
+     */
+    @ExcelProperty(value = "警告数量")
+    private Long alertNum;
+
+    /**
+     * 科室ID
+     */
+    private String deptId;
+
+    /**
+     * 过期库存
+     */
+    @ExcelProperty(value = "过期库存")
+    private Long expirationNum;
+
+    /**
+     * 货道号
+     */
+    @ExcelProperty(value = "货道号")
+    private String lineNo;
+
+    /**
+     * 库位Id
+     */
+    @ExcelProperty(value = "库位Id")
+    private Long locationId;
+
+    /**
+     * 库位名称
+     */
+    @ExcelProperty(value = "库位名称")
+    private String locationName;
+
+    /**
+     * 产品类型
+     */
+    @ExcelProperty(value = "产品类型")
+    @ExcelDictFormat(dictType = "product_type")
+    private String productType;
+
+    /**
+     * 产品Id
+     */
+
+    private Long productId;
+
+    /**
+     * 产品名称
+     */
+    @ExcelProperty(value = "产品名称")
+    private String productName;
+
+    /**
+     *
+     */
+    @ExcelProperty(value = "")
+    private Long num;
+
+    /**
+     * 预包装单位名称
+     */
+    @ExcelProperty(value = "预包装单位名称")
+    private String preUnitName;
+
+    /**
+     * 商品编码
+     */
+    @ExcelProperty(value = "商品编码")
+    private String productNo;
+
+    /**
+     * 入货价格
+     */
+    @ExcelProperty(value = "入货价格")
+    private BigDecimal purchasePrice;
+
+    /**
+     * 有效期限制
+     */
+    @ExcelProperty(value = "有效期限制")
+    private Long quGuaraPeriodLimit;
+
+    /**
+     * 可用库存
+     */
+    @ExcelProperty(value = "可用库存")
+    private Long stockNum;
+
+    /**
+     * 规格
+     */
+    @ExcelProperty(value = "规格")
+    private String specsName;
+
+    /**
+     * 总库存数量
+     */
+    @ExcelProperty(value = "总库存数量")
+    private Long totalStockNum;
+
+    /**
+     * 库存单位
+     */
+    @ExcelProperty(value = "库存单位")
+    private String stockUnit;
+
+    /**
+     * 库存单位名称
+     */
+    @ExcelProperty(value = "库存单位名称")
+    private String stockUnitName;
+
+    /**
+     * 单位名称
+     */
+    @ExcelProperty(value = "单位名称")
+    private String unitName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+
+}

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

@@ -50,6 +50,8 @@ public class SuppliesManageVo implements Serializable {
     @ExcelProperty(value = "商品编码")
     private String suppliesCode;
 
+    private Long stockNum;
+
     /**
      * 生产厂商
      */

+ 41 - 0
ruoyi-admin/src/main/java/org/dromara/web/mapper/FoodIngredientStockMapper.java

@@ -0,0 +1,41 @@
+package org.dromara.web.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.dromara.web.domain.FoodIngredientStock;
+import org.dromara.web.domain.vo.FoodIngredientStockVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.web.domain.vo.FoodStockBalanceVo;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 食材出入库Mapper接口
+ *
+ * @author Lion Li
+ * @date 2025-08-18
+ */
+@Repository
+public interface FoodIngredientStockMapper extends BaseMapperPlus<FoodIngredientStock, FoodIngredientStockVo> {
+
+@Select(" <script> SELECT fis.food_id AS foodId, fis.food_name as foodName, fis.main_class as mainClass, fis.main_class_name as mainClassName, fis.sub_class as subClass, fis.sub_class_name as subClassName, " +
+    "fis.stock_unit as stockUnit, " +
+    " SUM( CASE WHEN fis.type = '0' THEN num ELSE 0 END ) -  SUM( CASE WHEN fis.type IN ( '1', '2', '3' ) THEN fis.num ELSE 0 END ) AS stockNum " +
+    " FROM food_ingredient_stock fis where 1 =1 " +
+    "<if test='mainClass != null'> and (fis.main_class = #{mainClass} or fis.sub_class = #{mainClass}) </if> " +
+    "<if test='searchValue != null and searchValue != \"\"'> and (fis.food_name like concat('%', #{searchValue}, '%')) </if>" +
+    "GROUP BY fis.food_id, fis.main_class, fis.sub_class, fis.stock_unit, fis.food_name, fis.main_class_name, fis.sub_class_name " +
+    "<if test='offset != null and rows != null'>LIMIT #{offset}, #{rows}</if>" +
+    "</script>" )
+List<FoodStockBalanceVo> queryStockBalance(@Param("mainClass") Long mainClass, @Param("searchValue") String searchValue, @Param("offset") Integer offset, @Param("rows") Integer rows);
+
+    @Select(" <script> SELECT COUNT(*)  FROM ( SELECT 1  FROM food_ingredient_stock fis WHERE 1 = 1 " +
+        "    <if test='mainClass != null'> AND(fis.main_class = #{mainClass} or fis.sub_class = #{mainClass}) </if> " +
+        "    <if test='searchValue != null and searchValue != \"\"'> " +
+        "        AND (fis.food_name LIKE CONCAT('%', #{searchValue}, '%') ) </if> " +
+        "    GROUP BY fis.food_id, fis.main_class, fis.sub_class, fis.stock_unit " +
+        ") AS grouped_data " +
+        "</script>" )
+    Integer queryStockBalanceCount(@Param("mainClass") Long mainClass, @Param("searchValue") String searchValue);
+}

+ 40 - 0
ruoyi-admin/src/main/java/org/dromara/web/mapper/ProductInOutRecordMapper.java

@@ -0,0 +1,40 @@
+package org.dromara.web.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.dromara.web.domain.ProductInOutRecord;
+import org.dromara.web.domain.vo.FoodStockBalanceVo;
+import org.dromara.web.domain.vo.ProductInOutRecordVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * 产品出入库记录Mapper接口
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Repository
+public interface ProductInOutRecordMapper extends BaseMapperPlus<ProductInOutRecord, ProductInOutRecordVo> {
+    @Select(" <script> SELECT  pr.product_id AS productId, pr.category_id AS categoryId, pr.location_id AS locationId,pr.serial_no AS serialNo,pr.product_name AS productName, pr.product_type as productType, " +
+        "pr.category_name AS categoryName,pr.location_name AS locationName,pr.product_no AS productNo,SUM( CASE WHEN pr.type = '0' THEN pr.num ELSE - pr.num END ) AS stockNum," +
+        "pr.product_batch_no as productBatchNo,pr.unit, ANY_VALUE(pr.specs_name) as specsName, ANY_VALUE(pr.line_no) as lineNo,ANY_VALUE(pr.invoice_no) as invoiceNo, pr.price, " +
+        "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='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);
+
+    @Select(" <script> SELECT COUNT(*)  FROM ( SELECT 1  FROM product_in_out_record pr WHERE 1 = 1 " +
+        "    <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);
+}

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

@@ -0,0 +1,17 @@
+package org.dromara.web.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.web.domain.ProductStock;
+import org.dromara.web.domain.vo.ProductStockVo;
+import org.springframework.stereotype.Repository;
+
+/**
+ * 产品库存Mapper接口
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Repository
+public interface ProductStockMapper extends BaseMapperPlus<ProductStock, ProductStockVo> {
+
+}

+ 74 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/IFoodIngredientStockService.java

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

+ 74 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/IProductInOutRecordService.java

@@ -0,0 +1,74 @@
+package org.dromara.web.service;
+
+import org.dromara.web.domain.bo.FoodIngredientStockBo;
+import org.dromara.web.domain.vo.FoodStockBalanceVo;
+import org.dromara.web.domain.vo.ProductInOutRecordVo;
+import org.dromara.web.domain.bo.ProductInOutRecordBo;
+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-20
+ */
+public interface IProductInOutRecordService {
+
+    /**
+     * 查询产品出入库记录
+     *
+     * @param id 主键
+     * @return 产品出入库记录
+     */
+    ProductInOutRecordVo queryById(Long id);
+
+    /**
+     * 分页查询产品出入库记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品出入库记录分页列表
+     */
+    TableDataInfo<ProductInOutRecordVo> queryPageList(ProductInOutRecordBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的产品出入库记录列表
+     *
+     * @param bo 查询条件
+     * @return 产品出入库记录列表
+     */
+    List<ProductInOutRecordVo> queryList(ProductInOutRecordBo bo);
+
+    /**
+     * 新增产品出入库记录
+     *
+     * @param boList 产品出入库记录
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(List<ProductInOutRecordBo> boList);
+
+    /**
+     * 修改产品出入库记录
+     *
+     * @param bo 产品出入库记录
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ProductInOutRecordBo bo);
+
+    /**
+     * 校验并批量删除产品出入库记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    TableDataInfo<ProductInOutRecordVo>queryStockBalancePage(ProductInOutRecordBo bo, PageQuery pageQuery);
+
+    //TableDataInfo<ProductInOutRecordVo>queryStockBalance(ProductInOutRecordBo bo);
+}

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

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

+ 205 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/FoodIngredientStockServiceImpl.java

@@ -0,0 +1,205 @@
+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;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.SysUser;
+import org.dromara.system.mapper.SysUserMapper;
+import org.dromara.web.domain.FoodIngredientStock;
+import org.dromara.web.domain.bo.FoodIngredientStockBo;
+import org.dromara.web.domain.vo.FoodIngredientStockVo;
+import org.dromara.web.domain.vo.FoodStockBalanceVo;
+import org.dromara.web.mapper.FoodIngredientStockMapper;
+import org.dromara.web.service.IFoodIngredientStockService;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 食材出入库Service业务层处理
+ *
+ * @author Lion Li
+ * @date 2025-08-18
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class FoodIngredientStockServiceImpl implements IFoodIngredientStockService {
+
+    private final FoodIngredientStockMapper baseMapper;
+
+    private final SysUserMapper userMapper;
+
+    /**
+     * 查询食材出入库
+     *
+     * @param id 主键
+     * @return 食材出入库
+     */
+    @Override
+    public FoodIngredientStockVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询食材出入库列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 食材出入库分页列表
+     */
+    @Override
+    public TableDataInfo<FoodIngredientStockVo> queryPageList(FoodIngredientStockBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<FoodIngredientStock> lqw = buildQueryWrapper(bo);
+        Page<FoodIngredientStockVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        List<FoodIngredientStockVo> records = result.getRecords();
+        if (CollUtil.isNotEmpty(records)) {
+            Set<Long> userList = CollUtil.newHashSet();
+            records.forEach(v->{
+                userList.add(v.getCreateBy());
+            });
+            Map<Long, String> userMap = userMapper.selectList(Wrappers.lambdaQuery(SysUser.class)
+                .select(SysUser::getUserId, SysUser::getUserName)
+                .in(SysUser::getUserId, userList)).stream().collect(Collectors.toMap(k1 -> k1.getUserId(), k2 -> k2.getUserName(), (k1, k2) -> k1));
+            records.forEach(v->{
+               v.setCreateByName(userMap.get(v.getCreateBy()));
+            });
+        }
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的食材出入库列表
+     *
+     * @param bo 查询条件
+     * @return 食材出入库列表
+     */
+    @Override
+    public List<FoodIngredientStockVo> queryList(FoodIngredientStockBo bo) {
+        LambdaQueryWrapper<FoodIngredientStock> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<FoodIngredientStock> buildQueryWrapper(FoodIngredientStockBo bo) {
+        Map<String, Object> params = bo.getParams();
+        List<Date> dateList = bo.getDateRange();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        String startTime = null;
+        String endTime = null;
+
+        if (dateList != null && dateList.size() == 2) {
+            startTime = sdf.format(dateList.get(0)) + " 00:00:00";  // 开始时间
+            endTime = sdf.format(dateList.get(1)) + " 23:59:59";    // 结束时间
+        }
+        LambdaQueryWrapper<FoodIngredientStock> lqw = Wrappers.lambdaQuery();
+        lqw.orderByDesc(FoodIngredientStock::getCreateTime);
+        lqw.eq(StringUtils.isNotBlank(bo.getType()), FoodIngredientStock::getType, bo.getType());
+        lqw.eq(StringUtils.isNotBlank(bo.getVisitType()), FoodIngredientStock::getVisitType, bo.getVisitType());
+        if (StringUtils.isNotBlank(bo.getSearchValue())) {
+            lqw.and(wrapper -> wrapper.like(FoodIngredientStock::getFoodName, bo.getSearchValue()).or().like(FoodIngredientStock::getInvoiceNumber, bo.getSearchValue()));
+        }
+        if (bo.getMainClass()!=null) {
+            lqw.and(wrapper -> wrapper.eq(FoodIngredientStock::getMainClass, bo.getMainClass()).or().eq(FoodIngredientStock::getSubClass, bo.getMainClass()));
+        }
+        if (StringUtils.isNotBlank(startTime) && StringUtils.isNotBlank(endTime)) {
+            lqw.between(FoodIngredientStock::getCreateTime, startTime, endTime);
+        }
+        return lqw;
+    }
+
+    /**
+     * 新增食材出入库
+     *
+     * @param boList 食材出入库
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(List<FoodIngredientStockBo> boList) {
+        boolean flag =false;
+        if (CollUtil.isNotEmpty(boList)) {
+            FoodIngredientStockBo bo=null;
+            for (int i = 0; i < boList.size(); i++) {
+                bo = boList.get(i);
+                FoodIngredientStock add = MapstructUtils.convert(bo, FoodIngredientStock.class);
+                validEntityBeforeSave(add);
+                 flag = baseMapper.insert(add) > 0;
+                if (flag) {
+                    bo.setId(add.getId());
+                }
+
+            }
+
+        }
+        return flag;
+    }
+
+    /**
+     * 修改食材出入库
+     *
+     * @param bo 食材出入库
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(FoodIngredientStockBo bo) {
+        FoodIngredientStock update = MapstructUtils.convert(bo, FoodIngredientStock.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public TableDataInfo<FoodStockBalanceVo> queryStockBalancePage(FoodIngredientStockBo bo, PageQuery pageQuery) {
+        Map<String, Object> params = bo.getParams();
+
+        // 计算正确的offset
+        int offset = (pageQuery.getPageNum() - 1) * pageQuery.getPageSize();
+        Integer count = baseMapper.queryStockBalanceCount(bo.getMainClass(), bo.getSearchValue());
+        List<FoodStockBalanceVo> balanceVoList = baseMapper.queryStockBalance(bo.getMainClass(), bo.getSearchValue(), offset, pageQuery.getPageSize());
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        tableDataInfo.setRows(balanceVoList);
+        tableDataInfo.setTotal(count);
+        return tableDataInfo;
+    }
+
+    @Override
+    public TableDataInfo<FoodStockBalanceVo> queryStockBalance(FoodIngredientStockBo bo ) {
+        Map<String, Object> params = bo.getParams();
+
+        Integer count = baseMapper.queryStockBalanceCount(bo.getMainClass(), bo.getSearchValue());
+        List<FoodStockBalanceVo> balanceVoList = baseMapper.queryStockBalance(bo.getMainClass(), bo.getSearchValue(), null,null);
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        tableDataInfo.setRows(balanceVoList);
+        tableDataInfo.setTotal(count);
+        return tableDataInfo;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(FoodIngredientStock entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除食材出入库信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 235 - 0
ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductInOutRecordServiceImpl.java

@@ -0,0 +1,235 @@
+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;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.web.domain.*;
+import org.dromara.web.domain.bo.ProductInOutRecordBo;
+import org.dromara.web.domain.vo.ProductInOutRecordVo;
+import org.dromara.web.mapper.*;
+import org.dromara.web.service.IProductInOutRecordService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 产品出入库记录Service业务层处理
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProductInOutRecordServiceImpl implements IProductInOutRecordService {
+
+    private final ProductInOutRecordMapper baseMapper;
+
+    private final ProductStockMapper productStockMapper;
+
+    private final StorageLocationMapper locationMapper;
+
+    private final ProductCategoryMapper productCategoryMapper;
+
+    private final SuppliesCategoryMapper suppliesCategoryMapper;
+
+    /**
+     * 查询产品出入库记录
+     *
+     * @param id 主键
+     * @return 产品出入库记录
+     */
+    @Override
+    public ProductInOutRecordVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询产品出入库记录列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品出入库记录分页列表
+     */
+    @Override
+    public TableDataInfo<ProductInOutRecordVo> queryPageList(ProductInOutRecordBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ProductInOutRecord> lqw = buildQueryWrapper(bo);
+        Page<ProductInOutRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的产品出入库记录列表
+     *
+     * @param bo 查询条件
+     * @return 产品出入库记录列表
+     */
+    @Override
+    public List<ProductInOutRecordVo> queryList(ProductInOutRecordBo bo) {
+        LambdaQueryWrapper<ProductInOutRecord> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ProductInOutRecord> buildQueryWrapper(ProductInOutRecordBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProductInOutRecord> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProductInOutRecord::getId);
+        lqw.eq(StringUtils.isNotBlank(bo.getType()), ProductInOutRecord::getType, bo.getType());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductType()), ProductInOutRecord::getProductType, bo.getProductType());
+        lqw.eq(StringUtils.isNotBlank(bo.getVisitType()), ProductInOutRecord::getVisitType, bo.getVisitType());
+        lqw.eq(StringUtils.isNotBlank(bo.getPreUnit()), ProductInOutRecord::getPreUnit, bo.getPreUnit());
+        lqw.eq(StringUtils.isNotBlank(bo.getUnit()), ProductInOutRecord::getUnit, bo.getUnit());
+        lqw.eq(StringUtils.isNotBlank(bo.getSerialNo()), ProductInOutRecord::getSerialNo, bo.getSerialNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProductInOutRecord::getStatus, bo.getStatus());
+        if (bo.getSearchValue() != null) {
+            lqw.and(wrapper -> wrapper.eq(ProductInOutRecord::getProductName, bo.getSearchValue()).or().eq(ProductInOutRecord::getProductNo, bo.getSearchValue()));
+        }
+        return lqw;
+    }
+
+    /**
+     * 新增产品出入库记录
+     *
+     * @param boList 产品出入库记录
+     * @return 是否新增成功
+     */
+    @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()));
+
+                }
+                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);
+                    }
+                }
+            }
+        }
+        return flag;
+    }
+
+
+    /**
+     * 修改产品出入库记录
+     *
+     * @param bo 产品出入库记录
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProductInOutRecordBo bo) {
+        ProductInOutRecord update = MapstructUtils.convert(bo, ProductInOutRecord.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public TableDataInfo<ProductInOutRecordVo> queryStockBalancePage(ProductInOutRecordBo bo, PageQuery pageQuery) {
+        Map<String, Object> params = bo.getParams();
+
+        // 计算正确的offset
+        int offset = (pageQuery.getPageNum() - 1) * pageQuery.getPageSize();
+        Integer count = baseMapper.queryStockBalanceCount(bo.getSearchValue());
+        List<ProductInOutRecordVo> balanceVoList = baseMapper.queryStockBalance(bo.getSearchValue(), offset, pageQuery.getPageSize());
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        tableDataInfo.setRows(balanceVoList);
+        tableDataInfo.setTotal(count);
+        return tableDataInfo;
+    }
+
+ /*   @Override
+    public TableDataInfo<ProductInOutRecordVo> queryStockBalance(FoodIngredientStockBo bo ) {
+        Map<String, Object> params = bo.getParams();
+
+        Integer count = baseMapper.queryStockBalanceCount( bo.getSearchValue());
+        List<ProductInOutRecordVo> balanceVoList = baseMapper.queryStockBalance( bo.getSearchValue(), null,null);
+        TableDataInfo tableDataInfo = new TableDataInfo();
+        tableDataInfo.setRows(balanceVoList);
+        tableDataInfo.setTotal(count);
+        return tableDataInfo;
+    }*/
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProductInOutRecord entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除产品出入库记录信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 40 - 9
ruoyi-admin/src/main/java/org/dromara/web/service/impl/ProductNutritionServiceImpl.java

@@ -6,19 +6,16 @@ import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.idev.excel.FastExcel;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.domain.R;
 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.core.toolkit.Wrappers;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.dromara.system.domain.SysDiseaseLabel;
-import org.dromara.system.domain.bo.SysDiseaseLabelBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.system.domain.vo.SysDeptVo;
 import org.dromara.system.domain.vo.SysDictDataVo;
 import org.dromara.system.domain.vo.SysDiseaseLabelVo;
@@ -27,19 +24,22 @@ import org.dromara.system.service.ISysDictDataService;
 import org.dromara.system.service.ISysDiseaseLabelService;
 import org.dromara.web.domain.ProductCategory;
 import org.dromara.web.domain.ProductNutrition;
-import org.dromara.web.domain.TreatmentUser;
+import org.dromara.web.domain.ProductStock;
+import org.dromara.web.domain.StorageLocation;
 import org.dromara.web.domain.bo.BatchProductNuyritionBo;
 import org.dromara.web.domain.bo.ProductNutritionBo;
 import org.dromara.web.domain.vo.ProductCategoryVo;
 import org.dromara.web.domain.vo.ProductNutritionVo;
+import org.dromara.web.domain.vo.ProductStockVo;
 import org.dromara.web.mapper.ProductCategoryMapper;
 import org.dromara.web.mapper.ProductNutritionMapper;
+import org.dromara.web.mapper.ProductStockMapper;
+import org.dromara.web.mapper.StorageLocationMapper;
 import org.dromara.web.service.IProductCategoryService;
 import org.dromara.web.service.IProductNutritionService;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
 
-
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -68,6 +68,10 @@ public class ProductNutritionServiceImpl implements IProductNutritionService {
 
     private final ISysDictDataService dataService;
 
+    private final ProductStockMapper productStockMapper;
+
+    private final StorageLocationMapper locationMapper;
+
 
     /**
      * 查询营养产品信息
@@ -172,6 +176,33 @@ public class ProductNutritionServiceImpl implements IProductNutritionService {
     public TableDataInfo<ProductNutritionVo> queryPageList(ProductNutritionBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<ProductNutrition> lqw = buildQueryWrapper(bo);
         Page<ProductNutritionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        List<ProductNutritionVo> records = result.getRecords();
+        if (CollUtil.isNotEmpty(records)) {
+            List<StorageLocation> locationList = locationMapper.selectList(Wrappers.lambdaQuery(StorageLocation.class).select(StorageLocation::getId, StorageLocation::getStorageName));
+            Map<String, Long> locationMap = locationList.stream().collect(Collectors.toMap(k1 -> k1.getStorageName(), k2 -> k2.getId(), (k1, k2) -> k1));
+            Map<String, List<SysDictDataVo>> dictMap = dataService.selectGroupByType(
+                List.of(BizConst.PRODUCT_SPEC_UNIT, BizConst.PRODUCT_PACKAGE_UNIT)).getData();
+            Map<String, String> specUnitMap = dictMap.get(BizConst.PRODUCT_SPEC_UNIT).stream().collect(Collectors.toMap(k1 -> k1.getDictValue(), k2 -> k2.getDictLabel(), (k1, k2) -> k1));
+            Map<String, String> packageUnitMap = dictMap.get(BizConst.PRODUCT_PACKAGE_UNIT).stream().collect(Collectors.toMap(k1 -> k1.getDictValue(), k2 -> k2.getDictLabel(), (k1, k2) -> k1));
+
+            records.stream().map(ProductNutritionVo::getProductCategory).collect(Collectors.toSet());
+            productCategoryMapper.selectList(Wrappers.lambdaQuery(ProductCategory.class)
+                .select(ProductCategory::getCategoryName, ProductCategory::getCategoryId)
+                .in(ProductCategory::getCategoryId, records.stream().map(ProductNutritionVo::getProductCategory).collect(Collectors.toSet())));
+            //获取预包装库位的id
+            Long lId = locationMap.get("预包装库位");
+            records.forEach(v -> {
+                String packageUnit = StrUtil.emptyToDefault(packageUnitMap.get(v.getPackageUnit()), StrUtil.EMPTY);
+                String productSpecUnit = StrUtil.emptyToDefault(specUnitMap.get(v.getProductSpecUnit()), StrUtil.EMPTY);
+                ProductStockVo productStockVo = productStockMapper.selectVoOne(new LambdaQueryWrapper<ProductStock>().eq(ProductStock::getProductId, v.getId()).eq(ProductStock::getLocationId, lId));
+                if (null != productStockVo) {
+                    v.setStockNum(productStockVo.getStockNum());
+                }
+                v.setProductSpec(v.getProductSpec() + productSpecUnit + "/" + packageUnit);
+                v.setProductSpecUnit(productSpecUnit);
+                v.setPackageUnit(packageUnit);
+            });
+        }
         return TableDataInfo.build(result);
     }
 

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

@@ -0,0 +1,137 @@
+package org.dromara.web.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.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.web.domain.ProductStock;
+import org.dromara.web.domain.bo.ProductStockBo;
+import org.dromara.web.domain.vo.ProductStockVo;
+import org.dromara.web.mapper.ProductStockMapper;
+import org.dromara.web.service.IProductStockService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 产品库存Service业务层处理
+ *
+ * @author Lion Li
+ * @date 2025-08-20
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProductStockServiceImpl implements IProductStockService {
+
+    private final ProductStockMapper baseMapper;
+
+    /**
+     * 查询产品库存
+     *
+     * @param id 主键
+     * @return 产品库存
+     */
+    @Override
+    public ProductStockVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询产品库存列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品库存分页列表
+     */
+    @Override
+    public TableDataInfo<ProductStockVo> queryPageList(ProductStockBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ProductStock> lqw = buildQueryWrapper(bo);
+        Page<ProductStockVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的产品库存列表
+     *
+     * @param bo 查询条件
+     * @return 产品库存列表
+     */
+    @Override
+    public List<ProductStockVo> queryList(ProductStockBo bo) {
+        LambdaQueryWrapper<ProductStock> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ProductStock> buildQueryWrapper(ProductStockBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProductStock> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProductStock::getId);
+        lqw.eq(bo.getLocationId() != null, ProductStock::getLocationId, bo.getLocationId());
+        lqw.eq(bo.getCategoryId() != null, ProductStock::getCategoryId, bo.getCategoryId());
+        if (bo.getSearchValue() != null) {
+            lqw.and(wrapper -> wrapper.eq(ProductStock::getProductNo, bo.getSearchValue()).or().eq(ProductStock::getProductName, bo.getSearchValue()));
+        }
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProductStock::getStatus, bo.getStatus());
+        return lqw;
+    }
+
+    /**
+     * 新增产品库存
+     *
+     * @param bo 产品库存
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ProductStockBo bo) {
+        ProductStock add = MapstructUtils.convert(bo, ProductStock.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改产品库存
+     *
+     * @param bo 产品库存
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProductStockBo bo) {
+        ProductStock update = MapstructUtils.convert(bo, ProductStock.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProductStock entity) {
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除产品库存信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 29 - 14
ruoyi-admin/src/main/java/org/dromara/web/service/impl/SuppliesManageServiceImpl.java

@@ -2,7 +2,6 @@ package org.dromara.web.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.idev.excel.FastExcel;
@@ -17,11 +16,8 @@ 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.config.RedisUtil;
 import org.dromara.system.domain.SysDept;
 import org.dromara.system.domain.SysDiseaseLabel;
-import org.dromara.system.domain.SysFoodCategory;
-import org.dromara.system.domain.SysFoodIngredient;
 import org.dromara.system.domain.vo.SysDeptVo;
 import org.dromara.system.domain.vo.SysDictDataVo;
 import org.dromara.system.domain.vo.SysDiseaseLabelVo;
@@ -30,20 +26,13 @@ import org.dromara.system.mapper.SysDiseaseLabelMapper;
 import org.dromara.system.service.ISysDeptService;
 import org.dromara.system.service.ISysDictDataService;
 import org.dromara.system.service.ISysDiseaseLabelService;
-import org.dromara.web.domain.ProductManufacturer;
-import org.dromara.web.domain.ProductNutrition;
-import org.dromara.web.domain.ProductSupplier;
-import org.dromara.web.domain.SuppliesCategory;
-import org.dromara.web.domain.SuppliesManage;
+import org.dromara.web.domain.*;
 import org.dromara.web.domain.bo.BatchSuppliesManageBo;
 import org.dromara.web.domain.bo.SuppliesManageBo;
-import org.dromara.web.domain.vo.ProductNutritionVo;
+import org.dromara.web.domain.vo.ProductStockVo;
 import org.dromara.web.domain.vo.SuppliesCategoryVo;
 import org.dromara.web.domain.vo.SuppliesManageVo;
-import org.dromara.web.mapper.ProductManufacturerMapper;
-import org.dromara.web.mapper.ProductSupplierMapper;
-import org.dromara.web.mapper.SuppliesCategoryMapper;
-import org.dromara.web.mapper.SuppliesManageMapper;
+import org.dromara.web.mapper.*;
 import org.dromara.web.service.ISuppliesCategoryService;
 import org.dromara.web.service.ISuppliesManageService;
 import org.springframework.stereotype.Service;
@@ -86,6 +75,10 @@ public class SuppliesManageServiceImpl implements ISuppliesManageService {
 
     private final ISysDiseaseLabelService diseaseLabelService;
 
+    private final ProductStockMapper productStockMapper;
+
+    private final StorageLocationMapper locationMapper;
+
 
     /**
      * 查询耗材管理
@@ -177,6 +170,28 @@ public class SuppliesManageServiceImpl implements ISuppliesManageService {
     public TableDataInfo<SuppliesManageVo> queryPageList(SuppliesManageBo bo, PageQuery pageQuery) {
         LambdaQueryWrapper<SuppliesManage> lqw = buildQueryWrapper(bo);
         Page<SuppliesManageVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        List<SuppliesManageVo> records = result.getRecords();
+        if (CollUtil.isNotEmpty(records)) {
+            List<StorageLocation> locationList = locationMapper.selectList(Wrappers.lambdaQuery(StorageLocation.class).select(StorageLocation::getId, StorageLocation::getStorageName));
+            Map<String, Long> locationMap = locationList.stream().collect(Collectors.toMap(k1 -> k1.getStorageName(), k2 -> k2.getId(), (k1, k2) -> k1));
+            Map<String, List<SysDictDataVo>> dictMap = dictDataService.selectGroupByType(
+                List.of(BizConst.PRODUCT_SPEC_UNIT, BizConst.PRODUCT_PACKAGE_UNIT)).getData();
+            Map<String, String> specUnitMap = dictMap.get(BizConst.PRODUCT_SPEC_UNIT).stream().collect(Collectors.toMap(k1 -> k1.getDictValue(), k2 -> k2.getDictLabel(), (k1, k2) -> k1));
+            Map<String, String> packageUnitMap = dictMap.get(BizConst.PRODUCT_PACKAGE_UNIT).stream().collect(Collectors.toMap(k1 -> k1.getDictValue(), k2 -> k2.getDictLabel(), (k1, k2) -> k1));
+            //获取预包装库位的id
+            Long lId = locationMap.get("预包装库位");
+            records.forEach(v -> {
+                String packageUnit = StrUtil.emptyToDefault(packageUnitMap.get(v.getSuppliesUnit()), StrUtil.EMPTY);
+                String productSpecUnit = StrUtil.emptyToDefault(specUnitMap.get(v.getSuppliesSpecUnit()), StrUtil.EMPTY);
+                ProductStockVo productStockVo = productStockMapper.selectVoOne(new LambdaQueryWrapper<ProductStock>().eq(ProductStock::getProductId, v.getId()).eq(ProductStock::getLocationId, lId));
+                if (null != productStockVo) {
+                    v.setStockNum(productStockVo.getStockNum());
+                }
+                v.setSuppliesSpec(v.getSuppliesSpec() + productSpecUnit + "/" + packageUnit);
+                v.setSuppliesSpecUnit(productSpecUnit);
+                v.setSuppliesUnit(packageUnit);
+            });
+        }
         return TableDataInfo.build(result);
     }
 

+ 7 - 0
ruoyi-admin/src/main/resources/mapper/stock/FoodIngredientStockMapper.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.FoodIngredientStockMapper">
+
+</mapper>

+ 7 - 0
ruoyi-admin/src/main/resources/mapper/stock/ProductInOutRecordMapper.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.ProductInOutRecordMapper">
+
+</mapper>

+ 7 - 0
ruoyi-admin/src/main/resources/mapper/stock/ProductStockMapper.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.ProductStockMapper">
+
+</mapper>

+ 2 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/BizConst.java

@@ -27,6 +27,8 @@ public interface BizConst {
 
     String PRINT_STATUS = "print_status";//打印状态
 
+    String STOCK_OPER_TYPE= "stock_oper_type";  //库存操作类型
+
     String DEVICE_STATUS="device_status";
 
     String HOSPITAL_ROLE_TYPE = "hospital_role_type";

+ 20 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/biz/ProductType.java

@@ -0,0 +1,20 @@
+package org.dromara.common.core.enums.biz;
+
+import lombok.Getter;
+
+@Getter
+public enum ProductType {
+
+
+    TYPE_ONE("1", "营养产品"),
+
+    TYPE_THREE("3", "耗材");
+
+    private String code;
+    private String remark;
+
+    private ProductType(String code, String remark) {
+        this.code = code;
+        this.remark = remark;
+    }
+}

+ 20 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/biz/StockOperType.java

@@ -0,0 +1,20 @@
+package org.dromara.common.core.enums.biz;
+
+import lombok.Getter;
+
+@Getter
+public enum StockOperType {
+
+   OPER_TYPE_ZERO("0", "手动入库"),
+    OPER_TYPE_ONE("1", "手动出库"),
+    OPER_TYPE_TWO("2", "处方出库"),
+    OPER_TYPE_THREE("3", "处方退费");
+
+    private String code;
+    private String remark;
+
+    private StockOperType(String code, String remark) {
+        this.code = code;
+        this.remark = remark;
+    }
+}

+ 2 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysFoodIngredientVo.java

@@ -247,4 +247,6 @@ public class SysFoodIngredientVo implements Serializable {
     private String remark;
 
     private BigDecimal caloriesForInput;
+
+    private BigDecimal stockNum;
 }

+ 39 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/SysFoodStockBalanceVo.java

@@ -0,0 +1,39 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.SysFoodIngredient;
+
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = SysFoodIngredient.class)
+public class SysFoodStockBalanceVo implements Serializable {
+    private Long foodId;
+
+    @ExcelProperty(value = "食材名称")
+    private String foodName;
+
+    private Long mainClass;
+
+    @ExcelProperty(value = "食材大类")
+    private String mainClassName;
+
+    private Long subClass;
+
+    @ExcelProperty(value = "食材亚类")
+    private String subClassName;
+
+    @ExcelProperty(value = "库存数量")
+    private BigDecimal stockNum;
+
+    @ExcelProperty(value = "库存单位")
+    private String stockUnit;
+
+
+}

+ 20 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/mapper/SysFoodIngredientMapper.java

@@ -1,8 +1,14 @@
 package org.dromara.system.mapper;
 
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import org.dromara.system.domain.SysFoodIngredient;
 import org.dromara.system.domain.vo.SysFoodIngredientVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.system.domain.vo.SysFoodStockBalanceVo;
+
+import java.util.List;
+import java.util.Set;
 
 /**
  * 食材管理Mapper接口
@@ -12,4 +18,18 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  */
 public interface SysFoodIngredientMapper extends BaseMapperPlus<SysFoodIngredient, SysFoodIngredientVo> {
 
+    @Select(" <script> SELECT fis.food_id AS foodId, fis.food_name as foodName, fis.main_class as mainClass, fis.main_class_name as mainClassName, fis.sub_class as subClass, fis.sub_class_name as subClassName, " +
+        "fis.stock_unit as stockUnit, " +
+        " SUM( CASE WHEN fis.type = '0' THEN num ELSE 0 END ) -  SUM( CASE WHEN fis.type IN ( '1', '2', '3' ) THEN fis.num ELSE 0 END ) AS stockNum " +
+        " FROM food_ingredient_stock fis where 1 =1 " +
+        "<if test='foodIds != null and foodIds.size() > 0'>" +
+        "   AND fis.food_id IN " +
+        "   <foreach collection='foodIds' item='id' open='(' separator=',' close=')'>" +
+        "       #{id}" +
+        "   </foreach>" +
+        "</if>" +
+        "GROUP BY fis.food_id, fis.main_class, fis.sub_class, fis.stock_unit, fis.food_name, fis.main_class_name, fis.sub_class_name " +
+        "</script>" )
+    List<SysFoodStockBalanceVo> queryStockBalance(@Param("foodIds") Set<Long> foodIds);
+
 }

+ 10 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysFoodIngredientServiceImpl.java

@@ -24,6 +24,7 @@ import org.dromara.system.domain.SysDictData;
 import org.dromara.system.domain.SysDiseaseLabel;
 import org.dromara.system.domain.SysFoodCategory;
 import org.dromara.system.domain.vo.SysDictDataVo;
+import org.dromara.system.domain.vo.SysFoodStockBalanceVo;
 import org.dromara.system.mapper.SysFoodCategoryMapper;
 import org.dromara.system.service.ISysDictDataService;
 import org.springframework.stereotype.Service;
@@ -60,6 +61,7 @@ public class SysFoodIngredientServiceImpl implements ISysFoodIngredientService {
 
     private final ISysDictDataService dictDataService;
 
+
     /**
      * 查询食材管理
      *
@@ -85,16 +87,23 @@ public class SysFoodIngredientServiceImpl implements ISysFoodIngredientService {
 
         if (CollUtil.isNotEmpty(result.getRecords())) {
             Set<Long> ids = CollUtil.newHashSet();
+            Set<Long> foodIds = CollUtil.newHashSet();
+
             result.getRecords().forEach(v -> {
                 Arrays.stream(v.getFoodCategoryId().split(",")).forEach(id -> {
                     ids.add(Long.valueOf(id));
                 });
+                foodIds.add(v.getFoodIngredientId());
             });
 
             Map<String, SysDictDataVo> dictDataMap = dictDataService.selectMapByType(BizConst.FOOD_UNIT).getData();
             List<SysFoodCategory> categoryList = categoryMapper.selectList(Wrappers.lambdaQuery(SysFoodCategory.class)
                 .in(SysFoodCategory::getFoodCategoryId, ids));
             Map<String, String> categoryMap = categoryList.stream().collect(Collectors.toMap(k1 -> String.valueOf(k1.getFoodCategoryId()), k2 -> k2.getName(), (k1, k2) -> k1));
+
+            List<SysFoodStockBalanceVo> stockBalanceVos = baseMapper.queryStockBalance(foodIds);
+            Map<Long, BigDecimal> stockBalanceMap = stockBalanceVos.stream().collect(Collectors.toMap(k1 -> k1.getFoodId(), k2 -> k2.getStockNum(), (k1, k2) -> k1));
+
             result.getRecords().forEach(v -> {
                 String[] arr = v.getFoodCategoryId().split(",");
                 if (arr.length >= 1) {
@@ -107,6 +116,7 @@ public class SysFoodIngredientServiceImpl implements ISysFoodIngredientService {
                 if (ObjUtil.isNotNull(unit)) {
                     v.setUnitName(unit.getDictLabel());
                 }
+                v.setStockNum(stockBalanceMap.get(v.getFoodIngredientId()));
             });
         }