feat: 办公用品(日常用品),领用归还。领用归还记录 完成
ps:如果pro使用直接引入该commit
已添加3个文件
已修改3个文件
1429 ■■■■■ 文件已修改
doc/产品领用归还模块-前端对接文档.md 694 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/productBorrow.js 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInventory.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/OfficeRecord.vue 631 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/²úÆ·ÁìÓù黹ģ¿é-ǰ¶Ë¶Ô½ÓÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,694 @@
# äº§å“é¢†ç”¨å½’还模块 - å‰ç«¯å¯¹æŽ¥æ–‡æ¡£
## ä¸€ã€æ¨¡å—概述
产品领用归还模块用于管理产品的领用和归还流程:
- **领用**:新增领用时自动完成出库操作,自动审批通过,取消审批流程
- **归还**:新增归还时自动完成入库操作,自动审批通过
**注意**:所有接口统一使用 POST æ–¹å¼
---
## äºŒã€API æŽ¥å£è¯´æ˜Ž
### 1. äº§å“é¢†ç”¨æŽ¥å£
#### 1.1 åˆ†é¡µæŸ¥è¯¢é¢†ç”¨è®°å½•
**请求**
```
POST /productBorrow/listPage
Content-Type: application/json
```
**请求体**
```json
{
  "current": 1,                   // å½“前页码,默认1
  "size": 10,                     // æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10
  "borrowNo": "LY202605260001",   // é€‰å¡«ï¼Œé¢†ç”¨å•号,模糊查询
  "productModelId": 100,          // é€‰å¡«ï¼Œäº§å“è§„æ ¼ID
  "borrowerId": 1,                // é€‰å¡«ï¼Œé¢†ç”¨äººID
  "borrowerName": "张三",          // é€‰å¡«ï¼Œé¢†ç”¨äººå§“名,模糊查询
  "approvalStatus": 0,            // é€‰å¡«ï¼Œå®¡æ‰¹çŠ¶æ€ï¼š0待审批/1已通过/2已驳回
  "status": 0                     // é€‰å¡«ï¼Œå½’还状态:0未归还/1部分归还/2已全部归还
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1,
        "borrowNo": "LY202605260001",
        "productModelId": 100,
        "batchNo": "20260526-ABC001-001",
        "borrowQuantity": 10.0000,
        "returnedQuantity": 5.0000,
        "borrowerId": 1,
        "borrowerName": "张三",
        "borrowTime": "2026-05-26 10:00:00",
        "expectedReturnTime": "2026-06-26 10:00:00",
        "approvalStatus": 1,
        "approvalStatusName": "已通过",
        "status": 1,
        "statusName": "部分归还",
        "remark": "项目使用",
        "productName": "螺丝刀",
        "model": "M6",
        "productCode": "LSD-M6",
        "unit": "把",
        "remainingQuantity": 5.0000,
        "createTime": "2026-05-26 09:00:00"
      }
    ],
    "total": 100,
    "size": 10,
    "current": 1,
    "pages": 10
  }
}
```
#### 1.2 æŸ¥è¯¢é¢†ç”¨è®°å½•详情
**请求**
```
POST /productBorrow/getDetail
Content-Type: application/json
```
**请求体**
```json
{
  "id": 1    // å¿…填,领用记录ID
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "id": 1,
    "borrowNo": "LY202605260001",
    "productModelId": 100,
    "batchNo": "20260526-ABC001-001",
    "borrowQuantity": 10.0000,
    "returnedQuantity": 5.0000,
    "remainingQuantity": 5.0000,
    "borrowerId": 1,
    "borrowerName": "张三",
    "borrowTime": "2026-05-26 10:00:00",
    "expectedReturnTime": "2026-06-26 10:00:00",
    "approvalStatus": 1,
    "approvalStatusName": "已通过",
    "status": 1,
    "statusName": "部分归还",
    "remark": "项目使用",
    "productName": "螺丝刀",
    "model": "M6",
    "productCode": "LSD-M6",
    "unit": "把"
  }
}
```
#### 1.3 æ–°å¢žé¢†ç”¨è®°å½•
**请求**
```
POST /productBorrow/add
Content-Type: application/json
```
**请求体**
```json
{
  "productModelId": 100,          // å¿…填,产品规格ID
  "batchNo": "20260526-ABC001-001", // é€‰å¡«ï¼Œæ‰¹å·ï¼Œä¸å¡«åˆ™ä½¿ç”¨é»˜è®¤æ‰¹å·
  "borrowQuantity": 10.0000,      // å¿…填,领用数量
  "borrowerId": 1,                // å¿…填,领用人ID(系统用户ID)
  "borrowerName": "张三",          // å¿…填,领用人姓名
  "expectedReturnTime": "2026-06-26 10:00:00", // é€‰å¡«ï¼Œé¢„计归还时间
  "remark": "项目使用"             // é€‰å¡«ï¼Œå¤‡æ³¨
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
**自动执行的操作**:
- è‡ªåŠ¨ç”Ÿæˆé¢†ç”¨å•å·ï¼ˆæ ¼å¼ï¼šLY + å¹´æœˆæ—¥ + 4位序号)
- è‡ªåŠ¨åˆ›å»ºå‡ºåº“è®°å½•
- è‡ªåŠ¨å®¡æ ¸é€šè¿‡
- è‡ªåŠ¨æ‰£å‡åº“å­˜
- çŠ¶æ€ç›´æŽ¥è®¾ä¸º"已通过"
#### 1.4 ä¿®æ”¹é¢†ç”¨è®°å½•
**请求**
```
POST /productBorrow/update
Content-Type: application/json
```
**请求体**
```json
{
  "id": 1,                        // å¿…填,领用记录ID
  "productModelId": 100,
  "batchNo": "20260526-ABC001-001",
  "borrowQuantity": 15.0000,
  "borrowerId": 1,
  "borrowerName": "张三",
  "expectedReturnTime": "2026-06-26 10:00:00",
  "remark": "项目使用"
}
```
**注意**:只有待审批状态(approvalStatus=0)的记录才能修改
#### 1.5 åˆ é™¤é¢†ç”¨è®°å½•
**请求**
```
POST /productBorrow/delete
Content-Type: application/json
```
**请求体**
```json
[1, 2, 3]  // è¦åˆ é™¤çš„记录ID数组
```
**注意**:只有待审批状态的记录才能删除
#### 1.6 æ‰¹é‡å®¡æ‰¹é¢†ç”¨è®°å½•
**请求**
```
POST /productBorrow/approve
Content-Type: application/json
```
**请求体**
```json
{
  "ids": [1, 2, 3],           // å¿…填,要审批的记录ID数组
  "approvalStatusParam": 1    // å¿…填,审批状态:1通过/2驳回
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
---
### 2. äº§å“å½’还接口
#### 2.1 åˆ†é¡µæŸ¥è¯¢å½’还记录
**请求**
```
POST /productBorrowReturn/listPage
Content-Type: application/json
```
**请求体**
```json
{
  "current": 1,                   // å½“前页码
  "size": 10,                     // æ¯é¡µæ¡æ•°
  "borrowId": 1,                  // é€‰å¡«ï¼Œé¢†ç”¨è®°å½•ID
  "productModelId": 100,          // é€‰å¡«ï¼Œäº§å“è§„æ ¼ID
  "returnerId": 2,                // é€‰å¡«ï¼Œå½’还人ID
  "returnerName": "李四"          // é€‰å¡«ï¼Œå½’还人姓名
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1,
        "borrowId": 1,
        "borrowNo": "LY202605260001",
        "productModelId": 100,
        "batchNo": "20260526-ABC001-001",
        "returnQuantity": 5.0000,
        "returnerId": 2,
        "returnerName": "李四",
        "returnTime": "2026-05-28 14:00:00",
        "remark": "项目结束归还",
        "productName": "螺丝刀",
        "model": "M6",
        "productCode": "LSD-M6",
        "unit": "把",
        "borrowQuantity": 10.0000,
        "borrowerName": "张三"
      }
    ],
    "total": 50,
    "size": 10,
    "current": 1,
    "pages": 5
  }
}
```
#### 2.2 æŸ¥è¯¢æŸä¸ªé¢†ç”¨è®°å½•的归还记录
**请求**
```
POST /productBorrowReturn/listByBorrowId
Content-Type: application/json
```
**请求体**
```json
{
  "current": 1,       // å½“前页码
  "size": 10,         // æ¯é¡µæ¡æ•°
  "borrowId": 1       // å¿…填,领用记录ID
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [...],
    "total": 5,
    "size": 10,
    "current": 1,
    "pages": 1
  }
}
```
#### 2.3 æ–°å¢žå½’还记录
**请求**
```
POST /productBorrowReturn/add
Content-Type: application/json
```
**请求体**
```json
{
  "borrowId": 1,                 // å¿…填,领用记录ID
  "returnQuantity": 5.0000,      // å¿…填,归还数量
  "returnerId": 2,               // å¿…填,归还人ID(系统用户ID)
  "returnerName": "李四",         // å¿…填,归还人姓名
  "remark": "项目结束归还"         // é€‰å¡«ï¼Œå¤‡æ³¨
}
```
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功"
}
```
**自动执行的操作**:
- è‡ªåŠ¨åˆ›å»ºå…¥åº“è®°å½•
- è‡ªåŠ¨å®¡æ ¸é€šè¿‡
- è‡ªåŠ¨å¢žåŠ åº“å­˜
- è‡ªåŠ¨æ›´æ–°é¢†ç”¨è®°å½•çš„å·²å½’è¿˜æ•°é‡å’ŒçŠ¶æ€
**错误情况**:
```json
{
  "code": 500,
  "msg": "归还数量不能大于剩余可归还数量:5.0000"
}
```
---
### 3. åº“存和领用量查询接口
#### 3.1 åˆ†é¡µæŸ¥è¯¢äº§å“åº“存和领用量
**接口说明**:
- ä»¥"产品规格 + æ‰¹å·"为一条记录
- æ˜¾ç¤ºæ¯ä¸ªæ‰¹å·çš„库存数量和被领用量
- ç”¨äºŽé¢†ç”¨æ—¶é€‰æ‹©äº§å“å’Œæ‰¹å·ï¼ŒæŸ¥çœ‹å¯é¢†ç”¨æ•°é‡
**请求**
```
POST /stockInventory/pageStockAndBorrow
Content-Type: application/json
```
**请求体**
```json
{
  "current": 1,                    // å½“前页码
  "size": 10,                      // æ¯é¡µæ¡æ•°
  "topParentProductId": 277,       // å¿…填,顶部父产品ID(产品分类ID)
  "productName": "螺丝刀",          // é€‰å¡«ï¼Œäº§å“åç§°ï¼Œæ¨¡ç³ŠæŸ¥è¯¢
  "model": "M6",                   // é€‰å¡«ï¼Œè§„格型号,模糊查询
  "batchNo": "20260526"            // é€‰å¡«ï¼Œæ‰¹å·ï¼Œæ¨¡ç³ŠæŸ¥è¯¢
}
```
**参数说明**:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| current | Integer | å¦ | å½“前页码,默认1 |
| size | Integer | å¦ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
| topParentProductId | Long | æ˜¯ | é¡¶éƒ¨çˆ¶äº§å“ID(产品分类树根节点ID),用于筛选产品分类 |
| productName | String | å¦ | äº§å“åç§°ï¼Œæ¨¡ç³ŠæŸ¥è¯¢ |
| model | String | å¦ | è§„格型号,模糊查询 |
| batchNo | String | å¦ | æ‰¹å·ï¼Œæ¨¡ç³ŠæŸ¥è¯¢ |
**响应示例**
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "productModelId": 100,
        "model": "M6",
        "productCode": "LSD-M6",
        "unit": "把",
        "productName": "螺丝刀",
        "productId": 50,
        "batchNo": "20260526-ABC001-001",
        "qualitity": 100.0000,
        "lockedQuantity": 5.0000,
        "borrowedQuantity": 10.0000,
        "availableQuantity": 90.0000
      },
      {
        "productModelId": 100,
        "model": "M6",
        "productCode": "LSD-M6",
        "unit": "把",
        "productName": "螺丝刀",
        "productId": 50,
        "batchNo": "20260527-ABC001-001",
        "qualitity": 50.0000,
        "lockedQuantity": 0,
        "borrowedQuantity": 0,
        "availableQuantity": 50.0000
      }
    ],
    "total": 20,
    "size": 10,
    "current": 1,
    "pages": 2
  }
}
```
**响应字段说明**:
| å­—段名 | ç±»åž‹ | è¯´æ˜Ž |
|--------|------|------|
| productModelId | Long | äº§å“è§„æ ¼ID(用于领用时传递) |
| model | String | è§„格型号 |
| productCode | String | äº§å“ç¼–码 |
| unit | String | å•位 |
| productName | String | äº§å“åç§° |
| productId | Long | äº§å“ID |
| batchNo | String | æ‰¹å·ï¼ˆç”¨äºŽé¢†ç”¨æ—¶ä¼ é€’,可为null) |
| qualitity | BigDecimal | å½“前库存数量 |
| lockedQuantity | BigDecimal | å†»ç»“/锁定数量 |
| borrowedQuantity | BigDecimal | è¢«é¢†ç”¨ä¸”未归还的数量 |
| availableQuantity | BigDecimal | å¯é¢†ç”¨æ•°é‡ = åº“存数量 - è¢«é¢†ç”¨æ•°é‡ |
**计算公式**:
```
可领用数量(availableQuantity) = åº“存数量(qualitity) - è¢«é¢†ç”¨é‡(borrowedQuantity)
```
**被领用量计算逻辑**:
- åªç»Ÿè®¡å®¡æ‰¹é€šè¿‡çš„领用记录(approval_status = 1)
- åªç»Ÿè®¡æœªå…¨éƒ¨å½’还的记录(status != 2)
- è®¡ç®—公式:领用数量 - å·²å½’还数量
- æŒ‰äº§å“è§„æ ¼ID + æ‰¹å·åˆ†ç»„统计
**使用场景**:
1. **领用选择产品**:
    - è°ƒç”¨æ­¤æŽ¥å£æŸ¥è¯¢åº“å­˜
    - ç”¨æˆ·é€‰æ‹©äº§å“å’Œæ‰¹å·
    - æ˜¾ç¤ºå¯é¢†ç”¨æ•°é‡ä¾›å‚考
    - é¢†ç”¨æ—¶ä¼ é€’ `productModelId` å’Œ `batchNo`
