From 12d467a5d380f99d02bf7f15e6be83e284e95a7d Mon Sep 17 00:00:00 2001
From: buhuazhen <hua100783@gmail.com>
Date: 星期二, 26 五月 2026 17:43:55 +0800
Subject: [PATCH] feat: 办公用品(日常用品),领用归还。领用归还记录 完成 ps:如果pro使用直接引入该commit

---
 src/main/java/com/ruoyi/stock/service/StockInventoryService.java               |    5 
 src/main/java/com/ruoyi/stock/controller/ProductBorrowReturnController.java    |   51 +
 src/main/resources/mapper/stock/ProductBorrowMapper.xml                        |   95 ++
 doc/产品领用归还模块-前端对接文档.md                                                         |  696 +++++++++++++++
 src/main/java/com/ruoyi/stock/service/ProductBorrowReturnService.java          |   30 
 src/main/java/com/ruoyi/stock/service/impl/ProductBorrowReturnServiceImpl.java |  109 ++
 src/main/java/com/ruoyi/stock/controller/ProductBorrowController.java          |   96 ++
 src/main/java/com/ruoyi/stock/pojo/ProductBorrow.java                          |   96 ++
 src/main/java/com/ruoyi/stock/pojo/ProductBorrowReturn.java                    |   72 +
 doc/产品模块与库存模块分析文档.md                                                           |  714 ++++++++++++++++
 src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java             |   28 
 src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java      |    4 
 src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java      |   15 
 src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java       |    3 
 src/main/java/com/ruoyi/stock/dto/ProductBorrowDto.java                        |   49 +
 src/main/java/com/ruoyi/stock/mapper/ProductBorrowReturnMapper.java            |   28 
 src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java                       |   12 
 src/main/resources/mapper/stock/ProductBorrowReturnMapper.xml                  |   80 +
 src/main/resources/mapper/stock/StockInventoryMapper.xml                       |   54 +
 doc/20260526_product_borrow_tables.sql                                         |   49 +
 src/main/java/com/ruoyi/stock/controller/StockInventoryController.java         |    6 
 src/main/java/com/ruoyi/stock/service/ProductBorrowService.java                |   52 +
 src/main/java/com/ruoyi/stock/mapper/ProductBorrowMapper.java                  |   28 
 src/main/java/com/ruoyi/stock/dto/ProductBorrowReturnDto.java                  |   38 
 src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java                 |    5 
 src/main/java/com/ruoyi/stock/service/impl/ProductBorrowServiceImpl.java       |  214 ++++
 26 files changed, 2,615 insertions(+), 14 deletions(-)

