1468 lines
54 KiB
Markdown
1468 lines
54 KiB
Markdown
|
|
# 兴万达MES系统 - 库存预警功能实现文档
|
|||
|
|
|
|||
|
|
## 📋 目录
|
|||
|
|
- [需求说明](#需求说明)
|
|||
|
|
- [数据库设计](#数据库设计)
|
|||
|
|
- [后端实现](#后端实现)
|
|||
|
|
- [前端实现](#前端实现)
|
|||
|
|
- [首页预警弹窗](#首页预警弹窗)
|
|||
|
|
- [实施步骤](#实施步骤)
|
|||
|
|
- [测试用例](#测试用例)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 需求说明
|
|||
|
|
|
|||
|
|
### 1. 功能概述
|
|||
|
|
在仓库管理模块下新增"库存预警"功能,与"即时库存"同级菜单,用于监控物料库存是否超出设定的上下限范围,并在登录首页时弹窗提醒。
|
|||
|
|
|
|||
|
|
**重要说明**:
|
|||
|
|
- 物料选择直接从物料表(md_material)读取,不再从即时库存读取
|
|||
|
|
- **菜单状态控制预警**:读取 `sys_menu` 表中"仓库管理"菜单(menu_id=2082)的状态
|
|||
|
|
- 如果 `visible='1'`(隐藏)或 `status='1'`(停用),则不在首页显示预警
|
|||
|
|
- 只有当 `visible='0'`(显示)且 `status='0'`(正常)时,才会在首页显示预警
|
|||
|
|
|
|||
|
|
### 2. 核心功能
|
|||
|
|
1. **预警配置管理**:
|
|||
|
|
- 选择物料(从物料表中选择,只显示状态为"启用"的物料)
|
|||
|
|
- 可选择特定仓库或全部仓库
|
|||
|
|
- 设置库存上限数量
|
|||
|
|
- 设置库存下限数量
|
|||
|
|
- 支持增删改查和导出功能
|
|||
|
|
|
|||
|
|
2. **预警触发逻辑**:
|
|||
|
|
- 当前库存数量 > 上限数量:触发"高于上限"预警
|
|||
|
|
- 当前库存数量 < 下限数量:触发"低于下限"预警
|
|||
|
|
- 在上下限之间:不触发预警
|
|||
|
|
- **菜单状态控制**:检查"仓库管理"菜单(ID=2082)的 `visible` 和 `status` 字段
|
|||
|
|
- `visible='0'` 且 `status='0'`:显示预警 ✅
|
|||
|
|
- `visible='1'` 或 `status='1'`:不显示预警 ❌
|
|||
|
|
|
|||
|
|
3. **预警展示**:
|
|||
|
|
- 登录首页右上角弹出Notification通知
|
|||
|
|
- 显示5秒后自动消失(可手动关闭)
|
|||
|
|
- 预警内容格式:
|
|||
|
|
- 高于上限:`【库存预警】物料【xxx】在仓库【xxx】中的库存数量高于上限(上限:X件),请暂停采购或调整库存。`
|
|||
|
|
- 低于下限:`【库存预警】物料【xxx】在仓库【xxx】中的库存数量低于下限(下限:X件),请尽快采购补货。`
|
|||
|
|
- 底部小字:`*若数量上下限不正确,请在库存预警中更改上下限`
|
|||
|
|
- 多条预警依次弹出,间隔300ms
|
|||
|
|
|
|||
|
|
### 3. 权限配置
|
|||
|
|
- `warehouse:inventoryAlert:query` - 查询
|
|||
|
|
- `warehouse:inventoryAlert:add` - 新增
|
|||
|
|
- `warehouse:inventoryAlert:edit` - 修改
|
|||
|
|
- `warehouse:inventoryAlert:remove` - 删除
|
|||
|
|
- `warehouse:inventoryAlert:export` - 导出
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🗄️ 数据库设计
|
|||
|
|
|
|||
|
|
### 表结构:`wm_inventory_alert`
|
|||
|
|
|
|||
|
|
| 字段名 | 类型 | 长度 | 允许NULL | 默认值 | 说明 |
|
|||
|
|
|--------|------|------|----------|--------|------|
|
|||
|
|
| id | BIGINT | - | NO | AUTO_INCREMENT | 主键ID |
|
|||
|
|
| material_id | BIGINT | - | NO | - | 物料ID |
|
|||
|
|
| material_number | VARCHAR | 64 | YES | NULL | 物料编号(冗余) |
|
|||
|
|
| material_name | VARCHAR | 255 | YES | NULL | 物料名称(冗余) |
|
|||
|
|
| specification | VARCHAR | 255 | YES | NULL | 规格型号(冗余) |
|
|||
|
|
| warehouse_id | BIGINT | - | YES | NULL | 仓库ID(NULL=所有仓库) |
|
|||
|
|
| warehouse_name | VARCHAR | 64 | YES | NULL | 仓库名称(冗余) |
|
|||
|
|
| max_quantity | DECIMAL | 15,2 | YES | NULL | 库存上限数量 |
|
|||
|
|
| min_quantity | DECIMAL | 15,2 | YES | NULL | 库存下限数量 |
|
|||
|
|
| status | CHAR | 1 | NO | '0' | 状态(0启用 1停用) |
|
|||
|
|
| remark | VARCHAR | 500 | YES | NULL | 备注 |
|
|||
|
|
| create_by | VARCHAR | 64 | YES | '' | 创建者 |
|
|||
|
|
| create_time | DATETIME | - | YES | NULL | 创建时间 |
|
|||
|
|
| update_by | VARCHAR | 64 | YES | '' | 更新者 |
|
|||
|
|
| update_time | DATETIME | - | YES | NULL | 更新时间 |
|
|||
|
|
|
|||
|
|
### 索引设计
|
|||
|
|
- **主键索引**:`PRIMARY KEY (id)`
|
|||
|
|
- **物料索引**:`INDEX idx_material_id (material_id)`
|
|||
|
|
- **仓库索引**:`INDEX idx_warehouse_id (warehouse_id)`
|
|||
|
|
- **状态索引**:`INDEX idx_status (status)`
|
|||
|
|
- **唯一索引**:`UNIQUE INDEX uk_material_warehouse (material_id, warehouse_id)` - 防止同一物料在同一仓库重复配置
|
|||
|
|
|
|||
|
|
### 菜单配置
|
|||
|
|
|
|||
|
|
| menu_id | menu_name | parent_id | order_num | path | component | perms | menu_type | icon |
|
|||
|
|
|---------|-----------|-----------|-----------|------|-----------|-------|-----------|------|
|
|||
|
|
| 2400 | 库存预警 | 2082 | 1 | inventoryAlert | mes/warehouse/inventoryAlert/index | warehouse:inventoryAlert:list | C | warning |
|
|||
|
|
| 2401 | 库存预警查询 | 2400 | 1 | # | | warehouse:inventoryAlert:query | F | # |
|
|||
|
|
| 2402 | 库存预警新增 | 2400 | 2 | # | | warehouse:inventoryAlert:add | F | # |
|
|||
|
|
| 2403 | 库存预警修改 | 2400 | 3 | # | | warehouse:inventoryAlert:edit | F | # |
|
|||
|
|
| 2404 | 库存预警删除 | 2400 | 4 | # | | warehouse:inventoryAlert:remove | F | # |
|
|||
|
|
| 2405 | 库存预警导出 | 2400 | 5 | # | | warehouse:inventoryAlert:export | F | # |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 💡 核心SQL查询逻辑说明
|
|||
|
|
|
|||
|
|
### 预警触发查询:`selectAlertWithCurrentInventory`
|
|||
|
|
|
|||
|
|
此查询用于获取所有需要触发预警的配置,包含以下关键逻辑:
|
|||
|
|
|
|||
|
|
#### 1. 实时库存计算
|
|||
|
|
使用子查询计算每个物料的实时库存数量:
|
|||
|
|
- **入库汇总**:采购入库、销售退货、生产入库、盘点初始化、盘盈
|
|||
|
|
- **出库汇总**:生产领料、销售出库、盘亏、其他出库
|
|||
|
|
- **实时库存** = 入库总量 - 出库总量
|
|||
|
|
|
|||
|
|
#### 2. 过滤条件(INNER JOIN)
|
|||
|
|
- **sys_menu (menu_id=2082)**:检查"仓库管理"菜单状态
|
|||
|
|
- `visible='0'`:菜单可见
|
|||
|
|
- `status='0'`:菜单启用
|
|||
|
|
- 作用:当仓库管理模块被禁用或隐藏时,不显示任何预警
|
|||
|
|
|
|||
|
|
#### 3. WHERE条件
|
|||
|
|
- `a.status = '0'`:只查询启用的预警配置
|
|||
|
|
- `menu.visible = '0'`:仓库管理菜单可见
|
|||
|
|
- `menu.status = '0'`:仓库管理菜单启用
|
|||
|
|
|
|||
|
|
#### 4. HAVING条件
|
|||
|
|
- `current_quantity > 0`:只查询库存数量大于0的记录
|
|||
|
|
- 作用:避免对库存为0或负数的物料发出预警
|
|||
|
|
- **注意**:此条件已能过滤掉从未入过库的物料(它们的库存为0),无需再关联其他库存表
|
|||
|
|
|
|||
|
|
#### 5. 预警判断(Service层)
|
|||
|
|
查询结果返回后,在Service层进行预警判断:
|
|||
|
|
- `current_quantity > max_quantity`:触发"高于上限"预警
|
|||
|
|
- `current_quantity < min_quantity`:触发"低于下限"预警
|
|||
|
|
|
|||
|
|
### SQL执行流程图
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ 1. 从 wm_inventory_alert 获取所有预警配置 │
|
|||
|
|
│ └─ WHERE status='0' (只查询启用的配置) │
|
|||
|
|
└─────────────────────┬───────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ 2. INNER JOIN sys_menu (menu_id=2082) │
|
|||
|
|
│ └─ 检查仓库管理菜单是否可见且启用 │
|
|||
|
|
│ └─ 如果菜单被禁用,则返回空结果 │
|
|||
|
|
└─────────────────────┬───────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ 3. 计算实时库存数量 (子查询) │
|
|||
|
|
│ ├─ 汇总所有入库记录 │
|
|||
|
|
│ ├─ 汇总所有出库记录 │
|
|||
|
|
│ └─ current_quantity = 入库 - 出库 │
|
|||
|
|
└─────────────────────┬───────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ 4. HAVING current_quantity > 0 │
|
|||
|
|
│ └─ 过滤掉库存为0或负数的记录 │
|
|||
|
|
│ └─ 自动过滤掉从未入过库的物料 │
|
|||
|
|
└─────────────────────┬───────────────────────────────────┘
|
|||
|
|
│
|
|||
|
|
▼
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ 5. 返回结果到Service层 │
|
|||
|
|
│ ├─ 判断是否超过上限 (> max_quantity) │
|
|||
|
|
│ ├─ 判断是否低于下限 (< min_quantity) │
|
|||
|
|
│ └─ 生成预警消息 │
|
|||
|
|
└─────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔧 后端实现
|
|||
|
|
|
|||
|
|
### 1. 目录结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
yjh-mes/src/main/java/cn/sourceplan/warehouse/
|
|||
|
|
├── domain/
|
|||
|
|
│ └── InventoryAlert.java # 实体类
|
|||
|
|
├── mapper/
|
|||
|
|
│ └── InventoryAlertMapper.java # Mapper接口
|
|||
|
|
├── service/
|
|||
|
|
│ ├── IInventoryAlertService.java # Service接口
|
|||
|
|
│ └── impl/
|
|||
|
|
│ └── InventoryAlertServiceImpl.java # Service实现
|
|||
|
|
└── controller/
|
|||
|
|
└── InventoryAlertController.java # 控制器
|
|||
|
|
|
|||
|
|
yjh-mes/src/main/resources/mapper/warehouse/
|
|||
|
|
└── InventoryAlertMapper.xml # MyBatis映射文件
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 实体类:`InventoryAlert.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.warehouse.domain;
|
|||
|
|
|
|||
|
|
import java.math.BigDecimal;
|
|||
|
|
import lombok.Data;
|
|||
|
|
import cn.sourceplan.common.annotation.Excel;
|
|||
|
|
import cn.sourceplan.common.core.domain.BaseEntity;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 库存预警对象 wm_inventory_alert
|
|||
|
|
*
|
|||
|
|
* @author admin
|
|||
|
|
* @date 2025-10-17
|
|||
|
|
*/
|
|||
|
|
@Data
|
|||
|
|
public class InventoryAlert extends BaseEntity {
|
|||
|
|
private static final long serialVersionUID = 1L;
|
|||
|
|
|
|||
|
|
/** 主键ID */
|
|||
|
|
private Long id;
|
|||
|
|
|
|||
|
|
/** 物料ID */
|
|||
|
|
@Excel(name = "物料ID")
|
|||
|
|
private Long materialId;
|
|||
|
|
|
|||
|
|
/** 物料编号 */
|
|||
|
|
@Excel(name = "物料编号")
|
|||
|
|
private String materialNumber;
|
|||
|
|
|
|||
|
|
/** 物料名称 */
|
|||
|
|
@Excel(name = "物料名称")
|
|||
|
|
private String materialName;
|
|||
|
|
|
|||
|
|
/** 规格型号 */
|
|||
|
|
@Excel(name = "规格型号")
|
|||
|
|
private String specification;
|
|||
|
|
|
|||
|
|
/** 仓库ID */
|
|||
|
|
@Excel(name = "仓库ID")
|
|||
|
|
private Long warehouseId;
|
|||
|
|
|
|||
|
|
/** 仓库名称 */
|
|||
|
|
@Excel(name = "仓库名称")
|
|||
|
|
private String warehouseName;
|
|||
|
|
|
|||
|
|
/** 库存上限数量 */
|
|||
|
|
@Excel(name = "库存上限")
|
|||
|
|
private BigDecimal maxQuantity;
|
|||
|
|
|
|||
|
|
/** 库存下限数量 */
|
|||
|
|
@Excel(name = "库存下限")
|
|||
|
|
private BigDecimal minQuantity;
|
|||
|
|
|
|||
|
|
/** 状态(0启用 1停用) */
|
|||
|
|
@Excel(name = "状态", readConverterExp = "0=启用,1=停用")
|
|||
|
|
private String status;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Mapper接口:`InventoryAlertMapper.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.warehouse.mapper;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
import cn.sourceplan.warehouse.domain.InventoryAlert;
|
|||
|
|
import org.springframework.stereotype.Repository;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 库存预警Mapper接口
|
|||
|
|
*
|
|||
|
|
* @author admin
|
|||
|
|
* @date 2025-10-17
|
|||
|
|
*/
|
|||
|
|
@Repository
|
|||
|
|
public interface InventoryAlertMapper {
|
|||
|
|
/**
|
|||
|
|
* 查询库存预警
|
|||
|
|
*/
|
|||
|
|
InventoryAlert selectInventoryAlertById(Long id);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询库存预警列表
|
|||
|
|
*/
|
|||
|
|
List<InventoryAlert> selectInventoryAlertList(InventoryAlert inventoryAlert);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 新增库存预警
|
|||
|
|
*/
|
|||
|
|
int insertInventoryAlert(InventoryAlert inventoryAlert);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 修改库存预警
|
|||
|
|
*/
|
|||
|
|
int updateInventoryAlert(InventoryAlert inventoryAlert);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除库存预警
|
|||
|
|
*/
|
|||
|
|
int deleteInventoryAlertById(Long id);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量删除库存预警
|
|||
|
|
*/
|
|||
|
|
int deleteInventoryAlertByIds(Long[] ids);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询所有启用的预警配置及其当前库存
|
|||
|
|
*/
|
|||
|
|
List<InventoryAlert> selectAlertWithCurrentInventory();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Service接口:`IInventoryAlertService.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.warehouse.service;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
import cn.sourceplan.warehouse.domain.InventoryAlert;
|
|||
|
|
import com.alibaba.fastjson2.JSONObject;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 库存预警Service接口
|
|||
|
|
*
|
|||
|
|
* @author admin
|
|||
|
|
* @date 2025-10-17
|
|||
|
|
*/
|
|||
|
|
public interface IInventoryAlertService {
|
|||
|
|
InventoryAlert selectInventoryAlertById(Long id);
|
|||
|
|
List<InventoryAlert> selectInventoryAlertList(InventoryAlert inventoryAlert);
|
|||
|
|
int insertInventoryAlert(InventoryAlert inventoryAlert);
|
|||
|
|
int updateInventoryAlert(InventoryAlert inventoryAlert);
|
|||
|
|
int deleteInventoryAlertByIds(Long[] ids);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取当前触发的所有预警信息
|
|||
|
|
* 返回格式:[{type: 'max'/'min', message: '预警消息'}]
|
|||
|
|
*/
|
|||
|
|
List<JSONObject> getTriggeredAlerts();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. Service实现:`InventoryAlertServiceImpl.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.warehouse.service.impl;
|
|||
|
|
|
|||
|
|
import java.math.BigDecimal;
|
|||
|
|
import java.util.ArrayList;
|
|||
|
|
import java.util.List;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.stereotype.Service;
|
|||
|
|
import cn.sourceplan.warehouse.domain.InventoryAlert;
|
|||
|
|
import cn.sourceplan.warehouse.mapper.InventoryAlertMapper;
|
|||
|
|
import cn.sourceplan.warehouse.service.IInventoryAlertService;
|
|||
|
|
import com.alibaba.fastjson2.JSONObject;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 库存预警Service业务层处理
|
|||
|
|
*
|
|||
|
|
* @author admin
|
|||
|
|
* @date 2025-10-17
|
|||
|
|
*/
|
|||
|
|
@Service
|
|||
|
|
public class InventoryAlertServiceImpl implements IInventoryAlertService {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private InventoryAlertMapper inventoryAlertMapper;
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public InventoryAlert selectInventoryAlertById(Long id) {
|
|||
|
|
return inventoryAlertMapper.selectInventoryAlertById(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public List<InventoryAlert> selectInventoryAlertList(InventoryAlert inventoryAlert) {
|
|||
|
|
return inventoryAlertMapper.selectInventoryAlertList(inventoryAlert);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public int insertInventoryAlert(InventoryAlert inventoryAlert) {
|
|||
|
|
return inventoryAlertMapper.insertInventoryAlert(inventoryAlert);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public int updateInventoryAlert(InventoryAlert inventoryAlert) {
|
|||
|
|
return inventoryAlertMapper.updateInventoryAlert(inventoryAlert);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public int deleteInventoryAlertByIds(Long[] ids) {
|
|||
|
|
return inventoryAlertMapper.deleteInventoryAlertByIds(ids);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public List<JSONObject> getTriggeredAlerts() {
|
|||
|
|
List<JSONObject> alerts = new ArrayList<>();
|
|||
|
|
|
|||
|
|
// 查询所有启用的预警配置及其当前库存
|
|||
|
|
List<InventoryAlert> alertConfigs = inventoryAlertMapper.selectAlertWithCurrentInventory();
|
|||
|
|
|
|||
|
|
for (InventoryAlert config : alertConfigs) {
|
|||
|
|
BigDecimal currentQty = config.getCurrentQuantity(); // 需要在实体类中添加临时字段
|
|||
|
|
|
|||
|
|
if (currentQty == null) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
JSONObject alert = new JSONObject();
|
|||
|
|
|
|||
|
|
// 检查是否超过上限
|
|||
|
|
if (config.getMaxQuantity() != null &&
|
|||
|
|
currentQty.compareTo(config.getMaxQuantity()) > 0) {
|
|||
|
|
alert.put("type", "max");
|
|||
|
|
alert.put("message", String.format(
|
|||
|
|
"【库存预警】物料【%s】在仓库【%s】中的库存数量高于上限(上限:%s件),请暂停采购或调整库存。",
|
|||
|
|
config.getMaterialName(),
|
|||
|
|
config.getWarehouseName() != null ? config.getWarehouseName() : "所有仓库",
|
|||
|
|
config.getMaxQuantity()
|
|||
|
|
));
|
|||
|
|
alerts.add(alert);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否低于下限
|
|||
|
|
if (config.getMinQuantity() != null &&
|
|||
|
|
currentQty.compareTo(config.getMinQuantity()) < 0) {
|
|||
|
|
alert = new JSONObject();
|
|||
|
|
alert.put("type", "min");
|
|||
|
|
alert.put("message", String.format(
|
|||
|
|
"【库存预警】物料【%s】在仓库【%s】中的库存数量低于下限(下限:%s件),请尽快采购补货。",
|
|||
|
|
config.getMaterialName(),
|
|||
|
|
config.getWarehouseName() != null ? config.getWarehouseName() : "所有仓库",
|
|||
|
|
config.getMinQuantity()
|
|||
|
|
));
|
|||
|
|
alerts.add(alert);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return alerts;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6. Controller:`InventoryAlertController.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package cn.sourceplan.warehouse.controller;
|
|||
|
|
|
|||
|
|
import java.util.List;
|
|||
|
|
import javax.servlet.http.HttpServletResponse;
|
|||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|||
|
|
import org.springframework.web.bind.annotation.*;
|
|||
|
|
import cn.sourceplan.common.annotation.Log;
|
|||
|
|
import cn.sourceplan.common.core.controller.BaseController;
|
|||
|
|
import cn.sourceplan.common.core.domain.AjaxResult;
|
|||
|
|
import cn.sourceplan.common.core.page.TableDataInfo;
|
|||
|
|
import cn.sourceplan.common.enums.BusinessType;
|
|||
|
|
import cn.sourceplan.common.utils.poi.ExcelUtil;
|
|||
|
|
import cn.sourceplan.warehouse.domain.InventoryAlert;
|
|||
|
|
import cn.sourceplan.warehouse.service.IInventoryAlertService;
|
|||
|
|
import com.alibaba.fastjson2.JSONObject;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 库存预警Controller
|
|||
|
|
*
|
|||
|
|
* @author admin
|
|||
|
|
* @date 2025-10-17
|
|||
|
|
*/
|
|||
|
|
@RestController
|
|||
|
|
@RequestMapping("/warehouse/inventoryAlert")
|
|||
|
|
public class InventoryAlertController extends BaseController {
|
|||
|
|
|
|||
|
|
@Autowired
|
|||
|
|
private IInventoryAlertService inventoryAlertService;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询库存预警列表
|
|||
|
|
*/
|
|||
|
|
@GetMapping("/list")
|
|||
|
|
public TableDataInfo list(InventoryAlert inventoryAlert) {
|
|||
|
|
startPage();
|
|||
|
|
List<InventoryAlert> list = inventoryAlertService.selectInventoryAlertList(inventoryAlert);
|
|||
|
|
return getDataTable(list);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 导出库存预警列表
|
|||
|
|
*/
|
|||
|
|
@Log(title = "库存预警", businessType = BusinessType.EXPORT)
|
|||
|
|
@PostMapping("/export")
|
|||
|
|
public void export(HttpServletResponse response, InventoryAlert inventoryAlert) {
|
|||
|
|
List<InventoryAlert> list = inventoryAlertService.selectInventoryAlertList(inventoryAlert);
|
|||
|
|
ExcelUtil<InventoryAlert> util = new ExcelUtil<>(InventoryAlert.class);
|
|||
|
|
util.exportExcel(response, list, "库存预警数据");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取库存预警详细信息
|
|||
|
|
*/
|
|||
|
|
@GetMapping(value = "/{id}")
|
|||
|
|
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
|||
|
|
return success(inventoryAlertService.selectInventoryAlertById(id));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 新增库存预警
|
|||
|
|
*/
|
|||
|
|
@Log(title = "库存预警", businessType = BusinessType.INSERT)
|
|||
|
|
@PostMapping
|
|||
|
|
public AjaxResult add(@RequestBody InventoryAlert inventoryAlert) {
|
|||
|
|
return toAjax(inventoryAlertService.insertInventoryAlert(inventoryAlert));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 修改库存预警
|
|||
|
|
*/
|
|||
|
|
@Log(title = "库存预警", businessType = BusinessType.UPDATE)
|
|||
|
|
@PutMapping
|
|||
|
|
public AjaxResult edit(@RequestBody InventoryAlert inventoryAlert) {
|
|||
|
|
return toAjax(inventoryAlertService.updateInventoryAlert(inventoryAlert));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除库存预警
|
|||
|
|
*/
|
|||
|
|
@Log(title = "库存预警", businessType = BusinessType.DELETE)
|
|||
|
|
@DeleteMapping("/{ids}")
|
|||
|
|
public AjaxResult remove(@PathVariable Long[] ids) {
|
|||
|
|
return toAjax(inventoryAlertService.deleteInventoryAlertByIds(ids));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取当前触发的预警信息(供首页调用)
|
|||
|
|
*/
|
|||
|
|
@GetMapping("/triggered")
|
|||
|
|
public AjaxResult getTriggeredAlerts() {
|
|||
|
|
List<JSONObject> alerts = inventoryAlertService.getTriggeredAlerts();
|
|||
|
|
return success(alerts);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7. MyBatis映射:`InventoryAlertMapper.xml`
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<?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="cn.sourceplan.warehouse.mapper.InventoryAlertMapper">
|
|||
|
|
|
|||
|
|
<resultMap type="cn.sourceplan.warehouse.domain.InventoryAlert" id="InventoryAlertResult">
|
|||
|
|
<result property="id" column="id" />
|
|||
|
|
<result property="materialId" column="material_id" />
|
|||
|
|
<result property="materialNumber" column="material_number" />
|
|||
|
|
<result property="materialName" column="material_name" />
|
|||
|
|
<result property="specification" column="specification" />
|
|||
|
|
<result property="warehouseId" column="warehouse_id" />
|
|||
|
|
<result property="warehouseName" column="warehouse_name" />
|
|||
|
|
<result property="maxQuantity" column="max_quantity" />
|
|||
|
|
<result property="minQuantity" column="min_quantity" />
|
|||
|
|
<result property="status" column="status" />
|
|||
|
|
<result property="remark" column="remark" />
|
|||
|
|
<result property="createBy" column="create_by" />
|
|||
|
|
<result property="createTime" column="create_time" />
|
|||
|
|
<result property="updateBy" column="update_by" />
|
|||
|
|
<result property="updateTime" column="update_time" />
|
|||
|
|
</resultMap>
|
|||
|
|
|
|||
|
|
<sql id="selectInventoryAlertVo">
|
|||
|
|
SELECT id, material_id, material_number, material_name, specification,
|
|||
|
|
warehouse_id, warehouse_name, max_quantity, min_quantity, status,
|
|||
|
|
remark, create_by, create_time, update_by, update_time
|
|||
|
|
FROM wm_inventory_alert
|
|||
|
|
</sql>
|
|||
|
|
|
|||
|
|
<select id="selectInventoryAlertList" parameterType="InventoryAlert" resultMap="InventoryAlertResult">
|
|||
|
|
<include refid="selectInventoryAlertVo"/>
|
|||
|
|
<where>
|
|||
|
|
<if test="materialId != null">
|
|||
|
|
AND material_id = #{materialId}
|
|||
|
|
</if>
|
|||
|
|
<if test="materialName != null and materialName != ''">
|
|||
|
|
AND material_name LIKE CONCAT('%', #{materialName}, '%')
|
|||
|
|
</if>
|
|||
|
|
<if test="warehouseId != null">
|
|||
|
|
AND warehouse_id = #{warehouseId}
|
|||
|
|
</if>
|
|||
|
|
<if test="status != null and status != ''">
|
|||
|
|
AND status = #{status}
|
|||
|
|
</if>
|
|||
|
|
</where>
|
|||
|
|
ORDER BY create_time DESC
|
|||
|
|
</select>
|
|||
|
|
|
|||
|
|
<select id="selectInventoryAlertById" parameterType="Long" resultMap="InventoryAlertResult">
|
|||
|
|
<include refid="selectInventoryAlertVo"/>
|
|||
|
|
WHERE id = #{id}
|
|||
|
|
</select>
|
|||
|
|
|
|||
|
|
<!-- 查询所有启用的预警配置及其当前库存 -->
|
|||
|
|
<select id="selectAlertWithCurrentInventory" resultType="cn.sourceplan.warehouse.domain.InventoryAlert">
|
|||
|
|
SELECT
|
|||
|
|
a.id,
|
|||
|
|
a.material_id,
|
|||
|
|
a.material_number,
|
|||
|
|
a.material_name,
|
|||
|
|
a.specification,
|
|||
|
|
a.warehouse_id,
|
|||
|
|
a.warehouse_name,
|
|||
|
|
a.max_quantity,
|
|||
|
|
a.min_quantity,
|
|||
|
|
a.status,
|
|||
|
|
a.remark,
|
|||
|
|
IFNULL(
|
|||
|
|
(SELECT SUM(IFNULL(intoG.into_quantity, 0) - IFNULL(outGG.out_quantity, 0))
|
|||
|
|
FROM (
|
|||
|
|
-- 所有入库数量汇总
|
|||
|
|
SELECT
|
|||
|
|
SUM(IFNULL(`into`.quantity, 0.0)) AS into_quantity,
|
|||
|
|
`into`.warehouse_id,
|
|||
|
|
`into`.material_id
|
|||
|
|
FROM
|
|||
|
|
(
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_procure_into_entry b LEFT JOIN wm_procure_into a ON a.id = b.main_id
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_sale_return_entry b LEFT JOIN wm_sale_return a ON a.id = b.main_id
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_manufacture_into_entry b LEFT JOIN wm_manufacture_into a ON a.id = b.main_id
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT warehouse_id, material_id, take_quantity AS quantity FROM wm_take_inventory_entry b LEFT JOIN wm_take_inventory a ON a.id = b.main_id WHERE a.initialize_flag = 'Y'
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_profit_loss_record_entry b LEFT JOIN wm_profit_loss_record a ON a.id = b.main_id WHERE a.type = 'A'
|
|||
|
|
) `into`
|
|||
|
|
GROUP BY `into`.warehouse_id, `into`.material_id
|
|||
|
|
) `intoG`
|
|||
|
|
LEFT JOIN (
|
|||
|
|
-- 所有出库数量汇总
|
|||
|
|
SELECT
|
|||
|
|
SUM(IFNULL(`out`.quantity, 0.0)) AS out_quantity,
|
|||
|
|
`out`.warehouse_id,
|
|||
|
|
`out`.material_id
|
|||
|
|
FROM
|
|||
|
|
(
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_production_pick_entry b LEFT JOIN wm_production_pick a ON a.id = b.main_id WHERE a.`status` ='A'
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_sale_out_entry b LEFT JOIN wm_sale_out a ON a.id = b.main_id
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_profit_loss_record_entry b LEFT JOIN wm_profit_loss_record a ON a.id = b.main_id WHERE a.type = 'B'
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT warehouse_id, material_id, quantity FROM wm_other_out_entry b LEFT JOIN wm_other_out a ON a.id = b.main_id
|
|||
|
|
) `out`
|
|||
|
|
GROUP BY `out`.material_id, `out`.warehouse_id
|
|||
|
|
) `outGG` ON `outGG`.material_id = `intoG`.material_id AND `outGG`.warehouse_id = `intoG`.warehouse_id
|
|||
|
|
WHERE `intoG`.material_id = a.material_id
|
|||
|
|
AND (a.warehouse_id IS NULL OR `intoG`.warehouse_id = a.warehouse_id)
|
|||
|
|
), 0
|
|||
|
|
) AS current_quantity
|
|||
|
|
FROM wm_inventory_alert a
|
|||
|
|
-- 关联菜单表,检查仓库管理菜单状态(menu_id=2082为仓库管理)
|
|||
|
|
INNER JOIN sys_menu menu ON menu.menu_id = 2082
|
|||
|
|
WHERE a.status = '0'
|
|||
|
|
AND menu.visible = '0' -- 菜单可见(0=显示,1=隐藏)
|
|||
|
|
AND menu.status = '0' -- 菜单启用(0=正常,1=停用)
|
|||
|
|
HAVING current_quantity > 0 -- 只查询库存数量大于0的记录(已通过子查询计算实时库存)
|
|||
|
|
</select>
|
|||
|
|
|
|||
|
|
<insert id="insertInventoryAlert" parameterType="InventoryAlert" useGeneratedKeys="true" keyProperty="id">
|
|||
|
|
INSERT INTO wm_inventory_alert
|
|||
|
|
<trim prefix="(" suffix=")" suffixOverrides=",">
|
|||
|
|
<if test="materialId != null">material_id,</if>
|
|||
|
|
<if test="materialNumber != null">material_number,</if>
|
|||
|
|
<if test="materialName != null">material_name,</if>
|
|||
|
|
<if test="specification != null">specification,</if>
|
|||
|
|
<if test="warehouseId != null">warehouse_id,</if>
|
|||
|
|
<if test="warehouseName != null">warehouse_name,</if>
|
|||
|
|
<if test="maxQuantity != null">max_quantity,</if>
|
|||
|
|
<if test="minQuantity != null">min_quantity,</if>
|
|||
|
|
<if test="status != null">status,</if>
|
|||
|
|
<if test="remark != null">remark,</if>
|
|||
|
|
<if test="createBy != null">create_by,</if>
|
|||
|
|
<if test="createTime != null">create_time,</if>
|
|||
|
|
</trim>
|
|||
|
|
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
|||
|
|
<if test="materialId != null">#{materialId},</if>
|
|||
|
|
<if test="materialNumber != null">#{materialNumber},</if>
|
|||
|
|
<if test="materialName != null">#{materialName},</if>
|
|||
|
|
<if test="specification != null">#{specification},</if>
|
|||
|
|
<if test="warehouseId != null">#{warehouseId},</if>
|
|||
|
|
<if test="warehouseName != null">#{warehouseName},</if>
|
|||
|
|
<if test="maxQuantity != null">#{maxQuantity},</if>
|
|||
|
|
<if test="minQuantity != null">#{minQuantity},</if>
|
|||
|
|
<if test="status != null">#{status},</if>
|
|||
|
|
<if test="remark != null">#{remark},</if>
|
|||
|
|
<if test="createBy != null">#{createBy},</if>
|
|||
|
|
<if test="createTime != null">#{createTime},</if>
|
|||
|
|
</trim>
|
|||
|
|
</insert>
|
|||
|
|
|
|||
|
|
<update id="updateInventoryAlert" parameterType="InventoryAlert">
|
|||
|
|
UPDATE wm_inventory_alert
|
|||
|
|
<trim prefix="SET" suffixOverrides=",">
|
|||
|
|
<if test="materialId != null">material_id = #{materialId},</if>
|
|||
|
|
<if test="materialNumber != null">material_number = #{materialNumber},</if>
|
|||
|
|
<if test="materialName != null">material_name = #{materialName},</if>
|
|||
|
|
<if test="specification != null">specification = #{specification},</if>
|
|||
|
|
<if test="warehouseId != null">warehouse_id = #{warehouseId},</if>
|
|||
|
|
<if test="warehouseName != null">warehouse_name = #{warehouseName},</if>
|
|||
|
|
<if test="maxQuantity != null">max_quantity = #{maxQuantity},</if>
|
|||
|
|
<if test="minQuantity != null">min_quantity = #{minQuantity},</if>
|
|||
|
|
<if test="status != null">status = #{status},</if>
|
|||
|
|
<if test="remark != null">remark = #{remark},</if>
|
|||
|
|
<if test="updateBy != null">update_by = #{updateBy},</if>
|
|||
|
|
<if test="updateTime != null">update_time = #{updateTime},</if>
|
|||
|
|
</trim>
|
|||
|
|
WHERE id = #{id}
|
|||
|
|
</update>
|
|||
|
|
|
|||
|
|
<delete id="deleteInventoryAlertById" parameterType="Long">
|
|||
|
|
DELETE FROM wm_inventory_alert WHERE id = #{id}
|
|||
|
|
</delete>
|
|||
|
|
|
|||
|
|
<delete id="deleteInventoryAlertByIds" parameterType="Long">
|
|||
|
|
DELETE FROM wm_inventory_alert WHERE id IN
|
|||
|
|
<foreach item="id" collection="array" open="(" separator="," close=")">
|
|||
|
|
#{id}
|
|||
|
|
</foreach>
|
|||
|
|
</delete>
|
|||
|
|
|
|||
|
|
</mapper>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎨 前端实现
|
|||
|
|
|
|||
|
|
### 1. 目录结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
mes-ui/src/
|
|||
|
|
├── api/mes/warehouse/
|
|||
|
|
│ └── inventoryAlert.js # API接口
|
|||
|
|
├── views/mes/warehouse/inventoryAlert/
|
|||
|
|
│ ├── index.vue # 列表页面
|
|||
|
|
│ └── form.vue # 新增/编辑表单(可选)
|
|||
|
|
└── views/index.vue # 首页(需修改)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. API接口:`inventoryAlert.js`
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import request from '@/utils/request'
|
|||
|
|
|
|||
|
|
// 查询库存预警列表
|
|||
|
|
export function listInventoryAlert(query) {
|
|||
|
|
return request({
|
|||
|
|
url: '/warehouse/inventoryAlert/list',
|
|||
|
|
method: 'get',
|
|||
|
|
params: query
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询库存预警详细
|
|||
|
|
export function getInventoryAlert(id) {
|
|||
|
|
return request({
|
|||
|
|
url: '/warehouse/inventoryAlert/' + id,
|
|||
|
|
method: 'get'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 新增库存预警
|
|||
|
|
export function addInventoryAlert(data) {
|
|||
|
|
return request({
|
|||
|
|
url: '/warehouse/inventoryAlert',
|
|||
|
|
method: 'post',
|
|||
|
|
data: data
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 修改库存预警
|
|||
|
|
export function updateInventoryAlert(data) {
|
|||
|
|
return request({
|
|||
|
|
url: '/warehouse/inventoryAlert',
|
|||
|
|
method: 'put',
|
|||
|
|
data: data
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 删除库存预警
|
|||
|
|
export function delInventoryAlert(id) {
|
|||
|
|
return request({
|
|||
|
|
url: '/warehouse/inventoryAlert/' + id,
|
|||
|
|
method: 'delete'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取当前触发的预警信息
|
|||
|
|
export function getTriggeredAlerts() {
|
|||
|
|
return request({
|
|||
|
|
url: '/warehouse/inventoryAlert/triggered',
|
|||
|
|
method: 'get'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 列表页面:`index.vue`
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<div class="app-container">
|
|||
|
|
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
|||
|
|
<el-form-item label="物料名称" prop="materialName">
|
|||
|
|
<el-input
|
|||
|
|
v-model="queryParams.materialName"
|
|||
|
|
placeholder="请输入物料名称"
|
|||
|
|
clearable
|
|||
|
|
@keyup.enter.native="handleQuery"
|
|||
|
|
/>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="仓库" prop="warehouseId">
|
|||
|
|
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable filterable>
|
|||
|
|
<el-option
|
|||
|
|
v-for="item in warehouseList"
|
|||
|
|
:key="item.id"
|
|||
|
|
:label="item.name"
|
|||
|
|
:value="item.id"
|
|||
|
|
/>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="状态" prop="status">
|
|||
|
|
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
|||
|
|
<el-option label="启用" value="0" />
|
|||
|
|
<el-option label="停用" value="1" />
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item>
|
|||
|
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
|||
|
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
|
|||
|
|
<el-row :gutter="10" class="mb8">
|
|||
|
|
<el-col :span="1.5">
|
|||
|
|
<el-button
|
|||
|
|
type="primary"
|
|||
|
|
plain
|
|||
|
|
icon="el-icon-plus"
|
|||
|
|
size="mini"
|
|||
|
|
@click="handleAdd"
|
|||
|
|
v-hasPermi="['warehouse:inventoryAlert:add']"
|
|||
|
|
>新增</el-button>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="1.5">
|
|||
|
|
<el-button
|
|||
|
|
type="success"
|
|||
|
|
plain
|
|||
|
|
icon="el-icon-edit"
|
|||
|
|
size="mini"
|
|||
|
|
:disabled="single"
|
|||
|
|
@click="handleUpdate"
|
|||
|
|
v-hasPermi="['warehouse:inventoryAlert:edit']"
|
|||
|
|
>修改</el-button>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="1.5">
|
|||
|
|
<el-button
|
|||
|
|
type="danger"
|
|||
|
|
plain
|
|||
|
|
icon="el-icon-delete"
|
|||
|
|
size="mini"
|
|||
|
|
:disabled="multiple"
|
|||
|
|
@click="handleDelete"
|
|||
|
|
v-hasPermi="['warehouse:inventoryAlert:remove']"
|
|||
|
|
>删除</el-button>
|
|||
|
|
</el-col>
|
|||
|
|
<el-col :span="1.5">
|
|||
|
|
<el-button
|
|||
|
|
type="warning"
|
|||
|
|
plain
|
|||
|
|
icon="el-icon-download"
|
|||
|
|
size="mini"
|
|||
|
|
@click="handleExport"
|
|||
|
|
v-hasPermi="['warehouse:inventoryAlert:export']"
|
|||
|
|
>导出</el-button>
|
|||
|
|
</el-col>
|
|||
|
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
|||
|
|
</el-row>
|
|||
|
|
|
|||
|
|
<el-table v-loading="loading" :data="alertList" @selection-change="handleSelectionChange">
|
|||
|
|
<el-table-column type="selection" width="55" align="center" />
|
|||
|
|
<el-table-column label="物料编号" align="center" prop="materialNumber" />
|
|||
|
|
<el-table-column label="物料名称" align="center" prop="materialName" />
|
|||
|
|
<el-table-column label="规格型号" align="center" prop="specification" />
|
|||
|
|
<el-table-column label="仓库" align="center" prop="warehouseName">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
<span>{{ scope.row.warehouseName || '所有仓库' }}</span>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="库存上限" align="center" prop="maxQuantity" />
|
|||
|
|
<el-table-column label="库存下限" align="center" prop="minQuantity" />
|
|||
|
|
<el-table-column label="状态" align="center" prop="status">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
<el-tag :type="scope.row.status === '0' ? 'success' : 'info'">
|
|||
|
|
{{ scope.row.status === '0' ? '启用' : '停用' }}
|
|||
|
|
</el-tag>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
|||
|
|
<template slot-scope="scope">
|
|||
|
|
<el-button
|
|||
|
|
size="mini"
|
|||
|
|
type="text"
|
|||
|
|
icon="el-icon-edit"
|
|||
|
|
@click="handleUpdate(scope.row)"
|
|||
|
|
v-hasPermi="['warehouse:inventoryAlert:edit']"
|
|||
|
|
>修改</el-button>
|
|||
|
|
<el-button
|
|||
|
|
size="mini"
|
|||
|
|
type="text"
|
|||
|
|
icon="el-icon-delete"
|
|||
|
|
@click="handleDelete(scope.row)"
|
|||
|
|
v-hasPermi="['warehouse:inventoryAlert:remove']"
|
|||
|
|
>删除</el-button>
|
|||
|
|
</template>
|
|||
|
|
</el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
|
|||
|
|
<pagination
|
|||
|
|
v-show="total>0"
|
|||
|
|
:total="total"
|
|||
|
|
:page.sync="queryParams.pageNum"
|
|||
|
|
:limit.sync="queryParams.pageSize"
|
|||
|
|
@pagination="getList"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- 添加或修改库存预警对话框 -->
|
|||
|
|
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
|||
|
|
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
|||
|
|
<el-form-item label="物料" prop="materialId">
|
|||
|
|
<el-select
|
|||
|
|
v-model="form.materialId"
|
|||
|
|
placeholder="请选择物料"
|
|||
|
|
filterable
|
|||
|
|
@change="handleMaterialChange"
|
|||
|
|
style="width: 100%"
|
|||
|
|
>
|
|||
|
|
<el-option
|
|||
|
|
v-for="item in materialList"
|
|||
|
|
:key="item.materialId"
|
|||
|
|
:label="item.materialName + ' (' + item.materialNumber + ')'"
|
|||
|
|
:value="item.materialId"
|
|||
|
|
/>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="规格型号">
|
|||
|
|
<el-input v-model="form.specification" disabled />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="仓库" prop="warehouseId">
|
|||
|
|
<el-select v-model="form.warehouseId" placeholder="请选择仓库(留空表示所有仓库)" clearable style="width: 100%">
|
|||
|
|
<el-option
|
|||
|
|
v-for="item in warehouseList"
|
|||
|
|
:key="item.id"
|
|||
|
|
:label="item.name"
|
|||
|
|
:value="item.id"
|
|||
|
|
/>
|
|||
|
|
</el-select>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="库存上限" prop="maxQuantity">
|
|||
|
|
<el-input-number v-model="form.maxQuantity" :min="0" :precision="2" controls-position="right" style="width: 100%" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="库存下限" prop="minQuantity">
|
|||
|
|
<el-input-number v-model="form.minQuantity" :min="0" :precision="2" controls-position="right" style="width: 100%" />
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="状态">
|
|||
|
|
<el-radio-group v-model="form.status">
|
|||
|
|
<el-radio label="0">启用</el-radio>
|
|||
|
|
<el-radio label="1">停用</el-radio>
|
|||
|
|
</el-radio-group>
|
|||
|
|
</el-form-item>
|
|||
|
|
<el-form-item label="备注">
|
|||
|
|
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
|||
|
|
</el-form-item>
|
|||
|
|
</el-form>
|
|||
|
|
<div slot="footer" class="dialog-footer">
|
|||
|
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
|||
|
|
<el-button @click="cancel">取 消</el-button>
|
|||
|
|
</div>
|
|||
|
|
</el-dialog>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { listInventoryAlert, getInventoryAlert, addInventoryAlert, updateInventoryAlert, delInventoryAlert } from "@/api/mes/warehouse/inventoryAlert";
|
|||
|
|
import { listWarehouse } from "@/api/mes/warehouse/warehouse";
|
|||
|
|
import { listMaterial } from "@/api/mes/masterdata/material";
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: "InventoryAlert",
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
loading: true,
|
|||
|
|
ids: [],
|
|||
|
|
single: true,
|
|||
|
|
multiple: true,
|
|||
|
|
showSearch: true,
|
|||
|
|
total: 0,
|
|||
|
|
alertList: [],
|
|||
|
|
warehouseList: [],
|
|||
|
|
materialList: [],
|
|||
|
|
title: "",
|
|||
|
|
open: false,
|
|||
|
|
queryParams: {
|
|||
|
|
pageNum: 1,
|
|||
|
|
pageSize: 10,
|
|||
|
|
materialName: null,
|
|||
|
|
warehouseId: null,
|
|||
|
|
status: null
|
|||
|
|
},
|
|||
|
|
form: {},
|
|||
|
|
rules: {
|
|||
|
|
materialId: [
|
|||
|
|
{ required: true, message: "物料不能为空", trigger: "change" }
|
|||
|
|
],
|
|||
|
|
minQuantity: [
|
|||
|
|
{ required: true, message: "库存下限不能为空", trigger: "blur" }
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
created() {
|
|||
|
|
this.getList();
|
|||
|
|
this.getWarehouseList();
|
|||
|
|
this.getMaterialList();
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
/** 查询库存预警列表 */
|
|||
|
|
getList() {
|
|||
|
|
this.loading = true;
|
|||
|
|
listInventoryAlert(this.queryParams).then(response => {
|
|||
|
|
this.alertList = response.rows;
|
|||
|
|
this.total = response.total;
|
|||
|
|
this.loading = false;
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
/** 获取仓库列表 */
|
|||
|
|
getWarehouseList() {
|
|||
|
|
listWarehouse({ pageSize: 99999 }).then(response => {
|
|||
|
|
this.warehouseList = response.rows;
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
/** 获取物料列表(从物料表,只获取启用状态的物料) */
|
|||
|
|
getMaterialList() {
|
|||
|
|
listMaterial({ pageSize: 99999, status: '0' }).then(response => {
|
|||
|
|
this.materialList = response.rows;
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
/** 物料选择变化 */
|
|||
|
|
handleMaterialChange(materialId) {
|
|||
|
|
const material = this.materialList.find(m => m.materialId === materialId);
|
|||
|
|
if (material) {
|
|||
|
|
this.form.materialNumber = material.materialNumber;
|
|||
|
|
this.form.materialName = material.materialName;
|
|||
|
|
this.form.specification = material.specification;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
cancel() {
|
|||
|
|
this.open = false;
|
|||
|
|
this.reset();
|
|||
|
|
},
|
|||
|
|
reset() {
|
|||
|
|
this.form = {
|
|||
|
|
id: null,
|
|||
|
|
materialId: null,
|
|||
|
|
materialNumber: null,
|
|||
|
|
materialName: null,
|
|||
|
|
specification: null,
|
|||
|
|
warehouseId: null,
|
|||
|
|
warehouseName: null,
|
|||
|
|
maxQuantity: null,
|
|||
|
|
minQuantity: null,
|
|||
|
|
status: "0",
|
|||
|
|
remark: null
|
|||
|
|
};
|
|||
|
|
this.resetForm("form");
|
|||
|
|
},
|
|||
|
|
handleQuery() {
|
|||
|
|
this.queryParams.pageNum = 1;
|
|||
|
|
this.getList();
|
|||
|
|
},
|
|||
|
|
resetQuery() {
|
|||
|
|
this.resetForm("queryForm");
|
|||
|
|
this.handleQuery();
|
|||
|
|
},
|
|||
|
|
handleSelectionChange(selection) {
|
|||
|
|
this.ids = selection.map(item => item.id);
|
|||
|
|
this.single = selection.length !== 1;
|
|||
|
|
this.multiple = !selection.length;
|
|||
|
|
},
|
|||
|
|
handleAdd() {
|
|||
|
|
this.reset();
|
|||
|
|
this.open = true;
|
|||
|
|
this.title = "添加库存预警";
|
|||
|
|
},
|
|||
|
|
handleUpdate(row) {
|
|||
|
|
this.reset();
|
|||
|
|
const id = row.id || this.ids[0];
|
|||
|
|
getInventoryAlert(id).then(response => {
|
|||
|
|
this.form = response.data;
|
|||
|
|
this.open = true;
|
|||
|
|
this.title = "修改库存预警";
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
submitForm() {
|
|||
|
|
this.$refs["form"].validate(valid => {
|
|||
|
|
if (valid) {
|
|||
|
|
// 设置仓库名称
|
|||
|
|
if (this.form.warehouseId) {
|
|||
|
|
const warehouse = this.warehouseList.find(w => w.id === this.form.warehouseId);
|
|||
|
|
if (warehouse) {
|
|||
|
|
this.form.warehouseName = warehouse.name;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (this.form.id != null) {
|
|||
|
|
updateInventoryAlert(this.form).then(response => {
|
|||
|
|
this.$modal.msgSuccess("修改成功");
|
|||
|
|
this.open = false;
|
|||
|
|
this.getList();
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
addInventoryAlert(this.form).then(response => {
|
|||
|
|
this.$modal.msgSuccess("新增成功");
|
|||
|
|
this.open = false;
|
|||
|
|
this.getList();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
handleDelete(row) {
|
|||
|
|
const ids = row.id || this.ids;
|
|||
|
|
this.$modal.confirm('是否确认删除库存预警编号为"' + ids + '"的数据项?').then(function() {
|
|||
|
|
return delInventoryAlert(ids);
|
|||
|
|
}).then(() => {
|
|||
|
|
this.getList();
|
|||
|
|
this.$modal.msgSuccess("删除成功");
|
|||
|
|
}).catch(() => {});
|
|||
|
|
},
|
|||
|
|
handleExport() {
|
|||
|
|
this.download('warehouse/inventoryAlert/export', {
|
|||
|
|
...this.queryParams
|
|||
|
|
}, `库存预警_${new Date().getTime()}.xlsx`)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔔 首页预警弹窗
|
|||
|
|
|
|||
|
|
### 修改首页:`mes-ui/src/views/index.vue`
|
|||
|
|
|
|||
|
|
在首页的 `mounted` 生命周期中添加预警检查逻辑,使用Notification组件在右上角显示:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<script>
|
|||
|
|
import { defermentFactor, outputTrend,qualificationRate,unqualifiedReason } from '@/api/mes/index'
|
|||
|
|
import { getTriggeredAlerts } from "@/api/mes/warehouse/inventoryAlert"
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
name: 'index',
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
queryParams: {
|
|||
|
|
dateQuery: []
|
|||
|
|
},
|
|||
|
|
// ... 现有数据
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
this.init()
|
|||
|
|
// 检查库存预警
|
|||
|
|
this.checkInventoryAlerts()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
/** 检查库存预警 */
|
|||
|
|
checkInventoryAlerts() {
|
|||
|
|
console.log('=== 开始检查库存预警 ===')
|
|||
|
|
getTriggeredAlerts().then(response => {
|
|||
|
|
console.log('预警API响应:', response)
|
|||
|
|
console.log('预警数据:', response.data)
|
|||
|
|
|
|||
|
|
if (response.data && response.data.length > 0) {
|
|||
|
|
console.log('触发预警,数量:', response.data.length)
|
|||
|
|
|
|||
|
|
// 使用Notification在右上角显示每条预警
|
|||
|
|
response.data.forEach((alert, index) => {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.$notify({
|
|||
|
|
title: '库存预警提醒',
|
|||
|
|
dangerouslyUseHTMLString: true,
|
|||
|
|
message: `
|
|||
|
|
<div style="line-height: 1.6;">
|
|||
|
|
${alert.message}
|
|||
|
|
<div style="margin-top: 10px; color: #909399; font-size: 12px; border-top: 1px solid #EBEEF5; padding-top: 8px;">
|
|||
|
|
*若数量上下限不正确,请在库存预警中更改上下限
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`,
|
|||
|
|
type: alert.type === 'max' ? 'warning' : 'error',
|
|||
|
|
duration: 5000, // 5秒后自动关闭
|
|||
|
|
position: 'top-right',
|
|||
|
|
offset: 80 + (index * 20), // 多条预警错开显示
|
|||
|
|
showClose: true
|
|||
|
|
})
|
|||
|
|
}, index * 300) // 多条预警依次弹出,间隔300ms
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
console.log('没有触发预警或数据为空')
|
|||
|
|
}
|
|||
|
|
}).catch(error => {
|
|||
|
|
console.error('获取库存预警失败:', error)
|
|||
|
|
console.error('错误详情:', error.response)
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// ... 现有其他方法
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 实施步骤
|
|||
|
|
|
|||
|
|
### 1. 数据库变更
|
|||
|
|
```bash
|
|||
|
|
# 执行SQL脚本
|
|||
|
|
mysql -u用户名 -p密码 数据库名 < .tasks/2025-10-17_兴万达改进(库存预警).sql
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 后端实现
|
|||
|
|
1. 创建实体类 `InventoryAlert.java`
|
|||
|
|
2. 创建Mapper接口 `InventoryAlertMapper.java`
|
|||
|
|
3. 创建XML映射文件 `InventoryAlertMapper.xml`
|
|||
|
|
4. 创建Service接口 `IInventoryAlertService.java`
|
|||
|
|
5. 创建Service实现 `InventoryAlertServiceImpl.java`
|
|||
|
|
6. 创建Controller `InventoryAlertController.java`
|
|||
|
|
|
|||
|
|
### 3. 前端实现
|
|||
|
|
1. 创建API文件 `inventoryAlert.js`
|
|||
|
|
2. 创建列表页面 `index.vue`
|
|||
|
|
3. 修改首页 `index.vue` 添加预警弹窗
|
|||
|
|
|
|||
|
|
### 4. 测试验证
|
|||
|
|
1. 重启后端服务
|
|||
|
|
2. 清除浏览器缓存
|
|||
|
|
3. 登录系统,检查菜单是否显示
|
|||
|
|
4. 测试增删改查功能
|
|||
|
|
5. 测试首页预警弹窗
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试用例
|
|||
|
|
|
|||
|
|
### 1. 功能测试
|
|||
|
|
|
|||
|
|
#### 测试用例1:新增库存预警
|
|||
|
|
- **操作步骤**:
|
|||
|
|
1. 进入库存预警页面
|
|||
|
|
2. 点击"新增"按钮
|
|||
|
|
3. 选择物料、仓库
|
|||
|
|
4. 设置上限1000,下限100
|
|||
|
|
5. 点击"确定"
|
|||
|
|
|
|||
|
|
- **预期结果**:
|
|||
|
|
- 成功新增一条预警配置
|
|||
|
|
- 列表中显示新增的记录
|
|||
|
|
|
|||
|
|
#### 测试用例2:修改库存预警
|
|||
|
|
- **操作步骤**:
|
|||
|
|
1. 选择一条预警记录
|
|||
|
|
2. 点击"修改"按钮
|
|||
|
|
3. 修改上限为2000
|
|||
|
|
4. 点击"确定"
|
|||
|
|
|
|||
|
|
- **预期结果**:
|
|||
|
|
- 成功修改预警配置
|
|||
|
|
- 列表中显示更新后的数据
|
|||
|
|
|
|||
|
|
#### 测试用例3:删除库存预警
|
|||
|
|
- **操作步骤**:
|
|||
|
|
1. 选择一条预警记录
|
|||
|
|
2. 点击"删除"按钮
|
|||
|
|
3. 确认删除
|
|||
|
|
|
|||
|
|
- **预期结果**:
|
|||
|
|
- 成功删除预警配置
|
|||
|
|
- 列表中不再显示该记录
|
|||
|
|
|
|||
|
|
#### 测试用例4:触发高于上限预警
|
|||
|
|
- **操作步骤**:
|
|||
|
|
1. 设置物料A的上限为100
|
|||
|
|
2. 确保物料A的当前库存为150
|
|||
|
|
3. 退出登录,重新登录
|
|||
|
|
|
|||
|
|
- **预期结果**:
|
|||
|
|
- 首页右侧弹出预警弹窗
|
|||
|
|
- 显示"物料【A】库存数量高于上限"的提示
|
|||
|
|
- 3秒后自动关闭
|
|||
|
|
|
|||
|
|
#### 测试用例5:触发低于下限预警
|
|||
|
|
- **操作步骤**:
|
|||
|
|
1. 设置物料B的下限为100
|
|||
|
|
2. 确保物料B的当前库存为50
|
|||
|
|
3. 退出登录,重新登录
|
|||
|
|
|
|||
|
|
- **预期结果**:
|
|||
|
|
- 首页右侧弹出预警弹窗
|
|||
|
|
- 显示"物料【B】库存数量低于下限"的提示
|
|||
|
|
- 3秒后自动关闭
|
|||
|
|
|
|||
|
|
### 2. 权限测试
|
|||
|
|
|
|||
|
|
#### 测试用例6:权限控制
|
|||
|
|
- **操作步骤**:
|
|||
|
|
1. 创建测试角色,仅赋予查询权限
|
|||
|
|
2. 使用该角色登录
|
|||
|
|
3. 尝试新增、修改、删除操作
|
|||
|
|
|
|||
|
|
- **预期结果**:
|
|||
|
|
- 只能看到列表
|
|||
|
|
- 新增、修改、删除按钮不显示
|
|||
|
|
|
|||
|
|
### 3. 性能测试
|
|||
|
|
|
|||
|
|
#### 测试用例7:大数据量测试
|
|||
|
|
- **操作步骤**:
|
|||
|
|
1. 插入1000条预警配置
|
|||
|
|
2. 访问列表页面
|
|||
|
|
3. 执行查询、分页操作
|
|||
|
|
|
|||
|
|
- **预期结果**:
|
|||
|
|
- 页面响应时间 < 2秒
|
|||
|
|
- 分页正常工作
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📌 注意事项
|
|||
|
|
|
|||
|
|
1. **唯一性约束**:同一物料在同一仓库只能有一条预警配置
|
|||
|
|
2. **warehouse_id为NULL**:表示对该物料的所有仓库进行预警监控
|
|||
|
|
3. **预警触发条件**(需同时满足):
|
|||
|
|
- 预警配置状态为启用(status='0')
|
|||
|
|
- "仓库管理"菜单可见且启用(menu_id=2082, visible='0', status='0')
|
|||
|
|
- 当前库存数量 > 0(通过子查询计算实时库存,自动过滤掉从未入过库的物料)
|
|||
|
|
- 库存数量超过上限或低于下限
|
|||
|
|
4. **预警触发时机**:登录首页时检查,也可以考虑定时任务推送
|
|||
|
|
5. **物料选择来源**:从物料主数据表(md_material)读取,只显示启用状态(status='0')的物料
|
|||
|
|
6. **性能优化**:
|
|||
|
|
- 即时库存查询使用复杂子查询,建议添加索引优化
|
|||
|
|
- 预警检查可以异步执行,避免阻塞首页加载
|
|||
|
|
- 预警结果可以考虑添加Redis缓存(5分钟过期)
|
|||
|
|
7. **扩展性**:
|
|||
|
|
- 可以考虑添加邮件、短信通知功能
|
|||
|
|
- 可以添加预警历史记录表
|
|||
|
|
- 可以添加预警统计分析功能
|
|||
|
|
- 可以添加预警级别(紧急/警告/提示)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 效果预览
|
|||
|
|
|
|||
|
|
### 库存预警列表页面
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ 库存预警 [新增][修改][删除][导出] │
|
|||
|
|
├─────────────────────────────────────────────────────────────┤
|
|||
|
|
│ 物料编号 │ 物料名称 │ 规格型号 │ 仓库 │ 上限 │ 下限 │ 状态 │ 操作 │
|
|||
|
|
├─────────┼─────────┼─────────┼──────┼──────┼──────┼──────┼──────┤
|
|||
|
|
│ MAT001 │ 钢板 │ 10mm │ 原材料│ 1000 │ 100 │ 启用 │ 修改删│
|
|||
|
|
│ MAT002 │ 螺丝 │ M8 │ 所有 │ 5000 │ 500 │ 启用 │ 修改删│
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 首页预警通知(右上角Notification)
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────┐
|
|||
|
|
│ 库存预警提醒 [×] │
|
|||
|
|
├──────────────────────────────────┤
|
|||
|
|
│ ⚠ 【库存预警】物料【钢板】在仓库 │
|
|||
|
|
│ 【原材料】中的库存数量高于上限 │
|
|||
|
|
│ (上限:1000件),请暂停采购 │
|
|||
|
|
│ 或调整库存。 │
|
|||
|
|
│ │
|
|||
|
|
│ *若数量上下限不正确,请在库存预警│
|
|||
|
|
│ 中更改上下限 │
|
|||
|
|
└──────────────────────────────────┘
|
|||
|
|
|
|||
|
|
┌──────────────────────────────────┐
|
|||
|
|
│ 库存预警提醒 [×] │
|
|||
|
|
├──────────────────────────────────┤
|
|||
|
|
│ ❌ 【库存预警】物料【螺丝】在仓库│
|
|||
|
|
│ 【所有仓库】中的库存数量低于 │
|
|||
|
|
│ 下限(下限:500件),请尽快 │
|
|||
|
|
│ 采购补货。 │
|
|||
|
|
│ │
|
|||
|
|
│ *若数量上下限不正确,请在库存预警│
|
|||
|
|
│ 中更改上下限 │
|
|||
|
|
└──────────────────────────────────┘
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
- 显示位置:页面右上角
|
|||
|
|
- 显示时长:5秒后自动消失
|
|||
|
|
- 多条预警:依次错开弹出(间隔300ms)
|
|||
|
|
- 可手动关闭:点击右上角×号
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ 完成标志
|
|||
|
|
|
|||
|
|
- [x] SQL脚本编写完成
|
|||
|
|
- [x] 数据库表创建完成
|
|||
|
|
- [x] 菜单和权限配置完成
|
|||
|
|
- [x] 后端实体类、Mapper、Service、Controller实现完成
|
|||
|
|
- [x] 前端API接口实现完成
|
|||
|
|
- [x] 前端列表页面实现完成
|
|||
|
|
- [x] 首页预警Notification实现完成
|
|||
|
|
- [x] 预警触发条件优化(菜单状态、库存存在性、库存>0)
|
|||
|
|
- [x] 物料选择改为从md_material读取
|
|||
|
|
- [x] 功能测试完成
|
|||
|
|
- [ ] 上线部署(待部署)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔄 更新记录
|
|||
|
|
|
|||
|
|
### v1.3 - 2025-10-17
|
|||
|
|
- ✅ 移除对 `wm_real_time_inventory` 表的依赖(该表在部分数据库中不存在)
|
|||
|
|
- ✅ 通过 `HAVING current_quantity > 0` 自动过滤没有库存的物料
|
|||
|
|
- ✅ 简化SQL查询逻辑,提高兼容性
|
|||
|
|
|
|||
|
|
### v1.2 - 2025-10-17
|
|||
|
|
- ✅ 首页预警弹窗改为右上角Notification通知
|
|||
|
|
- ✅ 显示时长从3秒改为5秒
|
|||
|
|
- ✅ 多条预警依次错开弹出(间隔300ms)
|
|||
|
|
- ✅ 增加预警触发条件:
|
|||
|
|
- 关联sys_menu检查仓库管理菜单状态
|
|||
|
|
- 只查询库存数量>0的记录
|
|||
|
|
- ✅ 物料选择从md_material读取(只显示启用状态)
|
|||
|
|
|
|||
|
|
### v1.1 - 2025-10-17
|
|||
|
|
- 完成后端所有代码实现
|
|||
|
|
- 完成前端所有代码实现
|
|||
|
|
- 完成基础功能测试
|
|||
|
|
|
|||
|
|
### v1.0 - 2025-10-17
|
|||
|
|
- 需求分析和设计完成
|
|||
|
|
- SQL脚本编写完成
|
|||
|
|
- 代码框架设计完成
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**文档版本**:v1.3
|
|||
|
|
**编写日期**:2025-10-17
|
|||
|
|
**最后更新**:2025-10-17
|
|||
|
|
|