2. **前端显示建议**:
    - åˆ—表显示:产品名称、规格型号、批号、库存数量、被领用量、可领用量
    - å¯é¢†ç”¨æ•°é‡ä¸º0或负数的记录可以标红或禁用选择
    - æ”¯æŒæŒ‰äº§å“åç§°ã€è§„格型号、批号搜索
**前端页面示例**:
```
┌──────────────────────────────────────────────────────────────────────────┐
│  äº§å“åº“存和领用量查询                                                     â”‚
├──────────────────────────────────────────────────────────────────────────┤
│  äº§å“åˆ†ç±»: [选择分类 â–¼]  äº§å“åç§°: [____]  è§„格型号: [____]  [查询]       â”‚
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          â”‚
│  â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”¬â”€â”€â”€â”€â”€â”€â”€â”€â”   â”‚
│  â”‚产品名称│规格型号│  æ‰¹å·    â”‚库存数量│被领用量│可领用量│  æ“ä½œ  â”‚   â”‚
│  â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¤   â”‚
│  â”‚螺丝刀  â”‚  M6    â”‚20260526- â”‚  100   â”‚   10   â”‚   90   â”‚[领用] â”‚   â”‚
│  â”‚        â”‚        â”‚ABC001-001│        â”‚        â”‚        â”‚        â”‚   â”‚
│  â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¤   â”‚
│  â”‚螺丝刀  â”‚  M6    â”‚20260527- â”‚   50   â”‚    0   â”‚   50   â”‚[领用] â”‚   â”‚
│  â”‚        â”‚        â”‚ABC001-001│        â”‚        â”‚        â”‚        â”‚   â”‚
│  â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¼â”€â”€â”€â”€â”€â”€â”€â”€â”¤   â”‚
│  â”‚扳手    â”‚  10寸  â”‚ NULL     â”‚   30   â”‚    5   â”‚   25   â”‚[领用] â”‚   â”‚
│  â””────────┴────────┴──────────┴────────┴────────┴────────┴────────┘   â”‚
│                                                                          â”‚
└──────────────────────────────────────────────────────────────────────────┘
```
---
## ä¸‰ã€æŽ¥å£æ±‡æ€»è¡¨
### äº§å“é¢†ç”¨æŽ¥å£
| æŽ¥å£åç§° | è¯·æ±‚路径 | è¯·æ±‚方式 | è¯´æ˜Ž |
|----------|----------|----------|------|
| åˆ†é¡µæŸ¥è¯¢ | /productBorrow/listPage | POST | æŸ¥è¯¢é¢†ç”¨è®°å½•列表 |
| æŸ¥è¯¢è¯¦æƒ… | /productBorrow/getDetail | POST | æ ¹æ®ID查询详情 |
| æ–°å¢žé¢†ç”¨ | /productBorrow/add | POST | æ–°å¢žé¢†ç”¨ï¼ˆè‡ªåŠ¨å‡ºåº“ï¼‰ |
| ä¿®æ”¹é¢†ç”¨ | /productBorrow/update | POST | ä¿®æ”¹é¢†ç”¨è®°å½• |
| åˆ é™¤é¢†ç”¨ | /productBorrow/delete | POST | æ‰¹é‡åˆ é™¤é¢†ç”¨è®°å½• |
| æ‰¹é‡å®¡æ‰¹ | /productBorrow/approve | POST | æ‰¹é‡å®¡æ‰¹ï¼ˆé€šè¿‡/驳回) |
### äº§å“å½’还接口
| æŽ¥å£åç§° | è¯·æ±‚路径 | è¯·æ±‚方式 | è¯´æ˜Ž |
|----------|----------|----------|------|
| åˆ†é¡µæŸ¥è¯¢ | /productBorrowReturn/listPage | POST | æŸ¥è¯¢å½’还记录列表 |
| æŒ‰é¢†ç”¨æŸ¥è¯¢ | /productBorrowReturn/listByBorrowId | POST | æŸ¥è¯¢æŸé¢†ç”¨çš„归还记录 |
| æ–°å¢žå½’还 | /productBorrowReturn/add | POST | æ–°å¢žå½’还(自动入库) |
### åº“存查询接口
| æŽ¥å£åç§° | è¯·æ±‚路径 | è¯·æ±‚方式 | è¯´æ˜Ž |
|----------|----------|----------|------|
| åº“存和领用量 | /stockInventory/pageStockAndBorrow | POST | æŸ¥è¯¢åº“存和被领用量 |
---
## å››ã€çŠ¶æ€è¯´æ˜Ž
### 1. å®¡æ‰¹çŠ¶æ€ (approvalStatus)
| å€¼ | åç§° | è¯´æ˜Ž |
|----|------|------|
| 0 | å¾…审批 | æ–°å¢žåŽçš„默认状态(仅批量审批场景使用) |
| 1 | å·²é€šè¿‡ | å®¡æ‰¹é€šè¿‡ï¼Œå·²æ‰£å‡åº“存,可进行归还操作 |
| 2 | å·²é©³å›ž | å®¡æ‰¹æœªé€šè¿‡ï¼Œä¸æ‰£å‡åº“å­˜ |
**注意**:新增领用时直接自动审批通过,状态为1
### 2. å½’还状态 (status)
| å€¼ | åç§° | è¯´æ˜Ž |
|----|------|------|
| 0 | æœªå½’还 | å°šæœªå½’还任何数量 |
| 1 | éƒ¨åˆ†å½’还 | å·²å½’还部分数量 |
| 2 | å·²å…¨éƒ¨å½’还 | å·²å½’还全部数量,不能再归还 |
---
## äº”、业务流程图
```
┌─────────────────────────────────────────────────────────────┐
│                      é¢†ç”¨æµç¨‹ï¼ˆè‡ªåŠ¨å‡ºåº“ï¼‰                     â”‚
├─────────────────────────────────────────────────────────────┤
│                                                             â”‚
│   â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”       â”‚
│   â”‚ æ–°å¢žé¢†ç”¨   â”‚───>│ è‡ªåŠ¨å‡ºåº“   â”‚───>│ çŠ¶æ€è®¾ä¸º   â”‚       â”‚
│   â”‚ è®°å½•       â”‚    â”‚ æ‰£å‡åº“å­˜   â”‚    â”‚ å·²é€šè¿‡     â”‚       â”‚
│   â””────────────┘    â””────────────┘    â””────────────┘       â”‚
│                           â”‚                                 â”‚
│                           â–¼                                 â”‚
│                    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”                          â”‚
│                    â”‚ åˆ›å»ºå‡ºåº“   â”‚                          â”‚
│                    â”‚ è®°å½•       â”‚                          â”‚
│                    â””────────────┘                          â”‚
│                                                             â”‚
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                      å½’还流程(自动入库)                     â”‚
├─────────────────────────────────────────────────────────────┤
│                                                             â”‚
│   â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”    â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”       â”‚
│   â”‚ é€‰æ‹©é¢†ç”¨   â”‚───>│ å¡«å†™å½’还   â”‚───>│ è‡ªåŠ¨å…¥åº“   â”‚       â”‚
│   â”‚ è®°å½•       â”‚    â”‚ ä¿¡æ¯       â”‚    â”‚ å¢žåŠ åº“å­˜   â”‚       â”‚
│   â””────────────┘    â””────────────┘    â””────────────┘       â”‚
│                                              â”‚              â”‚
│                                              â–¼              â”‚
│                                        â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”       â”‚
│                                        â”‚ æ›´æ–°é¢†ç”¨   â”‚       â”‚
│                                        â”‚ çŠ¶æ€       â”‚       â”‚
│                                        â””────────────┘       â”‚
│                                                             â”‚
└─────────────────────────────────────────────────────────────┘
```
---
## å…­ã€æ³¨æ„äº‹é¡¹
### 1. æ•°æ®æ ¡éªŒ
**前端校验**:
- é¢†ç”¨æ•°é‡å¿…须大于0
- å½’还数量不能超过剩余可归还数量
- é¢†ç”¨äººã€å½’还人必须选择系统用户
**后端校验**:
- é¢†ç”¨æ—¶æ£€æŸ¥åº“存是否充足
- å½’还数量不能超过剩余数量
- å·²å…¨éƒ¨å½’还的记录不能再归还
### 2. ç”¨æˆ·é€‰æ‹©
领用人和归还人需要从系统用户表选择,调用系统用户接口:
```
POST /system/user/list
```
### 3. äº§å“è§„格选择
选择产品规格时,可以调用产品接口:
```
POST /basic/product/pageModel
```
### 4. åº“存联动
- **领用**:新增时自动出库(调用库存扣减接口)
- **归还**:新增时自动入库(调用库存增加接口)
---
## ä¸ƒã€é”™è¯¯ç è¯´æ˜Ž
| é”™è¯¯ä¿¡æ¯ | åŽŸå›  | è§£å†³æ–¹æ¡ˆ |
|----------|------|----------|
| é¢†ç”¨è®°å½•不存在 | ID无效 | æ£€æŸ¥è®°å½•ID |
| äº§å“åº“存不存在 | åº“存为空 | æ£€æŸ¥åº“存数据 |
| åº“存不足无法出库 | åº“存不足 | æ£€æŸ¥åº“存数量 |
| è¯¥é¢†ç”¨è®°å½•已全部归还 | å½’还完成 | æ— éœ€å†æ¬¡å½’还 |
| å½’还数量不能大于剩余可归还数量 | æ•°é‡è¶…限 | æ£€æŸ¥å‰©ä½™å¯å½’还数量 |
---
## å…«ã€æµ‹è¯•用例
### 1. æ–°å¢žé¢†ç”¨ï¼ˆè‡ªåŠ¨å‡ºåº“ï¼‰
```json
POST /productBorrow/add
{
  "productModelId": 1,
  "borrowQuantity": 10,
  "borrowerId": 1,
  "borrowerName": "管理员"
}
```
### 2. æŸ¥è¯¢åº“存和领用量
```json
POST /stockInventory/pageStockAndBorrow
{
  "current": 1,
  "size": 10,
  "topParentProductId": 277
}
```
### 3. å½’还产品(自动入库)
```json
POST /productBorrowReturn/add
{
  "borrowId": 1,
  "returnQuantity": 5,
  "returnerId": 1,
  "returnerName": "管理员"
}
```
### 4. æŸ¥è¯¢é¢†ç”¨è¯¦æƒ…
```json
POST /productBorrow/getDetail
{
  "id": 1
}
```
### 5. æŸ¥è¯¢å½’还记录
```json
POST /productBorrowReturn/listByBorrowId
{
  "borrowId": 1,
  "current": 1,
  "size": 10
}
```
src/api/inventoryManagement/productBorrow.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
import request from "@/utils/request";
// ========== é¢†ç”¨ç›¸å…³æŽ¥å£ ==========
// åˆ†é¡µæŸ¥è¯¢é¢†ç”¨è®°å½•
export const getBorrowListPage = (params) => {
  return request({
    url: "/productBorrow/listPage",
    method: "post",
    data: params,
  });
};
// æŸ¥è¯¢é¢†ç”¨è®°å½•详情
export const getBorrowDetail = (params) => {
  return request({
    url: "/productBorrow/getDetail",
    method: "post",
    data: params,
  });
};
// æ–°å¢žé¢†ç”¨è®°å½•
export const addBorrow = (params) => {
  return request({
    url: "/productBorrow/add",
    method: "post",
    data: params,
  });
};
// ä¿®æ”¹é¢†ç”¨è®°å½•
export const updateBorrow = (params) => {
  return request({
    url: "/productBorrow/update",
    method: "post",
    data: params,
  });
};
// åˆ é™¤é¢†ç”¨è®°å½•
export const deleteBorrow = (ids) => {
  return request({
    url: "/productBorrow/delete",
    method: "post",
    data: ids,
  });
};
// æ‰¹é‡å®¡æ‰¹é¢†ç”¨è®°å½•
export const approveBorrow = (params) => {
  return request({
    url: "/productBorrow/approve",
    method: "post",
    data: params,
  });
};
// ========== å½’还相关接口 ==========
// åˆ†é¡µæŸ¥è¯¢å½’还记录
export const getReturnListPage = (params) => {
  return request({
    url: "/productBorrowReturn/listPage",
    method: "post",
    data: params,
  });
};
// æŸ¥è¯¢æŸä¸ªé¢†ç”¨è®°å½•的归还记录
export const getReturnListByBorrowId = (params) => {
  return request({
    url: "/productBorrowReturn/listByBorrowId",
    method: "post",
    data: params,
  });
};
// æ–°å¢žå½’还记录
export const addReturn = (params) => {
  return request({
    url: "/productBorrowReturn/add",
    method: "post",
    data: params,
  });
};
src/api/inventoryManagement/stockInventory.js
@@ -103,3 +103,12 @@
    });
};
// åˆ†é¡µæŸ¥è¯¢äº§å“åº“存和领用量
export const pageStockAndBorrow = (params) => {
    return request({
        url: "/stockInventory/pageStockAndBorrow",
        method: "post",
        data: params,
    });
};
src/views/basicData/product/index.vue
@@ -9,8 +9,7 @@
                  @clear="searchFilter"
                  clearable
                  prefix-icon="Search" />
        <el-button v-if="false"
                   type="primary"
        <el-button type="primary"
                   @click="openProDia('addOne')"
                   style="margin-left: 10px">新增产品大类</el-button>
      </div>