diff --git a/doc/20260526_product_borrow_tables.sql b/doc/20260526_product_borrow_tables.sql
new file mode 100644
index 0000000..efe9598
--- /dev/null
+++ b/doc/20260526_product_borrow_tables.sql
@@ -0,0 +1,49 @@
+-- 浜у搧棰嗙敤琛�
+CREATE TABLE IF NOT EXISTS `product_borrow` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+  `borrow_no` varchar(50) DEFAULT NULL COMMENT '棰嗙敤鍗曞彿',
+  `product_model_id` bigint NOT NULL COMMENT '浜у搧瑙勬牸ID',
+  `batch_no` varchar(100) DEFAULT NULL COMMENT '鎵瑰彿',
+  `borrow_quantity` decimal(18,4) NOT NULL COMMENT '棰嗙敤鏁伴噺',
+  `returned_quantity` decimal(18,4) DEFAULT 0 COMMENT '宸插綊杩樻暟閲�',
+  `borrower_id` bigint DEFAULT NULL COMMENT '棰嗙敤浜篒D',
+  `borrower_name` varchar(100) DEFAULT NULL COMMENT '棰嗙敤浜哄鍚�',
+  `borrow_time` datetime NOT NULL COMMENT '棰嗙敤鏃堕棿',
+  `expected_return_time` datetime DEFAULT NULL COMMENT '棰勮褰掕繕鏃堕棿',
+  `approval_status` tinyint DEFAULT 0 COMMENT '瀹℃壒鐘舵�侊紙0-寰呭鎵癸紝1-宸查�氳繃锛�2-宸查┏鍥烇級',
+  `status` tinyint DEFAULT 0 COMMENT '褰掕繕鐘舵�侊紙0-鏈綊杩橈紝1-閮ㄥ垎褰掕繕锛�2-宸插叏閮ㄥ綊杩橈級',
+  `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+  `tenant_id` bigint DEFAULT NULL COMMENT '绉熸埛ID',
+  `dept_id` bigint DEFAULT NULL COMMENT '閮ㄩ棬ID',
+  `create_user` int DEFAULT NULL COMMENT '鍒涘缓浜�',
+  `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+  `update_user` int DEFAULT NULL COMMENT '鏇存柊浜�',
+  `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+  PRIMARY KEY (`id`),
+  KEY `idx_borrow_no` (`borrow_no`),
+  KEY `idx_product_model_id` (`product_model_id`),
+  KEY `idx_borrower_id` (`borrower_id`),
+  KEY `idx_approval_status` (`approval_status`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浜у搧棰嗙敤琛�';
+
+-- 浜у搧褰掕繕璁板綍琛�
+CREATE TABLE IF NOT EXISTS `product_borrow_return` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+  `borrow_id` bigint NOT NULL COMMENT '棰嗙敤璁板綍ID',
+  `product_model_id` bigint NOT NULL COMMENT '浜у搧瑙勬牸ID',
+  `batch_no` varchar(100) DEFAULT NULL COMMENT '鎵瑰彿',
+  `return_quantity` decimal(18,4) NOT NULL COMMENT '褰掕繕鏁伴噺',
+  `returner_id` bigint DEFAULT NULL COMMENT '褰掕繕浜篒D',
+  `returner_name` varchar(100) DEFAULT NULL COMMENT '褰掕繕浜哄鍚�',
+  `return_time` datetime NOT NULL COMMENT '褰掕繕鏃堕棿',
+  `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+  `tenant_id` bigint DEFAULT NULL COMMENT '绉熸埛ID',
+  `dept_id` bigint DEFAULT NULL COMMENT '閮ㄩ棬ID',
+  `create_user` int DEFAULT NULL COMMENT '鍒涘缓浜�',
+  `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+  PRIMARY KEY (`id`),
+  KEY `idx_borrow_id` (`borrow_id`),
+  KEY `idx_product_model_id` (`product_model_id`),
+  KEY `idx_returner_id` (`returner_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浜у搧褰掕繕璁板綍琛�';
diff --git "a/doc/\344\272\247\345\223\201\346\250\241\345\235\227\344\270\216\345\272\223\345\255\230\346\250\241\345\235\227\345\210\206\346\236\220\346\226\207\346\241\243.md" "b/doc/\344\272\247\345\223\201\346\250\241\345\235\227\344\270\216\345\272\223\345\255\230\346\250\241\345\235\227\345\210\206\346\236\220\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..49550cb
--- /dev/null
+++ "b/doc/\344\272\247\345\223\201\346\250\241\345\235\227\344\270\216\345\272\223\345\255\230\346\250\241\345\235\227\345\210\206\346\236\220\346\226\207\346\241\243.md"
@@ -0,0 +1,714 @@
+# 浜у搧妯″潡涓庡簱瀛樻ā鍧楄缁嗗垎鏋愭枃妗�
+
+## 涓�銆佷骇鍝佹ā鍧� (Product Module)
+
+### 1. 妯″潡姒傝堪
+
+浜у搧妯″潡璐熻矗绠$悊浜у搧鐨勫熀纭�淇℃伅锛岄噰鐢ㄦ爲褰㈢粨鏋勭粍缁囦骇鍝佸垎绫伙紝鏀寔浜у搧瑙勬牸鍨嬪彿鐨勭鐞嗐��
+
+### 2. 鏍稿績瀹炰綋
+
+#### 2.1 Product (浜у搧)
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭ID锛岃嚜澧� |
+| parentId | Long | 鐖朵骇鍝両D锛堟敮鎸佹爲褰㈢粨鏋勶級 |
+| productName | String | 浜у搧鍚嶇О |
+| tenantId | Long | 绉熸埛ID锛堣嚜鍔ㄥ~鍏咃級 |
+| createUser | Integer | 鍒涘缓鐢ㄦ埛锛堣嚜鍔ㄥ~鍏咃級 |
+| deptId | Long | 閮ㄩ棬ID锛堣嚜鍔ㄥ~鍏咃級 |
+| deptIds | Long[] | 閮ㄩ棬ID鏁扮粍锛堥潪鏁版嵁搴撳瓧娈碉級 |
+
+**鏁版嵁琛�**: `product`
+
+#### 2.2 ProductModel (浜у搧瑙勬牸鍨嬪彿)
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭ID锛岃嚜澧� |
+| productId | Long | 鍏宠仈浜у搧ID |
+| model | String | 瑙勬牸鍨嬪彿 |
+| productCode | String | 浜у搧缂栫爜 |
+| unit | String | 鍗曚綅 |
+| tenantId | Long | 绉熸埛ID |
+| createUser | Integer | 鍒涘缓鐢ㄦ埛 |
+| deptId | Long | 閮ㄩ棬ID |
+| inboundNum | BigDecimal | 鍏ュ簱鏁伴噺锛堥潪鏁版嵁搴撳瓧娈碉級 |
+| outboundNum | BigDecimal | 鍑哄簱鏁伴噺锛堥潪鏁版嵁搴撳瓧娈碉級 |
+| stockQuantity | BigDecimal | 鍓╀綑搴撳瓨锛堥潪鏁版嵁搴撳瓧娈碉級 |
+
+**鏁版嵁琛�**: `product_model`
+
+### 3. DTO 鏁版嵁浼犺緭瀵硅薄
+
+#### 3.1 ProductDto
+缁ф壙鑷� `Product`锛屾墿灞曞瓧娈碉細
+- `productModelList`: List<ProductModel> - 浜у搧瑙勬牸鍨嬪彿鍒楄〃
+
+#### 3.2 ProductModelDto
+缁ф壙鑷� `ProductModel`锛屾墿灞曞瓧娈碉細
+- `productStructureList`: List<ProductStructureDto> - 浜у搧缁撴瀯鍒楄〃锛堢敤浜嶣OM锛�
+
+#### 3.3 ProductTreeDto
+鐢ㄤ簬鏍戝舰缁撴瀯灞曠ず锛�
+- `id`, `parentId`, `productName`, `label`
+- `children`: List<ProductTreeDto> - 瀛愯妭鐐瑰垪琛�
+
+#### 3.4 ProductModelVo
+缁ф壙鑷� `ProductModel`锛屾墿灞曞瓧娈碉細
+- `batchNoList`: List<String> - 鎵瑰彿鍒楄〃
+
+### 4. Controller API 鎺ュ彛
+
+**ProductController** (`/basic/product`)
+
+| 鏂规硶 | HTTP | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| selectProductList | GET | /list | 鏌ヨ浜у搧鍒楄〃锛堟爲褰㈢粨鏋勶級 |
+| selectModelList | GET | /modelList | 鏍规嵁浜у搧ID鏌ヨ瑙勬牸鍨嬪彿鍒楄〃 |
+| addOrEditProduct | POST | /addOrEditProduct | 鏂板/鏇存柊浜у搧 |
+| addOrEditProductModel | POST | /addOrEditProductModel | 鏂板/鏇存柊浜у搧瑙勬牸鍨嬪彿 |
+| remove | DELETE | /delProduct | 鍒犻櫎浜у搧锛堟壒閲忥級 |
+| delProductModel | DELETE | /delProductModel | 鍒犻櫎浜у搧瑙勬牸鍨嬪彿锛堟壒閲忥級 |
+| selectModelListPage | GET | /modelListPage | 浜у搧瑙勬牸鍒嗛〉鏌ヨ |
+| listPageProductModel | GET | /pageModel | 鍒嗛〉鏌ヨ鎵�鏈変骇鍝佸瀷鍙� |
+| importProductModel | POST | /import | 瀵煎叆浜у搧瑙勬牸鍨嬪彿 |
+| importProduct | GET | /export | 涓嬭浇浜у搧瀵煎叆妯℃澘 |
+
+### 5. Service 涓氬姟閫昏緫
+
+#### 5.1 IProductService
+
+```java
+// 鏂板鎴栫紪杈戜骇鍝�
+int addOrEditProduct(ProductDto productDto)
+    - 楠岃瘉鐖惰妭鐐规槸鍚﹀瓨鍦�
+    - 楠岃瘉浜у搧鍚嶇О鍦ㄥ悓涓�鐖惰妭鐐逛笅鍞竴
+    - 鏂板/鏇存柊浜у搧
+
+// 鍒犻櫎浜у搧锛堟壒閲忥級
+int delProductByIds(Long[] ids)
+    - 绾ц仈鍒犻櫎鍏宠仈鐨勪骇鍝佽鏍煎瀷鍙�
+    - 鍒犻櫎浜у搧鏈韩
+
+// 鏌ヨ浜у搧鍒楄〃锛堟爲褰級
+List<ProductTreeDto> selectProductList(ProductDto productDto)
+    - 鏌ヨ鏍硅妭鐐癸紙parentId涓簄ull锛�
+    - 閫掑綊鏋勫缓瀛愭爲
+
+// 鍒嗛〉鏌ヨ浜у搧鍨嬪彿
+IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, ProductModel productModel)
+```
+
+#### 5.2 IProductModelService
+
+```java
+// 鏂板鎴栫紪杈戜骇鍝佽鏍煎瀷鍙�
+int addOrEditProductModel(ProductModelDto productModelDto)
+    - 楠岃瘉瑙勬牸鍨嬪彿+浜у搧缂栫爜鍞竴鎬�
+    - 鏂板/鏇存柊瑙勬牸鍨嬪彿
+
+// 鍒犻櫎浜у搧瑙勬牸鍨嬪彿
+int delProductModel(Long[] ids)
+    - 妫�鏌ユ槸鍚﹀瓨鍦ㄩ攢鍞彴璐�/閲囪喘鍙拌处鍏宠仈
+    - 妫�鏌ユ槸鍚﹀瓨鍦˙OM鏁版嵁鍏宠仈
+    - 鎵ц鍒犻櫎
+
+// 鏌ヨ瑙勬牸鍨嬪彿鍒楄〃
+List<ProductModel> selectModelList(ProductDto productDto)
+
+// 鍒嗛〉鏌ヨ
+IPage<ProductModel> modelListPage(Page page, ProductDto productDto)
+
+// 瀵煎叆浜у搧瑙勬牸鍨嬪彿
+AjaxResult importProductModel(MultipartFile file, Integer productId)
+    - 楠岃瘉浜у搧鏄惁瀛樺湪
+    - 楠岃瘉瀵煎叆鏁版嵁锛堜骇鍝佺紪鐮併�佽鏍煎瀷鍙枫�佸崟浣嶅繀濉級
+    - 鍘婚噸澶勭悊锛堣烦杩囧凡瀛樺湪鐨勫瀷鍙凤級
+    - 鎵归噺淇濆瓨
+```
+
+### 6. 涓氬姟绾︽潫
+
+1. **浜у搧鍒犻櫎绾︽潫**: 
+   - 瀛樺湪閿�鍞彴璐�/閲囪喘鍙拌处鍏宠仈鐨勪骇鍝佷笉鑳藉垹闄�
+
+2. **浜у搧瑙勬牸鍨嬪彿鍒犻櫎绾︽潫**:
+   - 瀛樺湪閿�鍞彴璐�/閲囪喘鍙拌处鍏宠仈涓嶈兘鍒犻櫎
+   - 瀛樺湪BOM鏁版嵁鍏宠仈涓嶈兘鍒犻櫎
+
+3. **鍞竴鎬х害鏉�**:
+   - 鍚屼竴鐖惰妭鐐逛笅浜у搧鍚嶇О鍞竴
+   - 瑙勬牸鍨嬪彿+浜у搧缂栫爜缁勫悎鍞竴
+
+### 7. 鏁版嵁娴佺▼鍥�
+
+```
+浜у搧绠$悊娴佺▼锛�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�  浜у搧鍒嗙被   鈹傗攢鈹�鈹�鈹�>鈹�   浜у搧      鈹傗攢鈹�鈹�鈹�>鈹� 浜у搧瑙勬牸鍨嬪彿 鈹�
+鈹�  (鐖惰妭鐐�)   鈹�     鈹�  (瀛愯妭鐐�)   鈹�     鈹�  (鍏蜂綋鍨嬪彿)  鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                           鈹�
+                           鈻�
+                    鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                    鈹�  閿�鍞彴璐�   鈹�
+                    鈹�  閲囪喘鍙拌处   鈹�
+                    鈹�  BOM鏁版嵁    鈹�
+                    鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 浜屻�佸簱瀛樻ā鍧� (Stock Module)
+
+### 1. 妯″潡姒傝堪
+
+搴撳瓨妯″潡绠$悊浜у搧鐨勫簱瀛樹俊鎭紝鏀寔鍏ュ簱銆佸嚭搴撱�佸簱瀛樻煡璇€�佸喕缁�/瑙e喕绛夊姛鑳斤紝閲囩敤鎵瑰彿绠$悊搴撳瓨銆�
+
+### 2. 鏍稿績瀹炰綋
+
+#### 2.1 StockInventory (搴撳瓨琛�)
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭ID |
+| productModelId | Long | 浜у搧瑙勬牸ID锛堝繀濉級 |
+| batchNo | String | 鎵瑰彿 |
+| qualitity | BigDecimal | 搴撳瓨鏁伴噺 |
+| lockedQuantity | BigDecimal | 鍐荤粨/閿佸畾鏁伴噺 |
+| warnNum | BigDecimal | 棰勮鏁伴噺 |
+| version | Integer | 鐗堟湰鍙� |
+| remark | String | 澶囨敞 |
+| createTime | LocalDateTime | 鍒涘缓鏃堕棿 |
+| updateTime | LocalDateTime | 鏇存柊鏃堕棿 |
+| createUser | Integer | 鍒涘缓鐢ㄦ埛 |
+| deptId | Long | 閮ㄩ棬ID |
+
+**鏁版嵁琛�**: `stock_inventory`
+
+#### 2.2 StockInRecord (鍏ュ簱璁板綍)
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭ID |
+| inboundBatches | String | 鍏ュ簱鎵规 |
+| stockInNum | BigDecimal | 鍏ュ簱鏁伴噺 |
+| batchNo | String | 鎵瑰彿 |
+| recordType | String | 璁板綍绫诲瀷锛堟灇涓撅級 |
+| recordId | Long | 鍏宠仈璁板綍ID |
+| productModelId | Long | 浜у搧瑙勬牸ID |
+| type | String | 绫诲瀷锛�0鍚堟牸/1涓嶅悎鏍硷級 |
+| warnNum | BigDecimal | 棰勮鏁伴噺 |
+| approvalStatus | Integer | 瀹℃壒鐘舵�侊紙0寰呭鎵�/1閫氳繃/2椹冲洖锛� |
+| remark | String | 澶囨敞 |
+
+**鏁版嵁琛�**: `stock_in_record`
+
+#### 2.3 StockOutRecord (鍑哄簱璁板綍)
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭ID |
+| outboundBatches | String | 鍑哄簱鎵规 |
+| stockOutNum | BigDecimal | 鍑哄簱鏁伴噺 |
+| batchNo | String | 鎵瑰彿 |
+| recordType | String | 璁板綍绫诲瀷锛堟灇涓撅級 |
+| recordId | Long | 鍏宠仈璁板綍ID |
+| productModelId | Long | 浜у搧瑙勬牸ID |
+| type | String | 绫诲瀷锛�0鍚堟牸/1涓嶅悎鏍硷級 |
+| approvalStatus | Integer | 瀹℃壒鐘舵�侊紙0寰呭鎵�/1閫氳繃/2椹冲洖/3閿�鍞嚭搴撳緟纭锛� |
+| remark | String | 澶囨敞 |
+
+**鏁版嵁琛�**: `stock_out_record`
+
+### 3. DTO 鏁版嵁浼犺緭瀵硅薄
+
+#### 3.1 StockInventoryDto
+缁ф壙鑷� `StockInventory`锛屾墿灞曞瓧娈碉細
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| productName | String | 浜у搧鍚嶇О |
+| model | String | 瑙勬牸鍨嬪彿 |
+| unit | String | 鍗曚綅 |
+| recordType | String | 鍏ュ簱绫诲瀷 |
+| recordId | Long | 鍏ュ簱绫诲瀷瀵瑰簲ID |
+| reportDate | LocalDate | 鎶ヨ〃鏃ユ湡 |
+| startMonth | endMonth | 搴撳瓨鏈堟姤鏌ヨ瀛楁 |
+| totalStockIn | BigDecimal | 鎬诲叆搴撻噺 |
+| totalStockOut | BigDecimal | 鎬诲嚭搴撻噺 |
+| currentStock | BigDecimal | 褰撳墠搴撳瓨 |
+| stockType | String | 搴撳瓨绫诲瀷锛坬ualified/unqualified锛� |
+| qualifiedQuantity | BigDecimal | 鍚堟牸搴撳瓨鏁伴噺 |
+| unQualifiedQuantity | BigDecimal | 涓嶅悎鏍煎簱瀛樻暟閲� |
+| qualifiedLockedQuantity | BigDecimal | 鍚堟牸搴撳瓨鍐荤粨鏁伴噺 |
+| unQualifiedLockedQuantity | BigDecimal | 涓嶅悎鏍煎簱瀛樺喕缁撴暟閲� |
+| qualifiedPendingOutQuantity | BigDecimal | 鍚堟牸搴撳瓨寰呭鏍稿嚭搴撴暟閲� |
+| productId | Long | 浜у搧ID |
+| topParentProductId | Long | 椤堕儴鐖朵骇鍝両D |
+
+### 4. Controller API 鎺ュ彛
+
+**StockInventoryController** (`/stockInventory`)
+
+| 鏂规硶 | HTTP | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| pagestockInventory | GET | /pagestockInventory | 鍒嗛〉鏌ヨ搴撳瓨 |
+| pageListCombinedStockInventory | GET | /pageListCombinedStockInventory | 鍒嗛〉鏌ヨ鑱斿悎搴撳瓨鍒楄〃 |
+| getBatchNoQty | GET | /getBatchNoQty | 鏌ヨ瀵瑰簲鎵瑰彿鍜屾暟閲� |
+| addstockInventory | POST | /addstockInventory | 鏂板搴撳瓨锛堝叆搴擄級 |
+| subtractStockInventory | POST | /subtractStockInventory | 鎵e噺搴撳瓨锛堝嚭搴擄級 |
+| addStockInRecordOnly | POST | /addStockInRecordOnly | 鏂板鍏ュ簱璁板綍锛堜笉璋冩暣搴撳瓨锛� |
+| addStockOutRecordOnly | POST | /addStockOutRecordOnly | 鏂板鍑哄簱璁板綍锛堜笉璋冩暣搴撳瓨锛� |
+| importStockInventory | POST | /importStockInventory | 瀵煎叆搴撳瓨 |
+| downloadStockInventory | POST | /downloadStockInventory | 涓嬭浇搴撳瓨瀵煎叆妯℃澘 |
+| exportStockInventory | POST | /exportStockInventory | 瀵煎嚭搴撳瓨 |
+| stockInventoryPage | GET | /stockInventoryPage | 搴撳瓨鎶ヨ〃鏌ヨ |
+| stockInAndOutRecord | GET | /stockInAndOutRecord | 缁熻鍏ュ簱鍑哄簱璁板綍 |
+| frozenStock | POST | /frozenStock | 鍐荤粨搴撳瓨 |
+| thawStock | POST | /thawStock | 瑙e喕搴撳瓨 |
+| getByModelId | GET | /getByModelId | 鏍规嵁浜у搧瑙勬牸ID鑾峰彇鍏ュ簱璁板綍 |
+
+**StockInRecordController** (`/stockInRecord`)
+
+| 鏂规硶 | HTTP | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| listPage | GET | /listPage | 鍏ュ簱绠$悊鍒楄〃 |
+| delete | DELETE | / | 鍒犻櫎鍏ュ簱璁板綍 |
+| deletePending | DELETE | /pending | 鍒犻櫎寰呭鎵瑰叆搴撹褰� |
+| exportStockInRecord | POST | /exportStockInRecord | 瀵煎嚭鍏ュ簱璁板綍 |
+| approve | POST | /approve | 鎵归噺瀹℃壒鍏ュ簱璁板綍 |
+| reAudit | POST | /reAudit | 鎵归噺鍙嶅鍏ュ簱璁板綍 |
+
+**StockOutRecordController** (`/stockOutRecord`)
+
+| 鏂规硶 | HTTP | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| listPage | GET | /listPage | 鍑哄簱绠$悊鍒楄〃 |
+| add | POST | / | 鏂板鍑哄簱璁板綍 |
+| update | PUT | /{id} | 鏇存柊鍑哄簱璁板綍 |
+| delete | DELETE | / | 鍒犻櫎鍑哄簱璁板綍 |
+| deletePending | DELETE | /pending | 鍒犻櫎寰呭鎵瑰嚭搴撹褰� |
+| exportStockOutRecord | POST | /exportStockOutRecord | 瀵煎嚭鍑哄簱璁板綍 |
+| approve | POST | /approve | 鎵归噺瀹℃壒鍑哄簱璁板綍 |
+
+### 5. Service 鏍稿績涓氬姟閫昏緫
+
+#### 5.1 StockInventoryService
+
+```java
+// 鍏ュ簱鎿嶄綔锛堣褰�+搴撳瓨璋冩暣锛�
+Boolean addstockInventory(StockInventoryDto dto)
+    1. 鐢熸垚鎴栭獙璇佹壒鍙�
+    2. 鍒涘缓鍏ュ簱璁板綍(StockInRecord)
+    3. 妫�鏌ュ簱瀛樻槸鍚﹀瓨鍦�
+       - 涓嶅瓨鍦細鏂板搴撳瓨璁板綍
+       - 瀛樺湪锛氭洿鏂板簱瀛樻暟閲�
+
+// 鍑哄簱鎿嶄綔锛堣褰�+搴撳瓨璋冩暣锛�
+Boolean subtractStockInventory(StockInventoryDto dto)
+    1. 鏌ヨ搴撳瓨璁板綍
+    2. 楠岃瘉搴撳瓨鍏呰冻锛堟墸闄ゅ喕缁撴暟閲忥級
+    3. 鍒涘缓鍑哄簱璁板綍(StockOutRecord)
+    4. 鎵e噺搴撳瓨鏁伴噺
+
+// 浠呭垱寤哄叆搴撹褰曪紙涓嶈皟鏁村簱瀛橈級
+Boolean addStockInRecordOnly(StockInventoryDto dto)
+    - 鐢ㄤ簬鎵嬪伐褰曞叆鍘嗗彶鏁版嵁
+
+// 浠呭垱寤哄嚭搴撹褰曪紙涓嶈皟鏁村簱瀛橈級
+Boolean addStockOutRecordOnly(StockInventoryDto dto)
+    - 楠岃瘉鍙敤搴撳瓨锛堝簱瀛�-鍐荤粨-寰呭鏍稿嚭搴擄級
+    - 鍒涘缓鍑哄簱璁板綍
+
+// 鎵瑰彿鑷姩鐢熸垚瑙勫垯
+// 鏍煎紡: 鏃ユ湡-浜у搧缂栫爜-搴忓彿
+// 绀轰緥: 20260526-ABC001-001
+String generateAutoBatchNo(Long productModelId)
+
+// 鍐荤粨搴撳瓨
+Boolean frozenStock(StockInventoryDto dto)
+    - 楠岃瘉鍐荤粨鏁伴噺涓嶈秴杩囧簱瀛樻暟閲�
+    - 绱姞鍐荤粨鏁伴噺
+
+// 瑙e喕搴撳瓨
+Boolean thawStock(StockInventoryDto dto)
+    - 楠岃瘉瑙e喕鏁伴噺涓嶈秴杩囧喕缁撴暟閲�
+    - 鎵e噺鍐荤粨鏁伴噺
+```
+
+### 6. 鏋氫妇绫诲瀷
+
+#### 6.1 鍏ュ簱璁板綍绫诲瀷 (StockInQualifiedRecordTypeEnum)
+
+| 浠g爜 | 璇存槑 |
+|------|------|
+| CUSTOMIZATION_STOCK_IN | 鑷畾涔夊叆搴擄紙鎵嬪伐鍏ュ簱锛� |
+
+#### 6.2 鍑哄簱璁板綍绫诲瀷 (StockOutQualifiedRecordTypeEnum)
+
+| 浠g爜 | 璇存槑 |
+|------|------|
+| CUSTOMIZATION_STOCK_OUT | 鑷畾涔夊嚭搴擄紙鎵嬪伐鍑哄簱锛� |
+
+#### 6.3 瀹℃壒鐘舵�� (ReviewStatusEnum)
+
+| 鍊� | 璇存槑 |
+|------|------|
+| 0 | 寰呭鎵� |
+| 1 | 閫氳繃 |
+| 2 | 椹冲洖 |
+| 3 | 閿�鍞嚭搴撳緟纭 |
+
+### 7. 涓氬姟娴佺▼鍥�
+
+```
+鍏ュ簱娴佺▼锛�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍏ュ簱鐢宠 鈹傗攢鈹�鈹�鈹�>鈹� 鍒涘缓鍏ュ簱 鈹傗攢鈹�鈹�鈹�>鈹� 鏇存柊搴撳瓨 鈹�
+鈹� (鏉ユ簮)   鈹�     鈹� 璁板綍     鈹�     鈹� 鏁伴噺     鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                      鈹�
+                      鈻�
+               鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+               鈹� 鐢熸垚鎵瑰彿 鈹�
+               鈹� (鑷姩/鎵嬪伐)鈹�
+               鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+鍑哄簱娴佺▼锛�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍑哄簱鐢宠 鈹傗攢鈹�鈹�鈹�>鈹� 楠岃瘉搴撳瓨 鈹傗攢鈹�鈹�鈹�>鈹� 鍒涘缓鍑哄簱 鈹�
+鈹�          鈹�     鈹� 鏄惁鍏呰冻 鈹�     鈹� 璁板綍     鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                      鈹�
+                      鈻�
+               鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+               鈹� 鎵e噺搴撳瓨 鈹�
+               鈹� 鏁伴噺     鈹�
+               鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+搴撳瓨鍐荤粨/瑙e喕锛�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍐荤粨鐢宠 鈹傗攢鈹�鈹�鈹�>鈹� 绱姞鍐荤粨 鈹�
+鈹�          鈹�     鈹� 鏁伴噺     鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 瑙e喕鐢宠 鈹傗攢鈹�鈹�鈹�>鈹� 鎵e噺鍐荤粨 鈹�
+鈹�          鈹�     鈹� 鏁伴噺     鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 8. 搴撳瓨璁$畻鍏紡
+
+```
+鍙敤搴撳瓨 = 搴撳瓨鏁伴噺 - 鍐荤粨鏁伴噺 - 寰呭鏍稿嚭搴撴暟閲�
+
+鍑哄簱楠岃瘉鏉′欢:
+鐢宠鍑哄簱鏁伴噺 <= 鍙敤搴撳瓨
+
+鍐荤粨楠岃瘉鏉′欢:
+鍐荤粨鏁伴噺 <= 搴撳瓨鏁伴噺
+
+瑙e喕楠岃瘉鏉′欢:
+瑙e喕鏁伴噺 <= 宸插喕缁撴暟閲�
+```
+
+---
+
+## 涓夈�佹ā鍧楀叧鑱斿叧绯�
+
+### 1. 鏁版嵁鍏宠仈
+
+```
+Product (浜у搧)
+    鈹�
+    鈹溾攢鈹� productId 鈹�鈹�> ProductModel (浜у搧瑙勬牸鍨嬪彿)
+    鈹�                        鈹�
+    鈹�                        鈹溾攢鈹� productModelId 鈹�鈹�> StockInventory (鍚堟牸搴撳瓨)
+    鈹�                        鈹�
+    鈹�                        鈹溾攢鈹� productModelId 鈹�鈹�> StockUninventory (涓嶅悎鏍煎簱瀛�)
+    鈹�                        鈹�
+    鈹�                        鈹溾攢鈹� productModelId 鈹�鈹�> StockInRecord (鍏ュ簱璁板綍)
+    鈹�                        鈹�
+    鈹�                        鈹斺攢鈹� productModelId 鈹�鈹�> StockOutRecord (鍑哄簱璁板綍)
+    鈹�
+    鈹斺攢鈹� 琚垹闄ゆ椂绾ц仈鍒犻櫎 ProductModel
+```
+
+### 2. 鍒犻櫎绾︽潫
+
+| 妯″潡 | 鍒犻櫎鍓嶆鏌� |
+|------|-----------|
+| Product | 妫�鏌ラ攢鍞彴璐﹀叧鑱� |
+| ProductModel | 妫�鏌ラ攢鍞彴璐︺�丅OM鏁版嵁鍏宠仈 |
+| StockInventory | 涓嶅厑璁哥洿鎺ュ垹闄わ紝闇�閫氳繃鍑哄簱鎵e噺 |
+
+### 3. 鍏抽敭涓氬姟鍦烘櫙
+
+#### 3.1 浜у搧鍏ュ簱鍦烘櫙
+1. 閲囪喘鍏ュ簱 鈫� 鐢熸垚鍏ュ簱璁板綍 鈫� 澧炲姞搴撳瓨
+2. 鐢熶骇鍏ュ簱 鈫� 鐢熸垚鍏ュ簱璁板綍 鈫� 澧炲姞搴撳瓨
+3. 鎵嬪伐鍏ュ簱 鈫� 鑷畾涔夊叆搴撶被鍨�
+
+#### 3.2 浜у搧鍑哄簱鍦烘櫙
+1. 閿�鍞嚭搴� 鈫� 妫�鏌ュ簱瀛� 鈫� 鍒涘缓鍑哄簱璁板綍 鈫� 鎵e噺搴撳瓨
+2. 鐢熶骇棰嗘枡 鈫� 妫�鏌ュ簱瀛� 鈫� 鍒涘缓鍑哄簱璁板綍 鈫� 鎵e噺搴撳瓨
+3. 鎵嬪伐鍑哄簱 鈫� 鑷畾涔夊嚭搴撶被鍨�
+
+#### 3.3 搴撳瓨鍐荤粨鍦烘櫙
+1. 璁㈠崟閿佸畾 鈫� 鍐荤粨搴撳瓨
+2. 璐ㄩ噺闂 鈫� 鍐荤粨搴撳瓨
+3. 闂瑙e喅 鈫� 瑙e喕搴撳瓨
+
+---
+
+## 鍥涖�佹枃浠剁粨鏋�
+
+### 浜у搧妯″潡鏂囦欢
+
+```
+com.ruoyi.basic/
+鈹溾攢鈹� controller/
+鈹�   鈹斺攢鈹� ProductController.java
+鈹溾攢鈹� service/
+鈹�   鈹溾攢鈹� IProductService.java
+鈹�   鈹溾攢鈹� IProductModelService.java
+鈹�   鈹斺攢鈹� impl/
+鈹�       鈹溾攢鈹� ProductServiceImpl.java
+鈹�       鈹斺攢鈹� ProductModelServiceImpl.java
+鈹溾攢鈹� mapper/
+鈹�   鈹溾攢鈹� ProductMapper.java
+鈹�   鈹斺攢鈹� ProductModelMapper.java
+鈹溾攢鈹� pojo/
+鈹�   鈹溾攢鈹� Product.java
+鈹�   鈹斺攢鈹� ProductModel.java
+鈹溾攢鈹� dto/
+鈹�   鈹溾攢鈹� ProductDto.java
+鈹�   鈹溾攢鈹� ProductModelDto.java
+鈹�   鈹斺攢鈹� ProductTreeDto.java
+鈹溾攢鈹� vo/
+鈹�   鈹斺攢鈹� ProductModelVo.java
+鈹斺攢鈹� excel/
+    鈹斺攢鈹� (瀵煎叆瀵煎嚭鐩稿叧)
+```
+
+### 搴撳瓨妯″潡鏂囦欢
+
+```
+com.ruoyi.stock/
+鈹溾攢鈹� controller/
+鈹�   鈹溾攢鈹� StockInventoryController.java
+鈹�   鈹溾攢鈹� StockInRecordController.java
+鈹�   鈹溾攢鈹� StockOutRecordController.java
+鈹�   鈹斺攢鈹� StockUninventoryController.java
+鈹溾攢鈹� service/
+鈹�   鈹溾攢鈹� StockInventoryService.java
+鈹�   鈹溾攢鈹� StockInRecordService.java
+鈹�   鈹溾攢鈹� StockOutRecordService.java
+鈹�   鈹斺攢鈹� impl/
+鈹�       鈹溾攢鈹� StockInventoryServiceImpl.java
+鈹�       鈹溾攢鈹� StockInRecordServiceImpl.java
+鈹�       鈹溾攢鈹� StockOutRecordServiceImpl.java
+鈹�       鈹斺攢鈹� StockUninventoryServiceImpl.java
+鈹溾攢鈹� mapper/
+鈹�   鈹溾攢鈹� StockInventoryMapper.java
+鈹�   鈹溾攢鈹� StockInRecordMapper.java
+鈹�   鈹斺攢鈹� StockOutRecordMapper.java
+鈹溾攢鈹� pojo/
+鈹�   鈹溾攢鈹� StockInventory.java
+鈹�   鈹溾攢鈹� StockInRecord.java
+鈹�   鈹斺攢鈹� StockOutRecord.java
+鈹溾攢鈹� dto/
+鈹�   鈹溾攢鈹� StockInventoryDto.java
+鈹�   鈹溾攢鈹� StockInRecordDto.java
+鈹�   鈹斺攢鈹� StockOutRecordDto.java
+鈹斺攢鈹� excel/
+    鈹溾攢鈹� StockInventoryExportData.java
+    鈹溾攢鈹� StockInRecordExportData.java
+    鈹斺攢鈹� StockOutRecordExportData.java
+```
+
+---
+
+## 浜斻�佹敞鎰忎簨椤�
+
+1. **浜嬪姟绠$悊**: 鍏ュ簱/鍑哄簱鎿嶄綔浣跨敤 `@Transactional(rollbackFor = Exception.class)` 淇濊瘉鏁版嵁涓�鑷存��
+
+2. **鎵瑰彿绠$悊**: 
+   - 鑷姩鐢熸垚鏍煎紡: `YYYYMMDD-浜у搧缂栫爜-搴忓彿`
+   - 鏀寔鎵嬪伐鎸囧畾鎵瑰彿
+   - 鍚屼竴鎵瑰彿鍚屼竴浜у搧瑙勬牸鍚堝苟搴撳瓨
+
+3. **搴撳瓨瀹夊叏**:
+   - 鍑哄簱鍓嶆鏌ュ彲鐢ㄥ簱瀛�
+   - 鍐荤粨鏁伴噺涓嶅彲鐢ㄤ簬鍑哄簱
+   - 寰呭鏍稿嚭搴撴暟閲忚鍏ュ彲鐢ㄥ簱瀛樿绠�
+
+4. **澶氱鎴锋敮鎸�**: 閫氳繃 `tenantId` 瀛楁瀹炵幇鏁版嵁闅旂
+
+5. **閮ㄩ棬闅旂**: 閫氳繃 `deptId` 瀛楁瀹炵幇閮ㄩ棬鏁版嵁闅旂
+
+---
+
+## 鍏�佷骇鍝侀鐢ㄥ綊杩樻ā鍧� (Product Borrow Module)
+
+### 1. 妯″潡姒傝堪
+
+浜у搧棰嗙敤褰掕繕妯″潡绠$悊浜у搧鐨勯鐢ㄥ拰褰掕繕娴佺▼锛屾敮鎸佸鎵规祦绋嬪拰搴撳瓨鑱斿姩銆�
+
+### 2. 鏍稿績瀹炰綋
+
+#### 2.1 ProductBorrow (浜у搧棰嗙敤琛�)
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭ID |
+| borrowNo | String | 棰嗙敤鍗曞彿锛堣嚜鍔ㄧ敓鎴愶級 |
+| productModelId | Long | 浜у搧瑙勬牸ID |
+| batchNo | String | 鎵瑰彿 |
+| borrowQuantity | BigDecimal | 棰嗙敤鏁伴噺 |
+| returnedQuantity | BigDecimal | 宸插綊杩樻暟閲� |
+| borrowerId | Long | 棰嗙敤浜篒D锛堢郴缁熺敤鎴凤級 |
+| borrowerName | String | 棰嗙敤浜哄鍚� |
+| borrowTime | LocalDateTime | 棰嗙敤鏃堕棿 |
+| expectedReturnTime | LocalDateTime | 棰勮褰掕繕鏃堕棿 |
+| approvalStatus | Integer | 瀹℃壒鐘舵�侊紙0寰呭鎵�/1宸查�氳繃/2宸查┏鍥烇級 |
+| status | Integer | 褰掕繕鐘舵�侊紙0鏈綊杩�/1閮ㄥ垎褰掕繕/2宸插叏閮ㄥ綊杩橈級 |
+| remark | String | 澶囨敞 |
+
+**鏁版嵁琛�**: `product_borrow`
+
+#### 2.2 ProductBorrowReturn (浜у搧褰掕繕璁板綍琛�)
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 涓婚敭ID |
+| borrowId | Long | 棰嗙敤璁板綍ID |
+| productModelId | Long | 浜у搧瑙勬牸ID |
+| batchNo | String | 鎵瑰彿 |
+| returnQuantity | BigDecimal | 褰掕繕鏁伴噺 |
+| returnerId | Long | 褰掕繕浜篒D锛堢郴缁熺敤鎴凤級 |
+| returnerName | String | 褰掕繕浜哄鍚� |
+| returnTime | LocalDateTime | 褰掕繕鏃堕棿 |
+| remark | String | 澶囨敞 |
+
+**鏁版嵁琛�**: `product_borrow_return`
+
+### 3. Controller API 鎺ュ彛
+
+**ProductBorrowController** (`/productBorrow`)
+
+| 鏂规硶 | HTTP | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| listPage | GET | /listPage | 鍒嗛〉鏌ヨ棰嗙敤璁板綍 |
+| getDetail | GET | /{id} | 鏌ヨ棰嗙敤璁板綍璇︽儏 |
+| add | POST | / | 鏂板棰嗙敤璁板綍锛堝緟瀹℃壒锛� |
+| update | PUT | /{id} | 淇敼棰嗙敤璁板綍 |
+| delete | DELETE | / | 鍒犻櫎棰嗙敤璁板綍 |
+| approve | POST | /approve | 鎵归噺瀹℃壒棰嗙敤璁板綍 |
+| reAudit | POST | /reAudit | 鎵归噺鍙嶅棰嗙敤璁板綍 |
+
+**ProductBorrowReturnController** (`/productBorrowReturn`)
+
+| 鏂规硶 | HTTP | 璺緞 | 璇存槑 |
+|------|------|------|------|
+| listPage | GET | /listPage | 鍒嗛〉鏌ヨ褰掕繕璁板綍 |
+| listByBorrowId | GET | /listByBorrowId/{borrowId} | 鏌ヨ鏌愰鐢ㄨ褰曠殑褰掕繕璁板綍 |
+| add | POST | / | 鏂板褰掕繕璁板綍锛堢洿鎺ュ鍔犲簱瀛橈級 |
+
+### 4. 涓氬姟娴佺▼
+
+```
+棰嗙敤娴佺▼锛�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鏂板棰嗙敤 鈹傗攢鈹�鈹�鈹�>鈹� 寰呭鎵�   鈹傗攢鈹�鈹�鈹�>鈹� 瀹℃壒閫氳繃 鈹�
+鈹� 璁板綍     鈹�     鈹� 鐘舵��     鈹�     鈹�          鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                                        鈹�
+                                        鈻�
+                               鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                               鈹� 鎵e噺搴撳瓨     鈹�
+                               鈹� (鍑哄簱璁板綍)   鈹�
+                               鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+褰掕繕娴佺▼锛�
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 閫夋嫨棰嗙敤 鈹傗攢鈹�鈹�鈹�>鈹� 濉啓褰掕繕 鈹傗攢鈹�鈹�鈹�>鈹� 楠岃瘉褰掕繕 鈹�
+鈹� 璁板綍    鈹�     鈹� 淇℃伅     鈹�     鈹� 鏁伴噺     鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+                      鈹�                鈹�
+                      鈻�                鈻�
+               鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+               鈹� 澧炲姞搴撳瓨 鈹�     鈹� 鏇存柊棰嗙敤鐘舵�� 鈹�
+               鈹� (鍏ュ簱)   鈹�     鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+               鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+### 5. 涓氬姟绾︽潫
+
+1. **棰嗙敤绾︽潫**:
+   - 棰嗙敤浜轰粠绯荤粺鐢ㄦ埛琛ㄩ�夋嫨
+   - 鏂板棰嗙敤璁板綍鐘舵�佷负"寰呭鎵�"
+   - 鍙湁寰呭鎵圭姸鎬佹墠鑳戒慨鏀�/鍒犻櫎
+
+2. **瀹℃壒绾︽潫**:
+   - 鍙湁寰呭鎵圭姸鎬佹墠鑳藉鎵�
+   - 瀹℃壒閫氳繃鍚庤嚜鍔ㄦ墸鍑忓簱瀛�
+   - 瀹℃壒椹冲洖涓嶆墸鍑忓簱瀛�
+
+3. **褰掕繕绾︽潫**:
+   - 褰掕繕鏁伴噺涓嶈兘瓒呰繃鍓╀綑鍙綊杩樻暟閲�
+   - 棰嗙敤璁板綍蹇呴』瀹℃壒閫氳繃鎵嶈兘褰掕繕
+   - 褰掕繕鐩存帴澧炲姞搴撳瓨锛屾棤闇�瀹℃壒
+   - 褰掕繕浜轰粠绯荤粺鐢ㄦ埛琛ㄩ�夋嫨
+
+### 6. 棰嗙敤鍗曞彿鐢熸垚瑙勫垯
+
+```
+鏍煎紡锛歀Y + 骞存湀鏃� + 4浣嶅簭鍙�
+绀轰緥锛歀Y202605260001
+```
+
+### 7. 鏋氫妇绫诲瀷鎵╁睍
+
+**鍏ュ簱绫诲瀷** (StockInQualifiedRecordTypeEnum):
+- `PRODUCT_BORROW_RETURN("23", "浜у搧褰掕繕鍏ュ簱")`
+
+**鍑哄簱绫诲瀷** (StockOutQualifiedRecordTypeEnum):
+- `PRODUCT_BORROW("16", "浜у搧棰嗙敤鍑哄簱")`
+- `PRODUCT_RETURN("17", "浜у搧褰掕繕鍏ュ簱")`
+
+### 8. 鏂囦欢缁撴瀯
+
+```
+com.ruoyi.stock/
+鈹溾攢鈹� controller/
+鈹�   鈹溾攢鈹� ProductBorrowController.java
+鈹�   鈹斺攢鈹� ProductBorrowReturnController.java
+鈹溾攢鈹� service/
+鈹�   鈹溾攢鈹� ProductBorrowService.java
+鈹�   鈹溾攢鈹� ProductBorrowReturnService.java
+鈹�   鈹斺攢鈹� impl/
+鈹�       鈹溾攢鈹� ProductBorrowServiceImpl.java
+鈹�       鈹斺攢鈹� ProductBorrowReturnServiceImpl.java
+鈹溾攢鈹� mapper/
+鈹�   鈹溾攢鈹� ProductBorrowMapper.java
+鈹�   鈹斺攢鈹� ProductBorrowReturnMapper.java
+鈹溾攢鈹� pojo/
+鈹�   鈹溾攢鈹� ProductBorrow.java
+鈹�   鈹斺攢鈹� ProductBorrowReturn.java
+鈹斺攢鈹� dto/
+    鈹溾攢鈹� ProductBorrowDto.java
+    鈹斺攢鈹� ProductBorrowReturnDto.java
+
+resources/mapper/stock/
+鈹溾攢鈹� ProductBorrowMapper.xml
+鈹斺攢鈹� ProductBorrowReturnMapper.xml
+```
+
+### 9. 鏁版嵁搴撹〃
+
+SQL鑴氭湰浣嶇疆: `doc/20260526_product_borrow_tables.sql`
diff --git "a/doc/\344\272\247\345\223\201\351\242\206\347\224\250\345\275\222\350\277\230\346\250\241\345\235\227-\345\211\215\347\253\257\345\257\271\346\216\245\346\226\207\346\241\243.md" "b/doc/\344\272\247\345\223\201\351\242\206\347\224\250\345\275\222\350\277\230\346\250\241\345\235\227-\345\211\215\347\253\257\345\257\271\346\216\245\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..e22c8f2
--- /dev/null
+++ "b/doc/\344\272\247\345\223\201\351\242\206\347\224\250\345\275\222\350\277\230\346\250\241\345\235\227-\345\211\215\347\253\257\345\257\271\346\216\245\346\226\207\346\241\243.md"
@@ -0,0 +1,696 @@
+# 浜у搧棰嗙敤褰掕繕妯″潡 - 鍓嶇瀵规帴鏂囨。
+
+## 涓�銆佹ā鍧楁杩�
+
+浜у搧棰嗙敤褰掕繕妯″潡鐢ㄤ簬绠$悊浜у搧鐨勯鐢ㄥ拰褰掕繕娴佺▼锛�
+- **棰嗙敤**锛氭柊澧為鐢ㄦ椂鑷姩瀹屾垚鍑哄簱鎿嶄綔锛岃嚜鍔ㄥ鎵归�氳繃锛屽彇娑堝鎵规祦绋�
+- **褰掕繕**锛氭柊澧炲綊杩樻椂鑷姩瀹屾垚鍏ュ簱鎿嶄綔锛岃嚜鍔ㄥ鎵归�氳繃
+
+**娉ㄦ剰**锛氭墍鏈夋帴鍙g粺涓�浣跨敤 POST 鏂瑰紡
+
+---
+
+## 浜屻�丄PI 鎺ュ彛璇存槑
+
+### 1. 浜у搧棰嗙敤鎺ュ彛
+
+#### 1.1 鍒嗛〉鏌ヨ棰嗙敤璁板綍
+
+**璇锋眰**
+```
+POST /productBorrow/listPage
+Content-Type: application/json
+```
+
+**璇锋眰浣�**
+```json
+{
+  "current": 1,                   // 褰撳墠椤电爜锛岄粯璁�1
+  "size": 10,                     // 姣忛〉鏉℃暟锛岄粯璁�10
+  "borrowNo": "LY202605260001",   // 閫夊~锛岄鐢ㄥ崟鍙凤紝妯$硦鏌ヨ
+  "productModelId": 100,          // 閫夊~锛屼骇鍝佽鏍糏D
+  "topParentProductId": 277,      // 閫夊~锛岄《閮ㄧ埗浜у搧ID锛堜骇鍝佸垎绫籌D锛夛紝鐢ㄤ簬绛涢�変骇鍝佸垎绫绘爲
+  "model": "M6",                  // 閫夊~锛岃鏍煎瀷鍙凤紝妯$硦鏌ヨ
+  "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    // 蹇呭~锛岄鐢ㄨ褰旾D
+}
+```
+
+**鍝嶅簲绀轰緥**
+```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,          // 蹇呭~锛屼骇鍝佽鏍糏D
+  "batchNo": "20260526-ABC001-001", // 閫夊~锛屾壒鍙凤紝涓嶅~鍒欎娇鐢ㄩ粯璁ゆ壒鍙�
+  "borrowQuantity": 10.0000,      // 蹇呭~锛岄鐢ㄦ暟閲�
+  "borrowerId": 1,                // 蹇呭~锛岄鐢ㄤ汉ID锛堢郴缁熺敤鎴稩D锛�
+  "borrowerName": "寮犱笁",          // 蹇呭~锛岄鐢ㄤ汉濮撳悕
+  "expectedReturnTime": "2026-06-26 10:00:00", // 閫夊~锛岄璁″綊杩樻椂闂�
+  "remark": "椤圭洰浣跨敤"             // 閫夊~锛屽娉�
+}
+```
+
+**鍝嶅簲绀轰緥**
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛"
+}
+```
+
+**鑷姩鎵ц鐨勬搷浣�**锛�
+- 鑷姩鐢熸垚棰嗙敤鍗曞彿锛堟牸寮忥細LY + 骞存湀鏃� + 4浣嶅簭鍙凤級
+- 鑷姩鍒涘缓鍑哄簱璁板綍
+- 鑷姩瀹℃牳閫氳繃
+- 鑷姩鎵e噺搴撳瓨
+- 鐘舵�佺洿鎺ヨ涓�"宸查�氳繃"
+
+#### 1.4 淇敼棰嗙敤璁板綍
+
+**璇锋眰**
+```
+POST /productBorrow/update
+Content-Type: application/json
+```
+
+**璇锋眰浣�**
+```json
+{
+  "id": 1,                        // 蹇呭~锛岄鐢ㄨ褰旾D
+  "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],           // 蹇呭~锛岃瀹℃壒鐨勮褰旾D鏁扮粍
+  "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,                  // 閫夊~锛岄鐢ㄨ褰旾D
+  "productModelId": 100,          // 閫夊~锛屼骇鍝佽鏍糏D
+  "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       // 蹇呭~锛岄鐢ㄨ褰旾D
+}
+```
+
+**鍝嶅簲绀轰緥**
+```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,                 // 蹇呭~锛岄鐢ㄨ褰旾D
+  "returnQuantity": 5.0000,      // 蹇呭~锛屽綊杩樻暟閲�
+  "returnerId": 2,               // 蹇呭~锛屽綊杩樹汉ID锛堢郴缁熺敤鎴稩D锛�
+  "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锛堜骇鍝佸垎绫籌D锛�
+  "productName": "铻轰笣鍒�",          // 閫夊~锛屼骇鍝佸悕绉帮紝妯$硦鏌ヨ
+  "model": "M6",                   // 閫夊~锛岃鏍煎瀷鍙凤紝妯$硦鏌ヨ
+  "batchNo": "20260526"            // 閫夊~锛屾壒鍙凤紝妯$硦鏌ヨ
+}
+```
+
+**鍙傛暟璇存槑**锛�
+
+| 鍙傛暟鍚� | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|--------|------|------|------|
+| current | Integer | 鍚� | 褰撳墠椤电爜锛岄粯璁�1 |
+| size | Integer | 鍚� | 姣忛〉鏉℃暟锛岄粯璁�10 |
+| topParentProductId | Long | 鏄� | 椤堕儴鐖朵骇鍝両D锛堜骇鍝佸垎绫绘爲鏍硅妭鐐笽D锛夛紝鐢ㄤ簬绛涢�変骇鍝佸垎绫� |
+| 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锛�
+- 璁$畻鍏紡锛氶鐢ㄦ暟閲� - 宸插綊杩樻暟閲�
+- 鎸変骇鍝佽鏍糏D + 鎵瑰彿鍒嗙粍缁熻
+
+**浣跨敤鍦烘櫙**锛�
+
+1. **棰嗙敤閫夋嫨浜у搧**锛�
+   - 璋冪敤姝ゆ帴鍙f煡璇㈠簱瀛�
+   - 鐢ㄦ埛閫夋嫨浜у搧鍜屾壒鍙�
+   - 鏄剧ず鍙鐢ㄦ暟閲忎緵鍙傝��
+   - 棰嗙敤鏃朵紶閫� `productModelId` 鍜� `batchNo`
+
+2. **鍓嶇鏄剧ず寤鸿**锛�
+   - 鍒楄〃鏄剧ず锛氫骇鍝佸悕绉般�佽鏍煎瀷鍙枫�佹壒鍙枫�佸簱瀛樻暟閲忋�佽棰嗙敤閲忋�佸彲棰嗙敤閲�
+   - 鍙鐢ㄦ暟閲忎负0鎴栬礋鏁扮殑璁板綍鍙互鏍囩孩鎴栫鐢ㄩ�夋嫨
+   - 鏀寔鎸変骇鍝佸悕绉般�佽鏍煎瀷鍙枫�佹壒鍙锋悳绱�
+
+**鍓嶇椤甸潰绀轰緥**锛�
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�  浜у搧搴撳瓨鍜岄鐢ㄩ噺鏌ヨ                                                     鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�  浜у搧鍒嗙被: [閫夋嫨鍒嗙被 鈻糫  浜у搧鍚嶇О: [____]  瑙勬牸鍨嬪彿: [____]  [鏌ヨ]       鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                                                                          鈹�
+鈹�  鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹備骇鍝佸悕绉扳攤瑙勬牸鍨嬪彿鈹�  鎵瑰彿    鈹傚簱瀛樻暟閲忊攤琚鐢ㄩ噺鈹傚彲棰嗙敤閲忊攤  鎿嶄綔  鈹�   鈹�
+鈹�  鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹傝灪涓濆垁  鈹�  M6    鈹�20260526- 鈹�  100   鈹�   10   鈹�   90   鈹俒棰嗙敤] 鈹�   鈹�
+鈹�  鈹�        鈹�        鈹侫BC001-001鈹�        鈹�        鈹�        鈹�        鈹�   鈹�
+鈹�  鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹傝灪涓濆垁  鈹�  M6    鈹�20260527- 鈹�   50   鈹�    0   鈹�   50   鈹俒棰嗙敤] 鈹�   鈹�
+鈹�  鈹�        鈹�        鈹侫BC001-001鈹�        鈹�        鈹�        鈹�        鈹�   鈹�
+鈹�  鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹尖攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�  鈹傛壋鎵�    鈹�  10瀵�  鈹� NULL     鈹�   30   鈹�    5   鈹�   25   鈹俒棰嗙敤] 鈹�   鈹�
+鈹�  鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹粹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�   鈹�
+鈹�                                                                          鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 涓夈�佹帴鍙f眹鎬昏〃
+
+### 浜у搧棰嗙敤鎺ュ彛
+
+| 鎺ュ彛鍚嶇О | 璇锋眰璺緞 | 璇锋眰鏂瑰紡 | 璇存槑 |
+|----------|----------|----------|------|
+| 鍒嗛〉鏌ヨ | /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 | 宸查�氳繃 | 瀹℃壒閫氳繃锛屽凡鎵e噺搴撳瓨锛屽彲杩涜褰掕繕鎿嶄綔 |
+| 2 | 宸查┏鍥� | 瀹℃壒鏈�氳繃锛屼笉鎵e噺搴撳瓨 |
+
+**娉ㄦ剰**锛氭柊澧為鐢ㄦ椂鐩存帴鑷姩瀹℃壒閫氳繃锛岀姸鎬佷负1
+
+### 2. 褰掕繕鐘舵�� (status)
+
+| 鍊� | 鍚嶇О | 璇存槑 |
+|----|------|------|
+| 0 | 鏈綊杩� | 灏氭湭褰掕繕浠讳綍鏁伴噺 |
+| 1 | 閮ㄥ垎褰掕繕 | 宸插綊杩橀儴鍒嗘暟閲� |
+| 2 | 宸插叏閮ㄥ綊杩� | 宸插綊杩樺叏閮ㄦ暟閲忥紝涓嶈兘鍐嶅綊杩� |
+
+---
+
+## 浜斻�佷笟鍔℃祦绋嬪浘
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      棰嗙敤娴佺▼锛堣嚜鍔ㄥ嚭搴擄級                     鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                                                             鈹�
+鈹�   鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�       鈹�
+鈹�   鈹� 鏂板棰嗙敤   鈹傗攢鈹�鈹�>鈹� 鑷姩鍑哄簱   鈹傗攢鈹�鈹�>鈹� 鐘舵�佽涓�   鈹�       鈹�
+鈹�   鈹� 璁板綍       鈹�    鈹� 鎵e噺搴撳瓨   鈹�    鈹� 宸查�氳繃     鈹�       鈹�
+鈹�   鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�       鈹�
+鈹�                           鈹�                                 鈹�
+鈹�                           鈻�                                 鈹�
+鈹�                    鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�                          鈹�
+鈹�                    鈹� 鍒涘缓鍑哄簱   鈹�                          鈹�
+鈹�                    鈹� 璁板綍       鈹�                          鈹�
+鈹�                    鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�                          鈹�
+鈹�                                                             鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                      褰掕繕娴佺▼锛堣嚜鍔ㄥ叆搴擄級                     鈹�
+鈹溾攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹�                                                             鈹�
+鈹�   鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�       鈹�
+鈹�   鈹� 閫夋嫨棰嗙敤   鈹傗攢鈹�鈹�>鈹� 濉啓褰掕繕   鈹傗攢鈹�鈹�>鈹� 鑷姩鍏ュ簱   鈹�       鈹�
+鈹�   鈹� 璁板綍       鈹�    鈹� 淇℃伅       鈹�    鈹� 澧炲姞搴撳瓨   鈹�       鈹�
+鈹�   鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�    鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�       鈹�
+鈹�                                              鈹�              鈹�
+鈹�                                              鈻�              鈹�
+鈹�                                        鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�       鈹�
+鈹�                                        鈹� 鏇存柊棰嗙敤   鈹�       鈹�
+鈹�                                        鈹� 鐘舵��       鈹�       鈹�
+鈹�                                        鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�       鈹�
+鈹�                                                             鈹�
+鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+---
+
+## 鍏�佹敞鎰忎簨椤�
+
+### 1. 鏁版嵁鏍¢獙
+
+**鍓嶇鏍¢獙**锛�
+- 棰嗙敤鏁伴噺蹇呴』澶т簬0
+- 褰掕繕鏁伴噺涓嶈兘瓒呰繃鍓╀綑鍙綊杩樻暟閲�
+- 棰嗙敤浜恒�佸綊杩樹汉蹇呴』閫夋嫨绯荤粺鐢ㄦ埛
+
+**鍚庣鏍¢獙**锛�
+- 棰嗙敤鏃舵鏌ュ簱瀛樻槸鍚﹀厖瓒�
+- 褰掕繕鏁伴噺涓嶈兘瓒呰繃鍓╀綑鏁伴噺
+- 宸插叏閮ㄥ綊杩樼殑璁板綍涓嶈兘鍐嶅綊杩�
+
+### 2. 鐢ㄦ埛閫夋嫨
+
+棰嗙敤浜哄拰褰掕繕浜洪渶瑕佷粠绯荤粺鐢ㄦ埛琛ㄩ�夋嫨锛岃皟鐢ㄧ郴缁熺敤鎴锋帴鍙o細
+```
+POST /system/user/list
+```
+
+### 3. 浜у搧瑙勬牸閫夋嫨
+
+閫夋嫨浜у搧瑙勬牸鏃讹紝鍙互璋冪敤浜у搧鎺ュ彛锛�
+```
+POST /basic/product/pageModel
+```
+
+### 4. 搴撳瓨鑱斿姩
+
+- **棰嗙敤**锛氭柊澧炴椂鑷姩鍑哄簱锛堣皟鐢ㄥ簱瀛樻墸鍑忔帴鍙o級
+- **褰掕繕**锛氭柊澧炴椂鑷姩鍏ュ簱锛堣皟鐢ㄥ簱瀛樺鍔犳帴鍙o級
+
+---
+
+## 涓冦�侀敊璇爜璇存槑
+
+| 閿欒淇℃伅 | 鍘熷洜 | 瑙e喅鏂规 |
+|----------|------|----------|
+| 棰嗙敤璁板綍涓嶅瓨鍦� | ID鏃犳晥 | 妫�鏌ヨ褰旾D |
+| 浜у搧搴撳瓨涓嶅瓨鍦� | 搴撳瓨涓虹┖ | 妫�鏌ュ簱瀛樻暟鎹� |
+| 搴撳瓨涓嶈冻鏃犳硶鍑哄簱 | 搴撳瓨涓嶈冻 | 妫�鏌ュ簱瀛樻暟閲� |
+| 璇ラ鐢ㄨ褰曞凡鍏ㄩ儴褰掕繕 | 褰掕繕瀹屾垚 | 鏃犻渶鍐嶆褰掕繕 |
+| 褰掕繕鏁伴噺涓嶈兘澶т簬鍓╀綑鍙綊杩樻暟閲� | 鏁伴噺瓒呴檺 | 妫�鏌ュ墿浣欏彲褰掕繕鏁伴噺 |
+
+---
+
+## 鍏�佹祴璇曠敤渚�
+
+### 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
+}
+```
diff --git a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
index 2b9eb40..222a9a4 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
@@ -89,9 +89,6 @@
 
     @Override
     public int addOrEditProduct(ProductDto productDto) {
-        if (ObjectUtils.isEmpty(productDto.getParentId())) {
-            throw new IllegalArgumentException("璇烽�夋嫨鐖惰妭鐐�");
-        }
         String productName = StringUtils.trim(productDto.getProductName());
         if (StringUtils.isEmpty(productName)) {
             throw new IllegalArgumentException("浜у搧鍚嶇О涓嶈兘涓虹┖");
@@ -100,15 +97,17 @@
         checkProductNameUnique(productDto.getParentId(), productName, productDto.getId());
         if (productDto.getId() == null) {
             // 鏂板浜у搧閫昏緫
-            // 妫�鏌ョ埗鑺傜偣鏄惁瀛樺湪锛堝彲閫夛紝鏍规嵁涓氬姟闇�姹傦級
-            Product parent = productMapper.selectById(productDto.getParentId());
-            if (parent == null) {
-                throw new IllegalArgumentException("鐖惰妭鐐逛笉瀛樺湪锛屾棤娉曟坊鍔犲瓙浜у搧");
+            // 濡傛灉鏈夌埗鑺傜偣锛屾鏌ョ埗鑺傜偣鏄惁瀛樺湪
+            if (productDto.getParentId() != null) {
+                Product parent = productMapper.selectById(productDto.getParentId());
+                if (parent == null) {
+                    throw new IllegalArgumentException("鐖惰妭鐐逛笉瀛樺湪锛屾棤娉曟坊鍔犲瓙浜у搧");
+                }
             }
             return productMapper.insert(productDto);
         } else {
             // 缂栬緫浜у搧閫昏緫
-            // 妫�鏌ヤ骇鍝佹槸鍚﹀瓨鍦紙鍙�夛紝鏍规嵁涓氬姟闇�姹傦級
+            // 妫�鏌ヤ骇鍝佹槸鍚﹀瓨鍦�
             Product existingProduct = productMapper.selectById(productDto.getId());
             if (existingProduct == null) {
                 throw new IllegalArgumentException("瑕佺紪杈戠殑浜у搧涓嶅瓨鍦�");
@@ -119,10 +118,15 @@
 
     private void checkProductNameUnique(Long parentId, String productName, Long currentId) {
         LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(Product::getParentId, parentId)
-                .eq(Product::getProductName, productName)
-                .ne(currentId != null, Product::getId, currentId)
-                .last("limit 1");
+        queryWrapper.eq(Product::getProductName, productName)
+                .ne(currentId != null, Product::getId, currentId);
+        // 澶勭悊 parentId 涓� null 鐨勬儏鍐�
+        if (parentId == null) {
+            queryWrapper.isNull(Product::getParentId);
+        } else {
+            queryWrapper.eq(Product::getParentId, parentId);
+        }
+        queryWrapper.last("limit 1");
         Product duplicateProduct = productMapper.selectOne(queryWrapper);
         if (duplicateProduct != null) {
             throw new IllegalArgumentException("瀵瑰簲鐨�" + productName + "宸茬粡瀛樺湪");
diff --git a/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
index 7c149bc..0a1200a 100644
--- a/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
@@ -19,7 +19,8 @@
     RETURN_HE_IN("14", "閿�鍞��璐�-鍚堟牸鍏ュ簱"),
     RETURN_UNSTOCK_IN("15", "閿�鍞��璐�-涓嶅悎鏍煎叆搴�"),
     PICK_RETURN_IN("20", "棰嗘枡閫�鏂�-鍚堟牸鍏ュ簱"),
-    FEED_RETURN_IN("22", "鐢熶骇閫�鏂�-鍚堟牸鍏ュ簱");
+    FEED_RETURN_IN("22", "鐢熶骇閫�鏂�-鍚堟牸鍏ュ簱"),
+    PRODUCT_BORROW_RETURN("23", "浜у搧褰掕繕鍏ュ簱");
 
 
 
diff --git a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
index ccc6d78..a810b31 100644
--- a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
@@ -12,7 +12,9 @@
     CUSTOMIZATION_UNSTOCK_OUT("10", "涓嶅悎鏍艰嚜瀹氫箟鍑哄簱"),
     SALE_SHIP_STOCK_OUT("13", "閿�鍞�-鍙戣揣鍑哄簱"),
     PICK_STOCK_OUT("14", "鐢熶骇棰嗘枡鍑哄簱"),
-    FEED_STOCK_OUT("15", "鐢熶骇琛ユ枡鍑哄簱");
+    FEED_STOCK_OUT("15", "鐢熶骇琛ユ枡鍑哄簱"),
+    PRODUCT_BORROW("16", "浜у搧棰嗙敤鍑哄簱"),
+    PRODUCT_RETURN("17", "浜у搧褰掕繕鍏ュ簱");
 
     private final String code;
     private final String value;
diff --git a/src/main/java/com/ruoyi/stock/controller/ProductBorrowController.java b/src/main/java/com/ruoyi/stock/controller/ProductBorrowController.java
new file mode 100644
index 0000000..03954ff
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/controller/ProductBorrowController.java
@@ -0,0 +1,96 @@
+package com.ruoyi.stock.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.stock.dto.ProductBorrowDto;
+import com.ruoyi.stock.service.ProductBorrowService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 浜у搧棰嗙敤 Controller
+ *
+ * @author ruoyi
+ */
+@Tag(name = "浜у搧棰嗙敤")
+@RestController
+@RequestMapping("/productBorrow")
+@RequiredArgsConstructor
+public class ProductBorrowController {
+
+    private final ProductBorrowService productBorrowService;
+
+    @PostMapping("/listPage")
+    @Log(title = "浜у搧棰嗙敤-鍒楄〃", businessType = BusinessType.OTHER)
+    @Operation(summary = "鍒嗛〉鏌ヨ棰嗙敤璁板綍")
+    public AjaxResult listPage(@RequestBody Page<ProductBorrowDto> page, @RequestBody ProductBorrowDto dto) {
+        IPage<ProductBorrowDto> result = productBorrowService.listPage(page, dto);
+        return AjaxResult.success(result);
+    }
+
+    @PostMapping("/getDetail")
+    @Log(title = "浜у搧棰嗙敤-璇︽儏", businessType = BusinessType.OTHER)
+    @Operation(summary = "鏌ヨ棰嗙敤璁板綍璇︽儏")
+    public AjaxResult getDetail(@RequestBody ProductBorrowDto dto) {
+        ProductBorrowDto result = productBorrowService.getDetailById(dto.getId());
+        return AjaxResult.success(result);
+    }
+
+    @PostMapping("/add")
+    @Log(title = "浜у搧棰嗙敤-鏂板", businessType = BusinessType.INSERT)
+    @Operation(summary = "鏂板棰嗙敤璁板綍")
+    public AjaxResult add(@RequestBody ProductBorrowDto dto) {
+        productBorrowService.add(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/update")
+    @Log(title = "浜у搧棰嗙敤-淇敼", businessType = BusinessType.UPDATE)
+    @Operation(summary = "淇敼棰嗙敤璁板綍")
+    public AjaxResult update(@RequestBody ProductBorrowDto dto) {
+        productBorrowService.update(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/delete")
+    @Log(title = "浜у搧棰嗙敤-鍒犻櫎", businessType = BusinessType.DELETE)
+    @Operation(summary = "鍒犻櫎棰嗙敤璁板綍")
+    public AjaxResult delete(@RequestBody List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        productBorrowService.delete(ids);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/approve")
+    @Log(title = "浜у搧棰嗙敤-瀹℃壒", businessType = BusinessType.UPDATE)
+    @Operation(summary = "鎵归噺瀹℃壒棰嗙敤璁板綍")
+    public AjaxResult approve(@RequestBody ProductBorrowDto dto) {
+        if (CollectionUtils.isEmpty(dto.getIds())) {
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        productBorrowService.batchApprove(dto.getIds(), dto.getApprovalStatusParam());
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/reAudit")
+    @Log(title = "浜у搧棰嗙敤-鍙嶅", businessType = BusinessType.UPDATE)
+    @Operation(summary = "鎵归噺鍙嶅棰嗙敤璁板綍")
+    public AjaxResult reAudit(@RequestBody ProductBorrowDto dto) {
+        if (CollectionUtils.isEmpty(dto.getIds())) {
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        // 鍙嶅锛氬皢宸查�氳繃鐨勮褰曠姸鎬佹敼鍥炲緟瀹℃壒锛岄渶瑕佽�冭檻鏄惁宸茬粡褰掕繕
+        // 姝ゅ鏆備笉瀹炵幇鍙嶅閫昏緫锛屽闇�瑕佸彲浠ユ墿灞�
+        return AjaxResult.error("鍙嶅鍔熻兘鏆傛湭瀹炵幇");
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/stock/controller/ProductBorrowReturnController.java b/src/main/java/com/ruoyi/stock/controller/ProductBorrowReturnController.java
new file mode 100644
index 0000000..7e7b88f
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/controller/ProductBorrowReturnController.java
@@ -0,0 +1,51 @@
+package com.ruoyi.stock.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.stock.dto.ProductBorrowReturnDto;
+import com.ruoyi.stock.service.ProductBorrowReturnService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 浜у搧褰掕繕 Controller
+ *
+ * @author ruoyi
+ */
+@Tag(name = "浜у搧褰掕繕")
+@RestController
+@RequestMapping("/productBorrowReturn")
+@RequiredArgsConstructor
+public class ProductBorrowReturnController {
+
+    private final ProductBorrowReturnService productBorrowReturnService;
+
+    @PostMapping("/listPage")
+    @Log(title = "浜у搧褰掕繕-鍒楄〃", businessType = BusinessType.OTHER)
+    @Operation(summary = "鍒嗛〉鏌ヨ褰掕繕璁板綍")
+    public AjaxResult listPage(@RequestBody Page<ProductBorrowReturnDto> page, ProductBorrowReturnDto dto) {
+        IPage<ProductBorrowReturnDto> result = productBorrowReturnService.listPage(page, dto);
+        return AjaxResult.success(result);
+    }
+
+    @PostMapping("/listByBorrowId")
+    @Log(title = "浜у搧褰掕繕-鏍规嵁棰嗙敤ID鏌ヨ", businessType = BusinessType.OTHER)
+    @Operation(summary = "鏌ヨ鏌愪釜棰嗙敤璁板綍鐨勫綊杩樿褰�")
+    public AjaxResult listByBorrowId(@RequestBody Page<ProductBorrowReturnDto> page, @RequestBody ProductBorrowReturnDto dto) {
+        IPage<ProductBorrowReturnDto> result = productBorrowReturnService.listByBorrowId(page, dto.getBorrowId());
+        return AjaxResult.success(result);
+    }
+
+    @PostMapping("/add")
+    @Log(title = "浜у搧褰掕繕-鏂板", businessType = BusinessType.INSERT)
+    @Operation(summary = "鏂板褰掕繕璁板綍锛堢洿鎺ュ鍔犲簱瀛橈紝鏃犻渶瀹℃壒锛�")
+    public AjaxResult add(@RequestBody ProductBorrowReturnDto dto) {
+        productBorrowReturnService.add(dto);
+        return AjaxResult.success();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
index a0a9fa5..d6d72c0 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
@@ -147,4 +147,10 @@
     public R getByModelId(Long productModelId) {
         return R.ok(stockInventoryService.getByModelId(productModelId));
     }
+
+    @PostMapping("/pageStockAndBorrow")
+    @Operation(summary = "鍒嗛〉鏌ヨ浜у搧搴撳瓨鍜岄鐢ㄩ噺")
+    public R pageStockAndBorrow(@RequestBody Page page, @RequestBody StockInventoryDto stockInventoryDto) {
+        return R.ok(stockInventoryService.pageStockAndBorrow(page, stockInventoryDto));
+    }
 }
diff --git a/src/main/java/com/ruoyi/stock/dto/ProductBorrowDto.java b/src/main/java/com/ruoyi/stock/dto/ProductBorrowDto.java
new file mode 100644
index 0000000..7f34e4c
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/dto/ProductBorrowDto.java
@@ -0,0 +1,49 @@
+package com.ruoyi.stock.dto;
+
+import com.ruoyi.stock.pojo.ProductBorrow;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * 浜у搧棰嗙敤DTO
+ *
+ * @author ruoyi
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Schema(description = "浜у搧棰嗙敤DTO")
+public class ProductBorrowDto extends ProductBorrow {
+
+    @Schema(description = "浜у搧鍚嶇О")
+    private String productName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿")
+    private String model;
+
+    @Schema(description = "浜у搧缂栫爜")
+    private String productCode;
+
+    @Schema(description = "鍗曚綅")
+    private String unit;
+
+    @Schema(description = "鍓╀綑鍙綊杩樻暟閲�")
+    private java.math.BigDecimal remainingQuantity;
+
+    @Schema(description = "瀹℃壒鐘舵�佸悕绉�")
+    private String approvalStatusName;
+
+    @Schema(description = "褰掕繕鐘舵�佸悕绉�")
+    private String statusName;
+
+    @Schema(description = "鎵归噺瀹℃壒ID鍒楄〃")
+    private List<Long> ids;
+
+    @Schema(description = "瀹℃壒鐘舵�侊紙鐢ㄤ簬瀹℃壒鎿嶄綔锛�")
+    private Integer approvalStatusParam;
+
+    @Schema(description = "椤堕儴鐖朵骇鍝両D锛堜骇鍝佸垎绫籌D锛�")
+    private Long topParentProductId;
+}
diff --git a/src/main/java/com/ruoyi/stock/dto/ProductBorrowReturnDto.java b/src/main/java/com/ruoyi/stock/dto/ProductBorrowReturnDto.java
new file mode 100644
index 0000000..367875f
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/dto/ProductBorrowReturnDto.java
@@ -0,0 +1,38 @@
+package com.ruoyi.stock.dto;
+
+import com.ruoyi.stock.pojo.ProductBorrowReturn;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 浜у搧褰掕繕DTO
+ *
+ * @author ruoyi
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Schema(description = "浜у搧褰掕繕DTO")
+public class ProductBorrowReturnDto extends ProductBorrowReturn {
+
+    @Schema(description = "棰嗙敤鍗曞彿")
+    private String borrowNo;
+
+    @Schema(description = "浜у搧鍚嶇О")
+    private String productName;
+
+    @Schema(description = "瑙勬牸鍨嬪彿")
+    private String model;
+
+    @Schema(description = "浜у搧缂栫爜")
+    private String productCode;
+
+    @Schema(description = "鍗曚綅")
+    private String unit;
+
+    @Schema(description = "棰嗙敤鏁伴噺")
+    private java.math.BigDecimal borrowQuantity;
+
+    @Schema(description = "棰嗙敤浜哄鍚�")
+    private String borrowerName;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java b/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
index 33b7599..5fa747a 100644
--- a/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
+++ b/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
@@ -80,4 +80,16 @@
 
     @Schema(description = "浜у搧id")
     private Long productId;
+
+    @Schema(description = "琚鐢ㄦ暟閲忥紙鏈綊杩橈級")
+    private BigDecimal borrowedQuantity;
+
+    @Schema(description = "鍙鐢ㄦ暟閲忥紙搴撳瓨 - 琚鐢級")
+    private BigDecimal availableQuantity;
+
+    @Schema(description = "浜у搧缂栫爜")
+    private String productCode;
+
+    @Schema(description = "瀹℃壒鐘舵�侊紙鐢ㄤ簬鍏ュ簱/鍑哄簱璁板綍锛�")
+    private Integer approvalStatus;
 }
diff --git a/src/main/java/com/ruoyi/stock/mapper/ProductBorrowMapper.java b/src/main/java/com/ruoyi/stock/mapper/ProductBorrowMapper.java
new file mode 100644
index 0000000..458a97d
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/mapper/ProductBorrowMapper.java
@@ -0,0 +1,28 @@
+package com.ruoyi.stock.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.stock.dto.ProductBorrowDto;
+import com.ruoyi.stock.pojo.ProductBorrow;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 浜у搧棰嗙敤 Mapper 鎺ュ彛
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface ProductBorrowMapper extends BaseMapper<ProductBorrow> {
+
+    /**
+     * 鍒嗛〉鏌ヨ棰嗙敤璁板綍
+     */
+    IPage<ProductBorrowDto> listPage(Page<ProductBorrowDto> page, @Param("ew") ProductBorrowDto dto);
+
+    /**
+     * 鏍规嵁ID鏌ヨ璇︽儏
+     */
+    ProductBorrowDto selectDetailById(@Param("id") Long id);
+}
diff --git a/src/main/java/com/ruoyi/stock/mapper/ProductBorrowReturnMapper.java b/src/main/java/com/ruoyi/stock/mapper/ProductBorrowReturnMapper.java
new file mode 100644
index 0000000..3039239
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/mapper/ProductBorrowReturnMapper.java
@@ -0,0 +1,28 @@
+package com.ruoyi.stock.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.stock.dto.ProductBorrowReturnDto;
+import com.ruoyi.stock.pojo.ProductBorrowReturn;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 浜у搧褰掕繕璁板綍 Mapper 鎺ュ彛
+ *
+ * @author ruoyi
+ */
+@Mapper
+public interface ProductBorrowReturnMapper extends BaseMapper<ProductBorrowReturn> {
+
+    /**
+     * 鍒嗛〉鏌ヨ褰掕繕璁板綍
+     */
+    IPage<ProductBorrowReturnDto> listPage(Page<ProductBorrowReturnDto> page, @Param("ew") ProductBorrowReturnDto dto);
+
+    /**
+     * 鏍规嵁棰嗙敤ID鏌ヨ褰掕繕璁板綍
+     */
+    IPage<ProductBorrowReturnDto> listByBorrowId(Page<ProductBorrowReturnDto> page, @Param("borrowId") Long borrowId);
+}
diff --git a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
index dbc271b..7fc1c41 100644
--- a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
+++ b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
@@ -58,4 +58,9 @@
     List<StockInventory> getByModelId(@Param("productModelId") Long productModelId);
 
     IPage<StockInventoryDto> getBatchNoQty(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
+
+    /**
+     * 鍒嗛〉鏌ヨ浜у搧搴撳瓨鍜岄鐢ㄩ噺
+     */
+    IPage<StockInventoryDto> pageStockAndBorrow(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
 }
diff --git a/src/main/java/com/ruoyi/stock/pojo/ProductBorrow.java b/src/main/java/com/ruoyi/stock/pojo/ProductBorrow.java
new file mode 100644
index 0000000..87b525d
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/pojo/ProductBorrow.java
@@ -0,0 +1,96 @@
+package com.ruoyi.stock.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 浜у搧棰嗙敤琛�
+ *
+ * @author ruoyi
+ */
+@Data
+@TableName("product_borrow")
+@Schema(name = "ProductBorrow瀵硅薄", description = "浜у搧棰嗙敤琛�")
+public class ProductBorrow implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "涓婚敭")
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "棰嗙敤鍗曞彿")
+    private String borrowNo;
+
+    @Schema(description = "浜у搧瑙勬牸ID")
+    private Long productModelId;
+
+    @Schema(description = "鎵瑰彿")
+    private String batchNo;
+
+    @Schema(description = "棰嗙敤鏁伴噺")
+    private BigDecimal borrowQuantity;
+
+    @Schema(description = "宸插綊杩樻暟閲�")
+    private BigDecimal returnedQuantity;
+
+    @Schema(description = "棰嗙敤浜篒D")
+    private Long borrowerId;
+
+    @Schema(description = "棰嗙敤浜哄鍚�")
+    private String borrowerName;
+
+    @Schema(description = "棰嗙敤鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime borrowTime;
+
+    @Schema(description = "棰勮褰掕繕鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime expectedReturnTime;
+
+    @Schema(description = "瀹℃壒鐘舵�侊紙0-寰呭鎵癸紝1-宸查�氳繃锛�2-宸查┏鍥烇級")
+    private Integer approvalStatus;
+
+    @Schema(description = "褰掕繕鐘舵�侊紙0-鏈綊杩橈紝1-閮ㄥ垎褰掕繕锛�2-宸插叏閮ㄥ綊杩橈級")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "绉熸埛ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @Schema(description = "閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @Schema(description = "鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @Schema(description = "鏇存柊浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @Schema(description = "鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+}
diff --git a/src/main/java/com/ruoyi/stock/pojo/ProductBorrowReturn.java b/src/main/java/com/ruoyi/stock/pojo/ProductBorrowReturn.java
new file mode 100644
index 0000000..3f7585d
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/pojo/ProductBorrowReturn.java
@@ -0,0 +1,72 @@
+package com.ruoyi.stock.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 浜у搧褰掕繕璁板綍琛�
+ *
+ * @author ruoyi
+ */
+@Data
+@TableName("product_borrow_return")
+@Schema(name = "ProductBorrowReturn瀵硅薄", description = "浜у搧褰掕繕璁板綍琛�")
+public class ProductBorrowReturn implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "涓婚敭")
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "棰嗙敤璁板綍ID")
+    private Long borrowId;
+
+    @Schema(description = "浜у搧瑙勬牸ID")
+    private Long productModelId;
+
+    @Schema(description = "鎵瑰彿")
+    private String batchNo;
+
+    @Schema(description = "褰掕繕鏁伴噺")
+    private BigDecimal returnQuantity;
+
+    @Schema(description = "褰掕繕浜篒D")
+    private Long returnerId;
+
+    @Schema(description = "褰掕繕浜哄鍚�")
+    private String returnerName;
+
+    @Schema(description = "褰掕繕鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime returnTime;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "绉熸埛ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @Schema(description = "閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @Schema(description = "鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+}
diff --git a/src/main/java/com/ruoyi/stock/service/ProductBorrowReturnService.java b/src/main/java/com/ruoyi/stock/service/ProductBorrowReturnService.java
new file mode 100644
index 0000000..139ba6d
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/service/ProductBorrowReturnService.java
@@ -0,0 +1,30 @@
+package com.ruoyi.stock.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.stock.dto.ProductBorrowReturnDto;
+import com.ruoyi.stock.pojo.ProductBorrowReturn;
+
+/**
+ * 浜у搧褰掕繕璁板綍 Service 鎺ュ彛
+ *
+ * @author ruoyi
+ */
+public interface ProductBorrowReturnService extends IService<ProductBorrowReturn> {
+
+    /**
+     * 鍒嗛〉鏌ヨ褰掕繕璁板綍
+     */
+    IPage<ProductBorrowReturnDto> listPage(Page<ProductBorrowReturnDto> page, ProductBorrowReturnDto dto);
+
+    /**
+     * 鏂板褰掕繕璁板綍锛堢洿鎺ュ鍔犲簱瀛橈紝鏃犻渶瀹℃壒锛�
+     */
+    Boolean add(ProductBorrowReturnDto dto);
+
+    /**
+     * 鏌ヨ鏌愪釜棰嗙敤璁板綍鐨勫綊杩樿褰�
+     */
+    IPage<ProductBorrowReturnDto> listByBorrowId(Page<ProductBorrowReturnDto> page, Long borrowId);
+}
diff --git a/src/main/java/com/ruoyi/stock/service/ProductBorrowService.java b/src/main/java/com/ruoyi/stock/service/ProductBorrowService.java
new file mode 100644
index 0000000..7f87951
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/service/ProductBorrowService.java
@@ -0,0 +1,52 @@
+package com.ruoyi.stock.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.stock.dto.ProductBorrowDto;
+import com.ruoyi.stock.pojo.ProductBorrow;
+
+import java.util.List;
+
+/**
+ * 浜у搧棰嗙敤 Service 鎺ュ彛
+ *
+ * @author ruoyi
+ */
+public interface ProductBorrowService extends IService<ProductBorrow> {
+
+    /**
+     * 鍒嗛〉鏌ヨ棰嗙敤璁板綍
+     */
+    IPage<ProductBorrowDto> listPage(Page<ProductBorrowDto> page, ProductBorrowDto dto);
+
+    /**
+     * 鏂板棰嗙敤璁板綍锛堝緟瀹℃壒锛�
+     */
+    Boolean add(ProductBorrowDto dto);
+
+    /**
+     * 鏇存柊棰嗙敤璁板綍
+     */
+    Boolean update(ProductBorrowDto dto);
+
+    /**
+     * 鍒犻櫎棰嗙敤璁板綍锛堜粎寰呭鎵圭姸鎬佸彲鍒犻櫎锛�
+     */
+    Boolean delete(List<Long> ids);
+
+    /**
+     * 鎵归噺瀹℃壒
+     */
+    Boolean batchApprove(List<Long> ids, Integer approvalStatus);
+
+    /**
+     * 瀹℃壒閫氳繃鍚庣殑鍥炶皟锛堟墸鍑忓簱瀛橈級
+     */
+    void onApprovePass(ProductBorrow borrow);
+
+    /**
+     * 鏍规嵁ID鏌ヨ璇︽儏
+     */
+    ProductBorrowDto getDetailById(Long id);
+}
diff --git a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
index 5d534d5..dd74407 100644
--- a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
@@ -49,4 +49,9 @@
     List<StockInventory> getByModelId(Long modelId);
 
     IPage<StockInventoryDto> getBatchNoQty(Page page, StockInventoryDto stockInventoryDto);
+
+    /**
+     * 鍒嗛〉鏌ヨ浜у搧搴撳瓨鍜岄鐢ㄩ噺
+     */
+    IPage<StockInventoryDto> pageStockAndBorrow(Page page, StockInventoryDto stockInventoryDto);
 }
diff --git a/src/main/java/com/ruoyi/stock/service/impl/ProductBorrowReturnServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/ProductBorrowReturnServiceImpl.java
new file mode 100644
index 0000000..06d8534
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/service/impl/ProductBorrowReturnServiceImpl.java
@@ -0,0 +1,109 @@
+package com.ruoyi.stock.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.stock.dto.ProductBorrowReturnDto;
+import com.ruoyi.stock.dto.StockInventoryDto;
+import com.ruoyi.stock.mapper.ProductBorrowReturnMapper;
+import com.ruoyi.stock.pojo.ProductBorrow;
+import com.ruoyi.stock.pojo.ProductBorrowReturn;
+import com.ruoyi.stock.service.ProductBorrowReturnService;
+import com.ruoyi.stock.service.ProductBorrowService;
+import com.ruoyi.stock.service.StockInventoryService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 浜у搧褰掕繕璁板綍 Service 瀹炵幇绫�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ProductBorrowReturnServiceImpl extends ServiceImpl<ProductBorrowReturnMapper, ProductBorrowReturn> implements ProductBorrowReturnService {
+
+    private final ProductBorrowReturnMapper productBorrowReturnMapper;
+    private final ProductBorrowService productBorrowService;
+    private final StockInventoryService stockInventoryService;
+
+    @Override
+    public IPage<ProductBorrowReturnDto> listPage(Page<ProductBorrowReturnDto> page, ProductBorrowReturnDto dto) {
+        return productBorrowReturnMapper.listPage(page, dto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean add(ProductBorrowReturnDto dto) {
+        // 1. 鏌ヨ棰嗙敤璁板綍
+        ProductBorrow borrow = productBorrowService.getById(dto.getBorrowId());
+        if (borrow == null) {
+            throw new ServiceException("棰嗙敤璁板綍涓嶅瓨鍦�");
+        }
+
+        // 2. 妫�鏌ラ鐢ㄨ褰曠姸鎬侊紙宸查�氳繃鐨勮褰曟墠鑳藉綊杩橈級
+        if (borrow.getApprovalStatus() != 1) {
+            throw new ServiceException("棰嗙敤璁板綍鏈畬鎴愬嚭搴擄紝鏃犳硶褰掕繕");
+        }
+        if (borrow.getStatus() == 2) {
+            throw new ServiceException("璇ラ鐢ㄨ褰曞凡鍏ㄩ儴褰掕繕");
+        }
+
+        // 3. 璁$畻鍓╀綑鍙綊杩樻暟閲�
+        BigDecimal returnedQty = borrow.getReturnedQuantity() != null ? borrow.getReturnedQuantity() : BigDecimal.ZERO;
+        BigDecimal remainingQty = borrow.getBorrowQuantity().subtract(returnedQty);
+
+        // 4. 楠岃瘉褰掕繕鏁伴噺
+        if (dto.getReturnQuantity().compareTo(remainingQty) > 0) {
+            throw new ServiceException("褰掕繕鏁伴噺涓嶈兘澶т簬鍓╀綑鍙綊杩樻暟閲忥細" + remainingQty);
+        }
+
+        // 5. 璁剧疆蹇呰瀛楁
+        dto.setProductModelId(borrow.getProductModelId());
+        dto.setBatchNo(borrow.getBatchNo());
+        dto.setReturnTime(LocalDateTime.now());
+
+        // 6. 淇濆瓨褰掕繕璁板綍
+        save(dto);
+
+        // 7. 鏇存柊棰嗙敤璁板綍鐨勫凡褰掕繕鏁伴噺鍜岀姸鎬�
+        BigDecimal newReturnedQty = returnedQty.add(dto.getReturnQuantity());
+        borrow.setReturnedQuantity(newReturnedQty);
+
+        // 鍒ゆ柇鏄惁鍏ㄩ儴褰掕繕
+        if (newReturnedQty.compareTo(borrow.getBorrowQuantity()) >= 0) {
+            borrow.setStatus(2); // 宸插叏閮ㄥ綊杩�
+        } else if (newReturnedQty.compareTo(BigDecimal.ZERO) > 0) {
+            borrow.setStatus(1); // 閮ㄥ垎褰掕繕
+        }
+        productBorrowService.updateById(borrow);
+
+        // 8. 澧炲姞搴撳瓨锛堝叆搴擄級锛岃缃鎵圭姸鎬佷负宸查�氳繃锛岃嚜鍔ㄥ鏍�
+        StockInventoryDto stockInDto = new StockInventoryDto();
+        stockInDto.setProductModelId(borrow.getProductModelId());
+        stockInDto.setBatchNo(borrow.getBatchNo());
+        stockInDto.setQualitity(dto.getReturnQuantity());
+        stockInDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.PRODUCT_BORROW_RETURN.getCode()));
+        stockInDto.setRecordId(dto.getId());
+        stockInDto.setRemark("浜у搧褰掕繕鍏ュ簱锛岄鐢ㄥ崟鍙凤細" + borrow.getBorrowNo());
+        // 璁剧疆瀹℃壒鐘舵�佷负宸查�氳繃锛岃嚜鍔ㄥ鏍�
+        stockInDto.setApprovalStatus(1);
+        stockInventoryService.addstockInventory(stockInDto);
+
+        log.info("浜у搧褰掕繕鎴愬姛锛岄鐢ㄥ崟鍙凤細{}锛屽綊杩樻暟閲忥細{}", borrow.getBorrowNo(), dto.getReturnQuantity());
+        return true;
+    }
+
+    @Override
+    public IPage<ProductBorrowReturnDto> listByBorrowId(Page<ProductBorrowReturnDto> page, Long borrowId) {
+        return productBorrowReturnMapper.listByBorrowId(page, borrowId);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/stock/service/impl/ProductBorrowServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/ProductBorrowServiceImpl.java
new file mode 100644
index 0000000..2fd71d3
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/service/impl/ProductBorrowServiceImpl.java
@@ -0,0 +1,214 @@
+package com.ruoyi.stock.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.stock.dto.ProductBorrowDto;
+import com.ruoyi.stock.dto.StockInventoryDto;
+import com.ruoyi.stock.mapper.ProductBorrowMapper;
+import com.ruoyi.stock.pojo.ProductBorrow;
+import com.ruoyi.stock.service.ProductBorrowService;
+import com.ruoyi.stock.service.StockInventoryService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+/**
+ * 浜у搧棰嗙敤 Service 瀹炵幇绫�
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class ProductBorrowServiceImpl extends ServiceImpl<ProductBorrowMapper, ProductBorrow> implements ProductBorrowService {
+
+    private final ProductBorrowMapper productBorrowMapper;
+    private final StockInventoryService stockInventoryService;
+
+    @Override
+    public IPage<ProductBorrowDto> listPage(Page<ProductBorrowDto> page, ProductBorrowDto dto) {
+        IPage<ProductBorrowDto> result = productBorrowMapper.listPage(page, dto);
+        // 濉厖鐘舵�佸悕绉�
+        result.getRecords().forEach(this::fillStatusName);
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean add(ProductBorrowDto dto) {
+        // 鐢熸垚棰嗙敤鍗曞彿
+        String borrowNo = generateBorrowNo();
+        dto.setBorrowNo(borrowNo);
+
+        // 璁剧疆鍒濆鐘舵�� - 鐩存帴璁句负宸查�氳繃锛堝彇娑堝鎵规祦绋嬶級
+        dto.setApprovalStatus(1); // 宸查�氳繃
+        dto.setStatus(0); // 鏈綊杩�
+        dto.setReturnedQuantity(BigDecimal.ZERO);
+        dto.setBorrowTime(LocalDateTime.now());
+
+        // 淇濆瓨棰嗙敤璁板綍
+        save(dto);
+
+        // 棰嗙敤鏃剁洿鎺ュ仛鍑哄簱鎿嶄綔锛岃嚜鍔ㄥ鏍搁�氳繃
+        doBorrowStockOut(dto);
+
+        return true;
+    }
+
+    /**
+     * 鎵ц棰嗙敤鍑哄簱鎿嶄綔锛堣嚜鍔ㄥ鏍搁�氳繃锛�
+     */
+    private void doBorrowStockOut(ProductBorrow borrow) {
+        // 鏋勫缓搴撳瓨鎵e噺鍙傛暟
+        StockInventoryDto stockInventoryDto = new StockInventoryDto();
+        stockInventoryDto.setProductModelId(borrow.getProductModelId());
+        stockInventoryDto.setBatchNo(borrow.getBatchNo());
+        stockInventoryDto.setQualitity(borrow.getBorrowQuantity());
+        stockInventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.PRODUCT_BORROW.getCode()));
+        stockInventoryDto.setRecordId(borrow.getId());
+        stockInventoryDto.setRemark("浜у搧棰嗙敤鍑哄簱锛岄鐢ㄥ崟鍙凤細" + borrow.getBorrowNo());
+        // 璁剧疆瀹℃壒鐘舵�佷负宸查�氳繃锛岃嚜鍔ㄥ鏍�
+        stockInventoryDto.setApprovalStatus(1);
+
+        // 璋冪敤搴撳瓨鏈嶅姟鎵e噺搴撳瓨
+        stockInventoryService.subtractStockInventory(stockInventoryDto);
+
+        log.info("浜у搧棰嗙敤鍑哄簱鎴愬姛锛岄鐢ㄥ崟鍙凤細{}锛屾暟閲忥細{}", borrow.getBorrowNo(), borrow.getBorrowQuantity());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean update(ProductBorrowDto dto) {
+        ProductBorrow existing = getById(dto.getId());
+        if (existing == null) {
+            throw new ServiceException("棰嗙敤璁板綍涓嶅瓨鍦�");
+        }
+        // 鍙湁鏈�氳繃瀹℃壒鐘舵�佹墠鑳戒慨鏀癸紙浣嗙幇鍦ㄩ粯璁ゅ凡閫氳繃锛屾墍浠ヤ笉鍏佽淇敼锛�
+        if (existing.getApprovalStatus() != 0) {
+            throw new ServiceException("棰嗙敤璁板綍宸插畬鎴愬嚭搴擄紝涓嶈兘淇敼");
+        }
+        return updateById(dto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean delete(List<Long> ids) {
+        for (Long id : ids) {
+            ProductBorrow borrow = getById(id);
+            if (borrow != null && borrow.getApprovalStatus() != 0) {
+                throw new ServiceException("棰嗙敤璁板綍宸插畬鎴愬嚭搴擄紝涓嶈兘鍒犻櫎");
+            }
+        }
+        return removeByIds(ids);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean batchApprove(List<Long> ids, Integer approvalStatus) {
+        for (Long id : ids) {
+            ProductBorrow borrow = getById(id);
+            if (borrow == null) {
+                throw new ServiceException("棰嗙敤璁板綍涓嶅瓨鍦�: " + id);
+            }
+            if (borrow.getApprovalStatus() != 0) {
+                throw new ServiceException("鍙湁寰呭鎵圭姸鎬佺殑璁板綍鎵嶈兘瀹℃壒");
+            }
+
+            borrow.setApprovalStatus(approvalStatus);
+            updateById(borrow);
+
+            // 瀹℃壒閫氳繃鍚庢墸鍑忓簱瀛�
+            if (approvalStatus == 1) {
+                onApprovePass(borrow);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void onApprovePass(ProductBorrow borrow) {
+        // 鏋勫缓搴撳瓨鎵e噺鍙傛暟锛岃缃鎵圭姸鎬佷负宸查�氳繃
+        StockInventoryDto stockInventoryDto = new StockInventoryDto();
+        stockInventoryDto.setProductModelId(borrow.getProductModelId());
+        stockInventoryDto.setBatchNo(borrow.getBatchNo());
+        stockInventoryDto.setQualitity(borrow.getBorrowQuantity());
+        stockInventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.PRODUCT_BORROW.getCode()));
+        stockInventoryDto.setRecordId(borrow.getId());
+        stockInventoryDto.setRemark("浜у搧棰嗙敤鍑哄簱锛岄鐢ㄥ崟鍙凤細" + borrow.getBorrowNo());
+        stockInventoryDto.setApprovalStatus(1);
+
+        stockInventoryService.subtractStockInventory(stockInventoryDto);
+        log.info("浜у搧棰嗙敤瀹℃壒閫氳繃锛屾墸鍑忓簱瀛樻垚鍔燂紝棰嗙敤鍗曞彿锛歿}锛屾暟閲忥細{}", borrow.getBorrowNo(), borrow.getBorrowQuantity());
+    }
+
+    @Override
+    public ProductBorrowDto getDetailById(Long id) {
+        ProductBorrowDto dto = productBorrowMapper.selectDetailById(id);
+        if (dto != null) {
+            fillStatusName(dto);
+            // 璁$畻鍓╀綑鍙綊杩樻暟閲�
+            if (dto.getBorrowQuantity() != null && dto.getReturnedQuantity() != null) {
+                dto.setRemainingQuantity(dto.getBorrowQuantity().subtract(dto.getReturnedQuantity()));
+            }
+        }
+        return dto;
+    }
+
+    /**
+     * 鐢熸垚棰嗙敤鍗曞彿
+     * 鏍煎紡锛歀Y + 骞存湀鏃� + 4浣嶅簭鍙�
+     */
+    private String generateBorrowNo() {
+        String dateStr = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        LambdaQueryWrapper<ProductBorrow> wrapper = new LambdaQueryWrapper<>();
+        wrapper.likeRight(ProductBorrow::getBorrowNo, "LY" + dateStr)
+               .orderByDesc(ProductBorrow::getBorrowNo)
+               .last("LIMIT 1");
+        ProductBorrow lastBorrow = getOne(wrapper);
+
+        int sequence = 1;
+        if (lastBorrow != null && lastBorrow.getBorrowNo() != null) {
+            String lastNo = lastBorrow.getBorrowNo();
+            if (lastNo.length() >= 14) {
+                try {
+                    sequence = Integer.parseInt(lastNo.substring(10)) + 1;
+                } catch (NumberFormatException ignored) {
+                }
+            }
+        }
+        return "LY" + dateStr + String.format("%04d", sequence);
+    }
+
+    /**
+     * 濉厖鐘舵�佸悕绉�
+     */
+    private void fillStatusName(ProductBorrowDto dto) {
+        // 瀹℃壒鐘舵�佸悕绉�
+        if (dto.getApprovalStatus() != null) {
+            switch (dto.getApprovalStatus()) {
+                case 0: dto.setApprovalStatusName("寰呭鎵�"); break;
+                case 1: dto.setApprovalStatusName("宸查�氳繃"); break;
+                case 2: dto.setApprovalStatusName("宸查┏鍥�"); break;
+            }
+        }
+        // 褰掕繕鐘舵�佸悕绉�
+        if (dto.getStatus() != null) {
+            switch (dto.getStatus()) {
+                case 0: dto.setStatusName("鏈綊杩�"); break;
+                case 1: dto.setStatusName("閮ㄥ垎褰掕繕"); break;
+                case 2: dto.setStatusName("宸插叏閮ㄥ綊杩�"); break;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
index c82ac34..857fdc6 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -92,6 +92,11 @@
         stockInRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
         stockInRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
         stockInRecordDto.setType("0");
+        stockInRecordDto.setRemark(stockInventoryDto.getRemark());
+        // 濡傛灉DTO涓寚瀹氫簡瀹℃壒鐘舵�侊紝鍒欎娇鐢紱鍚﹀垯榛樿寰呭鎵�
+        if (stockInventoryDto.getApprovalStatus() != null) {
+            stockInRecordDto.setApprovalStatus(stockInventoryDto.getApprovalStatus());
+        }
         stockInRecordService.add(stockInRecordDto);
         //鍐嶈繘琛屾柊澧炲簱瀛樻暟閲忓簱瀛�
         //鍏堟煡璇㈠簱瀛樿〃涓殑浜у搧鏄惁瀛樺湪锛屼笉瀛樺湪鏂板锛屽瓨鍦ㄦ洿鏂�
@@ -132,6 +137,11 @@
         stockOutRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
         stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
         stockOutRecordDto.setType("0");
+        stockOutRecordDto.setRemark(stockInventoryDto.getRemark());
+        // 濡傛灉DTO涓寚瀹氫簡瀹℃壒鐘舵�侊紝鍒欎娇鐢紱鍚﹀垯榛樿寰呭鎵�
+        if (stockInventoryDto.getApprovalStatus() != null) {
+            stockOutRecordDto.setApprovalStatus(stockInventoryDto.getApprovalStatus());
+        }
         stockOutRecordService.add(stockOutRecordDto);
 
 
@@ -440,4 +450,9 @@
     public IPage<StockInventoryDto> getBatchNoQty(Page page, StockInventoryDto stockInventoryDto) {
         return stockInventoryMapper.getBatchNoQty(page, stockInventoryDto);
     }
+
+    @Override
+    public IPage<StockInventoryDto> pageStockAndBorrow(Page page, StockInventoryDto stockInventoryDto) {
+        return stockInventoryMapper.pageStockAndBorrow(page, stockInventoryDto);
+    }
 }
diff --git a/src/main/resources/mapper/stock/ProductBorrowMapper.xml b/src/main/resources/mapper/stock/ProductBorrowMapper.xml
new file mode 100644
index 0000000..0aebc8a
--- /dev/null
+++ b/src/main/resources/mapper/stock/ProductBorrowMapper.xml
@@ -0,0 +1,95 @@
+<?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="com.ruoyi.stock.mapper.ProductBorrowMapper">
+
+    <resultMap id="ProductBorrowDtoMap" type="com.ruoyi.stock.dto.ProductBorrowDto">
+        <id column="id" property="id"/>
+        <result column="borrow_no" property="borrowNo"/>
+        <result column="product_model_id" property="productModelId"/>
+        <result column="batch_no" property="batchNo"/>
+        <result column="borrow_quantity" property="borrowQuantity"/>
+        <result column="returned_quantity" property="returnedQuantity"/>
+        <result column="borrower_id" property="borrowerId"/>
+        <result column="borrower_name" property="borrowerName"/>
+        <result column="borrow_time" property="borrowTime"/>
+        <result column="expected_return_time" property="expectedReturnTime"/>
+        <result column="approval_status" property="approvalStatus"/>
+        <result column="status" property="status"/>
+        <result column="remark" property="remark"/>
+        <result column="tenant_id" property="tenantId"/>
+        <result column="dept_id" property="deptId"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_user" property="updateUser"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="product_name" property="productName"/>
+        <result column="model" property="model"/>
+        <result column="product_code" property="productCode"/>
+        <result column="unit" property="unit"/>
+    </resultMap>
+
+    <select id="listPage" resultMap="ProductBorrowDtoMap">
+        WITH RECURSIVE product_tree AS (
+            SELECT id
+            FROM product
+            WHERE id = #{ew.topParentProductId}
+            UNION ALL
+            SELECT p.id
+            FROM product p
+            INNER JOIN product_tree pt ON p.parent_id = pt.id
+        )
+        SELECT
+            pb.*,
+            p.product_name,
+            pm.model,
+            pm.product_code,
+            pm.unit
+        FROM product_borrow pb
+        LEFT JOIN product_model pm ON pb.product_model_id = pm.id
+        LEFT JOIN product p ON pm.product_id = p.id
+        <where>
+            <if test="ew.borrowNo != null and ew.borrowNo != ''">
+                AND pb.borrow_no LIKE CONCAT('%', #{ew.borrowNo}, '%')
+            </if>
+            <if test="ew.productModelId != null">
+                AND pb.product_model_id = #{ew.productModelId}
+            </if>
+            <if test="ew.borrowerId != null">
+                AND pb.borrower_id = #{ew.borrowerId}
+            </if>
+            <if test="ew.borrowerName != null and ew.borrowerName != ''">
+                AND pb.borrower_name LIKE CONCAT('%', #{ew.borrowerName}, '%')
+            </if>
+            <if test="ew.approvalStatus != null">
+                AND pb.approval_status = #{ew.approvalStatus}
+            </if>
+            <if test="ew.status != null">
+                AND pb.status = #{ew.status}
+            </if>
+            <if test="ew.deptId != null">
+                AND pb.dept_id = #{ew.deptId}
+            </if>
+            <if test="ew.topParentProductId != null and ew.topParentProductId > 0">
+                AND p.id IN (SELECT id FROM product_tree)
+            </if>
+            <if test="ew.model != null and ew.model != ''">
+                AND pm.model LIKE CONCAT('%', #{ew.model}, '%')
+            </if>
+        </where>
+        ORDER BY pb.create_time DESC
+    </select>
+
+    <select id="selectDetailById" resultMap="ProductBorrowDtoMap">
+        SELECT
+            pb.*,
+            p.product_name,
+            pm.model,
+            pm.product_code,
+            pm.unit
+        FROM product_borrow pb
+        LEFT JOIN product_model pm ON pb.product_model_id = pm.id
+        LEFT JOIN product p ON pm.product_id = p.id
+        WHERE pb.id = #{id}
+    </select>
+
+</mapper>
diff --git a/src/main/resources/mapper/stock/ProductBorrowReturnMapper.xml b/src/main/resources/mapper/stock/ProductBorrowReturnMapper.xml
new file mode 100644
index 0000000..b0508a3
--- /dev/null
+++ b/src/main/resources/mapper/stock/ProductBorrowReturnMapper.xml
@@ -0,0 +1,80 @@
+<?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="com.ruoyi.stock.mapper.ProductBorrowReturnMapper">
+
+    <resultMap id="ProductBorrowReturnDtoMap" type="com.ruoyi.stock.dto.ProductBorrowReturnDto">
+        <id column="id" property="id"/>
+        <result column="borrow_id" property="borrowId"/>
+        <result column="product_model_id" property="productModelId"/>
+        <result column="batch_no" property="batchNo"/>
+        <result column="return_quantity" property="returnQuantity"/>
+        <result column="returner_id" property="returnerId"/>
+        <result column="returner_name" property="returnerName"/>
+        <result column="return_time" property="returnTime"/>
+        <result column="remark" property="remark"/>
+        <result column="tenant_id" property="tenantId"/>
+        <result column="dept_id" property="deptId"/>
+        <result column="create_user" property="createUser"/>
+        <result column="create_time" property="createTime"/>
+        <result column="borrow_no" property="borrowNo"/>
+        <result column="product_name" property="productName"/>
+        <result column="model" property="model"/>
+        <result column="product_code" property="productCode"/>
+        <result column="unit" property="unit"/>
+        <result column="borrow_quantity" property="borrowQuantity"/>
+        <result column="borrower_name" property="borrowerName"/>
+    </resultMap>
+
+    <select id="listPage" resultMap="ProductBorrowReturnDtoMap">
+        SELECT
+            pbr.*,
+            pb.borrow_no,
+            pb.borrow_quantity,
+            pb.borrower_name,
+            p.product_name,
+            pm.model,
+            pm.product_code,
+            pm.unit
+        FROM product_borrow_return pbr
+        LEFT JOIN product_borrow pb ON pbr.borrow_id = pb.id
+        LEFT JOIN product_model pm ON pbr.product_model_id = pm.id
+        LEFT JOIN product p ON pm.product_id = p.id
+        <where>
+            <if test="ew.borrowId != null">
+                AND pbr.borrow_id = #{ew.borrowId}
+            </if>
+            <if test="ew.productModelId != null">
+                AND pbr.product_model_id = #{ew.productModelId}
+            </if>
+            <if test="ew.returnerId != null">
+                AND pbr.returner_id = #{ew.returnerId}
+            </if>
+            <if test="ew.returnerName != null and ew.returnerName != ''">
+                AND pbr.returner_name LIKE CONCAT('%', #{ew.returnerName}, '%')
+            </if>
+            <if test="ew.deptId != null">
+                AND pbr.dept_id = #{ew.deptId}
+            </if>
+        </where>
+        ORDER BY pbr.create_time DESC
+    </select>
+
+    <select id="listByBorrowId" resultMap="ProductBorrowReturnDtoMap">
+        SELECT
+            pbr.*,
+            pb.borrow_no,
+            pb.borrow_quantity,
+            pb.borrower_name,
+            p.product_name,
+            pm.model,
+            pm.product_code,
+            pm.unit
+        FROM product_borrow_return pbr
+        LEFT JOIN product_borrow pb ON pbr.borrow_id = pb.id
+        LEFT JOIN product_model pm ON pbr.product_model_id = pm.id
+        LEFT JOIN product p ON pm.product_id = p.id
+        WHERE pbr.borrow_id = #{borrowId}
+        ORDER BY pbr.create_time DESC
+    </select>
+
+</mapper>
diff --git a/src/main/resources/mapper/stock/StockInventoryMapper.xml b/src/main/resources/mapper/stock/StockInventoryMapper.xml
index 6ca5bf7..99365a0 100644
--- a/src/main/resources/mapper/stock/StockInventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -671,4 +671,58 @@
         batch_no
     </select>
 
+    <!-- 鍒嗛〉鏌ヨ浜у搧搴撳瓨鍜岄鐢ㄩ噺锛堟寜鎵瑰彿鍖哄垎锛� -->
+    <select id="pageStockAndBorrow" resultType="com.ruoyi.stock.dto.StockInventoryDto">
+        WITH RECURSIVE product_tree AS (
+            SELECT id
+            FROM product
+            WHERE id = #{ew.topParentProductId}
+            UNION ALL
+            SELECT p.id
+            FROM product p
+            INNER JOIN product_tree pt ON p.parent_id = pt.id
+        )
+        SELECT
+            pm.id as product_model_id,
+            pm.model,
+            pm.product_code,
+            pm.unit,
+            p.product_name,
+            p.id as product_id,
+            si.batch_no,
+            IFNULL(si.qualitity, 0) as qualitity,
+            IFNULL(si.locked_quantity, 0) as locked_quantity,
+            IFNULL(borrowed.borrowed_quantity, 0) as borrowed_quantity,
+            IFNULL(si.qualitity, 0) - IFNULL(borrowed.borrowed_quantity, 0) as available_quantity
+        FROM product_model pm
+        LEFT JOIN product p ON pm.product_id = p.id
+        LEFT JOIN stock_inventory si ON pm.id = si.product_model_id
+        LEFT JOIN (
+            SELECT
+                product_model_id,
+                batch_no,
+                SUM(borrow_quantity - IFNULL(returned_quantity, 0)) as borrowed_quantity
+            FROM product_borrow
+            WHERE approval_status = 1
+              AND status != 2
+            GROUP BY product_model_id, batch_no
+        ) borrowed ON pm.id = borrowed.product_model_id
+            AND (si.batch_no = borrowed.batch_no OR (si.batch_no IS NULL AND borrowed.batch_no IS NULL))
+        <where>
+            <if test="ew.topParentProductId != null and ew.topParentProductId > 0">
+                AND p.id IN (SELECT id FROM product_tree)
+            </if>
+            <if test="ew.productName != null and ew.productName != ''">
+                AND p.product_name LIKE CONCAT('%', #{ew.productName}, '%')
+            </if>
+            <if test="ew.model != null and ew.model != ''">
+                AND pm.model LIKE CONCAT('%', #{ew.model}, '%')
+            </if>
+            <if test="ew.batchNo != null and ew.batchNo != ''">
+                AND si.batch_no LIKE CONCAT('%', #{ew.batchNo}, '%')
+            </if>
+        </where>
+        ORDER BY pm.id DESC, si.batch_no
+    </select>
+
 </mapper>

--
Gitblit v1.9.3