src/views/inventoryManagement/stockManagement/OfficeRecord.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,631 @@
<template>
  <div>
    <!-- æœç´¢è¡¨å• -->
    <div class="search_form mb10">
      <el-form ref="searchFormRef" :model="searchForm" class="demo-form-inline">
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item label="领用单号" prop="borrowNo">
              <el-input v-model="searchForm.borrowNo" style="width: 200px" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="产品大类" prop="productName">
              <el-input v-model="searchForm.productName" style="width: 200px" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="规格型号" prop="model">
              <el-input v-model="searchForm.model" style="width: 200px" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="领用人" prop="borrowerName">
              <el-input v-model="searchForm.borrowerName" style="width: 200px" placeholder="请输入" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="归还状态" prop="status">
              <el-select v-model="searchForm.status" style="width: 200px" placeholder="请选择" clearable>
                <el-option label="未归还" :value="0" />
                <el-option label="部分归还" :value="1" />
                <el-option label="已全部归还" :value="2" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item>
              <el-button type="primary" @click="handleQuery">搜索</el-button>
              <el-button @click="resetSearch">重置</el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div class="flex justify-end mt10">
        <el-button type="info" @click="showStockDrawer = true">查询库存</el-button>
        <el-button type="primary" @click="handleAdd">新增领用</el-button>
        <el-button type="primary" @click="isShowNewModal = true">新增库存</el-button>
      </div>
    </div>
    <!-- åˆ—表 -->
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" style="width: 100%" height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="index" label="序号" width="60" />
        <el-table-column label="领用单号" prop="borrowNo" show-overflow-tooltip width="160" />
        <el-table-column label="产品名称" prop="productName" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
        <el-table-column label="产品编码" prop="productCode" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" show-overflow-tooltip width="60" />
        <el-table-column label="批号" prop="batchNo" show-overflow-tooltip />
        <el-table-column label="领用数量" prop="borrowQuantity" show-overflow-tooltip width="100" />
        <el-table-column label="已归还数量" prop="returnedQuantity" show-overflow-tooltip width="100" />
        <el-table-column label="剩余可归还" show-overflow-tooltip width="100">
          <template #default="{ row }">
            {{ formatQuantity(row.borrowQuantity - row.returnedQuantity) }}
          </template>
        </el-table-column>
        <el-table-column label="领用人" prop="borrowerName" show-overflow-tooltip width="80" />
        <el-table-column label="领用时间" prop="borrowTime" show-overflow-tooltip width="160" />
        <el-table-column label="预计归还时间" prop="expectedReturnTime" show-overflow-tooltip width="160" />
<!--        <el-table-column label="审批状态" prop="approvalStatusName" show-overflow-tooltip width="90">-->
<!--          <template #default="{ row }">-->
<!--            <el-tag :type="getApprovalStatusType(row.approvalStatus)">{{ row.approvalStatusName }}</el-tag>-->
<!--          </template>-->
<!--        </el-table-column>-->
        <el-table-column label="归还状态" prop="statusName" show-overflow-tooltip width="90">
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)">{{ row.statusName }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="备注" prop="remark" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="120" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="handleDetail(scope.row)">详情</el-button>
            <el-button v-if="scope.row.status !== 2" link type="success" @click="handleReturn(scope.row)">归还</el-button>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" :page="page.current"
        :limit="page.size" @pagination="paginationChange" />
    </div>
    <!-- æ–°å¢ž/修改领用弹窗 -->
    <el-dialog v-model="borrowDialogVisible" :title="borrowDialogTitle" width="600px" destroy-on-close>
      <el-form ref="borrowFormRef" :model="borrowForm" :rules="borrowRules" label-width="100px">
        <el-form-item label="产品名称" prop="productModelId">
          <el-button type="primary" @click="showProductSelectDialog = true">
            {{ borrowForm.productName ? borrowForm.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item label="规格型号">
          <el-input v-model="borrowForm.model" disabled />
        </el-form-item>
        <el-form-item label="单位">
          <el-input v-model="borrowForm.unit" disabled />
        </el-form-item>
        <el-form-item label="批号" prop="batchNo">
          <el-select v-model="borrowForm.batchNo" filterable placeholder="请选择批号(可选)" style="width: 100%" clearable>
            <el-option v-for="item in batchNoList" :key="item" :label="item" :value="item" />
          </el-select>
        </el-form-item>
        <el-form-item label="领用数量" prop="borrowQuantity">
          <el-input-number v-model="borrowForm.borrowQuantity" :min="0.0001" :precision="4"
            placeholder="请输入领用数量" style="width: 100%" />
        </el-form-item>
        <el-form-item label="领用人" prop="borrowerId">
          <el-select v-model="borrowForm.borrowerId" filterable placeholder="请选择领用人" style="width: 100%"
            @change="handleBorrowerChange">
            <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
          </el-select>
        </el-form-item>
        <el-form-item label="预计归还时间" prop="expectedReturnTime">
          <el-date-picker v-model="borrowForm.expectedReturnTime" type="datetime" placeholder="请选择预计归还时间"
            style="width: 100%" value-format="YYYY-MM-DD HH:mm:ss" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="borrowForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect"
        :top-product-parent-id="props.productId" single />
      <template #footer>
        <el-button @click="borrowDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitBorrowForm">确定</el-button>
      </template>
    </el-dialog>
    <!-- å½’还弹窗 -->
    <el-dialog v-model="returnDialogVisible" title="产品归还" width="500px" destroy-on-close>
      <el-form ref="returnFormRef" :model="returnForm" :rules="returnRules" label-width="100px">
        <el-form-item label="领用单号">
          <el-input v-model="currentBorrow.borrowNo" disabled />
        </el-form-item>
        <el-form-item label="产品信息">
          <el-input :value="`${currentBorrow.productName} - ${currentBorrow.model}`" disabled />
        </el-form-item>
        <el-form-item label="领用数量">
          <el-input :value="currentBorrow.borrowQuantity" disabled />
        </el-form-item>
        <el-form-item label="已归还数量">
          <el-input :value="currentBorrow.returnedQuantity" disabled />
        </el-form-item>
        <el-form-item label="剩余可归还">
          <el-input :value="formatQuantity(currentBorrow.borrowQuantity - currentBorrow.returnedQuantity)" disabled />
        </el-form-item>
        <el-form-item label="归还数量" prop="returnQuantity">
          <el-input-number v-model="returnForm.returnQuantity" :min="0.0001"
            :max="currentBorrow.borrowQuantity - currentBorrow.returnedQuantity" :precision="4"
            placeholder="请输入归还数量" style="width: 100%" />
        </el-form-item>
        <el-form-item label="归还人" prop="returnerId">
          <el-select v-model="returnForm.returnerId" filterable placeholder="请选择归还人" style="width: 100%"
            @change="handleReturnerChange">
            <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
          </el-select>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="returnForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="returnDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitReturnForm">确定</el-button>
      </template>
    </el-dialog>
    <!-- è¯¦æƒ…弹窗 -->
    <el-dialog v-model="detailDialogVisible" title="领用详情" width="700px" destroy-on-close>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="领用单号">{{ detailData.borrowNo }}</el-descriptions-item>
        <el-descriptions-item label="产品名称">{{ detailData.productName }}</el-descriptions-item>
        <el-descriptions-item label="规格型号">{{ detailData.model }}</el-descriptions-item>
        <el-descriptions-item label="产品编码">{{ detailData.productCode }}</el-descriptions-item>
        <el-descriptions-item label="单位">{{ detailData.unit }}</el-descriptions-item>
        <el-descriptions-item label="批号">{{ detailData.batchNo }}</el-descriptions-item>
        <el-descriptions-item label="领用数量">{{ detailData.borrowQuantity }}</el-descriptions-item>
        <el-descriptions-item label="已归还数量">{{ detailData.returnedQuantity }}</el-descriptions-item>
        <el-descriptions-item label="剩余可归还">{{ formatQuantity(detailData.borrowQuantity - detailData.returnedQuantity) }}</el-descriptions-item>
        <el-descriptions-item label="领用人">{{ detailData.borrowerName }}</el-descriptions-item>
        <el-descriptions-item label="领用时间">{{ detailData.borrowTime }}</el-descriptions-item>
        <el-descriptions-item label="预计归还时间">{{ detailData.expectedReturnTime }}</el-descriptions-item>
        <el-descriptions-item label="审批状态">
          <el-tag :type="getApprovalStatusType(detailData.approvalStatus)">{{ detailData.approvalStatusName
          }}</el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="归还状态">
          <el-tag :type="getStatusType(detailData.status)">{{ detailData.statusName }}</el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="备注" :span="2">{{ detailData.remark }}</el-descriptions-item>
        <el-descriptions-item label="创建时间" :span="2">{{ detailData.createTime }}</el-descriptions-item>
      </el-descriptions>
      <!-- å½’还记录 -->
      <div v-if="detailData.approvalStatus === 1" class="mt20">
        <div class="mb10 fw-bold">归还记录</div>
        <el-table :data="returnRecordList" border style="width: 100%" max-height="200">
          <el-table-column align="center" label="序号" type="index" width="60" />
          <el-table-column label="归还数量" prop="returnQuantity" />
          <el-table-column label="归还人" prop="returnerName" />
          <el-table-column label="归还时间" prop="returnTime" />
          <el-table-column label="备注" prop="remark" />
        </el-table>
      </div>
    </el-dialog>
    <!-- æ–°å¢žåº“存弹窗 -->
    <NewStockInventory v-if="isShowNewModal" v-model:visible="isShowNewModal" :top-product-parent-id="props.productId"
      @completed="getList" />
    <!-- åº“存查询侧边栏 -->
    <el-drawer v-model="showStockDrawer" title="库存和领用量查询" size="1000px" direction="rtl" destroy-on-close
      @open="handleStockDrawerOpen">
      <div class="stock-drawer-content">
        <!-- æœç´¢è¡¨å• -->
        <el-form :inline="true" class="mb10">
          <el-form-item label="产品名称">
            <el-input v-model="stockSearchForm.productName" placeholder="请输入" clearable style="width: 150px" />
          </el-form-item>
          <el-form-item label="规格型号">
            <el-input v-model="stockSearchForm.model" placeholder="请输入" clearable style="width: 150px" />
          </el-form-item>
          <el-form-item label="批号">
            <el-input v-model="stockSearchForm.batchNo" placeholder="请输入" clearable style="width: 150px" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="getStockList">查询</el-button>
            <el-button @click="resetStockSearch">重置</el-button>
          </el-form-item>
        </el-form>
        <!-- åº“存列表 -->
        <el-table :data="stockTableData" border v-loading="stockTableLoading" style="width: 100%" height="calc(100vh - 200px)">
          <el-table-column align="center" label="序号" type="index" width="60" />
          <el-table-column label="产品名称" prop="productName" show-overflow-tooltip />
          <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
          <el-table-column label="批号" prop="batchNo" show-overflow-tooltip>
            <template #default="{ row }">
              {{ row.batchNo || '-' }}
            </template>
          </el-table-column>
          <el-table-column label="库存数量" prop="qualitity" show-overflow-tooltip width="100" />
          <el-table-column label="被领用量" prop="borrowedQuantity" show-overflow-tooltip width="90" />
          <el-table-column label="可领用量" prop="availableQuantity" show-overflow-tooltip width="90">
            <template #default="{ row }">
              <span :class="{ 'text-danger': row.availableQuantity <= 0 }">{{ formatQuantity(row.availableQuantity) }}</span>
            </template>
          </el-table-column>
          <el-table-column label="单位" prop="unit" show-overflow-tooltip width="60" />
        </el-table>
        <pagination v-show="stockTotal > 0" :total="stockTotal" layout="total, sizes, prev, pager, next, jumper"
          :page="stockPage.current" :limit="stockPage.size" @pagination="stockPaginationChange" />
      </div>
    </el-drawer>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import NewStockInventory from "@/views/inventoryManagement/stockManagement/New.vue";
import { ref, reactive, toRefs, onMounted, getCurrentInstance, defineAsyncComponent } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  getBorrowListPage,
  getBorrowDetail,
  addBorrow,
  getReturnListByBorrowId,
  addReturn,
} from "@/api/inventoryManagement/productBorrow.js";
import { pageStockAndBorrow } from "@/api/inventoryManagement/stockInventory.js";
import { userListNoPage } from "@/api/system/user.js";
const props = defineProps({
  productId: {
    type: Number,
    required: true,
    default: 0,
  },
});
const { proxy } = getCurrentInstance();
// è¡¨æ ¼æ•°æ®
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
  current: 1,
  size: 100,
});
const total = ref(0);
// æœç´¢è¡¨å•
const data = reactive({
  searchForm: {
    borrowNo: "",
    productName: "",
    model: "",
    borrowerName: "",
    status: null,
  },
});
const { searchForm } = toRefs(data);
const searchFormRef = ref(null);
// ç”¨æˆ·åˆ—表
const userList = ref([]);
// äº§å“é€‰æ‹©å¼¹çª—
const showProductSelectDialog = ref(false);
// æ‰¹å·åˆ—表
const batchNoList = ref([]);
// é¢†ç”¨å¼¹çª—
const borrowDialogVisible = ref(false);
const borrowDialogTitle = ref("新增领用");
const borrowFormRef = ref(null);
const borrowForm = ref({
  id: null,
  productModelId: null,
  productName: "",
  model: "",
  unit: "",
  batchNo: "",
  borrowQuantity: null,
  borrowerId: null,
  borrowerName: "",
  expectedReturnTime: "",
  remark: "",
});
const borrowRules = {
  productModelId: [{ required: true, message: "请选择产品", trigger: "change" }],
  borrowQuantity: [{ required: true, message: "请输入领用数量", trigger: "blur" }],
  borrowerId: [{ required: true, message: "请选择领用人", trigger: "change" }],
};
// å½’还弹窗
const returnDialogVisible = ref(false);
const returnFormRef = ref(null);
const returnForm = ref({
  borrowId: null,
  returnQuantity: null,
  returnerId: null,
  returnerName: "",
  remark: "",
});
const returnRules = {
  returnQuantity: [{ required: true, message: "请输入归还数量", trigger: "blur" }],
  returnerId: [{ required: true, message: "请选择归还人", trigger: "change" }],
};
const currentBorrow = ref({});
// è¯¦æƒ…弹窗
const detailDialogVisible = ref(false);
const detailData = ref({});
const returnRecordList = ref([]);
// æ–°å¢žåº“存弹窗
const isShowNewModal = ref(false);
// åº“存查询侧边栏
const showStockDrawer = ref(false);
const stockTableData = ref([]);
const stockTableLoading = ref(false);
const stockPage = reactive({
  current: 1,
  size: 10,
});
const stockTotal = ref(0);
const stockSearchForm = reactive({
  productName: "",
  model: "",
  batchNo: "",
});
// èŽ·å–ç”¨æˆ·åˆ—è¡¨
const getUserList = async () => {
  try {
    const res = await userListNoPage();
    userList.value = res.data || [];
  } catch (error) {
    console.error("获取用户列表失败", error);
  }
};
// æŸ¥è¯¢åˆ—表
const getList = async () => {
  tableLoading.value = true;
  try {
    const res = await getBorrowListPage({ ...searchForm.value, ...page });
    tableData.value = res.data?.records || [];
    total.value = res.data?.total || 0;
  } catch (error) {
    console.error("查询列表失败", error);
  } finally {
    tableLoading.value = false;
  }
};
const handleQuery = () => {
  page.current = 1;
  getList();
};
const resetSearch = () => {
  searchFormRef.value?.resetFields();
  page.current = 1;
  getList();
};
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
// çŠ¶æ€æ ‡ç­¾ç±»åž‹
const getApprovalStatusType = (status) => {
  const map = { 0: "warning", 1: "success", 2: "danger" };
  return map[status] || "";
};
const getStatusType = (status) => {
  const map = { 0: "danger", 1: "warning", 2: "success" };
  return map[status] || "";
};
// æ ¼å¼åŒ–数量,去掉尾部多余的0
const formatQuantity = (num) => {
  if (num == null) return "";
  const fixed = Number(num).toFixed(4);
  return parseFloat(fixed).toString();
};
// æ–°å¢žé¢†ç”¨
const handleAdd = () => {
  borrowDialogTitle.value = "新增领用";
  borrowForm.value = {
    id: null,
    productModelId: null,
    productName: "",
    model: "",
    unit: "",
    batchNo: "",
    borrowQuantity: null,
    borrowerId: null,
    borrowerName: "",
    expectedReturnTime: "",
    remark: "",
  };
  batchNoList.value = [];
  borrowDialogVisible.value = true;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    borrowForm.value.productModelId = product.id;
    borrowForm.value.productName = product.productName;
    borrowForm.value.model = product.model;
    borrowForm.value.unit = product.unit;
    borrowForm.value.batchNo = ""; // é‡ç½®æ‰¹å·é€‰æ‹©
    batchNoList.value = product.batchNoList || []; // ä»Žäº§å“æ•°æ®å¸¦å‡ºæ‰¹å·åˆ—表
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    borrowFormRef.value?.validateField("productModelId");
  }
};
// é¢†ç”¨äººå˜æ›´
const handleBorrowerChange = (val) => {
  const user = userList.value.find((item) => item.userId === val);
  borrowForm.value.borrowerName = user?.nickName || "";
};
// æäº¤é¢†ç”¨è¡¨å•
const submitBorrowForm = async () => {
  await borrowFormRef.value?.validate();
  try {
    await addBorrow(borrowForm.value);
    ElMessage.success("新增成功");
    borrowDialogVisible.value = false;
    getList();
  } catch (error) {
    console.error("提交失败", error);
  }
};
// å½’还
const handleReturn = (row) => {
  currentBorrow.value = row;
  returnForm.value = {
    borrowId: row.id,
    returnQuantity: null,
    returnerId: null,
    returnerName: "",
    remark: "",
  };
  returnDialogVisible.value = true;
};
// å½’还人变更
const handleReturnerChange = (val) => {
  const user = userList.value.find((item) => item.userId === val);
  returnForm.value.returnerName = user?.nickName || "";
};
// æäº¤å½’还表单
const submitReturnForm = async () => {
  await returnFormRef.value?.validate();
  try {
    await addReturn(returnForm.value);
    ElMessage.success("归还成功");
    returnDialogVisible.value = false;
    getList();
  } catch (error) {
    console.error("归还失败", error);
  }
};
// æŸ¥çœ‹è¯¦æƒ…
const handleDetail = async (row) => {
  try {
    const res = await getBorrowDetail({ id: row.id });
    detailData.value = res.data || {};
    // å¦‚果已通过,查询归还记录
    if (detailData.value.approvalStatus === 1) {
      const returnRes = await getReturnListByBorrowId({ borrowId: row.id, current: 1, size: 100 });
      returnRecordList.value = returnRes.data?.records || [];
    } else {
      returnRecordList.value = [];
    }
    detailDialogVisible.value = true;
  } catch (error) {
    console.error("获取详情失败", error);
  }
};
// æŸ¥è¯¢åº“存列表
const getStockList = async () => {
  stockTableLoading.value = true;
  try {
    const res = await pageStockAndBorrow({
      ...stockSearchForm,
      ...stockPage,
      topParentProductId: props.productId,
    });
    stockTableData.value = res.data?.records || [];
    stockTotal.value = res.data?.total || 0;
  } catch (error) {
    console.error("查询库存失败", error);
  } finally {
    stockTableLoading.value = false;
  }
};
// é‡ç½®åº“存搜索
const resetStockSearch = () => {
  stockSearchForm.productName = "";
  stockSearchForm.model = "";
  stockSearchForm.batchNo = "";
  stockPage.current = 1;
  getStockList();
};
// åº“存分页变化
const stockPaginationChange = (obj) => {
  stockPage.current = obj.page;
  stockPage.size = obj.limit;
  getStockList();
};
// ç›‘听库存侧边栏打开,自动加载数据
const handleStockDrawerOpen = () => {
  stockPage.current = 1;
  getStockList();
};
onMounted(() => {
  getUserList();
  getList();
});
</script>
<style scoped lang="scss">
.mt10 {
  margin-top: 10px;
}
.mt20 {
  margin-top: 20px;
}
.mb10 {
  margin-bottom: 10px;
}
.fw-bold {
  font-weight: bold;
}
.stock-drawer-content {
  padding: 0 20px;
}
.text-danger {
  color: #f56c6c;
}
.flex {
  display: flex;
}
.justify-end {
  justify-content: flex-end;
}
</style>
src/views/inventoryManagement/stockManagement/index.vue
@@ -6,7 +6,8 @@
                     :label="tab.productName"
                     :name="tab.id"
                     :key="tab.id">
          <Record :product-id="tab.id" v-if="tab.id === activeTab" />
          <OfficeRecord :product-id="tab.id" v-if="tab.id === activeTab && tab.productName === '办公用品'" />
          <Record :product-id="tab.id" v-else-if="tab.id === activeTab" />
        </el-tab-pane>
      </el-tabs>
    </div>
@@ -17,6 +18,7 @@
import { ref, onMounted } from 'vue';
import { productTreeList } from "@/api/basicData/product.js";
import Record from "@/views/inventoryManagement/stockManagement/Record.vue";
import OfficeRecord from "@/views/inventoryManagement/stockManagement/OfficeRecord.vue";
const products = ref([])
const activeTab = ref(null)
const loading = ref(false)
@@ -41,4 +43,4 @@
onMounted(() => {
  fetchProducts();
})
</script>
</script>