From 684e4306a08feeae188070f264148e6765da8dcf Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期日, 20 九月 2026 19:25:15 +0800
Subject: [PATCH] feat(account): 更新往来付款回款流水表及接口

---
 src/main/java/com/ruoyi/stock/service/StockInventoryService.java                       |    5 
 src/main/java/com/ruoyi/basic/pojo/Customer.java                                       |    5 
 src/main/java/com/ruoyi/common/enums/TypeEnums.java                                    |    3 
 src/main/java/com/ruoyi/outsourcing/controller/OutsourcingOrderController.java         |  141 +
 src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingOrderServiceImpl.java      |  427 ++++
 src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsVo.java           |   32 
 src/main/resources/mapper/basic/CustomerMapper.xml                                     |    8 
 src/main/java/com/ruoyi/sales/dto/CustomerDepositAmountDto.java                        |   21 
 src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java                         |   10 
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java           |   72 
 src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java                                 |    9 
 src/main/java/com/ruoyi/basic/service/IVehicleService.java                             |   65 
 src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java                    |   34 
 src/main/java/com/ruoyi/basic/controller/VehicleController.java                        |   85 
 src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java                        |    6 
 src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java                    |    4 
 doc/20260920_信托基金与代储台账前端联调文档.md                                                        |  309 +++
 src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerReportController.java |   53 
 src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java                |   21 
 src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java               |    2 
 src/main/java/com/ruoyi/outsourcing/excel/OutsourcingPartnerExcelDto.java              |   41 
 src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java                           |    6 
 src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java                            |    9 
 src/main/java/com/ruoyi/basic/service/impl/VehicleServiceImpl.java                     |   89 
 src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java          |    2 
 src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java              |    3 
 src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java              |    5 
 src/main/java/com/ruoyi/basic/service/impl/TankInfoServiceImpl.java                    |   68 
 doc/20260920_vehicle_and_residual_oil.sql                                              |   41 
 src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java                        |    3 
 src/main/resources/mapper/stock/StockOutRecordMapper.xml                               |   12 
 src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java              |  303 +++
 src/main/java/com/ruoyi/basic/enums/TankUsageTypeEnum.java                             |   29 
 src/main/java/com/ruoyi/basic/vo/CustomerVo.java                                       |    7 
 src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderMapper.java                 |   12 
 src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java                                 |   33 
 src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java                           |    2 
 src/main/java/com/ruoyi/account/pojo/PaymentRecord.java                                |    4 
 src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingPartnerServiceImpl.java    |  211 ++
 src/main/java/com/ruoyi/stock/service/StockOutRecordService.java                       |   36 
 doc/20260920_委外管理前端联调文档.md                                                             |  725 +++++++
 doc/20260920_add_usage_to_tank_info.sql                                                |    6 
 src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrderProduct.java                  |   99 +
 src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java                         |    5 
 src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsDetailsVo.java    |   44 
 src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingPartner.java                       |   86 
 doc/20260920_出库即发货与往来备件前端联调文档.md                                                       |  104 
 src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java                                  |   29 
 src/main/resources/mapper/sales/SalesLedgerMapper.xml                                  |   40 
 src/main/java/com/ruoyi/outsourcing/service/IOutsourcingPartnerService.java            |   70 
 src/main/resources/mapper/basic/SupplierManageMapper.xml                               |    8 
 doc/20260920_trust_fund_and_deposit_ledger.sql                                         |   35 
 src/main/java/com/ruoyi/sales/pojo/SalesLedger.java                                    |   41 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java          |   32 
 src/main/java/com/ruoyi/account/controller/PaymentRecordController.java                |    4 
 src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java                 |   13 
 src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerVo.java                       |   23 
 src/main/java/com/ruoyi/outsourcing/excel/OutsourcingOrderExcelDto.java                |   44 
 src/main/java/com/ruoyi/outsourcing/dto/OutsourcingOrderDto.java                       |   67 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java                 |  173 +
 doc/20260920_储罐号带入库存前端联调文档.md                                                          |   17 
 src/main/java/com/ruoyi/outsourcing/service/IOutsourcingOrderService.java              |   59 
 src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingPartnerMapper.java               |   29 
 doc/20260920_储罐启用与租赁信息前端联调文档.md                                                        |  206 ++
 src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java                 |    2 
 src/main/java/com/ruoyi/basic/pojo/Vehicle.java                                        |   96 +
 src/main/java/com/ruoyi/basic/mapper/VehicleMapper.java                                |   15 
 src/main/resources/mapper/stock/StockInventoryMapper.xml                               |   42 
 doc/20260920_outsourcing_menu.sql                                                      |   49 
 src/main/java/com/ruoyi/outsourcing/dto/OutsourcingPartnerDto.java                     |   23 
 src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrder.java                         |   96 +
 src/main/resources/mapper/sales/ShippingInfoMapper.xml                                 |   79 
 src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java                                   |    6 
 src/main/java/com/ruoyi/stock/controller/StockInventoryController.java                 |    7 
 doc/20260920_create_payment_record.sql                                                 |    8 
 src/main/java/com/ruoyi/outsourcing/enums/OutsourcingOrderStatusEnum.java              |   43 
 doc/20260920_create_outsourcing.sql                                                    |   93 +
 src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerController.java       |  133 +
 doc/20260920_车辆管理与残油联动前端联调文档.md                                                        |  304 +++
 src/main/java/com/ruoyi/stock/dto/OilOutAvailableDto.java                              |   25 
 doc/20260920_油品出库联动发货台账前端联调文档.md                                                       |  159 +
 src/main/java/com/ruoyi/account/enums/PartyTypeEnum.java                               |   47 
 src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java                 |    4 
 src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderProductMapper.java          |   12 
 src/main/resources/mapper/outsourcing/OutsourcingPartnerMapper.xml                     |  124 +
 src/main/java/com/ruoyi/basic/pojo/TankInfo.java                                       |   12 
 src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java                                |    4 
 87 files changed, 5,503 insertions(+), 67 deletions(-)

diff --git a/doc/20260920_add_usage_to_tank_info.sql b/doc/20260920_add_usage_to_tank_info.sql
new file mode 100644
index 0000000..cf60d08
--- /dev/null
+++ b/doc/20260920_add_usage_to_tank_info.sql
@@ -0,0 +1,6 @@
+-- 鍌ㄧ綈淇℃伅琛ㄦ柊澧炩�滄槸鍚﹀惎鐢ㄢ�濃�滅敤閫旓紙绉熻祦/鑷敤锛夆��
+-- 鎵ц搴擄細product-inventory-management锛堝悇绉熸埛搴撳悓锛�
+-- is_enabled 瀛橀噺鏁版嵁榛樿 1锛堝惎鐢級锛岃涓轰笌鏀瑰姩鍓嶄竴鑷达紱usage_type 鐣欑┖琛ㄧず鏈�夋嫨锛堝瓨閲忔暟鎹級
+ALTER TABLE `tank_info`
+    ADD COLUMN `is_enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '鏄惁鍚敤 1鍚敤 0鏈惎鐢�' AFTER `remaining`,
+    ADD COLUMN `usage_type` tinyint NULL DEFAULT NULL COMMENT '鐢ㄩ�� 1绉熻祦 2鑷敤锛屽惎鐢ㄥ悗鎵嶉渶瑕侀�夛紝涓虹┖琛ㄧず鏈�夋嫨' AFTER `is_enabled`;
diff --git a/doc/20260920_create_outsourcing.sql b/doc/20260920_create_outsourcing.sql
new file mode 100644
index 0000000..8b11990
--- /dev/null
+++ b/doc/20260920_create_outsourcing.sql
@@ -0,0 +1,93 @@
+#鍚堜綔鍟嗘。妗堣〃锛堝澶栫鐞� - 鍚堜綔鍟嗭紝鐙珛浜庝緵搴斿晢 supplier_manage锛�
+drop table if exists outsourcing_partner;
+create table outsourcing_partner
+(
+    id                           bigint auto_increment primary key,
+    partner_name                 varchar(100) not null comment '鍚堜綔鍟嗗悕绉�',
+    taxpayer_identification_num  varchar(50)  null comment '绾崇◣浜鸿瘑鍒彿',
+    company_address              varchar(255) null comment '鍏徃鍦板潃',
+    company_phone                varchar(50)  null comment '鍏徃鐢佃瘽',
+    bank_account_name            varchar(100) null comment '寮�鎴疯鍚嶇О',
+    bank_account_num             varchar(50)  null comment '閾惰璐﹀彿',
+    contact_user_name            varchar(50)  null comment '鑱旂郴浜哄鍚�',
+    contact_user_phone           varchar(50)  null comment '鑱旂郴浜虹數璇�',
+    maintain_user_id             bigint       null comment '缁存姢浜篿d',
+    maintain_time                date         null comment '缁存姢鏃堕棿',
+    partner_type                 varchar(50)  null comment '鍚堜綔鍟嗙被鍨�',
+    is_white                     tinyint(1)   default 1 comment '鏄惁鐧藉悕鍗� 1鏄� 0鍚�',
+    remark                       varchar(500) null comment '澶囨敞',
+    tenant_id                    bigint       null comment '绉熸埛ID',
+    dept_id                      bigint       null comment '閮ㄩ棬ID',
+    create_user                  bigint       null comment '鍒涘缓浜篒D',
+    create_time                  datetime     null comment '鍒涘缓鏃堕棿',
+    update_time                  datetime     null comment '鏇存柊鏃堕棿',
+    index idx_partner_name (partner_name),
+    index idx_tenant_partner (tenant_id, partner_name)
+);
+
+#濮斿璁㈠崟涓昏〃
+drop table if exists outsourcing_order;
+create table outsourcing_order
+(
+    id                     bigint auto_increment primary key,
+    order_no               varchar(50)    not null comment '濮斿璁㈠崟鍙�',
+    partner_id             bigint         null comment '鍚堜綔鍟唅d',
+    partner_name           varchar(100)   null comment '鍚堜綔鍟嗗悕绉�',
+    apply_date             date           null comment '鐢宠鏃ユ湡',
+    expected_delivery_date date           null comment '鏈熸湜浜や粯鏃ユ湡',
+    project_name           varchar(100)   null comment '椤圭洰鍚嶇О',
+    contract_amount        decimal(18, 2) null comment '濮斿閲戦锛堝惈绋庢�讳环锛�',
+    payment_method         varchar(255)   null comment '浠樻鏂瑰紡',
+    remarks                text           null comment '澶囨敞',
+    status                 tinyint        not null default 0 comment '鐘舵�� 0鑽夌 1宸茬‘璁� 2宸插畬鎴� 3宸蹭綔搴�',
+    confirm_time           datetime       null comment '纭鏃堕棿',
+    cancel_reason          varchar(255)   null comment '浣滃簾鍘熷洜',
+    tenant_id              bigint         null comment '绉熸埛ID',
+    dept_id                bigint         null comment '閮ㄩ棬ID',
+    create_user             bigint        null comment '鍒涘缓浜篒D',
+    create_time             datetime      null comment '鍒涘缓鏃堕棿',
+    update_time             datetime      null comment '鏇存柊鏃堕棿',
+    index idx_order_no (order_no),
+    index idx_partner_id (partner_id),
+    index idx_status (status)
+);
+
+#濮斿璁㈠崟浜у搧鏄庣粏琛�
+drop table if exists outsourcing_order_product;
+create table outsourcing_order_product
+(
+    id                        bigint auto_increment primary key,
+    outsourcing_order_id      bigint         not null comment '濮斿璁㈠崟id',
+    product_id                bigint         null comment '浜у搧id',
+    product_model_id          bigint         null comment '浜у搧瑙勬牸id',
+    product_category          varchar(100)   null comment '浜у搧澶х被',
+    specification_model       varchar(100)   null comment '瑙勬牸鍨嬪彿',
+    unit                      varchar(20)    null comment '鍗曚綅',
+    quantity                  decimal(18, 2) not null default 0 comment '鏁伴噺',
+    tax_rate                  decimal(5, 0)  null comment '绋庣巼',
+    tax_inclusive_unit_price  decimal(18, 2) null comment '鍚◣鍗曚环',
+    tax_inclusive_total_price decimal(18, 2) null comment '鍚◣鎬讳环',
+    tax_exclusive_total_price decimal(18, 2) null comment '涓嶅惈绋庢�讳环',
+    invoice_type              varchar(50)    null comment '鍙戠エ绫诲瀷',
+    is_scrap                  tinyint(1)     not null default 0 comment '鏄惁鎶ュ簾鍝侀�夋枡 0鍚� 1鏄�',
+    stock_inventory_id        bigint         null comment '閫夌敤鐨勬姤搴熷搧鍚堟牸搴撳瓨id锛坰tock_inventory.id锛屽彧璇诲紩鐢紝涓嶅洖鍐欏簱瀛橈級',
+    batch_no                  varchar(64)    null comment '鎶ュ簾鍝佹壒鍙�',
+    available_quantity        decimal(18, 2) null comment '涓嬪崟鏃惰鎵规鍙敤閲忓揩鐓�',
+    remark                    varchar(255)   null comment '澶囨敞',
+    tenant_id                 bigint         null comment '绉熸埛ID',
+    dept_id                   bigint         null comment '閮ㄩ棬ID',
+    create_user               bigint         null comment '鍒涘缓浜篒D',
+    create_time               datetime       null comment '鍒涘缓鏃堕棿',
+    update_time               datetime       null comment '鏇存柊鏃堕棿',
+    index idx_outsourcing_order_id (outsourcing_order_id),
+    index idx_stock_inventory_id (stock_inventory_id)
+);
+
+# 鎶ュ簾鍝侀�夋枡鏀硅蛋鍚堟牸搴撳瓨锛坰tock_inventory锛夛紝鏄庣粏鍒楅殢涔嬫敼鍚嶏紱宸插缓杩囪〃鐨勭幆澧冩墽琛岋紙2026-09-20 宸插湪寮�鍙戝簱鎵ц锛夛細
+# ALTER TABLE outsourcing_order_product
+#     CHANGE COLUMN stock_uninventory_id stock_inventory_id bigint null comment '閫夌敤鐨勬姤搴熷搧鍚堟牸搴撳瓨id锛坰tock_inventory.id锛屽彧璇诲紩鐢紝涓嶅洖鍐欏簱瀛橈級';
+# ALTER TABLE outsourcing_order_product RENAME INDEX idx_stock_uninventory_id TO idx_stock_inventory_id;
+
+# 鍚堜綔鍟嗗線鏉ュ彇 payment_record(party_type=3 and direction=1) 浣滀负銆屽凡浠樻銆嶅垪
+# 宸插缓杩囪〃鐨勭幆澧冩墽琛岋細
+# ALTER TABLE payment_record MODIFY COLUMN party_type tinyint not null comment '寰�鏉ユ柟绫诲瀷 1瀹㈡埛 2渚涘簲鍟� 3鍚堜綔鍟�';
diff --git a/doc/20260920_create_payment_record.sql b/doc/20260920_create_payment_record.sql
index 12056c2..a39e2ef 100644
--- a/doc/20260920_create_payment_record.sql
+++ b/doc/20260920_create_payment_record.sql
@@ -1,11 +1,13 @@
 # 寰�鏉ヤ粯娆惧洖娆炬祦姘磋〃
-# 瀹㈡埛寰�鏉ュ彇 party_type=1 and direction=1 浣滀负銆屼粯娆俱�嶅垪
-# 渚涘簲鍟嗗線鏉ュ彇 party_type=2 and direction=2 浣滀负銆屽洖娆俱�嶅垪
+# 瀹㈡埛寰�鏉ュ彇 party_type=1 and direction=1 浣滀负銆屽洖娆俱�嶅垪锛堣繑鍥炲瓧娈� receiptRecordAmount锛�
+# 渚涘簲鍟嗗線鏉ュ彇 party_type=2 and direction=2 浣滀负銆屼粯娆俱�嶅垪锛堣繑鍥炲瓧娈� paymentRecordAmount锛�
+# 娉ㄦ剰锛歞irection 鍙栧�兼寜椤甸潰鍥哄畾锛屼笉瑕佹寜涓嬮潰 搂鏂瑰悜 鐨勫瓧闈紙1浠樻 2鍥炴锛夊幓閫�
+# 鍚堜綔鍟嗗線鏉ュ彇 party_type=3 and direction=1 浣滀负銆屽凡浠樻銆嶅垪
 drop table if exists payment_record;
 create table payment_record
 (
     id             bigint auto_increment primary key,
-    party_type     tinyint        not null comment '寰�鏉ユ柟绫诲瀷 1瀹㈡埛 2渚涘簲鍟�',
+    party_type     tinyint        not null comment '寰�鏉ユ柟绫诲瀷 1瀹㈡埛 2渚涘簲鍟� 3鍚堜綔鍟�',
     party_id       bigint         not null comment '瀹㈡埛id鎴栦緵搴斿晢id',
     direction      tinyint        not null comment '鏂瑰悜 1浠樻 2鍥炴',
     amount         decimal(18, 2) not null default 0 comment '閲戦',
diff --git a/doc/20260920_outsourcing_menu.sql b/doc/20260920_outsourcing_menu.sql
new file mode 100644
index 0000000..2697db8
--- /dev/null
+++ b/doc/20260920_outsourcing_menu.sql
@@ -0,0 +1,49 @@
+#濮斿绠$悊 鑿滃崟鑴氭湰
+#鍓嶇椤甸潰璺緞绾﹀畾锛歰utsourcingManagement/{partner|order|transactions}/index
+#sys_menu 灞炰簬绉熸埛蹇界暐琛紝鏃犻渶 tenant_id
+#璇存槑锛氭樉寮忔寚瀹� menu_id锛堝綋鍓嶅簱 sys_menu 鏈�澶� id 涓� 2355锛夛紝鍙噸澶嶆墽琛�
+
+#鍏堟竻鐞嗘棫鏁版嵁锛屼繚璇佽剼鏈彲閲嶅鎵ц
+delete from sys_menu where menu_id between 2360 and 2380;
+
+#1. 鐩綍锛氬澶栫鐞�
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2360, '濮斿绠$悊', 0, 6, 'outsourcingManagement', null, 1, 0, 'M', '0', '0', '', 'shopping', 'admin', sysdate(), '', null, '濮斿绠$悊鐩綍');
+
+#2. 鍚堜綔鍟嗘。妗�
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2361, '鍚堜綔鍟嗘。妗�', 2360, 1, 'partner', 'outsourcingManagement/partner/index', 1, 0, 'C', '0', '0', 'outsourcing:partner:list', '#', 'admin', sysdate(), '', null, '鍚堜綔鍟嗘。妗堣彍鍗�');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2362, '鍚堜綔鍟嗘。妗堟煡璇�', 2361, 1, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:partner:query', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2363, '鍚堜綔鍟嗘。妗堟柊澧�', 2361, 2, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:partner:add', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2364, '鍚堜綔鍟嗘。妗堜慨鏀�', 2361, 3, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:partner:edit', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2365, '鍚堜綔鍟嗘。妗堝垹闄�', 2361, 4, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:partner:remove', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2366, '鍚堜綔鍟嗘。妗堝鍑�', 2361, 5, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:partner:export', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2367, '鍚堜綔鍟嗘。妗堝鍏�', 2361, 6, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:partner:import', '#', 'admin', sysdate(), '', null, '');
+
+#3. 濮斿璁㈠崟
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2368, '濮斿璁㈠崟', 2360, 2, 'order', 'outsourcingManagement/order/index', 1, 0, 'C', '0', '0', 'outsourcing:order:list', '#', 'admin', sysdate(), '', null, '濮斿璁㈠崟鑿滃崟');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2369, '濮斿璁㈠崟鏌ヨ', 2368, 1, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:order:query', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2370, '濮斿璁㈠崟鏂板', 2368, 2, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:order:add', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2371, '濮斿璁㈠崟淇敼', 2368, 3, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:order:edit', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2372, '濮斿璁㈠崟鍒犻櫎', 2368, 4, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:order:remove', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2373, '濮斿璁㈠崟瀵煎嚭', 2368, 5, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:order:export', '#', 'admin', sysdate(), '', null, '');
+
+#4. 鍚堜綔鍟嗗線鏉�
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2374, '鍚堜綔鍟嗗線鏉�', 2360, 3, 'transactions', 'outsourcingManagement/transactions/index', 1, 0, 'C', '0', '0', 'outsourcing:report:list', '#', 'admin', sysdate(), '', null, '鍚堜綔鍟嗗線鏉ヨ彍鍗�');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2375, '鍚堜綔鍟嗗線鏉ユ煡璇�', 2374, 1, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:report:query', '#', 'admin', sysdate(), '', null, '');
+insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+values (2376, '鍚堜綔鍟嗗線鏉ヤ粯娆�', 2374, 2, '#', '', 1, 0, 'F', '0', '0', 'outsourcing:report:pay', '#', 'admin', sysdate(), '', null, '');
diff --git a/doc/20260920_trust_fund_and_deposit_ledger.sql b/doc/20260920_trust_fund_and_deposit_ledger.sql
new file mode 100644
index 0000000..2415f99
--- /dev/null
+++ b/doc/20260920_trust_fund_and_deposit_ledger.sql
@@ -0,0 +1,35 @@
+#淇℃墭鍩洪噾 + 閿�鍞彴璐︾被鍨嬪尯鍒嗭紙鍗栨补/浠e偍锛�+ 閿�鍞彴璐﹀鎵�
+#瀵瑰簲闇�姹傦細闇�姹傛竻鍗曠 5 鏉°�屽鎴锋。妗堝姞淇℃墭鍩洪噾瀛楁锛堣窡浠e偍閲戦鑱斿姩锛夈�嶃��
+#          绗� 6 鏉°�岄攢鍞彴璐﹀尯鍒嗗崠娌�/鍗栧瓨鍌ㄤ綅缃紝鎺ュ凡鏈� OA 瀹℃壒娴佺▼锛屽悎鍚岄噾棰濊秴淇℃墭鍩洪噾瑕佹爣绾㈡彁閱掋��
+#鐩爣搴擄細product-inventory-management-rbhb
+#璇存槑锛氱函鏂板鍒楋紝涓嶆敼鍔ㄤ换浣曟棦鏈夊垪
+
+#1. 瀹㈡埛淇℃墭鍩洪噾
+alter table customer
+    add column trust_fund decimal(16, 2) null comment '淇℃墭鍩洪噾(鍏�)';
+
+#2. 閿�鍞彴璐︼細绫诲瀷鍖哄垎 + 浠e偍鐨勬补搴�/鍌ㄧ綈 + 瀹℃壒鐘舵��
+alter table sales_ledger
+    add column ledger_type     varchar(20)  null comment '閿�鍞被鍨嬶細鍗栨补/浠e偍' after customer_name,
+    add column oil_depot_id    bigint       null comment '娌瑰簱id锛坥il_depot.id锛夛紝绫诲瀷=浠e偍鏃朵娇鐢�' after ledger_type,
+    add column oil_depot_name  varchar(100) null comment '娌瑰簱鍚嶇О' after oil_depot_id,
+    add column tank_id         bigint       null comment '鍌ㄧ綈id锛坱ank_info.id锛夛紝绫诲瀷=浠e偍鏃朵娇鐢�' after oil_depot_name,
+    add column tank_no         varchar(50)  null comment '鍌ㄧ綈鍙�' after tank_id,
+    add column approval_status varchar(20)  null comment '瀹℃壒鐘舵�侊細寰呭鎵�/瀹℃牳涓�/閫氳繃/鎷掔粷' after tank_no;
+
+#3. 瀛橀噺鍙拌处鍥炲~涓哄崠娌癸紙瀛橀噺閮芥槸璧颁骇鍝佹槑缁嗙殑閿�鍞崟锛屽巻鍙插崟鎹笉琛ュ锛宎pproval_status 鐣欑┖锛�
+update sales_ledger set ledger_type = '鍗栨补' where ledger_type is null;
+
+#4. 閿�鍞彴璐﹀鎵规ā鏉匡紙鎸傚凡鏈� OA 瀹℃壒寮曟搸锛宐usiness_type=19 涓虹┖闂插�硷紱瀹℃壒浜� 閮戞箻淇� 涓庢姤浠峰鎵规ā鏉夸竴鑷达級
+insert into approval_template (template_name, enabled, description, template_type, business_type, deleted)
+values ('閿�鍞彴璐﹀鎵�', 1, '閿�鍞彴璐︼紙鍚唬鍌級鎻愪氦鍚庤蛋 OA 瀹℃壒', 1, 19, 0);
+
+set @tid = last_insert_id();
+
+insert into approval_template_node (template_id, level_no, approve_type)
+values (@tid, 1, 'AND');
+
+set @nid = last_insert_id();
+
+insert into approval_template_node_approver (node_id, template_id, approver_id, approver_name, sort_no, deleted)
+values (@nid, @tid, 323, '閮戞箻淇�', 1, 0);
diff --git a/doc/20260920_vehicle_and_residual_oil.sql b/doc/20260920_vehicle_and_residual_oil.sql
new file mode 100644
index 0000000..96ae29b
--- /dev/null
+++ b/doc/20260920_vehicle_and_residual_oil.sql
@@ -0,0 +1,41 @@
+#杞﹁締涓绘暟鎹� + 鍑哄簱娈嬫补
+#瀵瑰簲闇�姹傦細闇�姹傛竻鍗曠 7 鏉°�岀粰杞﹁締鍋氬敮涓�鏍囪瘑鈥﹁褰曟瘡娆℃渶鍚庨�佸畬璐у悗鍓╀綑鐨勬补閲忥紝涓嬫鍙戣揣涓轰笂娆″墿浣欓噺+搴撳瓨閲忋��
+#鐩爣搴擄細product-inventory-management-rbhb
+#璇存槑锛氱函鏂板锛屼笉鏀瑰姩浠讳綍鏃㈡湁鍒楋紝鍙噸澶嶆墽琛屾椂璇峰厛鍒ゆ柇鏄惁瀛樺湪
+
+#1. 杞﹁締淇℃伅琛�
+create table vehicle
+(
+    id            bigint          not null auto_increment comment '杞﹁締id',
+    plate_number  varchar(20)     not null comment '杞︾墝鍙凤紙杞﹁締鍞竴鏍囪瘑锛�',
+    driver_name   varchar(50)     null comment '榛樿鍙告満',
+    driver_phone  varchar(20)     null comment '鍙告満鐢佃瘽',
+    load_capacity decimal(16, 4)  null comment '鏍稿畾杞介噸(鍚�)',
+    status        varchar(20)     null default '鍚敤' comment '鐘舵�侊細鍚敤/鍋滅敤',
+    remark        varchar(255)    null comment '澶囨敞',
+    create_time   datetime        null comment '鍒涘缓鏃堕棿',
+    create_user   bigint          null comment '鍒涘缓浜�',
+    update_time   datetime        null comment '鏇存柊鏃堕棿',
+    update_user   bigint          null comment '鏇存柊浜�',
+    tenant_id     bigint          null comment '绉熸埛id',
+    dept_id       bigint          null comment '閮ㄩ棬id',
+    primary key (id),
+    unique key uk_plate_number (plate_number)
+) engine = InnoDB
+  default charset = utf8mb4
+  row_format = DYNAMIC comment ='杞﹁締淇℃伅琛�';
+
+#2. 鍑哄簱璁板綍锛氳溅杈� + 娈嬫补 + 瀹為檯鎵e噺閲�
+alter table stock_out_record
+    add column vehicle_id      bigint        null comment '杞﹁締ID锛坴ehicle.id锛�' after truck_plate_no,
+    add column residual_oil    decimal(16, 4) null comment '鏈閫佸畬璐у悗鍓╀綑娌归噺(鍚�)' after vehicle_id,
+    add column deduct_quantity decimal(16, 4) null comment '瀹℃壒閫氳繃鏃跺疄闄呬粠搴撳瓨鎵e噺鐨勬暟閲�(=鍑哄簱鏁伴噺-涓婃鍓╀綑娌归噺)' after residual_oil;
+
+#3. 鑿滃崟锛�2026-09-20 宸插湪寮�鍙戝簱鎵ц锛宮enu_id=2377锛�
+#娉ㄦ剰锛氬墠绔〉闈� src/views/basicData/vehicle/index.vue 灏氭湭鍒涘缓锛岀偣杩涘幓鏆傛椂鏄� 404锛岄〉闈㈠仛濂藉嵆鑷姩鍙敤
+insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
+values ('杞﹁締淇℃伅', 2000, 4, 'vehicle', 'basicData/vehicle/index', 1, 0, 'C', 0, 0, '', '#', 'admin', now(), '');
+
+#3.1 鑿滃崟鎺堟潈锛氫笌鍚岀骇鐨勬补搴撲俊鎭�/鍌ㄧ綈淇℃伅淇濇寔涓�鑷达紝鎺堟潈缁欍�屾妧鏈川閲忋�嶈鑹诧紙role_id=2锛�
+insert into sys_role_menu (role_id, menu_id)
+select 2, menu_id from sys_menu where menu_name = '杞﹁締淇℃伅' and parent_id = 2000;
diff --git "a/doc/20260920_\344\277\241\346\211\230\345\237\272\351\207\221\344\270\216\344\273\243\345\202\250\345\217\260\350\264\246\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260920_\344\277\241\346\211\230\345\237\272\351\207\221\344\270\216\344\273\243\345\202\250\345\217\260\350\264\246\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..1b90dac
--- /dev/null
+++ "b/doc/20260920_\344\277\241\346\211\230\345\237\272\351\207\221\344\270\216\344\273\243\345\202\250\345\217\260\350\264\246\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,309 @@
+# 淇℃墭鍩洪噾涓庨攢鍞彴璐︾被鍨�/浠e偍/瀹℃壒 鍓嶇鑱旇皟鏂囨。
+
+鏇存柊鏃堕棿锛�2026-09-20
+閫傜敤鐗堟湰锛氭湰娆″悗绔彉鏇村悗
+瀵瑰簲闇�姹傦細闇�姹傛竻鍗曠 5 鏉� + 绗� 6 鏉�
+
+- **绗� 5 鏉★紙瀹㈡埛妗f锛�**锛氫唬鍌ㄥ拰閿�鍞垎寮�涓や釜椤甸潰锛屽姞涓�涓�**淇℃墭鍩洪噾**瀛楁锛堣璺�**浠e偍閲戦**鑱斿姩锛岃秴杩囩浉搴旈噾棰濊鍋氭彁閱掞級銆�
+- **绗� 6 鏉★紙閿�鍞姤浠�/閿�鍞彴璐︼級**锛�
+  1. 涓ょ绫诲瀷锛�**鍗栨补** / **鍗栧瓨鍌ㄤ綅缃�**銆傞�夊畬绫诲埆鍚庯紝鍗栨补灏辨槸閫変互鍓嶇殑浜у搧澶х被鍜岃鏍煎瀷鍙凤紱鍗栧瓨鍌ㄤ綅缃氨閫�**娌瑰簱鍜屽偍缃�**銆�
+  2. **鍔犲叆閿�鍞鎵规祦绋�**锛�= 鎺ョ郴缁�**宸叉湁 OA 瀹℃壒**锛屼笉鏄柊鍐欎竴濂楋級銆�
+  3. 瑕佽窡瀹㈡埛鐨�**淇℃墭鍩洪噾**瀛楁鑱斿姩锛�**鍚堝悓閲戦瓒呰繃淇℃墭鍩洪噾瑕佸仛鏍囩孩鎻愰啋**锛岀浉搴旂殑**瀹℃壒娴佺▼涔熻鍋氬嚭鎻愰啋瀛楃溂**銆�
+
+**涓�鍙ヨ瘽鍙e緞**锛�
+
+- 鍙拌处鍒嗕袱绫� 鈥斺�� **鍗栨补**锛氳蛋浜у搧鏄庣粏锛屽悎鍚岄噾棰濈敱鏄庣粏姹囨�伙紝鍜屼互鍓嶄竴鏍凤紱**浠e偍**锛氬崠瀛樺偍浣嶇疆锛�**娌℃湁浜у搧鏄庣粏**锛岄��**娌瑰簱 + 鍌ㄧ綈**骞�**鎵嬪伐濉悎鍚岄噾棰�**銆�
+- 鍙拌处**淇濆瓨鍚庣珛鍗冲彂璧� OA 瀹℃壒**锛堟柊澧炪�佷慨鏀归兘鍙戯級锛屽鎵圭粨鏋滃彧鍥炲啓鍙拌处涓婄殑**瀹℃壒鐘舵��**銆�
+- 鏈瑪鍚堝悓閲戦 > 璇ュ鎴蜂俊鎵樺熀閲� 鈫� 鍙拌处鍒楄〃琛�**鏍囩孩**锛堝悗绔粰甯冨皵锛屽墠绔煋鑹诧級+ 瀹℃壒**鏍囬**甯︽彁閱掑瓧鐪笺��
+
+---
+
+## 1. 鍙樻洿鑼冨洿
+
+| 鍐呭 | 褰卞搷闈� |
+| --- | --- |
+| `customer` 鍔� `trust_fund` | 瀹㈡埛妗f鏂板/淇敼琛ㄥ崟銆佸鎴峰垪琛ㄣ�佸鍑� |
+| 瀹㈡埛鎺ュ彛杩斿洖浠e偍閲戦涓庤秴闄愭爣璁� | 瀹㈡埛妗f鍒楄〃銆佸鎴疯鎯� |
+| `sales_ledger` 鍔� 6 鍒� + `ledgerType` 蹇呭~ | 閿�鍞彴璐︽柊澧�/淇敼琛ㄥ崟锛�**鐮村潖鎬э紝瑙� 3.4**锛� |
+| 鍙拌处淇濆瓨鍚庤嚜鍔ㄨ蛋 OA 瀹℃壒 | 閿�鍞彴璐︿繚瀛樻寜閽紱瀹℃壒涓績鍒楄〃 |
+| 鍙拌处鍒楄〃/璇︽儏/瀵煎嚭鏂板瀛楁 | 閿�鍞彴璐﹀垪琛ㄣ�佸鍑� Excel |
+| `ledgerType` / `approvalStatus` 鏀寔绛涢�� | 鍗栨补銆佷唬鍌�**涓や釜椤甸潰鐨勬媶鍒�**闈犲畠瀹炵幇 |
+
+> 鏈粨搴撴槸**鍚庣浠撳簱**锛屽墠绔湪 `D:/鐗涢┈/鍗楅��/鍓嶇/product-inventory-management`锛屾湰鏂囨。鍙弿杩板悗绔凡灏辩华鐨勮兘鍔涖��
+> **绗� 5 鏉°�屼唬鍌ㄥ拰閿�鍞垎寮�涓や釜椤甸潰銆嶇敱鍓嶇鎷�**锛屽悗绔笉鏂板鍒楄〃鎺ュ彛锛岄潬鍚屼竴涓� `/sales/ledger/listPage` 浼� `ledgerType` 鍖哄垎銆�
+
+---
+
+## 2. 鍓嶇疆锛欴DL 涓庡鎵规ā鏉垮凡鎵ц
+
+鏂囦欢锛歚doc/20260920_trust_fund_and_deposit_ledger.sql`锛�**宸蹭簬 2026-09-20 鍦ㄥ紑鍙戝簱 `product-inventory-management-rbhb` 鎵ц瀹屾瘯**锛屽叾浠栫幆澧冧笂绾挎寜鍚屼竴鏂囦欢鎵ц鍗冲彲銆�
+
+鎵ц鍐呭锛堢函鏂板鍒楋紝涓嶆敼鍔ㄦ棦鏈夊垪锛夛細
+
+| 琛� | 鍙樻洿 |
+| --- | --- |
+| `customer` | 鏂板 `trust_fund decimal(16,2)` 鈥斺�� 淇℃墭鍩洪噾(鍏�) |
+| `sales_ledger` | 鏂板 `ledger_type`(varchar20)銆乣oil_depot_id`(bigint)銆乣oil_depot_name`(varchar100)銆乣tank_id`(bigint)銆乣tank_no`(varchar50)銆乣approval_status`(varchar20) |
+| `sales_ledger` 瀛橀噺鏁版嵁 | 3 鏉″巻鍙插彴璐﹀洖濉� `ledger_type='鍗栨补'`锛沗approval_status` 鐣欑┖锛堝巻鍙插崟鎹笉琛ュ锛� |
+| `approval_template` 绛� 3 寮犺〃 | seed 鍑� `business_type=19` 鐨勩�岄攢鍞彴璐﹀鎵广�嶆ā鏉匡紙瀹℃壒浜� **閮戞箻淇�**锛� |
+
+**宸叉牳瀵圭粨鏋�**锛氭ā鏉� `id=65`銆岄攢鍞彴璐﹀鎵广�峘enabled=1`銆乣template_type=1`銆乣business_type=19`锛涜妭鐐� `id=95` `level_no=1` `approve_type=AND`锛涘鎵逛汉 `323 閮戞箻淇瀈銆�
+
+> **DDL 鏈墽琛屽墠**锛氬彴璐︾殑鏂板/淇敼浼氱洿鎺ユ姤 `Unknown column 'ledger_type'`銆�
+
+---
+
+## 3. 閿�鍞彴璐︼細绫诲瀷鍖哄垎锛堢 6 鏉� 1锛�
+
+### 3.1 鏂板鐨勫瓧娈�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| `ledgerType` | String | **蹇呭~**锛屽彧鑳芥槸 `鍗栨补` / `浠e偍` |
+| `oilDepotId` | Long | 娌瑰簱 id锛�**浠e偍蹇呭~** |
+| `oilDepotName` | String | 娌瑰簱鍚嶇О锛�**鍚庣鎸� id 鍥炴煡锛屼笉閲囦俊鍓嶇浼犲��** |
+| `tankId` | Long | 鍌ㄧ綈 id锛�**浠e偍蹇呭~** |
+| `tankNo` | String | 鍌ㄧ綈鍙凤紝**鍚屼笂锛屽悗绔洖鏌�** |
+| `contractAmount` | BigDecimal | 鍚堝悓閲戦(鍏�)銆�**浠e偍蹇呭~涓� > 0**锛涘崠娌逛笉鐢ㄤ紶 |
+| `approvalStatus` | String | 瀹℃壒鐘舵�侊紝**鍚庣缁存姢锛屽墠绔笉瑕佷紶** |
+
+> 鈿狅笍 **`ledgerType` 鏄柊瀛楁锛屼笉瑕佸拰琛ㄥ崟閲屽凡鏈夌殑 `type` 娣风敤**銆俙type` 鏄�**浜у搧鏄庣粏琛�**鐨勭被鍨嬶紙1閿�鍞�/2閲囪喘/3椤圭洰锛夛紝鍓嶇琛ㄥ崟閲屽凡鏈夌殑閭d釜 `type` 淇濇寔鍘熸牱浼狅紝涓嶈鏀规垚 `ledgerType`銆�
+
+### 3.2 鍗栨补锛堝拰鏀归�犲墠鍩烘湰涓�鑷达級
+
+```json
+POST /sales/ledger/addOrUpdateSalesLedger
+{
+  "ledgerType": "鍗栨补",
+  "customerId": 88,
+  "entryDate": "2026-09-20",
+  "type": 1,
+  "productData": [
+    { "productId": 12, "productModelId": 407, "quantity": 10, "taxInclusiveUnitPrice": 5000, "taxInclusiveTotalPrice": 50000, "taxRate": 13, "unit": "鍚�" }
+  ]
+}
+```
+
+- 鍚堝悓閲戦锛坄contractAmount`锛�**鐢变骇鍝佹槑缁嗚嚜鍔ㄦ眹鎬�**锛屽墠绔紶浜嗕篃浼氳瑕嗙洊锛�**鍗栨补琛ㄥ崟涓嶈鏀俱�屽悎鍚岄噾棰濄�嶈緭鍏ユ**
+- 鍚庣浼氭妸娌瑰簱/鍌ㄧ綈/瀹℃壒鐘舵�佷箣澶栫殑浠e偍瀛楁娓呯┖
+
+### 3.3 浠e偍锛堝崠瀛樺偍浣嶇疆锛�
+
+```json
+POST /sales/ledger/addOrUpdateSalesLedger
+{
+  "ledgerType": "浠e偍",
+  "customerId": 88,
+  "entryDate": "2026-09-20",
+  "oilDepotId": 1,
+  "tankId": 3,
+  "contractAmount": 100000
+}
+```
+
+- **涓嶈浼� `productData`**锛堜唬鍌ㄦ病鏈変骇鍝佹槑缁嗭紱灏辩畻浼犱簡鍚庣涔熶細蹇界暐锛岄伩鍏嶆槑缁嗘眹鎬昏鐩栨墜宸ュ~鐨勫悎鍚岄噾棰濓級
+- **鍚堝悓閲戦蹇呴』鍓嶇鎵嬪伐濉�**锛屽悗绔笉浼氱畻
+- 娌瑰簱涓嬫媺鍙栧熀纭�鏁版嵁鐨勬补搴撳垪琛紝鍌ㄧ綈涓嬫媺鍙栧偍缃愬垪琛紙鍌ㄧ綈**涓嶆寕鍦ㄦ补搴撲笅**锛屼袱涓笅鎷変簰鐩哥嫭绔嬶級
+
+### 3.4 鈿狅笍 杩欐槸鐮村潖鎬у彉鏇达細鍓嶇蹇呴』鍏堟敼
+
+`ledgerType` 鏄�**蹇呭~**銆�**鍓嶇娌℃敼涔嬪墠锛岀幇鏈夌殑閿�鍞彴璐︽柊澧�/淇敼浼氱洿鎺ュけ璐�**锛�
+
+```json
+{ "code": 500, "msg": "閿�鍞被鍨嬩笉鑳戒负绌�", "data": null }
+```
+
+鎵�浠ヤ笂绾块『搴忥細**鍓嶇鍏堝姞銆岄攢鍞被鍨嬨�嶅崟閫夊苟鎻愪氦 `ledgerType` 鈫� 鍐嶆斁閲�**銆�
+锛堝悗绔凡鎶�**妯℃嫙鏁版嵁鐢熸垚**杩欑被鍐呴儴璋冪敤鐐硅ˉ鎴� `鍗栨补`锛屼笉鍙楀奖鍝嶃�傦級
+
+---
+
+## 4. 淇℃墭鍩洪噾鑱斿姩锛堢 5 鏉� + 绗� 6 鏉� 3锛�
+
+### 4.1 瀹㈡埛妗f锛氬啓鍏ヤ俊鎵樺熀閲�
+
+`/basic/customer/addCustomer`銆乣/basic/customer/updateCustomer` 鏀剁殑灏辨槸瀹㈡埛瀵硅薄锛�**鐩存帴澶氫紶涓�涓� `trustFund` 鍗冲彲**锛屼笉鐢ㄦ柊鎺ュ彛锛�
+
+```json
+POST /basic/customer/updateCustomer
+{ "id": 88, "customerName": "鏌愭煇鍖栧伐", "trustFund": 200000 }
+```
+
+- 鍗曚綅锛氬厓锛�**鍙互涓虹┖**锛屼负绌哄垯涓嶅仛浠讳綍鎻愰啋锛堝姛鑳介潤榛樺彲鐢級
+- 瀹㈡埛鍒楄〃瀵煎嚭 Excel 鏂板銆屼俊鎵樺熀閲戙�嶅垪
+
+### 4.2 瀹㈡埛鍒楄〃/璇︽儏锛氳繑鍥炰唬鍌ㄩ噾棰濅笌瓒呴檺鏍囪
+
+`GET /basic/customer/list`锛坄data.records`锛夊拰 `GET /basic/customer/{id}`锛坄data`锛夌殑姣忚**鏂板涓や釜鍙瀛楁**锛�
+
+| 瀛楁 | 璇存槑 |
+| --- | --- |
+| `depositTotalAmount` | 璇ュ鎴�**浠e偍閲戦鍚堣** = 璇ュ鎴�**鎵�鏈� `ledger_type='浠e偍'` 鍙拌处鐨勫悎鍚岄噾棰濅箣鍜�**锛堟棤璁板綍杩斿洖 `0`锛� |
+| `exceedsTrustFund` | `depositTotalAmount > trustFund` 鏃朵负 `true`锛沗trustFund` 涓虹┖鏃舵亽涓� `false` |
+
+```json
+// GET /basic/customer/88
+{ "code": 200, "msg": "鎿嶄綔鎴愬姛",
+  "data": { "id": 88, "customerName": "鏌愭煇鍖栧伐", "trustFund": 200000.00,
+            "depositTotalAmount": 260000.00, "exceedsTrustFund": true } }
+```
+
+> **涓や釜鍙e緞涓嶈鎼炴贩**锛�
+> - 瀹㈡埛妗f涓婄殑 `exceedsTrustFund` 姣旂殑鏄� **浠e偍閲戦鍚堣**锛堟墍鏈変唬鍌ㄥ崟鍔犳�伙級鈫� 瀹㈡埛妗f椤垫彁閱�
+> - 鍙拌处鍒楄〃涓婄殑 `exceedsTrustFund` 姣旂殑鏄� **鏈瑪鍚堝悓閲戦**锛堝崟绗旓級鈫� 鍙拌处鍒楄〃琛屾爣绾�
+>
+> 浠e偍閲戦鍚堣鏄�**璺ㄥ鎴锋眹鎬�**锛屽悗绔凡鏄惧紡璺宠繃鏁版嵁鏉冮檺锛岀湅鍒扮殑濮嬬粓鏄�**鍏ㄩ噺**閲戦锛堝惁鍒欐暟鎹寖鍥村彈闄愮殑璐﹀彿浼氬皯绠椼�佹紡鎶ヨ秴闄愶級銆�
+
+### 4.3 鍙拌处鍒楄〃锛氭湰绗旇秴闄愭爣绾�
+
+`GET /sales/ledger/listPage`銆乣GET /sales/ledger/list`銆佸鍑� Excel 鐨勬瘡琛�**鏂板涓や釜鍙瀛楁**锛�
+
+| 瀛楁 | 璇存槑 |
+| --- | --- |
+| `trustFund` | 璇ュ鎴风殑淇℃墭鍩洪噾锛堝鎴锋病濉垯涓� `null`锛� |
+| `exceedsTrustFund` | **鏈瑪** `contractAmount > trustFund` 鏃朵负 `true` |
+
+**瑙勫垯**锛�**鍗曠瑪瓒呭氨鏍囩孩**鈥斺�斿彧瑕佽繖涓�绗旂殑鍚堝悓閲戦瓒呰繃璇ュ鎴风殑淇℃墭鍩洪噾锛岃繖涓�琛屽氨鏍囩孩锛沗trustFund` 涓� `null` 鏃�**涓嶆爣绾�**銆�
+
+灞曠ず寤鸿锛歚exceedsTrustFund === true` 鏃舵妸**鍚堝悓閲戦**锛堟垨鏁磋锛夋爣绾紝骞跺姞 tooltip锛歚鍚堝悓閲戦宸茶秴鍑鸿瀹㈡埛淇℃墭鍩洪噾锛堜俊鎵樺熀閲戯細200000锛塦銆�
+
+> 缂栬緫琛ㄥ崟閲岀殑瀹炴椂棰勮锛氶�変腑瀹㈡埛鍚庤皟 `GET /basic/customer/{id}` 鎷� `trustFund`锛屽拰杈撳叆鐨勫悎鍚岄噾棰濇湰鍦版瘮杈冨嵆鍙紝涓嶉渶瑕佹柊鎺ュ彛銆�
+
+---
+
+## 5. 瀹℃壒锛氭帴宸叉湁 OA 瀹℃壒寮曟搸锛堢 6 鏉� 2锛�
+
+### 5.1 鍙戣捣鏃舵満
+
+**鍙拌处淇濆瓨鎺ュ彛 `POST /sales/ledger/addOrUpdateSalesLedger` 鍐呴儴鑷姩鍙戣捣**锛屽墠绔笉鐢ㄨ皟瀹℃壒鎺ュ彛锛�
+
+- **鏂板**锛歩nsert 鍙拌处 鈫� 鐘舵�佺疆 `寰呭鎵筦 鈫� 鍙戣捣瀹℃壒瀹炰緥
+- **淇敼**锛氭妸璇ュ彴璐�**姝ゅ墠鏈粨鏉熺殑瀹℃壒瀹炰緥浣滃簾**锛堢疆 `REJECTED`锛夆啋 鐘舵�佸洖 `寰呭鎵筦 鈫� 閲嶆柊鍙戣捣
+  - 鈿狅笍 **娉ㄦ剰锛氭敼涓娉ㄤ篃浼氶噸鏂拌蛋涓�閬嶅鎵�**锛堟部鐢ㄦ姤浠峰崟鐨勬棦鏈夎涓猴級
+
+鍙拌处涓婄殑 `approvalStatus` 鍙栧�硷細
+
+| 鍊� | 鍚箟 |
+| --- | --- |
+| `寰呭鎵筦 | 鍒氭彁浜わ紝绛夊緟瀹℃壒浜哄鐞� |
+| `瀹℃牳涓璥 | 澶氱骇娴佺▼娴佽浆涓紙鏈妯℃澘鍙湁 1 绾э紝瀹為檯瑙佸埌鐨勪笉澶氾級 |
+| `閫氳繃` | 瀹℃壒閫氳繃 |
+| `鎷掔粷` | 琚┏鍥� |
+| `null` | 鍘嗗彶鍗曟嵁锛堟湰娆℃敼閫犲墠鐨� 3 鏉★級锛屼笉琛ュ |
+
+> **瀹℃壒閫氳繃鍙洖鍐欑姸鎬�**锛氫笉閿佸畾鍗曟嵁銆佷笉鐢熸垚涓嬫父鍗曟嵁銆佷笉鍔ㄥ簱瀛樸�傞�氳繃鍚庣殑鍙拌处**浠嶇劧鍙互缂栬緫**锛岀紪杈戜細鍐嶆瑙﹀彂瀹℃壒銆�
+
+### 5.2 瓒呴檺鏃剁殑瀹℃壒鎻愰啋瀛楃溂
+
+瀹℃壒瀹炰緥鐨�**鏍囬**鎵胯浇鎻愰啋锛�
+
+- 鏈秴闄愶細`HT20260920001瀹℃壒`
+- 宸茶秴闄愶細`HT20260920001瀹℃壒锛堝悎鍚岄噾棰濊秴鍑哄鎴蜂俊鎵樺熀閲戯紝璇烽噸鐐瑰鏍革級`
+
+鎵�浠ュ鎵逛腑蹇冨垪琛ㄩ噷锛屽鎵逛汉**涓�鐪煎氨鑳界湅鍑鸿繖鍗曢噾棰濊秴浜�**锛屽墠绔�**涓嶉渶瑕侀澶栧鐞�**锛岀洿鎺ユ妸 `title` 鍘熸牱灞曠ず鍗冲彲銆�
+
+### 5.3 瀹℃壒涓績锛堝墠绔凡鏈夐〉闈紝鍙渶鐭ラ亾杩欎袱鐐癸級
+
+- 瀹℃壒瀹炰緥鍒楄〃 `GET /approvalInstance/listPage` 閲� `businessType=19` 鐨勮褰曪紝**鏂板杩斿洖 `salesContractNo`**锛堥攢鍞悎鍚屽彿锛夛紝涓庡凡鏈夌殑 `quotationNo`锛堟姤浠峰崟鍙凤級銆乣purchaseContractNumber`锛堥噰璐崟鍙凤級銆乣shippingNo`锛堝彂璐у崟鍙凤級骞跺垪
+- 瀹℃壒鍔ㄤ綔璧板凡鏈夌殑 `POST /approvalInstance/approve`锛�
+
+```json
+{ "id": 1234, "approveAction": "APPROVED", "approveComment": "鍚屾剰" }
+```
+
+锛坄approveAction` 鍙敮鎸� `APPROVED` / `REJECTED`锛�
+
+### 5.4 鍒犻櫎鍙拌处
+
+`DELETE /sales/ledger/delLedger`锛坆ody 涓� id 鏁扮粍锛�**浼氳嚜鍔ㄦ竻鎺夎鍙拌处瀵瑰簲鐨勫鎵瑰疄渚�**锛堝悓涓�鍙拌处鍙嶅鎻愪氦鍙兘鏈夊鏉★級锛屽墠绔笉鐢ㄧ銆�
+
+---
+
+## 6. 鍙拌处鍒楄〃鏂板瀛楁涓庣瓫閫�
+
+### 6.1 杩斿洖缁撴瀯锛堟敞鎰忎袱涓帴鍙g殑鍖呰涓嶅悓锛�
+
+| 鎺ュ彛 | 杩斿洖 |
+| --- | --- |
+| `GET /sales/ledger/listPage` | **鐩存帴杩斿洖 `IPage`**锛歚{ "records": [...], "total": 12, "size": 10, "current": 1, ... }`锛�**娌℃湁 `code`/`msg` 澶栧眰** |
+| `GET /sales/ledger/list` | `TableDataInfo`锛歚{ "total": 12, "rows": [...], "code": 200, "msg": "鏌ヨ鎴愬姛" }` |
+
+鍒嗛〉鍙傛暟娌跨敤骞冲彴绾﹀畾锛歚pageNum` / `pageSize`銆�
+
+### 6.2 姣忚鏂板瀛楁
+
+`ledgerType`銆乣oilDepotId`銆乣oilDepotName`銆乣tankId`銆乣tankNo`銆乣approvalStatus`銆乣trustFund`銆乣exceedsTrustFund`銆�
+
+瀵煎嚭 Excel锛坄POST /sales/ledger/export`锛夋柊澧炲垪锛�**閿�鍞被鍨嬨�佹补搴撱�佸偍缃愬彿銆佸鎵圭姸鎬併�佸鎴蜂俊鎵樺熀閲�**銆�
+
+### 6.3 鏂板涓や釜鏌ヨ鏉′欢锛堢 5 鏉°�屽垎寮�涓や釜椤甸潰銆嶇殑鍚庣鏀拺锛�
+
+| 鍙傛暟 | 璇存槑 |
+| --- | --- |
+| `ledgerType` | `鍗栨补` / `浠e偍`锛岀簿纭尮閰� |
+| `approvalStatus` | `寰呭鎵筦 / `瀹℃牳涓璥 / `閫氳繃` / `鎷掔粷`锛岀簿纭尮閰� |
+
+**寤鸿鍋氭硶**锛氬墠绔媶鎴愩�岄攢鍞彴璐︺�嶅拰銆屼唬鍌ㄥ彴璐︺�嶄袱涓〉闈紙鎴栦竴涓〉闈㈢殑涓や釜椤电锛夛紝鍚勮嚜鍥哄畾甯� `ledgerType='鍗栨补'` / `'浠e偍'` 璋�**鍚屼竴涓�** `/sales/ledger/listPage`锛屽悗绔笉鐢ㄦ柊澧炲垪琛ㄦ帴鍙c�備袱涓〉闈㈢殑**鏂板琛ㄥ崟涔熸寜绫诲瀷鍖哄垎**锛氬崠娌归〉鏀句骇鍝佹槑缁嗚〃鏍硷紝浠e偍椤垫斁銆屾补搴� + 鍌ㄧ綈 + 鍚堝悓閲戦銆嶃��
+
+---
+
+## 7. 鎶ラ敊鏂囨涓庡搷搴斿舰鎬�
+
+鏈鏂板鐨勬牎楠岋細
+
+| 鍦烘櫙 | 鎶ラ敊 |
+| --- | --- |
+| 娌′紶閿�鍞被鍨� | `閿�鍞被鍨嬩笉鑳戒负绌篳 |
+| 閿�鍞被鍨嬩笉鏄崠娌�/浠e偍 | `閿�鍞被鍨嬩笉鍚堟硶:xxx` |
+| 浠e偍娌¢�夋补搴� | `浠e偍蹇呴』閫夋嫨娌瑰簱` |
+| 浠e偍娌¢�夊偍缃� | `浠e偍蹇呴』閫夋嫨鍌ㄧ綈` |
+| 浠e偍娌″~鍚堝悓閲戦鎴� 鈮�0 | `浠e偍蹇呴』濉啓鍚堝悓閲戦` |
+| 娌瑰簱 id 鏌ヤ笉鍒� | `娌瑰簱涓嶅瓨鍦�:1` |
+| 鍌ㄧ綈 id 鏌ヤ笉鍒� | `鍌ㄧ綈涓嶅瓨鍦�:3` |
+| 鏈厤缃鎵规ā鏉� | `璇峰厛閰嶇疆閿�鍞彴璐﹀鎵规ā鏉縛 |
+| 瀹℃壒寮曟搸鎶ラ敊 | `瀹℃壒澶辫触: <鍘熷洜>` |
+
+**閿欒鍝嶅簲褰㈡��**锛坄BaseException` / `RuntimeException` 琚叏灞�寮傚父澶勭悊鍣ㄥ厹鎴� `{code:500,msg:...,data:null}`锛屾敞鎰� **HTTP 鐘舵�佺爜浠嶇劧鏄� 200**锛夛細
+
+```json
+{ "code": 500, "msg": "浠e偍蹇呴』閫夋嫨娌瑰簱", "data": null }
+```
+
+鎵�浠ュ墠绔�**鍒ゅけ璐ヨ鐪� `code`锛屼笉鑳藉彧鐪� HTTP 鐘舵�佺爜**锛屽苟涓旇鎶� `msg` 鍘熸牱閫忓嚭銆�
+
+> 鍙拌处淇濆瓨澶辫触鏃�**鏁翠釜浜嬪姟鍥炴粴**锛堝彴璐︺�佹槑缁嗐�佸鎵瑰疄渚嬮兘涓嶄細鐣欎笅鍗婃埅鏁版嵁锛夈��
+
+---
+
+## 8. 宸茬煡褰卞搷涓庢敞鎰忕偣
+
+1. **`ledgerType` 蹇呭~鏄牬鍧忔�у彉鏇�**锛屽墠绔笉鍏堟敼灏变細鎶� `閿�鍞被鍨嬩笉鑳戒负绌篳锛堣 3.4锛夈�傝繖鏄湰娆″敮涓�鐨勫己鍒惰仈璋冮」銆�
+2. **鍘嗗彶 3 鏉″彴璐�**宸插洖濉负 `鍗栨补`锛宍approvalStatus` 涓虹┖锛堜笉琛ュ锛夈�傚畠浠殑鍚堝悓閲戦銆佷骇鍝佹槑缁嗛兘涓嶅彉銆�
+3. **瀹㈡埛娌$淮鎶� `trustFund` 灏变笉鎻愰啋**锛歚trustFund=null` 鈫� 涓嶆爣绾€�佸鎵规爣棰樹笉甯︽彁閱掑瓧鐪硷紝鍔熻兘闈欓粯鍙敤锛屼笉浼氬洜涓哄熀纭�鏁版嵁娌$淮鎶ゅ氨璇姤銆�
+4. **瀹℃壒妯℃澘鐨勪笅鎷夐�夐」鏄墠绔‖缂栫爜鐨�**锛歚/approvalTemplate` 鐨勬柊寤洪〉閲� `business_type` 閫夐」鍐欐鍦ㄥ墠绔�傛湰娆¢潬 seed 钀戒簡 `19`锛�**妯℃澘鍒楄〃閲岃兘鐪嬪埌涔熻兘鏀�**锛堝鎵逛汉銆佸姞鑺傜偣锛夛紱浣�**濡傛灉瑕佹柊寤轰竴涓� 19 鐨勬ā鏉�**锛屽墠绔渶瑕佸厛鍦ㄤ笅鎷夐噷琛ヤ笂銆岄攢鍞彴璐﹀鎵�(19)銆嶈繖涓�椤广��
+5. **瀹℃壒閫氳繃涓嶅仛浠讳綍涓氬姟鍓綔鐢�**锛堜笉閿佸崟銆佷笉鍔ㄥ簱瀛樸�佷笉鐢熸垚涓嬫父鍗曟嵁锛夛紝閫氳繃鍚庝粛鍙紪杈戝苟鍐嶆瑙﹀彂瀹℃壒 鈥斺�� 杩欐槸鎸夐渶姹傜‘璁ょ殑鍙e緞銆�
+6. **浠e偍涓嶅崰鍌ㄧ綈瀹归噺**锛氭湰鎵�**娌℃湁**鍋氬偍缃愪綑閲忔墸鍑忥紙闇�姹傛病鎻愶級锛宍tank_info` 鐨勫墿浣欓噺涓嶅洜浠e偍鍙拌处鍙樺寲銆�
+7. **浠e偍閲戦鍚堣鏄法瀹㈡埛鐨勫叏閲忔眹鎬�**锛堝凡璺宠繃鏁版嵁鏉冮檺锛夛紝鍙拌处鍒楄〃鏈韩**浠嶅彈鏁版嵁鏉冮檺绾︽潫**锛堥潪绠$悊鍛樻寜閮ㄩ棬/鏈汉杩囨护锛夆�斺�旀墍浠ャ�屽鎴锋。妗堜笂鐨勮秴闄愩�嶅拰銆屾煇涓汉鍦ㄥ彴璐﹀垪琛ㄩ噷鐪嬪埌鐨勬潯鏁般�嶅彛寰勪笉鍚岋紝灞為鏈熴��
+8. **`ledgerType` 鐢ㄤ腑鏂囧瓧闈㈤噺**锛坄鍗栨补`/`浠e偍`锛夛紝涓庡嚭搴撳崟鐨� `outCategory`锛坄閿�鍞甡/`瀹㈠瓨`/`浠e偍`锛夐鏍间竴鑷达紱**涓嶆槸 1/2 鏁板瓧**銆�
+
+---
+
+## 9. 鍓嶇鑷煡娓呭崟
+
+- [ ] DDL 宸叉墽琛岋紙`customer.trust_fund`銆乣sales_ledger` 鐨� 6 涓柊鍒楅兘鍦級
+- [ ] 瀹㈡埛妗f琛ㄥ崟鏈夈�屼俊鎵樺熀閲戙�嶈緭鍏ユ锛屾柊澧�/淇敼鑳藉瓨涓娿�佽兘鍥炴樉
+- [ ] 瀹㈡埛鍒楄〃鑳界湅鍒般�屼俊鎵樺熀閲戙�嶅垪锛屽鍑� Excel 涔熸湁
+- [ ] 瀹㈡埛鍒楄〃/璇︽儏鑳芥嬁鍒� `depositTotalAmount`銆乣exceedsTrustFund`锛岃秴闄愭椂鏈夋彁閱掞紙姣旂殑鏄�**浠e偍閲戦鍚堣**锛�
+- [ ] 閿�鍞彴璐︽柊澧�/淇敼琛ㄥ崟鍔犱簡銆岄攢鍞被鍨嬨�嶅崟閫夛紝**鎻愪氦鏃跺甫 `ledgerType`**锛堝惁鍒欐姤 `閿�鍞被鍨嬩笉鑳戒负绌篳锛�
+- [ ] 閫夈�屽崠娌广�嶁啋 鏄剧ず浜у搧鏄庣粏琛ㄦ牸锛�**涓嶆樉绀�**銆屽悎鍚岄噾棰濄�嶈緭鍏ユ鍜屾补搴�/鍌ㄧ綈
+- [ ] 閫夈�屼唬鍌ㄣ�嶁啋 鏄剧ず銆屾补搴� + 鍌ㄧ綈 + 鍚堝悓閲戦銆嶏紝**涓嶆樉绀�**浜у搧鏄庣粏锛涘悎鍚岄噾棰濆繀濉�
+- [ ] 浠e偍鎻愪氦鏃朵笉甯� `productData`
+- [ ] 鍙拌处鍒楄〃鎷嗘垚鍗栨补/浠e偍涓や釜椤甸潰锛堟垨椤电锛夛紝鍚勮嚜甯� `ledgerType` 璋冨悓涓�涓� `/sales/ledger/listPage`
+- [ ] 鍙拌处鍒楄〃琛� `exceedsTrustFund === true` 鏃舵爣绾紙姣旂殑鏄�**鏈瑪鍚堝悓閲戦**锛�
+- [ ] 鍙拌处鍒楄〃鑳界湅鍒般�屽鎵圭姸鎬併�嶅垪锛屽苟涓旇兘鎸� `approvalStatus` 绛涢��
+- [ ] 鍙拌处淇濆瓨鍚庡埌瀹℃壒涓績鑳界湅鍒� `businessType=19` 鐨勮褰曪紝鏍囬涓� `閿�鍞悎鍚屽彿+瀹℃壒`
+- [ ] 鍚堝悓閲戦瓒呬俊鎵樺熀閲戞椂锛屽鎵规爣棰樺甫銆岋紙鍚堝悓閲戦瓒呭嚭瀹㈡埛淇℃墭鍩洪噾锛岃閲嶇偣瀹℃牳锛夈��
+- [ ] 瀹℃壒涓績鍒楄〃鑳芥樉绀哄彴璐︾殑 `salesContractNo`锛堥攢鍞悎鍚屽彿锛�
+- [ ] 鐢ㄥ鎵逛汉璐﹀彿鎶婂彴璐﹀鎵归�氳繃鍚庯紝鍥炲彴璐﹀垪琛ㄧ湅 `approvalStatus` 鍙樻垚 `閫氳繃`锛涢┏鍥炲彉 `鎷掔粷`
+- [ ] 鎺ュ彛鎶ラ敊鍒ゅ畾鐢ㄧ殑鏄� `code` 鑰屼笉鏄� HTTP 鐘舵�佺爜锛堝け璐ユ椂 HTTP 浠嶆槸 200锛�
+- [ ] `/sales/ledger/listPage` 鍙栨暟鎹敤鐨勬槸椤跺眰 `records`锛堟病鏈� `code`/`msg` 澶栧眰锛夛紝`/sales/ledger/list` 鐢ㄧ殑鏄� `rows`
diff --git "a/doc/20260920_\345\202\250\347\275\220\345\217\267\345\270\246\345\205\245\345\272\223\345\255\230\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260920_\345\202\250\347\275\220\345\217\267\345\270\246\345\205\245\345\272\223\345\255\230\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
index 9bb95b3..dd18aa3 100644
--- "a/doc/20260920_\345\202\250\347\275\220\345\217\267\345\270\246\345\205\245\345\272\223\345\255\230\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
+++ "b/doc/20260920_\345\202\250\347\275\220\345\217\267\345\270\246\345\205\245\345\272\223\345\255\230\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -45,9 +45,17 @@
 
 鍓嶇紑 `/basic/tankInfo`锛屾帴鍙e舰鎬佷笌娌瑰簱瀹屽叏涓�鑷达紙`list` / `all` / `{id}` / `add` / `update` / `del`锛夈��
 
-瀛楁锛歚id`銆乣tankNo`锛�**鍌ㄧ綈鍙�**锛屽 `01#`锛夈�乣category`锛堝ぇ绫伙紝濡� `鎴愬搧娌筦锛夈�乣oilProduct`锛堟补鍝侊紝濡� `鏌�#0`锛夈�乣capacity`锛堢綈瀹癸級銆乣remaining`锛堝簱瀛樹綑閲� m鲁锛夈�乣tenantInfo`锛堢瀹俊鎭級銆�
+瀛楁锛歚id`銆乣tankNo`锛�**鍌ㄧ綈鍙�**锛屽 `01#`锛夈�乣category`锛堝ぇ绫伙紝濡� `鎴愬搧娌筦锛夈�乣oilProduct`锛堟补鍝侊紝濡� `鏌�#0`锛夈�乣capacity`锛堢綈瀹癸級銆乣remaining`锛堝簱瀛樹綑閲� m鲁锛夈�乣isEnabled`锛堟槸鍚﹀惎鐢級銆乣usageType`锛堢敤閫旓級銆乣tenantInfo`锛堢瀹俊鎭級銆�
 
 **鍓嶇**锛氬偍缃愪俊鎭〃鍗曠殑銆屽ぇ绫汇�嶃�屾补鍝併�嶅缓璁仛鎴愪笅鎷夛紙褰撳墠搴撳唴鍊间负 澶х被=`鎴愬搧娌筦銆佹补鍝�=`鏌�#0`/`鏌�#-35`/`姹�#92`/`姹�#95`锛屽彲涓庝骇鍝佽鏍煎彛寰勫榻愬悗鍐嶅畾锛夈��
+
+#### 2.2.1 鏄惁鍚敤 / 鐢ㄩ�� / 绉熷淇℃伅锛堟湰娆℃柊澧烇級
+
+DDL 瑙� `doc/20260920_add_usage_to_tank_info.sql`銆�
+
+**瀛楁銆佽〃鍗曡仈鍔ㄣ�佸綊涓�鍖栬鍒欍�佹帴鍙h涓哄彉鍖栥�佹牎楠屾枃妗堛�佹敞鎰忎簨椤癸紝璇﹁鍗曠嫭鏂囨。锛歔`doc/20260920_鍌ㄧ綈鍚敤涓庣璧佷俊鎭墠绔仈璋冩枃妗�.md`](./20260920_鍌ㄧ綈鍚敤涓庣璧佷俊鎭墠绔仈璋冩枃妗�.md)銆�**
+
+涓�鍙ヨ瘽鎽樿锛歚isEnabled`锛�1 鍚敤 / 0 鏈惎鐢級+ `usageType`锛�1 绉熻祦 / 2 鑷敤锛�+ `tenantInfo`锛堣嚜鐢辨枃鏈級锛涙湭鍚敤鏃跺悗涓よ�呬竴寰嬫竻绌猴紝閫夌璧佹椂 `tenantInfo` 蹇呭~锛岄�夎嚜鐢ㄨ嚜鍔ㄦ竻绌恒�俙/basic/tankInfo/all` **鍙繑鍥炲凡鍚敤鐨勫偍缃�**锛堟湭鍚敤涓嶈繘鍏ュ簱琛ㄥ崟涓嬫媺锛夛紝`/basic/tankInfo/list` 浠嶈繑鍥炲叏閮ㄥ苟鏂板 `isEnabled`/`usageType` 杩囨护鍙傛暟銆�
 
 ---
 
@@ -190,6 +198,13 @@
 
 - [ ] 鑿滃崟 2353 娌瑰簱淇℃伅銆�2354 鍌ㄧ綈淇℃伅鑳芥墦寮�骞跺鍒犳敼鏌ワ紙姝ゅ墠鍚庣缂哄け锛岀偣寮�鎶ラ敊锛�
 - [ ] 鍌ㄧ綈淇℃伅鑳戒繚瀛� `tankNo`銆乣category`銆乣oilProduct`銆乣capacity`銆乣remaining`
+- [ ] 鍌ㄧ綈琛ㄥ崟锛氭墦寮�銆屾槸鍚﹀惎鐢ㄣ�嶆墠鍑虹幇鐢ㄩ�旓紝閫夈�岀璧併�嶆墠鍑虹幇绉熷淇℃伅杈撳叆妗�
+- [ ] 閫夈�岀璧併�嶄絾涓嶅~绉熷淇℃伅锛屼繚瀛樻姤銆岀璧佺殑鍌ㄧ綈蹇呴』濉啓绉熷淇℃伅銆�
+- [ ] 閫夈�岃嚜鐢ㄣ�嶄繚瀛樺悗閲嶆柊鏌ヨ鎯咃紝`tenantInfo` 鏄┖锛堣鏄庢竻绌虹敓鏁堬紝涓嶆槸娌″啓锛�
+- [ ] 鐢便�岀璧併�嶆敼鎴愩�屾湭鍚敤銆嶄繚瀛橈紝`usageType` 鍜� `tenantInfo` 閮藉彉绌�
+- [ ] 鏈惎鐢ㄧ殑鍌ㄧ綈**涓嶅湪** `/basic/tankInfo/all` 閲岋紱`/basic/tankInfo/list` 閲屼粛鑳界湅鍒�
+- [ ] 鍋滅敤涓�涓偍缃愬悗锛屽叆搴撹〃鍗曠殑鍌ㄧ綈涓嬫媺閲屾病鏈夊畠锛涢噸鏂板惎鐢ㄥ悗鍙堝嚭鐜�
+- [ ] 鍋滅敤鍌ㄧ綈鍚庯紝姝ゅ墠宸插叆搴撹褰曢噷鐨� `tankNo` 浠嶅湪锛堝巻鍙叉暟鎹笉鍙楀奖鍝嶏級
 - [ ] 鏂板鍏ュ簱琛ㄥ崟锛氭补搴�/鍌ㄧ綈鍋氭垚涓嬫媺锛坄/basic/oilDepot/all`銆乣/basic/tankInfo/all`锛夛紝閫夊偍缃愬悗 `tankNo` 鑷姩甯﹀嚭
 - [ ] 鏂板鍏ュ簱鎻愪氦鍚庯紝鍏ュ簱绠$悊鍒楄〃璇ヨ `tankNo` 鏈夊��
 - [ ] 璧扮殑鏄� `addStockInRecordOnly`锛堢敤 `addstockInventory` 鎻愪氦鍒� `tankNo` 涓虹┖ 鈥斺�� 鐢ㄦ潵鍙嶅悜纭 3.1锛�
diff --git "a/doc/20260920_\345\202\250\347\275\220\345\220\257\347\224\250\344\270\216\347\247\237\350\265\201\344\277\241\346\201\257\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260920_\345\202\250\347\275\220\345\220\257\347\224\250\344\270\216\347\247\237\350\265\201\344\277\241\346\201\257\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..5f06304
--- /dev/null
+++ "b/doc/20260920_\345\202\250\347\275\220\345\220\257\347\224\250\344\270\216\347\247\237\350\265\201\344\277\241\346\201\257\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,206 @@
+# 鍌ㄧ綈銆屾槸鍚﹀惎鐢� / 绉熻祦鑷敤 / 绉熷淇℃伅銆嶅墠绔仈璋冩枃妗�
+
+鏇存柊鏃堕棿锛�2026-09-20
+閫傜敤鐗堟湰锛氭湰娆″悗绔彉鏇村悗
+瀵瑰簲闇�姹傦細鍩虹鏁版嵁 - 鍌ㄧ綈淇℃伅鏂板銆屾槸鍚﹀惎鐢ㄣ�嶃�岀敤閫旓紙绉熻祦/鑷敤锛夈�嶃�岀瀹俊鎭�嶄笁绾ц仈鍔�
+
+---
+
+## 1. 鍙樻洿鑼冨洿
+
+| 鍐呭 | 褰卞搷闈� |
+| --- | --- |
+| 鍌ㄧ綈淇℃伅鏂板 **鏄惁鍚敤** | 鍌ㄧ綈淇℃伅椤佃〃鍗曘�佸垪琛� |
+| 鍚敤鍚庡彲閫� **鐢ㄩ��**锛堢璧� / 鑷敤锛� | 鍌ㄧ綈淇℃伅椤佃〃鍗� |
+| 鐢ㄩ�旈�夈�岀璧併�嶅悗濉� **绉熷淇℃伅** | 鍌ㄧ綈淇℃伅椤佃〃鍗� |
+| **鏈惎鐢ㄧ殑鍌ㄧ綈浠庝笅鎷夐殣钘�** | 鏂板鍏ュ簱琛ㄥ崟鐨勫偍缃愪笅鎷夛紙`/basic/tankInfo/all`锛� |
+
+DDL 瑙� `doc/20260920_add_usage_to_tank_info.sql`锛�**闇�鍏堝湪鐩爣搴撴墽琛�**銆�
+
+> 鍌ㄧ綈淇℃伅椤佃彍鍗� id `2354`锛堜笌娌瑰簱淇℃伅 `2353` 骞跺垪锛夈�傚偍缃愬彿甯﹀叆搴撳瓨鐨勫叾浣欏唴瀹硅 `doc/20260920_鍌ㄧ綈鍙峰甫鍏ュ簱瀛樺墠绔仈璋冩枃妗�.md`銆�
+
+---
+
+## 2. 琛ㄧ粨鏋勫彉鏇�
+
+`tank_info` 鏂板涓ゅ垪锛�
+
+| 鍒� | 绫诲瀷 | 榛樿鍊� | 璇存槑 |
+| --- | --- | --- | --- |
+| `is_enabled` | `tinyint(1) NOT NULL` | `1` | 鏄惁鍚敤 `1` 鍚敤 / `0` 鏈惎鐢� |
+| `usage_type` | `tinyint NULL` | `NULL` | 鐢ㄩ�� `1` 绉熻祦 / `2` 鑷敤锛屽惎鐢ㄥ悗鎵嶉渶瑕侀�� |
+
+> 瀛橀噺鏁版嵁 `is_enabled` 榛樿 `1`銆乣usage_type` 涓虹┖锛屾墍浠�**鍗囩骇鍚庤涓轰笌鏀瑰姩鍓嶄竴鑷�**锛屼笅鎷変笉浼氱獊鐒跺皯缃愩��
+
+---
+
+## 3. 鍓嶇琛ㄥ崟鑱斿姩
+
+```
+鏄惁鍚敤锛堝紑鍏筹級
+   鈹斺攢 鎵撳紑 鈫� 鐢ㄩ�旓紙绉熻祦 / 鑷敤锛�
+                鈹斺攢 閫夈�岀璧併�� 鈫� 绉熷淇℃伅锛堣緭鍏ユ锛�
+```
+
+**鍏抽棴銆屾槸鍚﹀惎鐢ㄣ�嶆椂锛岀敤閫斿拰绉熷淇℃伅涓や釜鎺т欢搴旈殣钘�**锛涢�夈�岃嚜鐢ㄣ�嶆椂绉熷淇℃伅闅愯棌銆�
+
+### 3.1 瀛楁
+
+| 瀛楁 | 绫诲瀷 | 鍙栧�� | 琛ㄥ崟鎺т欢 |
+| --- | --- | --- | --- |
+| `isEnabled` | Integer | `1` 鍚敤 / `0` 鏈惎鐢� | 寮�鍏筹紝榛樿鎵撳紑 |
+| `usageType` | Integer | `1` 绉熻祦 / `2` 鑷敤 | 鍗曢��/涓嬫媺锛屼粎鍦ㄥ惎鐢ㄦ椂鏄剧ず |
+| `tenantInfo` | String | 鑷敱鏂囨湰锛屾渶闀� 255 | **杈撳叆妗�**锛屼粎鍦� `usageType=1` 鏃舵樉绀� |
+
+> 鈿狅笍 **绉熷淇℃伅鏄嚜鐢辨枃鏈緭鍏ユ锛屼笉鏄笅鎷夐�夋嫨**銆傚悗绔�**娌℃湁**绉熷鍒楄〃鎺ュ彛锛屼篃娌℃湁绉熷琛ㄢ�斺�旇涓嶈鎸夈�屼笅鎷夐�夌瀹€�嶆潵瀹炵幇锛岀洿鎺ユ斁涓�涓� `input` 鍗冲彲銆�
+
+### 3.2 鍏朵綑瀛楁锛堟部鐢ㄥ師鏈夛紝鏈彉锛�
+
+`tankNo`锛堝偍缃愬彿锛夈�乣category`锛堝ぇ绫伙級銆乣oilProduct`锛堟补鍝侊級銆乣capacity`锛堢綈瀹癸級銆乣remaining`锛堝簱瀛樹綑閲� m鲁锛夈��
+
+---
+
+## 4. 鎺ュ彛
+
+鍓嶇紑 `/basic/tankInfo`锛岀粺涓�杩斿洖 `{ code, msg, data }`銆�
+
+| 鎺ュ彛 | 鏂规硶 | 璇存槑 |
+| --- | --- | --- |
+| `/basic/tankInfo/list` | GET | 鍒嗛〉鏌ヨ锛屽彇 `data.records` |
+| `/basic/tankInfo/all` | GET | 涓嬫媺鐢紝**鍙繑鍥炲凡鍚敤鐨勫偍缃�** |
+| `/basic/tankInfo/{id}` | GET | 璇︽儏 |
+| `/basic/tankInfo/add` | POST | 鏂板 |
+| `/basic/tankInfo/update` | POST | 淇敼锛屽繀椤诲甫 `id` |
+| `/basic/tankInfo/del` | DELETE | 鎵归噺鍒犻櫎锛宐ody 涓� id 鏁扮粍 |
+
+### 4.1 `GET /basic/tankInfo/list`锛堣涓哄彉鍖栵級
+
+**鏂板鍙�夎繃婊ゅ弬鏁�**锛�
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| `isEnabled` | Integer | 鎸夊惎鐢ㄧ姸鎬佽繃婊わ紝涓嶄紶涓哄叏閮� |
+| `usageType` | Integer | 鎸夌敤閫旇繃婊わ紝涓嶄紶涓哄叏閮� |
+
+鍘熸湁 `tankNo`銆乣category` 浠嶄负妯$硦鍖归厤锛�**鏈敼鍔�**銆傚垪琛�**杩斿洖鍏ㄩ儴鍌ㄧ綈锛堝惈鏈惎鐢級**锛屽缓璁墠绔姞涓�鍒椼�屾槸鍚﹀惎鐢ㄣ�嶅苟鐢ㄤ笂闈㈢殑鍙傛暟鍋氱瓫閫夈��
+
+```
+GET /basic/tankInfo/list?pageNum=1&pageSize=10&isEnabled=1
+```
+
+### 4.2 `GET /basic/tankInfo/all`锛�**琛屼负鍙樺寲锛岄噸鐐�**锛�
+
+**鍙繑鍥� `isEnabled=1` 鐨勫偍缃愩��** 鏈惎鐢ㄧ殑缃愯繕娌℃姇鐢紝涓嶅簲鍑虹幇鍦ㄥ叆搴撹〃鍗曠殑鍌ㄧ綈涓嬫媺閲屻��
+
+- 璇ユ帴鍙�**涓嶆帴鍙�** `isEnabled` 鍙傛暟锛屾案杩滃彧缁欏惎鐢ㄧ殑锛�
+- 瀛橀噺鏁版嵁杩佺Щ鍚� `is_enabled` 鍏ㄤ负 `1`锛屾墍浠ユ敼閫犲墠鍚庝笅鎷夊唴瀹逛竴鑷达紱
+- 鍋滅敤涓�涓偍缃� 鈫� 鍒锋柊涓嬫媺鍗虫秷澶憋紱閲嶆柊鍚敤 鈫� 鍙堝嚭鐜般��
+
+鍝嶅簲绀轰緥锛�
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": [
+    {
+      "id": 3,
+      "tankNo": "01#",
+      "category": "鎴愬搧娌�",
+      "oilProduct": "鏌�#0",
+      "capacity": 1000.00,
+      "remaining": 320.00,
+      "isEnabled": 1,
+      "usageType": 1,
+      "tenantInfo": "鍗楅�氭煇鏌愯繍杈撴湁闄愬叕鍙�"
+    }
+  ]
+}
+```
+
+### 4.3 `POST /basic/tankInfo/add` / `update`
+
+璇锋眰浣撳氨鏄偍缃愬璞★紝**鏂板瀛楁鐩存帴骞抽摵鍦ㄩ《灞�**锛�
+
+```json
+{
+  "id": 3,
+  "tankNo": "01#",
+  "category": "鎴愬搧娌�",
+  "oilProduct": "鏌�#0",
+  "capacity": 1000.00,
+  "remaining": 320.00,
+  "isEnabled": 1,
+  "usageType": 1,
+  "tenantInfo": "鍗楅�氭煇鏌愯繍杈撴湁闄愬叕鍙�"
+}
+```
+
+`update` 蹇呴』甯� `id`锛屽惁鍒欒繑鍥炪�岀己灏戝偍缃恑d銆嶃��
+
+> `id` / `createTime` / `updateTime` / `createUser` / `updateUser` / `tenantId` / `deptId` 鐢卞悗绔嚜鍔ㄧ淮鎶わ紝鍓嶇**涓嶈浼�**銆�
+
+---
+
+## 5. 鍚庣褰掍竴鍖栬鍒欙紙鍓嶇涓嶇敤鑷繁娓呭瓧娈碉級
+
+鍚庣浼氬杩欎笁涓瓧娈靛仛涓�娆″綊涓�鍖栵紝鎸変笅闈㈢殑鍙e緞钀藉簱锛�
+
+| 鎻愪氦鐨勫�� | 鍚庣琛屼负 |
+| --- | --- |
+| `isEnabled=0`锛堟湭鍚敤锛� | `usageType` 鍜� `tenantInfo` **涓�寰嬫竻绌�** 鈥斺�� 鍚敤涔嬪悗鎵嶈皥绉熻祦/鑷敤 |
+| `isEnabled=1` + `usageType=1`锛堢璧侊級 | `tenantInfo` **蹇呭~**锛屼负绌烘姤閿� |
+| `isEnabled=1` + `usageType=2`锛堣嚜鐢級 | `tenantInfo` **鑷姩娓呯┖**锛屽墠绔紶浜嗕篃浼氳鎶规帀 |
+| `isEnabled=1` + `usageType` 涓嶄紶 / 涓� `null` | 瑙嗕负**鏈�夋嫨**锛氭棦涓嶆牎楠屼篃涓嶆竻绌� |
+
+### 5.1 鏍¢獙閿欒鏂囨
+
+| 鍦烘櫙 | `msg` |
+| --- | --- |
+| `usageType` 浼犱簡 `1`/`2` 涔嬪鐨勬暣鏁� | 鍌ㄧ綈鐢ㄩ�斿彧鑳芥槸绉熻祦鎴栬嚜鐢� |
+| `usageType=1`锛堢璧侊級浣� `tenantInfo` 涓虹┖ | 绉熻祦鐨勫偍缃愬繀椤诲~鍐欑瀹俊鎭� |
+| `update` 鏈甫 `id` | 缂哄皯鍌ㄧ綈id |
+
+閿欒鍝嶅簲褰㈡�侊紙涓氬姟寮傚父缁熶竴璧板叏灞�澶勭悊鍣級锛�
+
+```json
+{ "code": 500, "msg": "绉熻祦鐨勫偍缃愬繀椤诲~鍐欑瀹俊鎭�" }
+```
+
+---
+
+## 6. 娉ㄦ剰浜嬮」
+
+**鈶� `usageType` 鍙互涓虹┖锛岃繖鏄湁鎰忕殑鍏煎鍙e瓙**
+
+鍚敤鐘舵�佷笅鍏佽 `usageType` 涓虹┖锛堣〃绀�"杩樻病閫�"锛夈�傚洜涓鸿縼绉诲悗瀛橀噺鍌ㄧ綈灏辨槸 `usage_type=NULL` + `tenant_info` 鍙兘鏈夊�硷紝濡傛灉寮哄埗瑕佹眰蹇呴�夛紝鍚勭鎴峰簱閲岀殑鑰佸偍缃�**绗竴娆$紪杈戝氨浼氳鎷�**锛岃�屼笖閫夈�岃嚜鐢ㄣ�嶈繕浼氭妸鍘熸湁绉熷淇℃伅鎶规帀銆�
+
+鎵�浠ワ細**鍓嶇琛ㄥ崟鍙互寮哄埗瑕佹眰閫�**锛堟柊鏁版嵁瑙勮寖锛夛紝浣嗗悗绔笉浼氭嫤銆傝嫢涓氬姟瑕佹眰鍚庣涔熷己鍒讹紝闇�瑕佸厛缁欏瓨閲忔暟鎹ˉ `usage_type`锛屽睘浜庨澶栦竴姝ャ��
+
+**鈶� 鍋滅敤鍌ㄧ綈涓嶅奖鍝嶅巻鍙蹭笟鍔℃暟鎹�**
+
+閿�鍞彴璐︺�佸簱瀛樸�佸嚭鍏ュ簱璁板綍閲岀殑 `tankId` / `tankNo` 閮芥槸**钀藉簱鏃舵妱涓嬫潵鐨勫揩鐓�**锛屽偍缃愬悗鏉ヨ鍋滅敤銆佹敼鐢ㄩ�斻�佺敋鑷冲垹闄わ紝閮戒笉浼氬洖鍐欒繖浜涘巻鍙叉暟鎹��
+
+**鈶� 鍒犻櫎鍌ㄧ綈娌℃湁寮曠敤鏍¢獙**
+
+`/basic/tankInfo/del` 鐩存帴鍒狅紝涓嶆牎楠屾槸鍚﹁閿�鍞彴璐�/搴撳瓨寮曠敤銆傚洜涓烘湁 鈶� 鐨勫揩鐓ф満鍒讹紝鍘嗗彶鏁版嵁鏄剧ず涓嶅彈褰卞搷锛屽彧鏄笅鎷夐噷浼氬皯涓�涓綈銆傚墠绔垹闄ゅ墠寤鸿浜屾纭銆�
+
+**鈶� 鍌ㄧ綈鍒楄〃鍙楁暟鎹潈闄愯繃婊�**
+
+`tank_info` 涓嶅湪鏁版嵁鏉冮檺蹇界暐琛ㄧ櫧鍚嶅崟閲岋紝`/list` 浼氳鑷姩杩藉姞 `create_user` / `dept_id` 鏉′欢锛�**鎹㈣处鍙峰彲鑳界湅涓嶅埌鍒汉寤虹殑鍌ㄧ綈**銆傝繖涓庡叾瀹冨熀纭�鏁版嵁椤电幇鐘朵竴鑷淬�傝仈璋冩椂鐢ㄥ悓涓�璐﹀彿锛屾垨鐢� `dataScope=1` 鐨勮处鍙凤紝鍚﹀垯瀹规槗璇垽鎴�"淇濆瓨澶辫触"銆�
+
+---
+
+## 7. 鑱旇皟鑷煡娓呭崟
+
+- [ ] 鍌ㄧ綈淇℃伅鍒楄〃鍑虹幇銆屾槸鍚﹀惎鐢ㄣ�嶅垪锛屽彲鐢� `isEnabled` 鍙傛暟绛涢��
+- [ ] 琛ㄥ崟锛氭墦寮�銆屾槸鍚﹀惎鐢ㄣ�嶆墠鍑虹幇鐢ㄩ�旓紱鍏抽棴鏃剁敤閫斾笌绉熷淇℃伅閮介殣钘�
+- [ ] 琛ㄥ崟锛氱敤閫旈�夈�岀璧併�嶆墠鍑虹幇绉熷淇℃伅杈撳叆妗嗭紱閫夈�岃嚜鐢ㄣ�嶆椂闅愯棌
+- [ ] 閫夈�岀璧併�嶄絾涓嶅~绉熷淇℃伅 鈫� 淇濆瓨杩斿洖銆岀璧佺殑鍌ㄧ綈蹇呴』濉啓绉熷淇℃伅銆�
+- [ ] 閫夈�岃嚜鐢ㄣ�嶄繚瀛樺悗閲嶆柊鏌ヨ鎯咃紝`tenantInfo` 涓虹┖锛堢‘璁ゆ竻绌虹敓鏁堬紝鑰屼笉鏄病鍐欙級
+- [ ] 鐢便�岀璧併�嶆敼鎴愩�屾湭鍚敤銆嶄繚瀛樺悗锛宍usageType` 鍜� `tenantInfo` 閮藉彉绌�
+- [ ] `usageType` 浼� `9` 鈫� 杩斿洖銆屽偍缃愮敤閫斿彧鑳芥槸绉熻祦鎴栬嚜鐢ㄣ��
+- [ ] 鏈惎鐢ㄧ殑鍌ㄧ綈**涓嶅湪** `/basic/tankInfo/all` 閲岋紱`/basic/tankInfo/list` 閲屼粛鑳界湅鍒�
+- [ ] 鍋滅敤涓�涓偍缃� 鈫� 鏂板鍏ュ簱琛ㄥ崟鐨勫偍缃愪笅鎷夐噷娌℃湁瀹冿紱閲嶆柊鍚敤 鈫� 鍙堝嚭鐜�
+- [ ] 鍋滅敤鍌ㄧ綈鍚庯紝姝ゅ墠宸插叆搴撹褰曢噷鐨� `tankNo` 浠嶅湪锛堝巻鍙叉暟鎹笉鍙楀奖鍝嶏級
+- [ ] 涓嶆敼鏂板瓧娈电洿鎺ヤ繚瀛樹竴涓瓨閲忓偍缃愶紝鑳戒繚瀛樻垚鍔熶笖 `tenantInfo` 鍘熷�间笉涓�
diff --git "a/doc/20260920_\345\207\272\345\272\223\345\215\263\345\217\221\350\264\247\344\270\216\345\276\200\346\235\245\345\244\207\344\273\266\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260920_\345\207\272\345\272\223\345\215\263\345\217\221\350\264\247\344\270\216\345\276\200\346\235\245\345\244\207\344\273\266\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
index 81b68c0..d239d78 100644
--- "a/doc/20260920_\345\207\272\345\272\223\345\215\263\345\217\221\350\264\247\344\270\216\345\276\200\346\235\245\345\244\207\344\273\266\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
+++ "b/doc/20260920_\345\207\272\345\272\223\345\215\263\345\217\221\350\264\247\344\270\216\345\276\200\346\235\245\345\244\207\344\273\266\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -11,7 +11,7 @@
 | 闇�姹� | 鍐呭 | 褰卞搷闈� |
 | --- | --- | --- |
 | 13 | 娌瑰搧**閿�鍞嚭搴撳嵆鍙戣揣**锛氬嚭搴撴椂鑷姩鐢熸垚涓�寮犮�屽凡鍙戣揣銆嶅彂璐у彴璐︼紝涓嶅彂璧峰彂璐у鎵� | 鍑哄簱琛ㄥ崟銆佸嚭搴撳彴璐﹀垪琛� |
-| 11 | 瀹㈡埛/渚涘簲鍟嗗線鏉ュ悇**鏂板涓�涓笟鍔″瓧娈�**锛堝鎴枫�屼粯娆俱�嶃�佷緵搴斿晢銆屽洖娆俱�嶏級骞跺彲鎵嬪伐缁存姢娴佹按 | 瀹㈡埛寰�鏉ラ〉銆佷緵搴斿晢寰�鏉ラ〉 |
+| 11 | 瀹㈡埛/渚涘簲鍟嗗線鏉ュ悇**鏂板涓�涓笟鍔″瓧娈�**锛堝鎴枫�屽洖娆俱�嶃�佷緵搴斿晢銆屼粯娆俱�嶏級骞跺彲鎵嬪伐缁存姢娴佹按 | 瀹㈡埛寰�鏉ラ〉銆佷緵搴斿晢寰�鏉ラ〉 |
 | 15 | 澶囦欢**鍏宠仈浜у搧瑙勬牸**锛岄噰璐叆搴撹嚜鍔ㄧ疮鍔犲浠跺簱瀛橈紝骞舵柊澧炲浠跺嚭鍏ュ簱娴佹按 | 澶囦欢缁存姢椤点�佸浠舵祦姘撮〉 |
 | 14 | 鏂板鍏ュ簱閫夐噰璐鍗曞悗**绮剧‘鍙嶆樉鏄庣粏**锛堜慨澶嶅悗绔� SQL锛� | 鏂板鍏ュ簱椤� |
 
@@ -87,8 +87,14 @@
 
 | 寰�鏉� | 鏂板瀛楁 | 鍙栧�兼潵婧� |
 | --- | --- | --- |
-| 瀹㈡埛寰�鏉� | `paymentAmount`锛�**浠樻**锛� | `payment_record` 涓� `party_type=1 and direction=1` 鐨勫悎璁� |
-| 渚涘簲鍟嗗線鏉� | `receiptAmount`锛�**鍥炴**锛� | `payment_record` 涓� `party_type=2 and direction=2` 鐨勫悎璁� |
+| 瀹㈡埛寰�鏉� | `receiptRecordAmount`锛�**鍥炴**锛� | `payment_record` 涓� `party_type=1 and direction=1` 鐨勫悎璁� |
+| 渚涘簲鍟嗗線鏉� | `paymentRecordAmount`锛�**浠樻**锛� | `payment_record` 涓� `party_type=2 and direction=2` 鐨勫悎璁� |
+
+> 鈿狅笍 **鍙e緞宸蹭慨姝o紙2026-09-20锛�**锛氭渶鍒濆疄鐜版垚浜嗐�屽鎴蜂粯娆俱�佷緵搴斿晢鍥炴銆嶏紝涓庝笟鍔′笉绗︺�傜幇鎸夈��**瀹㈡埛鏄洖娆俱�佷緵搴斿晢鏄粯娆�**銆嶅睍绀恒��
+>
+> **鍙崲浜嗗垪鐨勮涔変笌瀛楁鍚嶏紝`direction` 鐨勫彇鍊间竴涓兘娌″姩** 鈥斺�� 瀹㈡埛渚т粛鏄� `direction=1`锛屼緵搴斿晢渚т粛鏄� `direction=2`銆傝繖涓や釜鍊兼槸鍘嗗彶娴佹按宸茬粡鍦ㄧ敤鐨勮惤搴撴柟鍚戯紝鏀逛簡灏变細鏌ヤ笉鍒板凡鏈夋暟鎹�傛墍浠ャ�宍direction=1` 鍦ㄥ鎴峰線鏉ラ噷璇讳綔鍥炴銆佸湪渚涘簲鍟嗗線鏉ラ噷璇讳綔浠樻銆嶈繖绫�**鎸夐〉鍙栧��**鐨勫搴斿叧绯绘槸鍒绘剰鐨勶紝涓嶈鎸夊瓧娈靛悕鍘诲弽鎺ㄣ��
+>
+> 瀛楁鍚嶅悓鏃舵敼鎺変簡锛氬鎴蜂晶 `paymentAmount` 鈫� `receiptRecordAmount`锛屼緵搴斿晢渚� `receiptAmount` 鈫� `paymentRecordAmount`銆�**渚涘簲鍟嗕晶鐨勪粯娆惧垪鏃犳硶鐩存帴鍙� `paymentAmount`** 鈥斺�� 璇ュ悕瀛楀凡琚湡瀹炰粯娆惧崟锛坄account_purchase_payment`锛夌殑姹囨�诲垪鍗犵敤锛岄噸鍚嶄細璁╁悓涓� resultMap 鐨勪袱涓垪浜掔浉瑕嗙洊銆�
 
 **鍙湁涓�涓柟鍚戞槸鎵嬪伐娴佹按**锛氬鎴蜂晶鐨勩�屾敹娆鹃噾棰濄�峘receiptPaymentAmount`銆佷緵搴斿晢渚х殑銆屼粯娆鹃噾棰濄�峘paymentAmount` 浠嶆潵鑷師鏈夌殑閲嶅崟鎹〃锛�**璁$畻鍙e緞鏈彉**锛沗receiptableAmount`锛堝簲鏀讹級涓� `payableAmount`锛堝簲浠橈級鍏紡**淇濇寔涓嶅彉**锛屾柊瀛楁鍙仛灞曠ず锛屼笉鍙備笌浣欓璁$畻銆�
 
@@ -96,12 +102,12 @@
 
 | 鎺ュ彛 | 鏂板杩斿洖瀛楁 |
 | --- | --- |
-| `GET /metricStatistics/customewTransactions` | `paymentAmount` |
-| `GET /metricStatistics/customewTransactionsDetails` | `paymentAmount` |
-| `GET /purchase/report/supplierTransactions` | `receiptAmount` |
-| `GET /purchase/report/supplierTransactionsDetails` | `receiptAmount` |
+| `GET /metricStatistics/customewTransactions` | `receiptRecordAmount`锛堝洖娆撅級 |
+| `GET /metricStatistics/customewTransactionsDetails` | `receiptRecordAmount`锛堝洖娆撅級 |
+| `GET /purchase/report/supplierTransactions` | `paymentRecordAmount`锛堜粯娆撅級 |
+| `GET /purchase/report/supplierTransactionsDetails` | `paymentRecordAmount`锛堜粯娆撅級 |
 
-> 鏄庣粏鎺ュ彛鐨� `paymentAmount` / `receiptAmount` 鏄�**寰�鏉ユ柟缁村害**鐨勫悎璁★紙璇ュ鎴�/渚涘簲鍟嗗叏閮ㄦ祦姘村悎璁★級锛屾祦姘存湰韬笉鎸傚埌鍗曞紶鍚堝悓涓婏紝鍥犳鏄庣粏鍚勮璇ュ垪鍊肩浉鍚屻�傝嫢鍓嶇甯屾湜鍙湪鍒楄〃椤靛睍绀猴紝鏄庣粏椤靛彲闅愯棌璇ュ垪銆�
+> 鏄庣粏鎺ュ彛鐨� `receiptRecordAmount` / `paymentRecordAmount` 鏄�**寰�鏉ユ柟缁村害**鐨勫悎璁★紙璇ュ鎴�/渚涘簲鍟嗗叏閮ㄦ祦姘村悎璁★級锛屾祦姘存湰韬笉鎸傚埌鍗曞紶鍚堝悓涓婏紝鍥犳鏄庣粏鍚勮璇ュ垪鍊肩浉鍚屻�傝嫢鍓嶇甯屾湜鍙湪鍒楄〃椤靛睍绀猴紝鏄庣粏椤靛彲闅愯棌璇ュ垪銆�
 
 ### 3.3 鏂板锛氫粯娆惧洖娆炬祦姘村洓鎺ュ彛
 
@@ -129,13 +135,50 @@
 
 `tenantId` / `deptId` / `createUser` / `createTime` / `updateUser` / `updateTime` 鐢卞悗绔嚜鍔ㄥ~鍏咃紝**鍓嶇涓嶈浼�**銆�
 
-**鏌ヨ鍙傛暟绀轰緥**锛堝鎴峰線鏉ラ〉銆屾柊澧炰粯娆俱�嶅悗鏌ョ湅璇ュ鎴锋祦姘达級锛�
+**鏌ヨ鍙傛暟绀轰緥**锛堝鎴峰線鏉ラ〉銆屾柊澧炲洖娆俱�嶅悗鏌ョ湅璇ュ鎴锋祦姘达級锛�
 
 ```
 GET /paymentRecord/listPage?pageNum=1&pageSize=10&partyType=1&partyId=449&direction=1
 ```
 
+> 鍚勫線鏉ラ〉鏂板娴佹按鏃惰浼犵殑鍊硷紙**鏄撻敊锛屽姟蹇呮寜姝や紶锛沗direction` 涓嶈鎸夈��1浠樻 2鍥炴銆嶇殑瀛楅潰鍘婚��**锛夛細
+
+| 椤甸潰 | 璇ラ〉鐨勫垪璇箟 | `partyType` | `direction` |
+| --- | --- | --- | --- |
+| 瀹㈡埛寰�鏉� | 鍥炴 | `1` | `1` |
+| 渚涘簲鍟嗗線鏉� | 浠樻 | `2` | `2` |
+| 鍚堜綔鍟嗗線鏉� | 宸蹭粯娆� | `3` | `1` |
+
 `listPage` 杩斿洖缁撴瀯涓� MyBatis-Plus 鍒嗛〉瀵硅薄锛屽墠绔彇 `data.records`銆�
+
+### 3.4 娉ㄦ剰浜嬮」
+
+**鈶� 鏂板瓧娈靛彧鍋氬睍绀猴紝涓嶅弬涓庝綑棰濊绠�**
+
+`receiptRecordAmount`锛堝鎴峰洖娆撅級銆乣paymentRecordAmount`锛堜緵搴斿晢浠樻锛夋槸鐙珛鐨勬墜宸ユ祦姘达紝**涓嶄細**鏀瑰彉搴旀敹 `receiptableAmount` / 搴斾粯 `payableAmount`銆傛彃鍏ヤ竴鏉� `partyType=1&direction=1` 鐨勫鎴峰洖娆炬祦姘村悗锛屻�屽洖娆俱�嶅垪浼氬彉锛屻�屽簲鏀躲�嶅垪**涓嶅彉**鈥斺�旇繖鏄鏈熻涓猴紝涓嶆槸 bug銆�
+
+**鈶� `direction` 蹇呴』鍙紶 `1` 鎴� `2`**
+
+鍚庣瀵� `partyType` 鍋氫簡鐧藉悕鍗曪紙`1` 瀹㈡埛 / `2` 渚涘簲鍟� / `3` 鍚堜綔鍟嗭紝浼犲叾瀹冨�兼姤銆屽線鏉ユ柟绫诲瀷涓嶅悎娉曘�嶏級锛屼絾 **`direction` 娌℃湁鐧藉悕鍗�**锛屼紶 `1`/`2` 涔嬪鐨勬暣鏁颁篃浼氬叆搴撱�傚墠绔笅鎷夊浐瀹氱粰 `1` 浠樻銆乣2` 鍥炴涓や釜閫夐」锛屼笉瑕佹斁寮�銆�
+
+**鈶� `/paymentRecord/delete` 娌℃湁褰掑睘鏍¢獙锛堟棦鏈夐闄╋級**
+
+璇ユ帴鍙d笉鏍¢獙娴佹按褰掑睘锛�**浠讳綍鐧诲綍鐢ㄦ埛閮借兘鍒犱换鎰忎竴鏉℃祦姘�**锛屽寘鎷埆鐨勫鎴�/渚涘簲鍟嗐�佷箖鑷冲悎浣滃晢鐨勩�傛湰娆℃湭鏀瑰姩鍚庣琛屼负锛屽墠绔鍔″繀锛�
+
+- 鍒犻櫎鍓嶅姞浜屾纭锛�
+- 涓嶈鍦ㄩ〉闈笂鏆撮湶銆屾壒閲忓叏閫夊垹闄ゃ�嶈繖绫诲叆鍙o紝閬垮厤璇垹涓�鐗囥��
+
+鑻ヤ笟鍔¤姹傛敹绱э紝闇�瑕佸悗绔ˉ銆屽綊灞炴牎楠屻�嶆垨銆屽垱寤轰汉鏍¢獙銆嶏紝灞炰簬鏂板闇�姹傘��
+
+**鈶� 娴佹按鍒楄〃鍙楁暟鎹潈闄愯繃婊�**
+
+`payment_record` 涓嶅湪鏁版嵁鏉冮檺蹇界暐琛ㄧ櫧鍚嶅崟閲岋紝`/paymentRecord/listPage` 浼氳鑷姩杩藉姞 `create_user` / `dept_id` 鏉′欢銆�**鎹竴涓处鍙峰彲鑳界湅涓嶅埌鍒汉褰曞叆鐨勬祦姘�**鈥斺�旇繖涓庝緵搴斿晢銆侀噰璐彴璐﹀垪琛ㄧ殑鐜扮姸涓�鑷达紝涓嶆槸鏂伴棶棰樸�傝仈璋冩椂璇风敤涓庡綍鍏ヤ汉鍚屾暟鎹寖鍥寸殑璐﹀彿锛屾垨鐢� `dataScope=1` 鐨勮处鍙凤紝鍚﹀垯瀹规槗璇垽鎴愩�屾柊澧炴病鐢熸晥銆嶃��
+
+> 寰�鏉ユ姤琛紙`customewTransactions` / `supplierTransactions`锛夐《灞傛槸鑱氬悎瀛愭煡璇紝涓嶅彈璇ラ檺鍒讹紝涓や晶閲戦浠嶈兘瀵逛笂锛涗絾鍥犱负娴佹按鍒楄〃琚繃婊わ紝鍙兘鍑虹幇銆屾姤琛ㄩ噷娴佹按鍒楁湁鏁般�佺偣杩涙祦姘寸湅涓嶅埌璁板綍銆嶇殑瑙傛劅宸紓锛屽睘鍚屾簮闂銆�
+
+**鈶� 寮�鍙戝簱鐜版湁娴嬭瘯娴佹按閮借兘鐢�**
+
+`payment_record` 鐜板瓨 `id=2`锛坄party_type=3, direction=1`锛屽悎浣滃晢锛夈�乣id=4`锛坄party_type=2, direction=2`锛屼緵搴斿晢锛夈�乣id=5`锛坄party_type=1, direction=1`锛屽鎴� 449锛夈�俙direction` 鍙栧�兼湭鍙橈紝鎵�浠ヨ繖涓夋潯鍒嗗埆瀵瑰簲鍚堜綔鍟嗐�屽凡浠樻銆嶃�佷緵搴斿晢銆屼粯娆俱�嶃�佸鎴枫�屽洖娆俱�嶏紝鍙洿鎺ョ敤浜庤仈璋冿紙瀹㈡埛 449 鐨勫洖娆惧垪搴斾负 10.00锛屼緵搴斿晢 243 鐨勪粯娆惧垪涓� 1.00锛夈��
 
 ---
 
@@ -150,14 +193,27 @@
 | `productId` | Long | 鍏宠仈浜у搧澶х被 id |
 | `productModelId` | Long | 鍏宠仈浜у搧**瑙勬牸鍨嬪彿** id |
 
-**杩欐槸鎵撻�氶噰璐殑涓氬姟閽╁瓙**锛氶噰璐彴璐︽柊澧炴槑缁嗘椂锛屽悗绔敤鏄庣粏鐨� `productModelId` 鍘诲尮閰� `spare_parts.product_model_id`銆傚尮閰嶄笂鍒欙細
+**杩欐槸鎵撻�氶噰璐殑涓氬姟閽╁瓙**锛氶噰璐彴璐︿繚瀛樻槑缁嗘椂锛屽悗绔敤鏄庣粏鐨� `productModelId` 鍘诲尮閰� `spare_parts.product_model_id`銆傚尮閰嶄笂鍒欙細
 
 1. `spare_parts.quantity` 绱姞璇ユ槑缁嗘暟閲忥紱
 2. 鍐欎竴鏉″浠跺叆搴撴祦姘达紙`direction=1`銆乣sourceType=1`銆乣sourceId=閲囪喘鍙拌处id`銆乣sourceNo=閲囪喘鍚堝悓鍙穈锛夈��
 
 鏈淮鎶� `productModelId` 鐨勫浠�**涓嶄細**琚尮閰嶏紝闈欓粯璺宠繃锛屼笉褰卞搷閲囪喘鍙拌处淇濆瓨銆�
 
-> 浠呭湪閲囪喘鍙拌处**鏂板鏄庣粏**鏃跺洖鍐欙紱缂栬緫閲囪喘鍙拌处璧版洿鏂板垎鏀紝涓嶄細閲嶅璁℃暟銆�
+> 鍙**閲囪喘鏄庣粏**锛坄type=2`锛夌敓鏁堬紝閿�鍞�/椤圭洰鏄庣粏涓嶅姩澶囦欢銆�
+
+**缂栬緫鍙拌处鏄�屽厛鍥炴粴銆佸啀閲嶅缓銆�**锛氬悗绔厛鎶婅鍙拌处涓婃浜х敓鐨勫浠跺叆搴撴祦姘村叏閮ㄥ噺鍥炲苟鍒犻櫎锛屽啀鎸夊墠绔湰娆℃彁浜ょ殑**瀹屾暣鏄庣粏鍒楄〃**閲嶆柊绱姞銆傛墍浠ワ細
+
+- 鏄庣粏琛岃**绉婚櫎** 鈫� 瀵瑰簲澶囦欢鏁伴噺鑷姩鍑忓洖鍘伙紙涓嶉渶瑕佸墠绔澶栬皟鎺ュ彛锛夛紱
+- 鏄庣粏琛�**鏁伴噺琚敼** 鈫� 鎸夋柊鏁伴噺閲嶇畻锛屼笉浼氬彔鍔犳垚鍙屼唤锛�
+- 鏄庣粏琛�**鏂板** 鈫� 姝e父绱姞锛屼笉浼氶噸澶嶈鏁般��
+
+> 鈿狅笍 鍥犳缂栬緫鍙拌处鏃�**蹇呴』鎻愪氦瀹屾暣鏄庣粏鍒楄〃**锛屼笉鑳藉彧鎻愪氦鏀瑰姩鐨勯偅鍑犺銆傚彧浼犻儴鍒嗚浼氳褰撴垚銆屽叾浣欒宸插垹闄ゃ�嶈�屽洖婊氭帀銆�
+
+**鍒犻櫎鍙拌处浼氳繛甯﹀洖婊氬浠�**锛歚POST` 鎵归噺鍒犻櫎閲囪喘鍙拌处鏃讹紝鍚庣浼氭妸璇ュ彴璐︾疮鍔犲埌澶囦欢涓婄殑鏁伴噺鍑忓洖鍘伙紝骞跺垹鎺夊搴旂殑 `spare_parts_record` 鍏ュ簱娴佹按銆�
+
+- 鑻ュ浠�**宸茶缁翠慨/淇濆吇棰嗙敤**锛屽噺鍥炰細瀵艰嚧璐熷簱瀛橈紝姝ゆ椂**鍒犻櫎浼氳鎷掔粷**骞舵彁绀恒�屽浠躲�寈xx銆嶅綋鍓嶅簱瀛� N锛屼笉瓒充互鍥炴粴鏈閲囪喘鍏ュ簱鐨� M锛岃鍏堝鐞嗛鐢ㄨ褰曘�嶃�傝繖鏄湁鎰忕殑淇濇姢锛屼笉鍋氶潤榛樿礋搴撳瓨銆�
+- 鍒犻櫎涓庡洖婊氬湪鍚屼竴涓簨鍔″唴锛屼换涓�姝ュけ璐ユ暣浣撳洖婊氥��
 
 ### 4.2 鏂板锛氬浠跺嚭鍏ュ簱娴佹按
 
@@ -178,17 +234,19 @@
 | `quantity` | 鏁伴噺 |
 | `sourceType` | 鏉ユ簮绫诲瀷锛岃涓嬭〃 |
 | `sourceId` | 鏉ユ簮鍗曟嵁 id |
-| `sourceNo` | 鏉ユ簮鍗曞彿 |
+| `sourceNo` | 鏉ユ簮鍗曞彿锛岃涓嬭〃 |
 | `remark` | 澶囨敞 |
 | `createTime` | 鍙戠敓鏃堕棿 |
 
 `sourceType` 鏋氫妇锛�
 
-| 鍊� | 鍚箟 | 瑙﹀彂鐐� |
-| --- | --- | --- |
-| `1` | 閲囪喘鍏ュ簱 | 閲囪喘鍙拌处鏂板鍚瑙勬牸鐨勬槑缁� |
-| `2` | 缁翠慨棰嗙敤 | 璁惧鎶ヤ慨鍗曟洿鏂版椂棰嗙敤澶囦欢 |
-| `3` | 淇濆吇棰嗙敤 | 璁惧淇濆吇鍗曟洿鏂版椂棰嗙敤澶囦欢 |
+| 鍊� | 鍚箟 | 瑙﹀彂鐐� | `sourceNo` 鍙栧�� |
+| --- | --- | --- | --- |
+| `1` | 閲囪喘鍏ュ簱 | 閲囪喘鍙拌处淇濆瓨鍚瑙勬牸鐨勯噰璐槑缁� | 閲囪喘鍚堝悓鍙� |
+| `2` | 缁翠慨棰嗙敤 | 璁惧鎶ヤ慨鍗曟洿鏂版椂棰嗙敤澶囦欢 | **璁惧鍚嶇О** |
+| `3` | 淇濆吇棰嗙敤 | 璁惧淇濆吇鍗曟洿鏂版椂棰嗙敤澶囦欢 | **璁惧鍚嶇О** |
+
+> 鎶ヤ慨鍗曘�佷繚鍏诲崟鏈韩**娌℃湁鍗曞彿瀛楁**锛屾墍浠� `sourceType=2/3` 鐨� `sourceNo` 鏀剧殑鏄澶囧悕绉帮紝渚夸簬鍦ㄦ祦姘撮〉鐪嬪嚭澶囦欢鐢ㄥ湪鍝彴璁惧涓婏紱瑕佷笅閽诲埌鍗曟嵁鐢� `sourceId`銆傝繖涓�鍒楀墠绔洿鎺ュ睍绀哄嵆鍙紝涓嶈鎸夈�屽崟鍙枫�嶆牸寮忓仛鏍¢獙銆�
 
 > 缁翠慨/淇濆吇**棰嗙敤**浠嶆部鐢ㄥ師鏈� `spare_parts_requisition_record`锛堣澶囩淮搴︼紝鍙︽湁椤甸潰锛夛紱鏈鏂板鐨� `spare_parts_record` 鏄�**澶囦欢缁村害**鐨勮繘鍑烘祦姘达紝涓よ�呭苟瀛橈紝鑱岃矗涓嶅悓銆�
 
@@ -227,9 +285,15 @@
 - [ ] 鍙戣揣鍙拌处涓兘鐪嬪埌璇ヨ嚜鍔ㄧ敓鎴愮殑鍗曪紝鐘舵�佷负 `宸插彂璐
 - [ ] 鍚屼竴浜у搧鍐嶆鍑哄簱锛岀敓鎴�**鍙︿竴寮�**鍙戣揣鍗曪紙涓嶆槸琚嫤鎴級
 - [ ] 鍒犻櫎鍑哄簱鍗曞悗锛屽彂璐у彴璐︺�佸彂璐ф槑缁嗐�佸簱瀛樻墸鍑忚褰曚竴骞舵秷澶�
-- [ ] 瀹㈡埛寰�鏉ュ垪琛�/鏄庣粏鍑虹幇銆屼粯娆俱�嶅垪锛屾彃鍏ヤ竴鏉� `direction=1` 娴佹按鍚庢暟鍊煎彉鍖栵紝涓斻�屽簲鏀躲�嶄笉鍙�
-- [ ] 渚涘簲鍟嗗線鏉ュ垪琛�/鏄庣粏鍑虹幇銆屽洖娆俱�嶅垪锛岄�昏緫鍚屼笂
+- [ ] 瀹㈡埛寰�鏉ュ垪琛�/鏄庣粏鍑虹幇銆屽洖娆炬祦姘淬�嶅垪锛坄receiptRecordAmount`锛夛紝鎻掑叆涓�鏉� `partyType=1&direction=1` 娴佹按鍚庢暟鍊煎彉鍖栵紝涓斻�屽簲鏀躲�嶄笉鍙�
+- [ ] 渚涘簲鍟嗗線鏉ュ垪琛�/鏄庣粏鍑虹幇銆屼粯娆炬祦姘淬�嶅垪锛坄paymentRecordAmount`锛夛紝鎻掑叆涓�鏉� `partyType=2&direction=2` 娴佹按鍚庢暟鍊煎彉鍖栵紝涓斻�屽簲浠樸�嶄笉鍙�
+- [ ] 娴佹按鍥涙帴鍙i兘閫氾細`/paymentRecord/listPage`銆乣add`銆乣update`銆乣delete`锛沗partyType` 浼� `9` 杩斿洖銆屽線鏉ユ柟绫诲瀷涓嶅悎娉曘��
+- [ ] 鐢ㄥ彟涓�涓暟鎹寖鍥寸殑璐﹀彿璋� `listPage`锛岀‘璁ゃ�岀湅涓嶅埌鍒汉褰曠殑娴佹按銆嶆槸棰勬湡锛堜笉鏄柊澧炲け璐ワ級
 - [ ] 澶囦欢缁存姢鑳戒繚瀛樹骇鍝佸ぇ绫� + 瑙勬牸鍨嬪彿
 - [ ] 鏂板涓�寮犲惈璇ヨ鏍肩殑閲囪喘鍙拌处锛屽浠舵暟閲忓鍔狅紝澶囦欢娴佹按鍑虹幇 `direction=1` 璁板綍
-- [ ] 璁惧鎶ヤ慨/淇濆吇棰嗙敤澶囦欢鍚庯紝澶囦欢娴佹按鍑虹幇 `direction=2` 璁板綍
+- [ ] 缂栬緫璇ュ彴璐�**鍒犳帀**閭f潯鏄庣粏锛屽浠舵暟閲忓噺鍥炲幓锛屽搴斿叆搴撴祦姘存秷澶�
+- [ ] 缂栬緫璇ュ彴璐�**鏀规暟閲�**锛屽浠舵暟閲忔寜鏂版暟閲忛噸绠楋紙涓嶆槸涓や唤鐩稿姞锛�
+- [ ] 鍒犻櫎璇ラ噰璐彴璐︼紝澶囦欢鏁伴噺鍑忓洖鍘伙紝鍏ュ簱娴佹按娑堝け
+- [ ] 澶囦欢宸茶棰嗙敤鍚庡啀鍒犲彴璐︼紝鍒犻櫎琚嫆缁濆苟缁欏嚭銆屼笉瓒充互鍥炴粴銆嶆彁绀猴紙涓嶆槸璐熷簱瀛橈級
+- [ ] 璁惧鎶ヤ慨/淇濆吇棰嗙敤澶囦欢鍚庯紝澶囦欢娴佹按鍑虹幇 `direction=2` 璁板綍锛屼笖銆屾潵婧愬崟鍙枫�嶆樉绀鸿澶囧悕绉�
 - [ ] 鏂板鍏ュ簱閫夎鍗曞悗锛屾槑缁嗗彧鏄剧ず閲囪喘绫伙紙type=2锛変骇鍝�
diff --git "a/doc/20260920_\345\247\224\345\244\226\347\256\241\347\220\206\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260920_\345\247\224\345\244\226\347\256\241\347\220\206\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..9e3d6d6
--- /dev/null
+++ "b/doc/20260920_\345\247\224\345\244\226\347\256\241\347\220\206\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,725 @@
+# 濮斿绠$悊妯″潡鍓嶇鑱旇皟鏂囨。
+
+鏇存柊鏃堕棿锛�2026-09-20
+閫傜敤鐗堟湰锛氭湰娆″悗绔彉鏇村悗
+鍚庣鏂板鍖咃細`com.ruoyi.outsourcing`
+
+## 1. 鍙樻洿鑼冨洿
+
+鏂板涓�绾ц彍鍗曘��**濮斿绠$悊**銆嶏紝涓嬪惈涓変釜椤甸潰锛�
+
+| 椤甸潰 | 璺緞锛堣彍鍗曪級 | 鍓嶇缁勪欢璺緞 | 鎺ュ彛鍓嶇紑 |
+| --- | --- | --- | --- |
+| 鍚堜綔鍟嗘。妗� | `outsourcingManagement/partner` | `outsourcingManagement/partner/index` | `/outsourcing/partner` |
+| 濮斿璁㈠崟 | `outsourcingManagement/order` | `outsourcingManagement/order/index` | `/outsourcing/order` |
+| 鍚堜綔鍟嗗線鏉� | `outsourcingManagement/transactions` | `outsourcingManagement/transactions/index` | `/outsourcing/report` |
+
+### 1.1 鏁版嵁搴撹剼鏈紙闇�鍏堟墽琛岋級
+
+| 鑴氭湰 | 浣滅敤 |
+| --- | --- |
+| `doc/20260920_create_outsourcing.sql` | 寤� `outsourcing_partner`銆乣outsourcing_order`銆乣outsourcing_order_product` 涓夊紶琛� |
+| `doc/20260920_outsourcing_menu.sql` | 寤鸿彍鍗曪紙`menu_id` 2360~2376锛夛紝鍙噸澶嶆墽琛� |
+| `doc/20260920_create_payment_record.sql` | `party_type` 娉ㄩ噴鏀逛负 `1瀹㈡埛 2渚涘簲鍟� 3鍚堜綔鍟哷 |
+
+宸插缓杩� `outsourcing_order_product` 鐨勭幆澧冿紝鎶ュ簾鍝佹敼鍚堟牸搴撳瓨鍚庤繕闇�鎵ц鍒楁敼鍚嶏紙2026-09-20 宸插湪寮�鍙戝簱鎵ц锛夛細
+
+```sql
+ALTER TABLE outsourcing_order_product
+    CHANGE COLUMN stock_uninventory_id stock_inventory_id bigint null comment '閫夌敤鐨勬姤搴熷搧鍚堟牸搴撳瓨id锛坰tock_inventory.id锛屽彧璇诲紩鐢紝涓嶅洖鍐欏簱瀛橈級';
+ALTER TABLE outsourcing_order_product RENAME INDEX idx_stock_uninventory_id TO idx_stock_inventory_id;
+```
+
+> 绾敼鍚嶏紝鍒楅噷瀛樼殑鍘嗗彶鍊间笉鍔ㄣ�傚紑鍙戝簱璇ヨ〃鏃犳暟鎹紝鏃犻闄┿��
+
+> 宸插缓杩� `payment_record` 鐨勭幆澧冩墽琛岋細
+> `ALTER TABLE payment_record MODIFY COLUMN party_type tinyint not null comment '寰�鏉ユ柟绫诲瀷 1瀹㈡埛 2渚涘簲鍟� 3鍚堜綔鍟�';`
+> 璇ユ搷浣滀负娉ㄩ噴鍙樻洿锛屾棤鏁版嵁椋庨櫓銆�
+
+### 1.2 瀵规棦鏈夋ā鍧楃殑鏀瑰姩
+
+濮斿璁㈠崟鎻愪氦鏃朵細鍗犵敤鎶ュ簾鍝佸簱瀛橈紝璧扮殑鏄�**鐜版湁鍑哄簱瀹℃壒鏈哄埗**锛屽洜姝ゆ敼鍒颁簡 `stock` 妯″潡锛屽墠绔渶涓�骞剁煡鏅擄細
+
+| 鏀瑰姩 | 璇存槑 |
+| --- | --- |
+| 鏂板鍑哄簱绫诲瀷 `record_type=30` | 銆屽澶�-鎶ュ簾鍝佸嚭搴撱�嶏紙**`type=0` 鍚堟牸鍑哄簱**锛夈��**鍑哄簱绠$悊椤电殑绫诲瀷鍒楅渶瑕佸姞涓婅繖涓槧灏�**锛屽惁鍒欐樉绀轰负绌� |
+| 澶嶇敤 `POST /stockOutRecord/approve` | 濮斿鍑哄簱璁板綍鐢ㄧ幇鏈夊鎵规帴鍙o紝**娌℃湁鏂板鍚庣鎺ュ彛** |
+| 鏂板鍐呴儴鏂规硶 | `StockOutRecordService#listByRecordIds` / `releaseByRecordIds`锛屽彧缁欏澶栬鍗曞唴閮ㄧ敤锛屽墠绔笉鐩存帴璋冪敤 |
+| 鎶ュ簾鍝佹敼璧板悎鏍煎簱瀛� | 鎶ュ簾鍝侀�夋枡浠� `stock_uninventory` 鏀逛负 `stock_inventory`锛屽嚭搴撹褰� `type` 鐢� `1` 鏀逛负 `0`锛沗outsourcing_order_product.stock_uninventory_id` **鏀瑰悕涓� `stock_inventory_id`**锛堥渶鎵ц 1.1 鐨� ALTER锛� |
+| 閫夋枡鎺ュ彛鎸綅缃� | `GET /stockUninventory/pageScrapStock` **宸插垹闄�**锛屾敼涓� `GET /stockInventory/pageScrapStock`锛涘澶栬鍗曠殑 `GET /outsourcing/order/scrapStockPage` 璺緞涓嶅彉銆佸嚭鍙傚瓧娈典笉鍙� |
+| 鍑哄簱绫诲瀷 `30` 鎸埌鍚堟牸鍑哄簱鏋氫妇 | `OUTSOURCING_SCRAP_OUT("30","濮斿-鎶ュ簾鍝佸嚭搴�")` 浠�**鍏ュ簱**鏋氫妇 `StockInQualifiedRecordTypeEnum` 绉诲埌**鍑哄簱**鏋氫妇 `StockOutQualifiedRecordTypeEnum`锛堝嚭搴撹褰曞鍑�/涓嬫媺鎸� `type` 鍙栨灇涓撅紝`type=0` 鍙栫殑灏辨槸鍚庤�咃紝涓嶆尓浼氭姤绌烘寚閽堬級 |
+| 閫夋枡鑼冨洿鏀剁獎 | 濮斿鎶ュ簾鍝侀�夋枡**鍙垪浜у搧鏍戙�屽嵄搴熷缃鐢熷嵄搴熴�嶅ぇ绫伙紙鍚瓙鑺傜偣锛�**涓嬫湁鍚堟牸搴撳瓨鐨勬壒娆★紝涓嶅啀鍒楀叏搴撳簱瀛� |
+| 鍑哄簱瀹℃壒椤哄甫瀹屾垚璁㈠崟 | `POST /stockOutRecord/approve` 閫氳繃鏃讹紝鑻ヨ濮斿璁㈠崟鐨勬姤搴熷搧鍑哄簱宸�**鍏ㄩ儴閫氳繃**锛岃嚜鍔ㄦ妸璁㈠崟缃负銆屽凡瀹屾垚銆峘2`锛坄StockOutRecordServiceImpl#batchApprove` 鍐呯殑鍓綔鐢紝涓嶆槸鏂版帴鍙o級 |
+
+---
+
+## 2. 涓氬姟鍙e緞锛�**蹇呰**锛屽拰鍓嶇灞曠ず寮虹浉鍏筹級
+
+### 2.1 濮斿璁㈠崟鐘舵�佹祦杞�
+
+| 鍊� | 鍚箟 | 鍏佽鎿嶄綔 |
+| --- | --- | --- |
+| `0` | 鑽夌 | 缂栬緫銆佸垹闄ゃ�佹彁浜ゃ�佷綔搴� |
+| `1` | 宸茬‘璁� | 瀹屾垚锛�**闇�鎶ュ簾鍝佸嚭搴撳凡瀹℃壒閫氳繃**锛夈�佷綔搴� |
+| `2` | 宸插畬鎴� | 浠呮煡鐪嬶紙缁堟�侊級 |
+| `3` | 宸蹭綔搴� | 浠呮煡鐪嬶紙缁堟�侊級 |
+
+- `0 --submit--> 1 --complete--> 2`
+- `0 / 1 --cancel--> 3`
+- 鏂板鐨勫澶栬鍗�**寮哄埗涓鸿崏绋�**锛屽墠绔笉闇�瑕佷篃涓嶅厑璁镐紶 `status`銆�
+- **涓嶈蛋 OA 瀹℃壒**锛屽鎵规祦涓嶆秹鍙娿�屽鎵圭鐞嗐�嶈彍鍗曘��
+- 鎻愪氦浼氬崰鐢ㄦ姤搴熷搧搴撳瓨锛屽畬鎴愯姹傚嚭搴撳凡瀹℃壒锛屼綔搴熶細閲婃斁鍗犵敤 鈥斺�� 璇﹁ 2.3銆�
+- 鈿狅笍 **`1 鈫� 2` 鐜板湪澶氬崐鏄嚜鍔ㄥ彂鐢熺殑**锛氳璁㈠崟鐨勬姤搴熷搧鍑哄簱璁板綍**鍏ㄩ儴瀹℃壒閫氳繃**鏃讹紝鍚庣浼氳嚜鍔ㄦ妸璁㈠崟鎺ㄥ埌銆屽凡瀹屾垚銆嶏紝涓嶉渶瑕佸啀鐐广�屽畬鎴愩�嶃�俙POST /outsourcing/order/complete` 淇濈暀锛屼絾璁㈠崟宸叉槸 `2` 鏃惰皟鐢ㄤ細杩斿洖銆屼粎宸茬‘璁ょ姸鎬佺殑濮斿璁㈠崟鍏佽瀹屾垚銆嶏紝**鍓嶇寤鸿鍦ㄧ姸鎬佷负 `1` 鏃朵粛鏄剧ず銆屽畬鎴愩�嶆寜閽綔涓哄厹搴曪紝鐘舵�佷负 `2` 鏃堕殣钘�**銆�
+
+### 2.2 鍚堜綔鍟嗗線鏉ュ簲浠樺彛寰�
+
+```
+搴斾粯浣欓 = 濮斿璁㈠崟閲戦锛坰tatus in (1,2)锛� 鈭� 宸蹭粯娆撅紙payment_record: party_type=3 and direction=1锛�
+```
+
+涓や釜蹇呴』娉ㄦ剰鐨勭偣锛�
+
+1. **鑽夌(0) 鍜� 宸蹭綔搴�(3) 鐨勮鍗曚笉璁″叆搴斾粯**銆傚彧鏈夊凡纭鍜屽凡瀹屾垚璁″叆銆�
+2. **鏄庣粏姣忚鐨勩�屽凡浠樻銆嶆槸鍚堜綔鍟嗙骇鍚堣锛屼笉鏄鍗曠骇**銆俙payment_record` 琛ㄦ病鏈夎鍗曠淮搴﹀瓧娈碉紝鎵�浠ュ悓涓�鍚堜綔鍟嗗悕涓嬫墍鏈夋槑缁嗚鐨� `paidAmount` 鏄悓涓�涓�硷紙鐜版湁瀹㈡埛寰�鏉ャ�佷緵搴斿晢寰�鏉ヤ篃鏄繖涓彛寰勶級銆�
+   鈫� 鍥犳**鏄庣粏琛屽彧鏈夈�岃鍗曢噾棰濄�嶆槸閫愯鍑嗙‘鐨�**锛沗paidAmount` / `payableAmount` 寤鸿鍙湪姹囨�昏锛堝垪琛ㄩ〉锛夊己璋冿紝鏄庣粏琛岃嫢瑕佸睍绀洪渶鍔犺鏄庢枃妗堛��
+
+### 2.3 鎶ュ簾鍝侀�夋枡鐨勫簱瀛樿仈鍔紙**鎻愪氦鍗冲崰鐢ㄣ�佸嚭搴撳鎵规墠瀹炴墸**锛�
+
+濮斿璁㈠崟鏄庣粏鍙�夈�屾姤搴熷搧銆嶅簱瀛樻壒娆� 鈥斺�� 閫夋枡寮圭獥**鍙垪鍩虹鏁版嵁浜у搧鏍戦噷銆屽嵄搴熷缃鐢熷嵄搴熴�嶅ぇ绫伙紙鍚叾鎵�鏈夊瓙鑺傜偣锛変笅銆佷笖鏈夊悎鏍煎簱瀛樼殑鎵规**锛屽叾瀹冧骇鍝佸ぇ绫荤殑搴撳瓨涓嶅嚭鐜般�傞�夋枡鍙栫殑鏄�**鍚堟牸搴撳瓨 `stock_inventory`**锛岃仈鍔ㄨ蛋鐨勬槸**鐜版湁鍑哄簱瀹℃壒閭e鏈哄埗**锛屾椂搴忓涓嬶細
+
+| 鐜妭 | 搴撳瓨鍔ㄤ綔 |
+| --- | --- |
+| 閫夋枡锛堝墠绔脊绐楋級 | 鍙鏌ヨ锛屼笉鍔ㄥ簱瀛� |
+| 寤鸿崏绋� / 缂栬緫鑽夌 / 鍒犻櫎鑽夌 | **涓嶅姩搴撳瓨** |
+| **鎻愪氦**锛坄0 鈫� 1`锛� | 姣忔潯閫夋枡琛屽啓涓�鏉�**寰呭鎵瑰嚭搴撹褰�**锛�**`type=0` 鍚堟牸鍑哄簱**锛夈�傝褰曚竴钀藉簱灏遍�氳繃銆屽緟瀹℃壒鍑哄簱閲忋�嶆妸鍙敤閲忓崰浣忥紝**浣嗕笉鏀� `qualitity`** |
+| **鍑哄簱瀹℃壒**锛堝嚭搴撶鐞嗛〉 `/stockOutRecord/approve`锛� | 瀹℃壒**閫氳繃**鏃舵墠鐪熸鎵� `stock_inventory.qualitity`锛堟寜瑙勬牸+鎵瑰彿锛� |
+| **瀹屾垚**锛坄1 鈫� 2`锛� | **鑷姩瑙﹀彂**锛氳璁㈠崟鐨勬姤搴熷搧鍑哄簱璁板綍**鍏ㄩ儴瀹℃壒閫氳繃**鐨勯偅涓�鍒伙紝鍚庣鑷姩鎶婅鍗曟帹鍒般�屽凡瀹屾垚銆嶃�備篃鍙互鎵嬪伐璋� `/complete`锛屽墠缃牎楠屽悓鏍锋槸銆屽嚭搴撳叏閮ㄥ凡瀹℃壒閫氳繃銆嶏紝鍚﹀垯鎷掔粷 |
+| **浣滃簾**锛坄1 鈫� 3`锛� | 閲婃斁鍗犵敤锛氬緟瀹℃壒/椹冲洖鐨勮褰�**鐩存帴鍒犻櫎**锛堝崰鐢ㄩ殢涔嬮噴鏀撅級锛涘凡瀹℃壒閫氳繃鐨勮褰�**鎸夋壒娆″綊杩� `stock_inventory.qualitity`**锛屽啀鍒犻櫎 |
+
+鍏抽敭鍙e緞锛�
+
+- **鍙敤閲� = `qualitity` 鈭� `locked_quantity` 鈭� 寰呭鎵瑰嚭搴撻噺**銆傛彁浜ゆ椂灏辨槸鎸夎繖涓彛寰勬牎楠岀殑锛屼笉瓒充細鐩存帴鎶ラ敊骞舵彁绀哄綋鍓嶅彲鐢ㄩ噺銆傚洜涓哄緟瀹℃壒璁板綍涔熺畻鍗犵敤锛�**鍚屼竴鎵规涓嶄細琚寮犲澶栬鍗曡秴閫�**銆�
+- 鎵�浠ュ嚭搴撹褰曢噷鏃㈡湁濮斿**鑷繁**鍗犵殑锛屼篃鏈夐噰璐��璐с�侀攢鍞彂璐с�佺敓浜ч鏂欑瓑**鍏跺畠涓氬姟**鍗犵殑锛岄兘鏄悓涓�濂楃畻娉曪紝浜掔浉鍙銆�
+- 鏄庣粏琛屼笂鐨� `availableQuantity` 浠嶆槸**涓嬪崟閭d竴鍒荤殑蹇収**锛屼粎渚涘弬鐓э紱鐪熷疄鍙敤閲忎互鎻愪氦鏃剁殑鏍¢獙缁撴灉涓哄噯銆�
+- `lockedQuantity`锛坄stock_inventory.locked_quantity`锛夋槸**鍐荤粨閲�**锛屽嚭搴撶瓑涓氬姟鍦ㄧ敤锛屽拰濮斿鍗犵敤涓嶆槸涓�鍥炰簨銆�
+
+### 2.4 濮斿鍑哄簱璁板綍闀夸粈涔堟牱銆佸幓鍝鎵�
+
+- 鍑哄簱绫诲瀷鍥哄畾涓� **`30`銆屽澶�-鎶ュ簾鍝佸嚭搴撱��**锛坄record_type=30`銆�**`type=0` 鍚堟牸鍑哄簱**锛夈�傝鍙栧�煎凡鍔犺繘**鍚堟牸鍑哄簱鏋氫妇 `StockOutQualifiedRecordTypeEnum`**锛屽洜姝� `/basic/enum/stockRecordType` 浼氳嚜鍔ㄥ甫涓婂畠锛屽悗绔鍑� Excel 涔熻兘姝g‘鏄剧ず銆�**鍓嶇鑻ョ敤鐨勬槸杩欎釜鎺ュ彛鍙栫被鍨嬩笅鎷�/鏄犲皠锛屾棤闇�鏀瑰姩**锛涘彧鏈夊綋绫诲瀷鍒楁槸鍓嶇纭紪鐮佹槧灏勮〃鏃舵墠闇�瑕佽ˉ `30`銆�
+- `record_id` = **濮斿璁㈠崟鏄庣粏琛� id**锛坄outsourcing_order_product.id`锛夛紝涓嶆槸璁㈠崟 id銆�
+- 鍑哄簱鎵规鍙凤紙`outboundBatches`锛夊舰濡� `CK20260920001`锛屾彁浜ゅ悗鍙湪鍑哄簱绠$悊椤垫寜绫诲瀷 `30` 杩囨护銆�
+- 瀹℃壒鐢�**鐜版湁**鎺ュ彛 `POST /stockOutRecord/approve`锛宐ody `{ "ids": [鍑哄簱璁板綍id], "approvalStatus": 1 }`锛坄1` 閫氳繃 / `2` 椹冲洖锛夈��**涓嶉渶瑕佹柊澧炲悗绔帴鍙�**銆�
+- **瀹℃壒閫氳繃浼氶『甯﹀畬鎴愬澶栬鍗�**锛氬鏋滆繖鏉″嚭搴撹褰曟槸璇ヨ鍗曟姤搴熷搧鍑哄簱鐨�**鏈�鍚庝竴鏉″緟瀹℃壒**锛屽鎵规帴鍙h繑鍥炲悗濮斿璁㈠崟鐘舵�佸氨宸茬粡鏄� `2`銆屽凡瀹屾垚銆嶄簡銆傚墠绔鎵瑰畬鍒锋柊灏变細鐪嬪埌璁㈠崟鍙樺凡瀹屾垚锛屼笉闇�瑕佸啀璋� `/complete`銆�
+  - 涓�娆″鎵瑰鏉★紙`ids` 浼犲鏉°�佸垎灞炰笉鍚岃鍗曪級鏃讹紝姣忎釜璁㈠崟鍚勮嚜鍒ゆ柇锛屽鏉′欢鐨勯兘浼氳嚜鍔ㄥ畬鎴愩��
+  - **椹冲洖锛坄approvalStatus=2`锛変笉浼氬洖閫�璁㈠崟鐘舵��**锛氳鍗曚粛鏄�屽凡纭銆嶏紝璧般�屽弽瀹� 鈫� 閫氳繃銆嶆垨銆屼綔搴熴�嶄袱鏉″嚭璺紙瑙� 2.3 鐨勫叧閿彛寰勶級銆�
+  - 宸茶嚜鍔ㄥ畬鎴愮殑璁㈠崟鑻ヤ箣鍚庤**鍙嶅**锛坄/stockOutRecord/reAudit`锛夛紝璁㈠崟鐘舵��**涔熶繚鎸佸湪銆屽凡瀹屾垚銆嶄笉鍥為��**锛岄渶瑕佷汉宸ラ噸鏂板鎵瑰悗鍐嶅鐞嗐��
+- 鈿狅笍 **鏁版嵁鏉冮檺褰卞搷**锛歚stock_out_record` 涓嶅湪鏁版嵁鏉冮檺鐧藉悕鍗曢噷锛�**鍑哄簱绠$悊鍒楄〃鍜屽鎵归兘浼氳 `create_user`/`dept_id` 杩囨护**銆備篃灏辨槸璇村嚭搴撳鎵瑰繀椤荤敱**涓庢彁浜や汉鍚屾暟鎹寖鍥�**鐨勮处鍙凤紝鎴� `dataScope=1`/admin 璐﹀彿鏉ュ仛锛屽惁鍒欎細鎶ャ�屽嚭搴撹褰曚笉瀛樺湪銆嶃�傝繖鏄棦鏈夎涓猴紙鍚堟牸/涓嶅悎鏍�/閿�鍞嚭搴撶殑瀹℃壒閮戒竴鏍凤級锛屾湰娆℃湭鏀广��
+  - 濮斿璁㈠崟鑷繁鐨勪袱涓姩浣滐紙瀹屾垚鐨勫嚭搴撴牎楠屻�佷綔搴熺殑鍗犵敤閲婃斁锛�**宸叉樉寮忚烦杩囨暟鎹潈闄�**锛屼笉浼氬洜涓烘崲璐﹀彿鑰屾紡鏍¢獙鎴栭噴鏀句笉鎺夈��
+- **鍑哄簱琚┏鍥炴�庝箞鍔�**锛氶┏鍥炲悗璇ヨ褰曠殑寰呭鎵瑰崰鐢ㄤ細绔嬪嵆閲婃斁锛屼絾濮斿璁㈠崟浠嶆槸銆屽凡纭銆嶏紝姝ゆ椂 `complete` 浼氳鎷掋�備袱鏉″嚭璺細鍒板嚭搴撶鐞嗙偣銆屽弽瀹°�峘POST /stockOutRecord/reAudit` 閫�鍥炲緟瀹℃壒鍐嶅鎵归�氳繃锛涙垨鐩存帴 `/cancel` 浣滃簾璇ュ澶栬鍗曪紙椹冲洖鐨勮褰曚細琚垹鎺夛級銆�
+
+---
+
+## 3. 鍏叡瀛楁璇存槑锛堝悗绔嚜鍔ㄧ淮鎶わ紝鍓嶇鍙锛�
+
+| 瀛楁 | 璇存槑 |
+| --- | --- |
+| `orderNo` | 濮斿璁㈠崟鍙凤紝鏂板鏃跺悗绔敓鎴愶紝鏍煎紡 `WWDD` + 鏃ユ湡 + 褰撴棩娴佹按锛屽 `WWDD20260920001`銆�**鍓嶇涓嶈浼�**锛屼紶浜嗕篃浼氳蹇界暐銆� |
+| `partnerName` | 渚濇嵁 `partnerId` 鍙嶆煡鍥炲~锛屽墠绔笉蹇呬紶锛堜紶浜嗕互鍚庣缁撴灉涓哄噯锛夈�� |
+| `contractAmount` | **鍚庣鎸夋槑缁� `taxInclusiveTotalPrice` 鍚堣瑕嗙洊鍐欏叆**锛屽墠绔紶浜嗕篃浼氳瑕嗙洊銆傝淇濊瘉鏄庣粏閲屽惈绋庢�讳环姝g‘銆� |
+| `applyDate` | 涓嶄紶鏃堕粯璁ゅ綋澶┿�� |
+| `productCategory` / `specificationModel` | **鏄庣粏琛�**涓繖涓や釜瀛楁鐢卞悗绔牴鎹� `productId` / `productModelId` 鍙嶆煡鍐欏叆锛屽墠绔彧闇�浼� id銆� |
+| `status` / `confirmTime` / `cancelReason` | 鐢辩姸鎬佹祦杞帴鍙e啓鍏ワ紝鍓嶇涓嶈鐩存帴鏀广�� |
+| `tenantId` / `deptId` / `createUser` / `createTime` / `updateTime` | 鍚庣鑷姩濉厖锛屽墠绔笉瑕佷紶銆� |
+
+---
+
+## 4. 鍚堜綔鍟嗘。妗堟帴鍙�
+
+### 4.1 鍒嗛〉鏌ヨ
+
+`GET /outsourcing/partner/listPage`
+
+**璇锋眰鍙傛暟**锛堝潎涓哄彲閫夛級
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| `partnerName` | string | 鍚堜綔鍟嗗悕绉帮紝妯$硦鍖归厤 |
+| `partnerType` | string | 鍚堜綔鍟嗙被鍨嬶紝绮剧‘鍖归厤 |
+| `isWhite` | number | 鏄惁鐧藉悕鍗曪紝`1` 鏄� / `0` 鍚� |
+| `pageNum` / `pageSize` | number | 鍒嗛〉鍙傛暟 |
+
+**杩斿洖**锛歚{ total, records, size, current, ... }`锛�**娉ㄦ剰**锛氭鎺ュ彛杩斿洖鐨勬槸 MyBatis-Plus 鐨� `IPage` 缁撴瀯锛屽瓧娈垫槸 `records`锛屼笉鏄� RuoYi 鐨� `rows`锛夈��
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "records": [
+      {
+        "id": 1,
+        "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃",
+        "taxpayerIdentificationNum": "91320600MA1XXXXXXX",
+        "companyAddress": "鍗楅�氬競XX鍖篨X璺�1鍙�",
+        "companyPhone": "0513-88888888",
+        "bankAccountName": "涓浗閾惰鍗楅�氬垎琛�",
+        "bankAccountNum": "5200000000000000",
+        "contactUserName": "鏉庡洓",
+        "contactUserPhone": "13800000000",
+        "maintainUserId": 1,
+        "maintainUserName": "绠$悊鍛�",
+        "maintainTime": "2026-09-20",
+        "partnerType": "鍗卞簾澶勭疆",
+        "isWhite": 1,
+        "remark": "",
+        "createTime": "2026-09-20 10:12:33"
+      }
+    ],
+    "total": 1,
+    "size": 10,
+    "current": 1
+  }
+}
+```
+
+### 4.2 璇︽儏锛堝惈闄勪欢鍥炴樉锛�
+
+`GET /outsourcing/partner/{id}`
+
+**杩斿洖**锛歚data` 涓哄悎浣滃晢瀵硅薄锛屽彟鍚� `storageBlobVOS`锛堥檮浠舵暟缁勶級銆�
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "id": 1,
+    "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃",
+    "maintainUserName": "绠$悊鍛�",
+    "storageBlobVOS": [
+      {
+        "id": 301,
+        "originalFilename": "钀ヤ笟鎵х収.pdf",
+        "url": "http://xxx/preview/xxx",
+        "downloadURL": "http://xxx/download/xxx",
+        "storageAttachmentId": 501
+      }
+    ]
+  }
+}
+```
+
+### 4.3 鏂板
+
+`POST /outsourcing/partner/add`锛宍Content-Type: application/json`
+
+**璇锋眰浣�**
+
+```json
+{
+  "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃",
+  "taxpayerIdentificationNum": "91320600MA1XXXXXXX",
+  "companyAddress": "鍗楅�氬競XX鍖篨X璺�1鍙�",
+  "companyPhone": "0513-88888888",
+  "bankAccountName": "涓浗閾惰鍗楅�氬垎琛�",
+  "bankAccountNum": "5200000000000000",
+  "contactUserName": "鏉庡洓",
+  "contactUserPhone": "13800000000",
+  "partnerType": "鍗卞簾澶勭疆",
+  "isWhite": 1,
+  "remark": "",
+  "storageBlobDTOS": [
+    { "id": 301 }
+  ]
+}
+```
+
+**杩斿洖**锛歚data` 涓烘柊寤哄悎浣滃晢 id銆�
+
+```json
+{ "code": 200, "msg": "鎿嶄綔鎴愬姛", "data": 1 }
+```
+
+> 闄勪欢娴佺▼锛氬厛璋� `POST /common/upload`锛坄multipart/form-data`锛屽瓧娈靛悕 `files`锛屽彲澶氭枃浠讹級鎷垮埌 `List<StorageBlobVO>`锛屽彇鍑烘瘡鏉$殑 `id` 缁勬垚 `storageBlobDTOS` 涓�璧锋彁浜ゅ嵆鍙��**涓嶉渶瑕�**鍐嶅崟鐙皟 `/storageAttachment/add`銆�
+> 鏂板鏃� `storageBlobDTOS` 鍙笉浼犳垨浼犵┖鏁扮粍銆�
+
+### 4.4 淇敼
+
+`POST /outsourcing/partner/update`锛宍Content-Type: application/json`
+
+璇锋眰浣撳悓 4.3锛�**蹇呴』甯� `id`**銆�
+
+> **闄勪欢鏄叏閲忚鐩栫殑**锛氭彁浜ゆ椂 `storageBlobDTOS` 浼犱粈涔堝氨鏄粈涔堛�備紶 `[]` 鎴� `null` 浼�**娓呯┖**璇ュ悎浣滃晢宸茬粦瀹氱殑鎵�鏈夐檮浠躲�傚墠绔紪杈戞椂璇锋妸鍥炴樉鐨� `storageBlobVOS` 杞崲鎴� `storageBlobDTOS` 涓�骞舵彁浜ゃ��
+
+### 4.5 鍒犻櫎
+
+`DELETE /outsourcing/partner/del`锛宍Content-Type: application/json`
+
+**璇锋眰浣�**锛歩d 鏁扮粍锛屾敮鎸佹壒閲忋��
+
+```json
+[1, 2]
+```
+
+> 鍒犻櫎鍓嶄細鏍¢獙寮曠敤锛氳鍚堜綔鍟嗚嫢宸插瓨鍦�**濮斿璁㈠崟**鎴�**寰�鏉ユ敹浠樻璁板綍**锛屼細琚嫆缁濓紙瑙� 7. 甯歌閿欒锛夈��
+
+### 4.6 瀵煎嚭
+
+`POST /outsourcing/partner/export`锛屽弬鏁板悓 4.1 鏌ヨ鏉′欢锛坒orm 褰㈠紡锛夛紝杩斿洖 Excel 鏂囦欢娴侊紝鐩存帴 `responseType: 'blob'` 涓嬭浇銆�
+
+### 4.7 涓嬭浇瀵煎叆妯℃澘
+
+`POST /outsourcing/partner/downloadTemplate`锛屾棤鍙傛暟锛岃繑鍥� Excel 鏂囦欢娴併��
+
+### 4.8 瀵煎叆
+
+`POST /outsourcing/partner/import`锛宍multipart/form-data`锛屽瓧娈靛悕 `file`銆�
+
+```json
+{ "code": 200, "msg": "瀵煎叆鎴愬姛" }
+```
+
+### 4.9 涓嬫媺閫夐」
+
+`GET /outsourcing/partner/getOptions`
+
+**杩斿洖**锛歚data` 涓哄悎浣滃晢鏁扮粍锛屼粎鍚� `id`銆乣partnerName`锛屾寜 id 鍊掑簭銆�
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": [ { "id": 1, "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃" } ]
+}
+```
+
+---
+
+## 5. 濮斿璁㈠崟鎺ュ彛
+
+### 5.1 鍒嗛〉鏌ヨ
+
+`GET /outsourcing/order/list`
+
+**璇锋眰鍙傛暟**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| `orderNo` | string | 璁㈠崟鍙凤紝妯$硦鍖归厤 |
+| `partnerId` | number | 鍚堜綔鍟唅d锛岀簿纭尮閰� |
+| `partnerName` | string | 鍚堜綔鍟嗗悕绉帮紝妯$硦鍖归厤 |
+| `projectName` | string | 椤圭洰鍚嶇О锛屾ā绯婂尮閰� |
+| `status` | number | 鐘舵�侊紝瑙� 2.1 |
+| `startDate` | string | 鐢宠鏃ユ湡璧凤紝`yyyy-MM-dd` |
+| `endDate` | string | 鐢宠鏃ユ湡姝紝`yyyy-MM-dd` |
+| `pageNum` / `pageSize` | number | 鍒嗛〉鍙傛暟 |
+
+**杩斿洖**锛歊uoYi 鏍囧噯鍒嗛〉缁撴瀯 `{ total, rows, code, msg }`锛�**娉ㄦ剰姝ゅ鏄� `rows`**锛夈��
+
+```json
+{
+  "total": 1,
+  "rows": [
+    {
+      "id": 7,
+      "orderNo": "WWDD20260920001",
+      "partnerId": 1,
+      "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃",
+      "applyDate": "2026-09-20",
+      "expectedDeliveryDate": "2026-09-30",
+      "projectName": "鍗卞簾澶勭疆",
+      "contractAmount": 5000.00,
+      "paymentMethod": "鐢垫眹",
+      "remarks": "",
+      "status": 1,
+      "confirmTime": "2026-09-20 11:00:00",
+      "cancelReason": null,
+      "createTime": "2026-09-20 10:12:33"
+    }
+  ],
+  "code": 200,
+  "msg": "鏌ヨ鎴愬姛"
+}
+```
+
+> 鍒楄〃**涓嶈繑鍥�** `productData` 鏄庣粏銆�
+
+### 5.2 璇︽儏
+
+`GET /outsourcing/order/getById?id={id}`
+
+**杩斿洖**锛歚data` 涓鸿鍗曞璞★紙瀛楁鍚� 5.1锛屽彟鍚� `productData` 鏄庣粏鏁扮粍锛夈��
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "id": 7,
+    "orderNo": "WWDD20260920001",
+    "partnerId": 1,
+    "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃",
+    "status": 1,
+    "contractAmount": 5000.00,
+    "productData": [
+      {
+        "id": 31,
+        "outsourcingOrderId": 7,
+        "productId": 491,
+        "productModelId": 1001,
+        "productCategory": "绮樻补搴熺墿",
+        "specificationModel": "HW08-1",
+        "unit": "鍚�",
+        "quantity": 5,
+        "taxRate": 13,
+        "taxInclusiveUnitPrice": 1000.00,
+        "taxInclusiveTotalPrice": 5000.00,
+        "isScrap": true,
+        "stockInventoryId": 9001,
+        "batchNo": "P20260901",
+        "availableQuantity": 100.00,
+        "remark": ""
+      }
+    ]
+  }
+}
+```
+
+### 5.3 鏂板锛堣崏绋匡級
+
+`POST /outsourcing/order/add`锛宍Content-Type: application/json`
+
+**璇锋眰浣�**锛氬悓 5.2 鐨� `data` 缁撴瀯锛宍id`銆乣orderNo`銆乣status`銆乣confirmTime` 涓嶉渶瑕佷紶銆�
+
+```json
+{
+  "partnerId": 1,
+  "projectName": "鍗卞簾澶勭疆",
+  "applyDate": "2026-09-20",
+  "expectedDeliveryDate": "2026-09-30",
+  "paymentMethod": "鐢垫眹",
+  "remarks": "",
+  "productData": [
+    {
+      "productId": 491,
+      "productModelId": 1001,
+      "unit": "鍚�",
+      "quantity": 5,
+      "taxRate": 13,
+      "taxInclusiveUnitPrice": 1000.00,
+      "taxInclusiveTotalPrice": 5000.00,
+      "isScrap": true,
+      "stockInventoryId": 9001,
+      "batchNo": "P20260901",
+      "availableQuantity": 100.00
+    }
+  ]
+}
+```
+
+**杩斿洖**锛歚data` 涓烘柊寤鸿鍗� id銆�
+
+### 5.4 淇敼
+
+`POST /outsourcing/order/edit`锛宍Content-Type: application/json`
+
+璇锋眰浣撳悓 5.3锛�**蹇呴』甯� `id`**锛屾槑缁嗛渶**鍏ㄩ噺鎻愪氦**锛堝悗绔负銆屽厛鍒犲悗鎻掋�嶏級銆�**浠呰崏绋跨姸鎬佸厑璁�**銆�
+
+### 5.5 鍒犻櫎
+
+`DELETE /outsourcing/order/delete`锛宍Content-Type: application/json`
+
+**璇锋眰浣�**锛歩d 鏁扮粍銆�**浠呰崏绋跨姸鎬佸厑璁�**銆�
+
+```json
+[7, 8]
+```
+
+### 5.6 鎻愪氦
+
+`POST /outsourcing/order/submit?id={id}`
+
+> `id` 鏄� **query 鍙傛暟**锛屼笉鏄姹備綋銆�
+
+鑽夌 鈫� 宸茬‘璁ゃ�傚墠缃牎楠岋細鍚堜綔鍟嗗瓨鍦ㄣ�佹槑缁嗛潪绌恒�侀噾棰� > 0銆�**鎶ュ簾鍝侀�夋枡琛岀殑鍙敤閲忚冻澶�**銆�
+**浼氬崰鐢ㄦ姤搴熷搧搴撳瓨**锛堝啓寰呭鎵瑰嚭搴撹褰曪級锛岃瑙� 2.3銆�
+杩斿洖 `{ "code": 200, "msg": "鎿嶄綔鎴愬姛" }`銆�
+
+### 5.7 瀹屾垚
+
+`POST /outsourcing/order/complete?id={id}`
+
+宸茬‘璁� 鈫� 宸插畬鎴愩��**鍓嶇疆鏍¢獙锛氭姤搴熷搧閫夋枡鐨勫嚭搴撹褰曞繀椤诲叏閮ㄥ鎵归�氳繃**锛堣 2.3 / 2.4锛夛紝
+鏈�氳繃鏃惰繑鍥炪�屾姤搴熷搧鍑哄簱鏈鎵归�氳繃锛岃鍏堝埌鍑哄簱绠$悊瀹℃壒鍑哄簱鎵规锛欳Kxxxx銆嶃��
+
+> 鈿狅笍 杩欎竴姝�**閫氬父涓嶉渶瑕佸墠绔皟鐢�**浜嗭細鍑哄簱璁板綍鍏ㄩ儴瀹℃壒閫氳繃鏃跺悗绔細鑷姩瀹屾垚璁㈠崟锛堣 2.3 / 2.4锛夈��
+> 鏈帴鍙d繚鐣欎綔鍏滃簳锛岃鍗曞凡鏄� `2` 鏃惰皟鐢ㄨ繑鍥炪�屼粎宸茬‘璁ょ姸鎬佺殑濮斿璁㈠崟鍏佽瀹屾垚銆嶃��
+
+### 5.8 浣滃簾
+
+`POST /outsourcing/order/cancel?id={id}&cancelReason={鍘熷洜}`
+
+鑽夌鎴栧凡纭 鈫� 宸蹭綔搴熴�俙cancelReason` 鍙�夈��
+**宸茬‘璁ょ殑鍗曚綔搴熶細閲婃斁鎶ュ簾鍝佸崰鐢�**锛氭湭瀹℃壒鐨勫嚭搴撹褰曠洿鎺ュ垹鎺夛紝宸插鎵归�氳繃鐨勬妸搴撳瓨鎸夋壒娆″綊杩橈紙瑙� 2.3锛夈��
+
+> 鈿狅笍 鍥犱负鍑哄簱鍏ㄩ儴閫氳繃浼氳嚜鍔ㄥ畬鎴愯鍗曪紙鐘舵�佸彉 `2`锛夛紝**宸茶嚜鍔ㄥ畬鎴愮殑鍗曚笉鑳藉啀浣滃簾**锛屼細杩斿洖銆岃濮斿璁㈠崟宸插畬鎴愭垨宸蹭綔搴燂紝涓嶅厑璁搁噸澶嶄綔搴熴�嶃��
+> 銆屼綔搴熷綊杩樺凡瀹℃壒搴撳瓨銆嶈繖鏉¤矾寰勭幇鍦ㄥ彧瀵�**閮ㄥ垎鍑哄簱宸查�氳繃**鐨勫崟锛堜粛澶勪簬 `1`锛夋湁鏁堛��
+
+### 5.9 瀵煎嚭
+
+`POST /outsourcing/order/export`锛屽弬鏁板悓 5.1锛岃繑鍥� Excel 鏂囦欢娴併��
+
+### 5.10 鎶ュ簾鍝侀�夋枡鍒嗛〉锛堢函鏌ヨ锛屼笉鍗犲簱瀛橈級
+
+`GET /outsourcing/order/scrapStockPage`
+
+> **缁撴灉宸叉敹绐勫埌銆屽嵄搴熷缃鐢熷嵄搴熴��**锛氬彧杩斿洖浜у搧鏍戦噷銆屽嵄搴熷缃鐢熷嵄搴熴�嶅ぇ绫伙紙鍚叾鎵�鏈夊瓙鑺傜偣锛変笅鏈夊悎鏍煎簱瀛樼殑鎵规銆傚叾瀹冨ぇ绫荤殑搴撳瓨**涓嶄細鍑虹幇**锛屽墠绔笉闇�瑕侀澶栦紶澶х被鍙傛暟锛屼篃涓嶉渶瑕佽嚜宸辫繃婊ゃ��
+> 鑻ヨ澶х被鍦ㄤ骇鍝佹爲閲屼笉瀛樺湪锛堟垨鍚嶅瓧琚敼杩囷級锛屾帴鍙h繑鍥炵┖鍒楄〃 鈥斺�� 杩欐槸鍞竴浼氳寮圭獥绌烘帀鐨勫師鍥狅紝鎺掓煡鏃跺厛纭浜у搧鏍戦噷鐨勫ぇ绫诲悕浠嶆槸銆屽嵄搴熷缃鐢熷嵄搴熴�嶃��
+
+**璇锋眰鍙傛暟**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| `productModelId` | number | 瑙勬牸id锛岀簿纭尮閰嶏紱涓嶄紶鍒欒繑鍥炶澶х被涓嬪叏閮ㄦ壒娆� |
+| `batchNo` | string | 鎵瑰彿锛屾ā绯婂尮閰� |
+| `productName` | string | 浜у搧鍚嶇О锛屾ā绯婂尮閰嶏紙鍙湪璇ュぇ绫荤殑鑼冨洿鍐呰繃婊わ級 |
+| `pageNum` / `pageSize` | number | 鍒嗛〉鍙傛暟 |
+
+**杩斿洖**锛歚data` 涓� `IPage` 缁撴瀯锛堝瓧娈垫槸 `records`锛夈��
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "records": [
+      {
+        "id": 9001,
+        "productModelId": 1001,
+        "batchNo": "P20260901",
+        "qualitity": 100.00,
+        "lockedQuantity": 0,
+        "unLockedQuantity": 100.00,
+        "model": "HW08-1",
+        "unit": "鍚�",
+        "productName": "绮樻补搴熺墿",
+        "remark": "",
+        "version": 0
+      }
+    ],
+    "total": 1
+  }
+}
+```
+
+> 杩欓噷鏌ョ殑鏄�**鍚堟牸搴撳瓨 `stock_inventory`**锛堜笌 `/stockInventory/pageScrapStock` 鍚屾簮锛夈�傚簱瀛樼鐞嗛〉鐨� `/stockInventory/pagestockInventory` 鏈仛浠讳綍鏀瑰姩锛堝畠涓嶈繑鍥� `batchNo`锛夛紝閫夋枡璇风敤鏈帴鍙c��
+> 鈿狅笍 鏈帴鍙f槸绾煡璇紝**涓嶄細**鍗犵敤搴撳瓨銆傜湡姝g殑鍗犵敤鍙戠敓鍦ㄥ澶栬鍗�**鎻愪氦**鏃讹紙瑙� 2.3锛夈�傚彟澶栬繖閲岃繑鍥炵殑 `unLockedQuantity` = `qualitity 鈭� lockedQuantity`锛�**娌℃湁鍑忓幓寰呭鎵瑰嚭搴撻噺**锛屾墍浠ュ畠鏄�岃处闈㈠彲鐢ㄩ噺銆嶏紝涓嶆槸鎻愪氦鏃舵牎楠岀敤鐨勭湡瀹炲彲鐢ㄩ噺銆�
+
+---
+
+## 6. 鍚堜綔鍟嗗線鏉ユ帴鍙�
+
+### 6.1 寰�鏉ユ眹鎬伙紙鍒楄〃锛�
+
+`GET /outsourcing/report/partnerTransactions`
+
+**璇锋眰鍙傛暟**
+
+| 鍙傛暟 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| `partnerName` | string | 鍚堜綔鍟嗗悕绉帮紝妯$硦鍖归厤 |
+| `pageNum` / `pageSize` | number | 鍒嗛〉鍙傛暟 |
+
+**杩斿洖**锛歚data` 涓� `IPage` 缁撴瀯锛堝瓧娈垫槸 `records`锛夈��
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "records": [
+      {
+        "partnerId": 1,
+        "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃",
+        "contractAmounts": 5000.00,
+        "paidAmount": 2000.00,
+        "receiptAmount": 0.00,
+        "payableAmount": 3000.00
+      }
+    ],
+    "total": 1
+  }
+}
+```
+
+### 6.2 寰�鏉ユ槑缁嗭紙閫愯鍗曪級
+
+`GET /outsourcing/report/partnerTransactionsDetails?partnerId={id}`
+
+**杩斿洖**锛歚data` 涓� `IPage` 缁撴瀯锛堝瓧娈垫槸 `records`锛夈��
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {
+    "records": [
+      {
+        "outsourcingOrderId": 7,
+        "orderNo": "WWDD20260920001",
+        "partnerId": 1,
+        "partnerName": "鍗楅�歑X鐜繚绉戞妧鏈夐檺鍏徃",
+        "applyDate": "2026-09-20",
+        "contractAmount": 5000.00,
+        "status": 1,
+        "paidAmount": 2000.00,
+        "payableAmount": 3000.00
+      }
+    ],
+    "total": 1
+  }
+}
+```
+
+> 鈿狅笍 姣忚鐨� `paidAmount` 鏄�**鍚堜綔鍟嗙骇鍚堣**锛堝悓涓�鍚堜綔鍟嗘墍鏈夋槑缁嗚閮戒竴鏍凤級锛岃 2.2銆�
+
+### 6.3 鏄庣粏琛屻�屾柊澧炰粯娆俱��
+
+鏄庣粏**姣忎竴鏉�**閮藉彲浠ュ姞銆屾柊澧炰粯娆俱�嶆寜閽紝璋冪敤**鐜版湁鐨�**寰�鏉ユ祦姘存帴鍙o紙鏃犻渶鏂板鍚庣鎺ュ彛锛夛細
+
+`POST /paymentRecord/add`锛宍Content-Type: application/json`
+
+```json
+{
+  "partyType": 3,
+  "partyId": 1,
+  "direction": 1,
+  "amount": 1000.00,
+  "recordDate": "2026-09-20",
+  "paymentMethod": "鐢垫眹",
+  "remark": "绗竴绗斿澶栨眹娆�"
+}
+```
+
+| 鍙傛暟 | 鍙栧�� |
+| --- | --- |
+| `partyType` | 鍥哄畾 `3`锛堝悎浣滃晢锛� |
+| `partyId` | 鏄庣粏琛岀殑 `partnerId`锛�**涓嶆槸** `outsourcingOrderId`锛� |
+| `direction` | `1` 浠樻锛堝噺灏戝簲浠橈級 / `2` 鍥炴 |
+| `amount` | 閲戦 |
+
+**娉ㄦ剰**锛�
+
+- 鍚庣鏂板浜� `partyType` 鐧藉悕鍗曟牎楠岋紝鍙厑璁� `1` / `2` / `3`锛屼紶鍏跺畠鍊艰繑鍥炪�屽線鏉ユ柟绫诲瀷涓嶅悎娉曘�嶃��
+- 鏂板鍚�**鍚堜綔鍟嗗線鏉ュ垪琛ㄧ殑 `paidAmount` 浼氬鍔犮�乣payableAmount` 浼氬噺灏�**锛涚敱浜� `payment_record` 娌℃湁璁㈠崟缁村害锛屾槑缁嗛噷**鎵�鏈夎**鐨� `paidAmount` 閮戒細鍙樸��
+- 鈿狅笍 鐜版湁 `/paymentRecord/delete` 鎺ュ彛**娌℃湁褰掑睘鏍¢獙**锛屼换浣曠櫥褰曚汉閮借兘鍒犱换鎰忔祦姘达紙鍚悎浣滃晢浠樻锛夈�傝繖鏄棦鏈夐闄╋紝鏈鏈敼锛屽墠绔瑕佸仛鍒犻櫎鎸夐挳璇峰姞浜屾纭銆�
+- 鏌ヨ/淇敼/鍒犻櫎娴佹按娌跨敤鐜版湁鎺ュ彛锛歚GET /paymentRecord/listPage`銆乣POST /paymentRecord/update`銆乣DELETE /paymentRecord/delete`銆�
+
+---
+
+## 7. 甯歌閿欒杩斿洖
+
+缁熶竴涓� `{ "code": 500, "msg": "<閿欒淇℃伅>" }`锛屽墠绔洿鎺� toast `msg`銆�
+
+| msg | 瑙﹀彂鍦烘櫙 |
+| --- | --- |
+| `璇峰~鍐欏悎浣滃晢鍚嶇О` | 鏂板鍚堜綔鍟嗘椂 `partnerName` 涓虹┖ |
+| `鍚堜綔鍟嗗凡瀛樺湪` | 鍚岀鎴蜂笅 `partnerName` 閲嶅锛堟柊澧�/淇敼锛� |
+| `鍚堜綔鍟唅d涓嶈兘涓虹┖` | 淇敼鏈紶 `id` |
+| `鍚堜綔鍟嗕笉瀛樺湪` | 璇︽儏/淇敼浼犲叆鐨� id 涓嶅瓨鍦� |
+| `鍚堜綔鍟嗕笉瀛樺湪锛岃閲嶆柊閫夋嫨` | 鏂板/淇敼濮斿璁㈠崟鏃� `partnerId` 鏌ヤ笉鍒� |
+| `鍚堜綔鍟嗗凡瀛樺湪濮斿璁㈠崟锛屼笉鍏佽鍒犻櫎` | 鍒犻櫎鍚堜綔鍟嗘椂鍏跺悕涓嬭繕鏈夊澶栬鍗� |
+| `鍚堜綔鍟嗗凡瀛樺湪寰�鏉ユ敹浠樻璁板綍锛屼笉鍏佽鍒犻櫎` | 鍒犻櫎鍚堜綔鍟嗘椂鍏跺悕涓嬭繕鏈変粯娆炬祦姘� |
+| `璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹甡 | 鍒犻櫎鍚堜綔鍟嗘椂璇锋眰浣撲负绌� |
+| `濮斿璁㈠崟涓嶅瓨鍦╜ | 璇︽儏/缂栬緫/鐘舵�佹祦杞紶鍏ョ殑 id 涓嶅瓨鍦� |
+| `濮斿璁㈠崟id涓嶈兘涓虹┖` | 淇敼/鐘舵�佹祦杞湭浼� id |
+| `浠呰崏绋跨姸鎬佺殑濮斿璁㈠崟鍏佽淇敼` | 瀵归潪鑽夌鍗曡皟鐢ㄧ紪杈� |
+| `浠呰崏绋跨姸鎬佺殑濮斿璁㈠崟鍏佽鍒犻櫎锛�<鍗曞彿>` | 瀵归潪鑽夌鍗曡皟鐢ㄥ垹闄わ紝浼氬洖鏄惧叿浣撳崟鍙� |
+| `浠呰崏绋跨姸鎬佺殑濮斿璁㈠崟鍏佽鎻愪氦` | 瀵归潪鑽夌鍗曡皟鐢ㄦ彁浜� |
+| `璇峰厛閫夋嫨鏈夋晥鍚堜綔鍟嗗啀鎻愪氦` | 鎻愪氦鏃� `partnerId` 涓虹┖鎴栨煡涓嶅埌 |
+| `濮斿璁㈠崟娌℃湁浜у搧鏄庣粏锛屾棤娉曟彁浜 | 鎻愪氦鏃舵棤鏄庣粏琛� |
+| `濮斿璁㈠崟閲戦蹇呴』澶т簬 0 鎵嶈兘鎻愪氦` | 鎻愪氦鏃堕噾棰濅负 0 |
+| `璇ュ澶栬鍗曞凡鎻愪氦锛岃鍕块噸澶嶆搷浣渀 | 骞跺彂/閲嶅鐐瑰嚮鎻愪氦 |
+| `鎶ュ簾鍝侀�夋枡琛岀己灏戜骇鍝佽鏍兼垨鎵瑰彿锛屾棤娉曟彁浜 | 閫夋枡琛岀殑 `productModelId` 鎴� `batchNo` 涓虹┖ |
+| `鎶ュ簾鍝侀�夋枡琛屾暟閲忓繀椤诲ぇ浜� 0` | 閫夋枡琛屾暟閲忎负 0 鎴栬礋鏁� |
+| `鎶ュ簾鍝併�孹銆嶆壒鍙枫�孻銆嶇敵璇锋暟閲忚秴杩囧彲鐢ㄥ簱瀛橈紝褰撳墠鍙敤搴撳瓨涓猴細Z` | 鎻愪氦鏃惰鎵规鍙敤閲忎笉澶燂紙瀹炴椂鍙e緞锛屽惈鍏跺畠涓氬姟鐨勫緟瀹℃壒鍗犵敤锛� |
+| `浠呭凡纭鐘舵�佺殑濮斿璁㈠崟鍏佽瀹屾垚` | 瀵归潪宸茬‘璁ゅ崟璋冪敤瀹屾垚 |
+| `鎶ュ簾鍝佸嚭搴撴湭瀹℃壒閫氳繃锛岃鍏堝埌鍑哄簱绠$悊瀹℃壒鍑哄簱鎵规锛欳Kxxxx` | 瀹屾垚鏃舵姤搴熷搧鍑哄簱杩樻病瀹℃壒閫氳繃 |
+| `鎶ュ簾鍝侀�夋枡缂哄皯鍑哄簱璁板綍锛岃鍏堜綔搴熷悗閲嶆柊鎻愪氦` | 宸茬‘璁ゅ崟鐨勫嚭搴撹褰曚笉鍏紙鏁版嵁寮傚父锛� |
+| `璇ュ澶栬鍗曞凡瀹屾垚鎴栧凡浣滃簾锛屼笉鍏佽閲嶅浣滃簾` | 瀵圭粓鎬佸崟璋冪敤浣滃簾 |
+| `搴撳瓨璁板綍涓病鏈夊搴旂殑浜у搧,鏃犳硶褰掕繕,鍑哄簱鎵规:CKxxxx` | 浣滃簾褰掕繕鏃跺簱瀛樿宸蹭笉瀛樺湪锛堟暟鎹紓甯革級 |
+| `鍑哄簱璁板綍绫诲瀷寮傚父锛屾棤娉曞綊杩樺簱瀛�,鍑哄簱鎵规:CKxxxx` | 浣滃簾褰掕繕鏃跺嚭搴撹褰曠殑 `type` 鏃笉鏄� `0` 涔熶笉鏄� `1`锛堟暟鎹紓甯革級 |
+| `寰�鏉ユ柟绫诲瀷涓嶅悎娉昤 | `/paymentRecord/add`銆乣/update` 鐨� `partyType` 涓嶅湪 1/2/3 鍐� |
+| `璇烽�夋嫨瑕佸垹闄ょ殑濮斿璁㈠崟` | 鍒犻櫎鎺ュ彛璇锋眰浣撲负绌烘暟缁� |
+
+---
+
+## 8. 鑱旇皟妫�鏌ユ竻鍗�
+
+**鐜鍓嶆彁**
+
+1. 涓変釜 SQL 鑴氭湰宸叉墽琛岋紱`payment_record` 鐨� `party_type` 娉ㄩ噴宸叉敼涓轰笁鍊硷紱**骞跺凡鎵ц 1.1 鑺傜殑 `outsourcing_order_product` 鍒楁敼鍚� ALTER**銆傚簱瀛樿仈鍔ㄦ湰韬鐢ㄧ幇鏈� `stock_out_record` / `stock_inventory`锛屼笉闇�瑕佹柊澧炶〃銆�
+2. 宸插噯澶囨姤搴熷搧鍩虹鏁版嵁锛歚product` 鍦ㄣ�屽嵄搴熷缃鐢熷嵄搴熴�嶄笅鏈変竴涓骇鍝侊紝`product_model` 鏈夊叾瑙勬牸锛�**`stock_inventory`锛堝悎鏍煎簱瀛橈級鏈夊搴� `product_model_id` 鐨勬壒娆�**銆�
+3. 鈿狅笍 **鏁版嵁鏉冮檺鎻愰啋**锛歚outsourcing_partner`銆乣outsourcing_order` 鏄垪琛ㄦ煡璇㈢殑椤跺眰涓昏〃锛�**浼氬彈鏁版嵁鏉冮檺杩囨护**锛坄create_user` / `dept_id`锛夛紝鍜� `supplier_manage`銆乣purchase_ledger` 鐜扮姸涓�鑷淬��
+   鈫� 鑱旇皟鐢� `dataScope=1` 鐨勮处鍙锋垨 admin 楠岃瘉锛屽惁鍒欏鏄撹鍒ゃ�屽姛鑳藉潖浜嗐�嶏紙琛ㄧ幇涓猴細鍒汉寤虹殑鏁版嵁鑷繁鐪嬩笉鍒帮級銆�
+   鈫� 鍚堜綔鍟嗗線鏉ュ垪琛ㄤ笌鏄庣粏**涓や晶鍙e緞宸插榻�**锛堜笉鏂藉姞琛岀骇鏁版嵁鏉冮檺锛夛紝涓嶄細鍑虹幇銆屽垪琛ㄦ湁搴斾粯銆佺偣杩涙槑缁� 0 琛屻�嶃��
+4. 鈿狅笍 **鍑哄簱瀹℃壒鐨勮处鍙疯姹�**锛歚stock_out_record` 浠嶅湪鏁版嵁鏉冮檺绠℃帶鍐咃紝**鍑哄簱绠$悊椤电殑鍒楄〃鍜屽鎵瑰繀椤荤敤涓庢彁浜や汉鍚屾暟鎹寖鍥寸殑璐﹀彿锛屾垨 `dataScope=1`/admin**锛屽惁鍒欐姤銆屽嚭搴撹褰曚笉瀛樺湪銆嶃�傝瑙� 2.4銆�
+
+**鍔熻兘妫�鏌�**
+
+5. 鑿滃崟鍑虹幇銆屽澶栫鐞� 鈫� 鍚堜綔鍟嗘。妗� / 濮斿璁㈠崟 / 鍚堜綔鍟嗗線鏉ャ�嶏紝鐐瑰嚮鏃� 404銆�
+6. `/outsourcing/partner/add` 鍙紶 `partnerName`锛氳兘鏂板鎴愬姛锛岃繑鍥� id锛宍/listPage` 鑳界湅鍒般��
+7. 鍚堜綔鍟嗛噸鍚嶆柊澧烇細杩斿洖銆屽悎浣滃晢宸插瓨鍦ㄣ�嶃��
+8. 闄勪欢锛氫笂浼犳枃浠� 鈫� 鎶婅繑鍥炵殑 blob `id` 鏀捐繘 `storageBlobDTOS` 鎻愪氦 鈫� `/outsourcing/partner/{id}` 鐨� `storageBlobVOS` 鑳藉洖鏄撅紱鍐嶇紪杈戞彁浜ょ┖鏁扮粍 鈫� 闄勪欢琚竻绌猴紙绗﹀悎 4.4 鐨勫叏閲忚鐩栬涔夛級銆�
+   鏁版嵁搴撴牳瀵癸細`select a.*, b.original_filename from storage_attachment a join storage_blob b on a.storage_blob_id = b.id where a.record_type = 'outsourcing_partner' and a.record_id = ?`
+9. 鍚堜綔鍟嗗鍑恒�佷笅杞芥ā鏉裤�佸鍏ヤ笁涓帴鍙h兘姝e父璧伴�氥��
+10. `/outsourcing/partner/getOptions` 杩斿洖 `id` + `partnerName`銆�
+11. 鍒犻櫎鏈夎鍗曠殑鍚堜綔鍟嗭細杩斿洖銆屽悎浣滃晢宸插瓨鍦ㄥ澶栬鍗曪紝涓嶅厑璁稿垹闄ゃ�嶃��
+12. `scrapStockPage?pageNum=1&pageSize=10`锛�**鍙繑鍥炪�屽嵄搴熷缃鐢熷嵄搴熴�嶅ぇ绫讳笅鐨勬壒娆�**锛屽甫 `batchNo`銆乣unLockedQuantity`銆傛牳瀵癸細
+    `select si.id, si.product_model_id, si.batch_no, si.qualitity from stock_inventory si join product_model pm on si.product_model_id = pm.id` 鈥斺�� 杩斿洖鐨勮鏁板簲绛変簬銆岃澶х被鍙婂叾瀛愯妭鐐逛笅鐨勮鏍笺�嶅搴旂殑搴撳瓨琛屾暟锛屾瘮鍏ㄨ〃灏戯紙寮�鍙戝簱 10 琛屽簲鍙墿 1 琛岋級銆�
+    **鍥炲綊**锛歚/stockInventory/pagestockInventory` 杩斿洖鍊间笌鏀瑰姩鍓嶄竴鑷达紙涓嶅甫 `batchNo`锛夛紱`/stockUninventory/pagestockUninventory`锛堜笉鍚堟牸搴撳瓨锛夊畬鍏ㄤ笉鍙楀奖鍝嶃��
+13. `/outsourcing/order/add` 寤鸿崏绋匡紙鍚姤搴熷搧閫夋枡琛岋級锛氭垚鍔燂紝`orderNo` 褰㈠ `WWDD20260920001`锛宍contractAmount` = 鏄庣粏鍚◣鎬讳环鍚堣銆�
+14. 鑽夌鍗� `/edit`锛氭槑缁嗗叏閲忔浛鎹㈢敓鏁堬紙鏀规暟閲忋�佸鍒犺閮芥纭級銆�
+15. 鑽夌鍗� `/submit`锛氱姸鎬佸彉 `1`锛宍confirmTime` 鏈夊�硷紱**鍐嶇偣涓�娆�**杩斿洖銆岃濮斿璁㈠崟宸叉彁浜わ紝璇峰嬁閲嶅鎿嶄綔銆嶃��
+16. 闈炶崏绋垮崟 `/edit`銆乣/delete`锛氳鎷掔粷骞剁粰鍑哄搴旀彁绀恒��
+17. `/outsourcing/report/partnerTransactions`锛歚contractAmounts` = 璇ュ悎浣滃晢 `status in (1,2)` 璁㈠崟閲戦鍚堣銆�
+    **閲嶇偣**锛氬缓涓�寮犺崏绋裤�佷竴寮犱綔搴熷崟锛岀‘璁ゅ畠浠殑閲戦**娌℃湁**琚畻杩� `contractAmounts`銆�
+18. `/paymentRecord/add` 鎻掍袱绗� `{partyType:3, partyId, direction:1, amount:1000}`锛氬線鏉ュ垪琛� `paidAmount` 鍙� 2000銆乣payableAmount` 鐩稿簲鍑忓皯銆�
+19. `/paymentRecord/add` 浼� `partyType=9`锛氳繑鍥炪�屽線鏉ユ柟绫诲瀷涓嶅悎娉曘�嶃��
+20. `/outsourcing/report/partnerTransactionsDetails?partnerId=xxx`锛氭槑缁嗚鏁颁笌璁㈠崟鏁颁竴鑷达紝`paidAmount` 姣忚鐩稿悓锛堢鍚� 2.2 鍙e緞锛夈��
+
+**鎶ュ簾鍝佸簱瀛樿仈鍔紙閲嶇偣锛�**
+
+21. **寤鸿崏绋夸笉鍗犲簱瀛�**锛氬缓涓�寮犲甫鎶ュ簾鍝侀�夋枡琛岀殑鑽夌锛屾瘮瀵� `stock_inventory` 鐨� `qualitity` **鍜�** `stock_out_record` 璁板綍鏁� 鈥斺�� 閮戒笉搴旀湁鍙樺寲銆�
+22. **鎻愪氦鍗冲崰鐢�**锛歚/submit` 鍚庡簲鍑虹幇涓�鏉� `record_type='30' and type='0' and approval_status=0` 鐨勫嚭搴撹褰曪紝`record_id` = 璇ラ�夋枡鏄庣粏琛� id锛宍stock_out_num` = 鏄庣粏鏁伴噺锛宍batch_no` = 閫変腑鐨勬壒鍙枫��
+    姝ゆ椂 `qualitity` **浠嶆湭鍙�**锛屼絾 `scrapStockPage` 杩斿洖鐨勫彲鐢ㄩ噺锛坄unLockedQuantity`锛�**涓嶅寘鍚�**寰呭鎵瑰嚭搴撻噺锛岃鏀圭敤浠ヤ笅 SQL 鏍稿鐪熷疄鍙敤閲忥細
+    `select si.qualitity - ifnull(si.locked_quantity,0) - (select ifnull(sum(sor.stock_out_num),0) from stock_out_record sor where sor.product_model_id=si.product_model_id and sor.batch_no<=>si.batch_no and sor.type='0' and sor.approval_status=0) as available from stock_inventory si where si.id=?`
+23. **闃茶秴閫�**锛氬鍚屼竴鎵规鍐嶅缓涓�寮犲崟銆佹暟閲忓~銆岃秴杩囩湡瀹炲彲鐢ㄩ噺銆嶏紝鎻愪氦搴旇繑鍥炪�屾姤搴熷搧銆孹銆嶆壒鍙枫�孻銆嶇敵璇锋暟閲忚秴杩囧彲鐢ㄥ簱瀛橈紝褰撳墠鍙敤搴撳瓨涓猴細Z銆嶃��
+24. **瀹屾垚鍓嶅繀椤诲嚭搴撳鎵�**锛氬鏈鎵瑰嚭搴撶殑宸茬‘璁ゅ崟璋� `/complete`锛岃繑鍥炪�屾姤搴熷搧鍑哄簱鏈鎵归�氳繃锛岃鍏堝埌鍑哄簱绠$悊瀹℃壒鍑哄簱鎵规锛欳Kxxxx銆嶃��
+25. **鍑哄簱瀹℃壒鎵嶅疄鎵� + 鑷姩瀹屾垚**锛氬埌鍑哄簱绠$悊椤垫寜绫诲瀷銆屽澶�-鎶ュ簾鍝佸嚭搴撱�嶆壘鍒拌璁板綍 鈫� `POST /stockOutRecord/approve` `{"ids":[id],"approvalStatus":1}` 鈫� 姝ゆ椂 `stock_inventory.qualitity` **鎵嶅噺灏�**锛堝噺灏戦噺 = 鍑哄簱鏁伴噺锛夛紝涓�**璇ュ澶栬鍗曠姸鎬佸簲宸茶嚜鍔ㄥ彉鎴� `2`銆屽凡瀹屾垚銆�**锛堟煡 `select id, status from outsourcing_order where id=?`锛夈��
+26. **鎵嬪伐瀹屾垚浠嶅彲鐢紙鍏滃簳锛�**锛氳鍗曞凡鏄� `2` 鏃跺啀璋� `/complete`锛岃繑鍥炪�屼粎宸茬‘璁ょ姸鎬佺殑濮斿璁㈠崟鍏佽瀹屾垚銆嶃��
+27. **澶氳璁㈠崟瑕佺瓑鏈�鍚庝竴鏉�**锛氫竴寮犺鍗曟寕 2 鏉℃姤搴熷搧閫夋枡琛岋紝鍑哄簱璁板綍 2 鏉★紝鍏堝鎵� 1 鏉� 鈫� 璁㈠崟浠嶆槸 `1`锛涘鎵圭 2 鏉� 鈫� 璁㈠崟鍙� `2`銆�
+28. **椹冲洖涓嶅洖閫�**锛氬彟涓�寮犲凡纭鍗曠殑鍑哄簱璁板綍瀹℃壒椹冲洖锛坄approvalStatus=2`锛夆啋 濮斿璁㈠崟浠嶄负 `1`锛屾鏃� `/complete` 浼氳鎷掞紙鍑哄簱鏈叏閮ㄩ�氳繃锛夈��
+29. **浣滃簾閲婃斁锛堟湭瀹℃壒锛�**锛氬彟涓�寮犲凡纭鍗曪紙鍑哄簱浠嶅緟瀹℃壒锛夎皟 `/cancel` 鈫� 鍑哄簱璁板綍琚垹闄ゃ�乣qualitity` 涓嶅彉銆佸崰鐢ㄩ噴鏀撅紙鐪熷疄鍙敤閲忔仮澶嶏級銆�
+30. **浣滃簾褰掕繕锛堥儴鍒嗗凡瀹℃壒锛�**锛氳鍗曟寕 2 鏉¢�夋枡琛岋紝鍑哄簱璁板綍瀹℃壒閫氳繃鍏朵腑 1 鏉★紙姝ゆ椂璁㈠崟杩樻槸 `1`锛屽洜涓烘湭鍏ㄩ儴閫氳繃锛夆啋 璋� `/cancel` 鈫� 宸查�氳繃鐨勯偅鏉�**鎸夋壒娆″綊杩� `qualitity`**锛屽嚭搴撹褰曡鍒犻櫎銆�
+31. **宸茶嚜鍔ㄥ畬鎴愮殑鍗曚笉鑳藉啀浣滃簾**锛氬嚭搴�**鍏ㄩ儴**閫氳繃鍚庤鍗曞凡鏄� `2`锛宍/cancel` 杩斿洖銆岃濮斿璁㈠崟宸插畬鎴愭垨宸蹭綔搴燂紝涓嶅厑璁搁噸澶嶄綔搴熴�嶃�傝鎾ら攢鍙兘鍏堝埌鍑哄簱绠$悊銆屽弽瀹°�嶅苟閲嶆柊澶勭悊 鈥斺�� **璁㈠崟鐘舵�佷笉浼氳嚜鍔ㄥ洖閫�鍒般�屽凡纭銆�**銆傝繖鏄嚜鍔ㄥ畬鎴愬甫鏉ョ殑琛屼负鍙樺寲锛屽墠绔笉瑕佺粰鐘舵�� `2` 鐨勫崟鏄剧ず銆屼綔搴熴�嶆寜閽��
+32. **鍥炲綊**锛歚locked_quantity` 鍏ㄧ▼涓嶅彉锛沗/stockInventory/pagestockInventory`銆乣/stockUninventory/pagestockUninventory` 杩斿洖鍊间笌鏀瑰姩鍓嶄竴鑷达紱涓嶅悎鏍煎簱瀛� `stock_uninventory` 鍏ㄧ▼涓嶅彈濮斿褰卞搷锛�**鍏跺畠涓氬姟锛堥攢鍞彂璐с�侀噰璐��璐с�佺敓浜ч鏂欙級鐨勫嚭搴撳鎵逛笉鍙楀奖鍝�**锛堣嚜鍔ㄥ畬鎴愬彧鍦� `record_type=30` 鏃惰Е鍙戯級銆�
+29. **鍥炲綊**锛歚locked_quantity` 鍏ㄧ▼涓嶅彉锛沗/stockInventory/pagestockInventory`銆乣/stockUninventory/pagestockUninventory` 杩斿洖鍊间笌鏀瑰姩鍓嶄竴鑷达紱涓嶅悎鏍煎簱瀛� `stock_uninventory` 鍏ㄧ▼涓嶅彈濮斿褰卞搷銆�
+
+---
+
+## 9. 鍓嶇鏀归�犲缓璁�
+
+1. 鏂板缓涓変釜椤甸潰缁勪欢锛堣矾寰勮绗� 1 鑺傝〃鏍硷級锛屽悎浣滃晢妗f椤甸潰鍙洿鎺ュ弬鐓с�屼緵搴斿晢妗f銆嶉〉闈㈢粨鏋勶紝**澶氫竴涓檮浠朵笂浼犲尯鍩�**銆�
+2. 濮斿璁㈠崟鏂板/缂栬緫琛ㄥ崟寤鸿鍙傜収銆岄噰璐鍗曘�嶉〉闈紝鏄庣粏琛屽鍔犮�岄�夋姤搴熷搧銆嶆寜閽紝寮圭獥璋冪敤 `scrapStockPage`锛岄�変腑鍚庢妸 `stockInventoryId`銆乣batchNo`銆乣availableQuantity`銆乣productModelId`銆乣unit` 鍥炲~鍒版槑缁嗚銆�
+3. 娉ㄦ剰杩斿洖缁撴瀯涓嶇粺涓�锛氬悎浣滃晢 `listPage` / 閫夋枡 / 寰�鏉ヤ笁涓帴鍙f槸 `data.records`锛屽澶栬鍗� `list` 鏄《灞� `rows`锛圧uoYi `TableDataInfo`锛夈��
+4. 鐘舵�佸垪鐢� tag 鍖哄垎棰滆壊锛宍0` 鑽夌锛堢伆锛�/ `1` 宸茬‘璁わ紙钃濓級/ `2` 宸插畬鎴愶紙缁匡級/ `3` 宸蹭綔搴燂紙绾級銆�
+5. 鎸夐挳鏄鹃殣鎸� 2.1 鐨勭姸鎬佽〃鎺у埗锛宍submit` / `complete` / `cancel` 寤鸿鍔犱簩娆$‘璁ゃ��
+   鈿狅笍 **鍑哄簱瀹℃壒閫氳繃浼氳嚜鍔ㄦ妸璁㈠崟鎺ㄥ埌銆屽凡瀹屾垚銆�**锛屾墍浠ュ鎵瑰畬鍒锋柊鍒楄〃鏃剁姸鎬佸彲鑳藉凡缁忓彉浜嗭細
+   - 銆屽畬鎴愩�嶆寜閽細鍙湪鐘舵�� `1` 鏃舵樉绀猴紙浣滀负鍏滃簳锛夛紝鐘舵�� `2` 鏃堕殣钘忋��
+   - 銆屼綔搴熴�嶆寜閽細鐘舵�� `2` 鏃朵笉鑳芥樉绀猴紙鍚庣浼氭嫆缁濓級銆�
+6. 鍚堜綔鍟嗗線鏉ョ殑鏄庣粏寮圭獥閲岋紝姣忚鏀俱�屾柊澧炰粯娆俱�嶆寜閽紝寮硅〃鍗曟彁浜ゅ埌 `/paymentRecord/add`锛宍partyType` 鍥哄畾 `3`銆乣partyId` 鍙栧垪琛ㄨ鐨� `partnerId`锛�**涓嶆槸**璁㈠崟 id锛夛紱鎻愪氦鎴愬姛鍚庡埛鏂板線鏉ユ眹鎬诲拰鏄庣粏銆�
+7. 缂栬緫鍚堜綔鍟嗘椂锛屽姟蹇呮妸璇︽儏鎺ュ彛杩斿洖鐨� `storageBlobVOS` 杞垚 `{ id }` 瀛樿繘 `storageBlobDTOS` 鍐嶆彁浜わ紝鍚﹀垯浼氭竻绌洪檮浠躲��
+8. **鍑哄簱绠$悊椤电殑绫诲瀷鏄犲皠**锛歚record_type=30`銆屽澶�-鎶ュ簾鍝佸嚭搴撱�嶅凡鍔犺繘鍚庣鍚堟牸鍑哄簱鏋氫妇锛宍/basic/enum/stockRecordType` 浼氬甫鍑烘潵銆傝嫢鍑哄簱绠$悊椤电殑绫诲瀷鍒�/涓嬫媺鏄�**鍓嶇纭紪鐮佹槧灏勮〃**锛岄渶瑕佽ˉ `30 鈫� 濮斿-鎶ュ簾鍝佸嚭搴揱锛涘惁鍒欏澶栦骇鐢熺殑鍑哄簱璁板綍浼氭樉绀烘垚绌烘垨閿欏�笺��
+9. 濮斿璁㈠崟鐨勩�岀姸鎬併�嶄笌銆屽嚭搴撴槸鍚﹀凡瀹℃壒銆嶆槸涓や欢浜嬶細宸茬‘璁ょ殑鍗曠偣銆屽畬鎴愩�嶅彲鑳藉洜涓哄嚭搴撴病瀹℃壒鑰岃鎷掋�傚缓璁湪璁㈠崟璇︽儏鎴栧垪琛ㄤ笂鎶婂叧鑱斿嚭搴撹褰曠殑鐘舵�佷竴骞跺睍绀猴紙`GET /stockOutRecord/listPage?recordType=30` 鍙寜绫诲瀷杩囨护锛夛紝鍚﹀垯鐢ㄦ埛浼氫互涓烘槸绯荤粺鎶ラ敊銆�
+10. 鎻愪氦濮斿璁㈠崟鍓嶅缓璁厛鎻愮ず銆屾彁浜ゅ悗灏嗗崰鐢ㄦ墍閫夋姤搴熷搧鎵规鐨勫彲鐢ㄩ噺锛岄渶鍒板嚭搴撶鐞嗗鎵归�氳繃鍚庢墠鑳藉畬鎴愯鍗曘�嶏紝閬垮厤鐢ㄦ埛涓嶇悊瑙d负浠�涔堟彁浜ゅ悗搴撳瓨鍙敤閲忓彉浜嗐��
diff --git "a/doc/20260920_\346\262\271\345\223\201\345\207\272\345\272\223\350\201\224\345\212\250\345\217\221\350\264\247\345\217\260\350\264\246\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260920_\346\262\271\345\223\201\345\207\272\345\272\223\350\201\224\345\212\250\345\217\221\350\264\247\345\217\260\350\264\246\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..f8f8a4d
--- /dev/null
+++ "b/doc/20260920_\346\262\271\345\223\201\345\207\272\345\272\223\350\201\224\345\212\250\345\217\221\350\264\247\345\217\260\350\264\246\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,159 @@
+# 娌瑰搧鍑哄簱鑱斿姩鍙戣揣鍙拌处 鍓嶇鑱旇皟鏂囨。
+
+鏇存柊鏃堕棿锛�2026-09-20
+閫傜敤鐗堟湰锛氭湰娆″悗绔彉鏇村悗
+闇�姹傛潵婧愶細鑱旇皟鍙嶉銆屽嚭搴撲簡涓�鏉★紙鍑哄簱绫诲埆=閿�鍞級锛屼絾 `/shippingInfo/listPage` 鍙戣揣鍙拌处娌℃湁鏁版嵁銆�
+
+---
+
+## 1. 鑳屾櫙锛氫负浠�涔堜箣鍓嶆槸绌虹殑
+
+**涓嶆槸鎺ュ彛鍧忎簡锛屾槸銆屽嚭搴撳彴璐︺�嶅拰銆屽彂璐у彴璐︺�嶆湰鏉ユ槸涓ゅ涓滆タ銆�**
+
+- 鍙戣揣鍙拌处锛堣彍鍗� 2230锛夎鐨勬槸 `shipping_info` 琛紝璇ヨ〃鍦ㄦ敼鍓嶆槸 **0 琛�**銆�
+- `shipping_info` 鍙湁涓や釜鍐欏叆鏂癸細`POST /shippingInfo/add`锛堣蛋鍙戣揣瀹℃壒锛夈�佷互鍙� `POST /stockmanagement/stockout` 涓� `type=2`锛堥攢鍞嚭搴擄級銆�
+- 娌瑰搧鍑哄簱琛ㄥ崟璧扮殑鏄� `POST /stockInventory/addOilOutRecordOnly`锛屽畠鐨勮涔夋槸**鍙惤涓�鏉� `stock_out_record`锛屼笉鍔ㄥ簱瀛樸�佷笉寤哄彂璐у崟**鈥斺�旀墍浠ュ彂璐у彴璐︽案杩滅湅涓嶅埌瀹冦��
+
+缁忎笌闇�姹傛柟纭锛岄噰鐢ㄤ袱涓彛寰勶紝鏈宸插疄鐜帮細
+
+| 鍙e緞 | 瀹炵幇 |
+| --- | --- |
+| 鍙戣揣鍙拌处**鐩存帴璇�** `stock_out_record` | `/shippingInfo/listPage` 鏀逛负 `shipping_info UNION ALL stock_out_record` |
+| 閿�鍞被鍑哄簱**瑕佹墸搴撳瓨** | 鍑哄簱瀹℃壒閫氳繃鏃舵寜銆屼骇鍝佽鏍� + 鎵规銆嶆墸鍑� `stock_inventory` |
+
+**鏈娌℃湁浠讳綍 DDL**锛氱敤鍒扮殑 `stock_out_record.batch_no`銆乣out_category`銆乣tank_no`銆乣truck_plate_no` 绛夊垪閮藉凡瀛樺湪銆�
+
+---
+
+## 2. 鍙戣揣鍙拌处鍒楄〃濂戠害锛�**蹇呯湅 `source`**锛�
+
+`GET /shippingInfo/listPage` 杩斿洖 `R.data.records`锛�**鐜板湪鍖呭惈涓ょ被鏉ユ簮鐨勮**锛岄潬鏂板瀛楁 `source` 鍖哄垎锛�
+
+| `source` | 鏉ユ簮琛� | 璇存槑 |
+| --- | --- | --- |
+| `shipping` | `shipping_info` | 鍘熸湁鐨勫彂璐у崟锛堥攢鍞彴璐﹀彂璐� / 閲囪喘璁板綍鍑哄簱鑷姩鐢熸垚锛� |
+| `oilOut` | `stock_out_record` | 鏈鏂板锛歚out_category='閿�鍞�'` 涓� `approval_status=1` 鐨勬补鍝佸嚭搴� |
+
+### 2.1 鈿狅笍 `id` 浼氭挒鍙凤紝缁濅笉鑳芥寜 `id` 鐩存帴鎿嶄綔
+
+涓ょ被琛ㄧ殑 `id` 鏄�**鍚勮嚜鐨勫簭鍒楋紝浼氭挒鍙�**銆傘�屽彂璐� / 鍒犻櫎 / 璇︽儏 / 閫�璐с�嶈繖浜涙帴鍙h儗鍚庨兘鏄� `shipping_info`锛�
+
+- `POST /shippingInfo/deductStock`
+- `DELETE /shippingInfo/delete`
+- `POST /shippingInfo/update`
+- 閫�璐х浉鍏虫帴鍙�
+
+**瀵� `source='oilOut'` 鐨勮璋冭繖浜涙帴鍙d細鎿嶄綔鍒板彟涓�鏉″畬鍏ㄦ棤鍏崇殑鍙戣揣鍗曘��** 鍓嶇蹇呴』鎸� `source` 鍒嗘敮锛�
+
+- `source='shipping'` 鈫� 鎸夐挳鐓ф棫
+- `source='oilOut'` 鈫� **闅愯棌**銆屽彂璐� / 鍒犻櫎 / 閫�璐с�嶆寜閽紱瑕佹搷浣滆璺炽�屽嚭搴撳彴璐︺�嶉〉锛岀敤鏂板瀛楁 `stockOutRecordId` 瀹氫綅
+
+### 2.2 娌瑰搧鍑哄簱琛岀殑瀛楁鏄犲皠
+
+| 鍙戣揣鍙拌处鍒� | `oilOut` 琛屽彇鍊� |
+| --- | --- |
+| `stockOutRecordId` | `stock_out_record.id`锛�**鐢ㄨ繖涓紝涓嶈鐢� `id`**锛� |
+| `shippingNo` | `outbound_batches`锛堝嵆 CK 鍑哄簱鍗曞彿锛� |
+| `status` | 鍥哄畾 `宸插彂璐 |
+| `shippingDate` | `out_date` |
+| `shippingCarNumber` | `truck_plate_no` |
+| `customerName` | `customer_name` |
+| `specificationModel` / `unit` / `productName` | 鐢� `product_model_id` 鍏宠仈 `product_model` 鈫� `product` |
+| `totalQuantity` | `stock_out_num` |
+| `outboundBatches` | `outbound_batches`锛圕K 鍑哄簱鍗曞彿锛�**涓嶆槸鎵瑰彿**锛� |
+| `outBatchNo` | `batch_no`锛堟壒鍙凤紱`source='shipping'` 琛屾亽涓� `null`锛� |
+| `batchNoList` | 鐢辨湰琛屽瓧娈垫嫾鎴愮殑鍗曞厓绱犳暟缁勶紝渚涜鎯呭脊妗嗐�屾壒鍙枫�嶆槑缁嗚〃鐢紙浠� `oilOut` 琛岋級 |
+| `outCategory` / `tankNo` / `oilProduct` / `oilDepotName` / `driverName` / `contactPerson` / `contactPhone` | 鍚屽悕鍑哄簱渚у垪锛堟柊澧炲嚭鍙傦紝鍙敤浣滃睍绀猴級 |
+| `salesContractNo` / `expressNumber` / `expressCompany` / `salesLedgerId` | **鎭掍负绌�**锛堟补鍝佸嚭搴撴病鏈夊悎鍚屽彿/蹇�掑崟锛� |
+
+### 2.2.1 鎵瑰彿鎬庝箞鏄剧ず锛堣鎯呭脊妗嗭級
+
+娌瑰搧鍑哄簱鐨�**鎵瑰彿**锛堝舰濡� `20260917-CY001-001`锛夋鍓嶅悗绔竴涓瓧娈甸兘娌″悙锛屾湰娆¤ˉ涓婁簡锛�
+
+- 鏂板鍑哄弬 `outBatchNo` = `stock_out_record.batch_no`銆�**鍒拰 `outboundBatches` 鎼炴贩**锛屽悗鑰呮槸 CK 鍑哄簱鍗曞彿銆�
+- 鏂板鍑哄弬 `batchNoList`锛�**浠� `source='oilOut'` 琛屾湁鍊�**锛堜竴鏉″嚭搴撳搴斾竴涓壒鍙凤級锛岀粨鏋勶細
+
+```json
+"batchNoList": [
+  {
+    "batchNo": "20260917-CY001-001",
+    "productName": "鏌存补",
+    "specificationModel": "02#鏌存补",
+    "deliveryQuantity": 9.0000
+  }
+]
+```
+
+鍓嶇 `deliveryLedger/index.vue` 鐨勮鎯呭脊妗� `getDeliveryProductInfoList()` 鏈潵灏变細璇� `row.batchNoList`锛堝畠鏄厹搴曢摼 `batchNoDetailList 鈫� batchNoList 鈫� shippingBatchList 鈫� 鈥 鐨勭浜岄」锛夛紝**鎵�浠ヨ椤典笉鏀逛唬鐮佷篃鑳芥樉绀哄嚭鎵瑰彿**銆�
+
+> 鈿狅笍 **璇︽儏寮规鍙︽湁涓�涓繀椤讳慨鐨勫潙**锛歚openDetail()` 浼氱敤 `getDeliveryDetail(row.id)` 鍐嶈皟涓�娆� `/shippingInfo/getDateil/{id}`锛岃�岃鎺ュ彛鏌ョ殑鏄� `shipping_info` + `shipping_product_detail`銆傚 `source='oilOut'` 鐨勮浼犺繘鍘荤殑鏄�**鍑哄簱璁板綍 id**锛屼竴鏃︽挒涓婂悓鍙风殑 `shipping_info`锛屽氨浼�**鎶婂彟涓�鏉″彂璐у崟鐨勬槑缁嗘樉绀哄嚭鏉�**鈥斺�斿洜涓� `detailProductList` 鐨勪紭鍏堢骇楂樹簬 `row.batchNoList`銆�
+> **寤鸿**锛歚openDetail` 閲屾寜 `source` 鍒嗘敮锛宍oilOut` 琛�**涓嶈**鍐嶈皟 `getDateil`锛岀洿鎺ョ敤 `row.batchNoList`銆�
+
+鍙﹀锛�**涓诲垪琛ㄥ綋鍓嶆病鏈夈�屾壒鍙枫�嶅垪**锛堢幇鏈夊垪锛氶攢鍞鍗� / 鍙戣揣璁㈠崟鍙� / 椤圭洰鍚嶇О / 瀹㈡埛鍚嶇О / 浜у搧鍚嶇О / 瑙勬牸鍨嬪彿 / 鍙戣揣鏃堕棿 / 鍙戣揣鏁伴噺 / 鍙戣揣杞︾墝鍙� / 蹇�掑叕鍙� / 蹇�掑崟鍙� / 瀹℃牳鐘舵�� / 鍑哄簱鍗曞彿 / 閫�璐у崟 / 鎿嶄綔锛夈�傝嫢瑕佸湪涓诲垪琛ㄤ篃灞曠ず锛屽姞涓�鍒楃粦瀹� `outBatchNo` 鍗冲彲銆�
+
+### 2.3 绛涢�夎涓�
+
+| 鏌ヨ鍙傛暟 | `shipping` 琛� | `oilOut` 琛� |
+| --- | --- | --- |
+| `shippingNo` | `shipping_info.shipping_no` | `stock_out_record.outbound_batches` |
+| `shippingCarNumber` | `shipping_info.shipping_car_number` | `stock_out_record.truck_plate_no` |
+| `salesContractNo` | `sales_ledger.sales_contract_no` | **鎭掍笉鍖归厤**锛堟寜鍚堝悓鍙风瓫閫夋椂涓嶄細杩斿洖娌瑰搧琛岋紝杩欐槸鍒绘剰鐨勶級 |
+| `expressNumber` | `shipping_info.express_number` | **鎭掍笉鍖归厤** |
+
+---
+
+## 3. 娌瑰搧鍑哄簱琛ㄥ崟锛�**銆屽嚭搴撴壒娆°�嶅繀濉�**
+
+鏂板鏍¢獙锛歚out_category='閿�鍞�'` 鏃� `batchNo` 涓嶈兘涓虹┖锛屽惁鍒欐柊澧炴帴鍙g洿鎺ヨ繑鍥為敊璇� **銆岄攢鍞被鍑哄簱蹇呴』閫夋嫨鍑哄簱鎵规銆�**銆�
+
+> 娉ㄦ剰锛氳繖涓牎楠屽湪**鏂板鏃�**灏辩敓鏁堬紝涓嶆槸绛夊鎵广�傚墠绔鎶娿�屽嚭搴撴壒娆°�嶅湪鍑哄簱绫诲埆=閿�鍞椂鍋氭垚蹇呭~锛岄伩鍏嶇敤鎴锋彁浜ゅ悗鎵嶅彂鐜般��
+>
+> 鍑哄簱绫诲埆鏄�屼唬鍌ㄣ�嶃�屽瀛樸�嶆椂**涓嶉渶瑕�**鎵规锛屼篃涓嶆墸搴撳瓨銆�
+
+鎵规鍊欓�夊缓璁笌璇ヨ鏍肩殑鍏ュ簱鎵规涓�鑷达細`GET /stockInventory/getBatchNoQty?productModelId=xxx`銆�
+
+---
+
+## 4. 鎵e簱瀛樼殑鏃舵満
+
+| 鍔ㄤ綔 | `stock_inventory` 鍙樺姩 |
+| --- | --- |
+| 鏂板閿�鍞被娌瑰搧鍑哄簱 | 涓嶅彉锛堝彧鍦ㄥ緟瀹℃壒锛� |
+| `POST /stockOutRecord/approve` 瀹℃壒**閫氳繃** | 鎸夈�宍product_model_id` + `batch_no`銆嶆墸鍑� `stock_out_num` |
+| 瀹℃壒**椹冲洖** | 涓嶅彉锛堟湰鏉ュ氨娌℃墸锛� |
+| 鍒犻櫎**宸查�氳繃**鐨勯攢鍞被鍑哄簱璁板綍 | 鎸夊悓鎵规**褰掕繕** |
+| 鍒犻櫎**寰呭鎵�**鐨勯攢鍞被鍑哄簱璁板綍 | 涓嶅彉 |
+
+澶辫触鎯呭舰锛氬簱瀛樹笉瓒虫椂瀹℃壒浼氭姤 **銆屽簱瀛樹笉瓒虫棤娉曞嚭搴�,鍑哄簱鎵规:CK鈥︺��**锛屼笖鏁存潯瀹℃壒浜嬪姟鍥炴粴锛坄approval_status` 涓嶄細鍙樻垚 1锛夈��
+
+鍑哄簱鏁伴噺蹇呴』 > 0锛屽惁鍒欏鎵规姤銆岄攢鍞被鍑哄簱鏁伴噺蹇呴』澶т簬0銆嶃��
+
+---
+
+## 5. 宸茬煡褰卞搷锛堣仈璋冨墠闇�鐭ユ儏锛�
+
+1. **鍘嗗彶鏁版嵁涓嶄細琛ユ墸**銆傛敼鍓嶉偅鏉� `CK20260920001`锛坄batch_no` 涓虹┖銆佸凡瀹℃壒閫氳繃锛変粠鏈墸杩囧簱瀛橈紝鐜板湪涔熶笉浼氳ˉ鎵c�傚畠浼氬嚭鐜板湪鍙戣揣鍙拌处锛堟樉绀恒�屽凡鍙戣揣銆嶏級锛屼絾**璐﹀疄涓嶇**銆傝涔堣涓氬姟琛ュ綍鎵规鍚庨噸寮�涓�鏉★紝瑕佷箞鎺ュ彈杩欐潯鍘嗗彶鏁版嵁涓嶆墸銆�
+2. **涓嶈鍐嶇粰閿�鍞被娌瑰搧鍑哄簱鎵嬪伐寤哄彂璐у崟**銆傚惁鍒欏彂璐у彴璐︿細鍚屾椂鍑虹幇 `shipping_info` 琛屽拰 `stock_out_record` 琛岋紝鍚屼竴鎵硅揣鏄剧ず涓ゆ銆�
+3. **鍙戣揣鍙拌处鐨勩�屽垹闄ゃ�嶃�岄��璐с�嶅 `oilOut` 琛屼笉鍙敤**銆傞��璐ф祦绋嬶紙`ReturnManagementServiceImpl`锛変緷璧� `shipping_info` + `record_type='13'`锛屾补鍝佸嚭搴撲笉婊¤冻銆傞攢鍞被鍑哄簱鐨勯��璐ц璧般�屽嚭搴撳彴璐︺�嶅垹闄わ紙浼氬綊杩樺簱瀛橈級銆�
+4. **鍑哄簱绫诲埆涓恒�屼唬鍌ㄣ�嶃�屽瀛樸�嶇殑鍑哄簱**鏃笉鎵e簱瀛樸�佷篃涓嶈繘鍙戣揣鍙拌处銆�
+5. **瀹℃壒閫氳繃鍚庡啀鐢� `PUT /stockOutRecord/{id}` 鏀规暟閲�/鎵规锛屼笉浼氬悓姝ヨ皟鏁村簱瀛�**锛堟棦鏈夊疄鐜版湭瑕嗙洊锛屾湰娆℃湭鏀癸級銆傝鏀规暟閲忚鍏堝垹闄わ紙浼氬綊杩橈級鍐嶉噸寮�銆�
+6. **鎵e噺鍙牎楠� `qualitity >= 鍑哄簱鏁伴噺`锛屾湭鑰冭檻 `locked_quantity`锛堝喕缁撻噺锛�**銆傝繖涓庣幇鏈夈�屽悎鏍煎嚭搴撱�嶅垎鏀殑琛屼负涓�鑷达紱鑻ヤ笟鍔¤姹傚喕缁撻噺涓嶅彲鍔ㄧ敤锛岄渶鍙﹁纭銆�
+
+---
+
+## 6. 鑱旇皟鑷煡娓呭崟
+
+- [ ] 鍙戣揣鍙拌处鍒楄〃鍑虹幇 `CK20260920001` 閭d竴琛岋紝`source='oilOut'`锛屽鎴峰悕/瑙勬牸/鍌ㄧ綈鍙�/瀵规帴浜洪兘鏈夊��
+- [ ] `CK20260920002` 琛岋紙`batch_no=20260917-CY001-001`锛夌殑 `outBatchNo` = `20260917-CY001-001`锛屼笖 `batchNoList` 鏈変笖浠呮湁 1 涓厓绱�
+- [ ] 鎵撳紑璇ヨ璇︽儏寮规锛屻�屾壒鍙枫�嶆槑缁嗚〃鏄剧ず `20260917-CY001-001` / 鏌存补 / 02#鏌存补 / 9锛�**涓嶆槸 `--`**锛�
+- [ ] 鎵撳紑璇ヨ璇︽儏寮规鏃�**娌℃湁**鍙� `/shippingInfo/getDateil/{鍑哄簱璁板綍id}` 璇锋眰锛堝惁鍒欏彲鑳芥樉绀哄埆鐨勫彂璐у崟鏄庣粏锛�
+- [ ] 璇ヨ銆屽彂璐� / 鍒犻櫎 / 閫�璐с�嶆寜閽�**涓嶅嚭鐜�**锛堟垨鐐逛簡璧扮殑鏄嚭搴撳彴璐︼紝涓嶆槸 `shipping_info`锛�
+- [ ] 鍒嗛〉 `total` 涓庡疄闄呰鏁颁竴鑷达紙鏈鏀逛簡 UNION锛岄噸鐐瑰洖褰掗」锛�
+- [ ] 鎸夈�屽彂璐у崟鍙枫�嶇瓫閫夛細`shipping` 鍜� `oilOut` 涓ょ被琛岄兘鑳借绛涘埌
+- [ ] 鎸夈�岄攢鍞悎鍚屽彿銆嶇瓫閫夛細鍙墿 `shipping` 琛岋紝娌瑰搧琛屼笉鍑虹幇
+- [ ] 娌瑰搧鍑哄簱琛ㄥ崟锛氬嚭搴撶被鍒�=閿�鍞椂涓嶅~鎵规锛屾彁浜ゆ姤銆岄攢鍞被鍑哄簱蹇呴』閫夋嫨鍑哄簱鎵规銆�
+- [ ] 娌瑰搧鍑哄簱琛ㄥ崟锛氬嚭搴撶被鍒�=浠e偍/瀹㈠瓨鏃朵笉濉壒娆¤兘姝e父鎻愪氦
+- [ ] 鏂板缓閿�鍞被鍑哄簱锛堟壒娆� `CG20260815005-187`銆佹暟閲� 10锛夆啋 瀹℃壒閫氳繃 鈫� `stock_inventory.qualitity` 鐢� 1000 鍙� 990
+- [ ] 鍐嶅垹闄よ鍑哄簱璁板綍 鈫� 搴撳瓨鍥炲埌 1000
+- [ ] 鏁伴噺濉� 99999 鈫� 瀹℃壒鎶ャ�屽簱瀛樹笉瓒虫棤娉曞嚭搴撱�嶏紝涓旇璁板綍 `approval_status` 浠嶄负 0
+- [ ] 鍘熸湁鍙戣揣鍗曪紙`source='shipping'`锛夌殑鍙戣揣/鍒犻櫎鍔熻兘涓嶅彈褰卞搷
diff --git "a/doc/20260920_\350\275\246\350\276\206\347\256\241\347\220\206\344\270\216\346\256\213\346\262\271\350\201\224\345\212\250\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md" "b/doc/20260920_\350\275\246\350\276\206\347\256\241\347\220\206\344\270\216\346\256\213\346\262\271\350\201\224\345\212\250\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
new file mode 100644
index 0000000..be756d1
--- /dev/null
+++ "b/doc/20260920_\350\275\246\350\276\206\347\256\241\347\220\206\344\270\216\346\256\213\346\262\271\350\201\224\345\212\250\345\211\215\347\253\257\350\201\224\350\260\203\346\226\207\346\241\243.md"
@@ -0,0 +1,304 @@
+# 杞﹁締绠$悊涓庡嚭搴撴畫娌硅仈鍔� 鍓嶇鑱旇皟鏂囨。
+
+鏇存柊鏃堕棿锛�2026-09-20
+閫傜敤鐗堟湰锛氭湰娆″悗绔彉鏇村悗
+瀵瑰簲闇�姹傦細闇�姹傛竻鍗曠 7 鏉°�屽彂璐э細缁欒溅杈嗗仛鍞竴鏍囪瘑锛屽仛鍑哄簱缂栫爜锛屽姞涓�涓溅杈嗙鐞嗛〉闈紝璁板綍姣忔鏈�鍚庨�佸畬璐у悗鍓╀綑鐨勬补閲忥紝涓嬫鍙戣揣杩樻槸瑕佺户缁娇鐢紝涓嬫鍙戣揣涓轰笂娆″墿浣欓噺+搴撳瓨閲忋��
+
+**涓�鍙ヨ瘽鍙e緞**锛氬嚭搴撴暟閲� = **鏈瑁呰溅鎬婚噺**锛堣溅涓婂墿娌� + 鏂拌娌癸級锛涚湡姝d粠搴撳瓨閲屾墸鍑忕殑鍙湁 **鍑哄簱鏁伴噺 鈭� 涓婃鍓╀綑娌归噺**銆�
+
+---
+
+## 1. 鍙樻洿鑼冨洿
+
+| 鍐呭 | 褰卞搷闈� |
+| --- | --- |
+| 鏂板缓杞﹁締涓绘暟鎹ā鍧� `/basic/vehicle` | 杞﹁締绠$悊椤甸潰锛堝墠绔柊寤猴紝鍚庣鎺ュ彛宸插氨缁級 |
+| 鍑哄簱琛ㄥ崟銆岃溅杈嗐�嶇敱鎵嬪~杞︾墝鍙锋敼涓轰笅鎷� | 娌瑰搧鍑哄簱鏂板琛ㄥ崟 |
+| 鏂板鍙敤閲忔煡璇㈡帴鍙� | 娌瑰搧鍑哄簱琛ㄥ崟鐨勩�屼笂娆″墿浣� / 褰撳墠搴撳瓨 / 鍙敤閲忋�嶆彁绀� |
+| 鏂板娈嬫补褰曞叆鎺ュ彛 | 鍑哄簱鍙拌处琛屾搷浣滐紙閫佸畬璐у悗鍥炲~鍓╀綑娌归噺锛� |
+| 鍑哄簱鍙拌处鍒楄〃鏂板 3 涓瓧娈� | 鍑哄簱绠$悊鍒楄〃銆佸鍑� Excel |
+
+> **鏈粨搴撴病鏈夈�屾补鍝佸嚭搴撱�嶈〃鍗曢〉**锛�20 涓墠绔垎鏀叏鏌ヨ繃锛宍娌瑰搧鍑哄簱`銆乣瀹㈠瓨`銆乣浠e偍`銆乣outTonnage`銆乣addOilOutRecordOnly` 鍧囬浂鍛戒腑銆傛墍浠ユ湰娆�**鍙氦浠樺悗绔兘鍔�**锛屼笅鎷夈�佹彁绀恒�佹畫娌瑰綍鍏ュ叆鍙i渶瑕佸墠绔帴銆�
+>
+> 鍓嶇鍙︽湁涓�涓偓绌虹殑 `inventoryManagement/vehicleManagement/index.vue`锛堢粦 `/vehicle/info/*`锛岃溅杈嗕俊鎭�+骞存+淇濋櫓+璐圭敤+鍔犳补鍗★級锛岄偅**鏄埆鐨勭鎴烽仐鐣欑殑妯″潡**锛屽悗绔病鏈夊搴旀帴鍙o紝涓庢畫娌规鏃犲叧绯伙紝**涓嶈澶嶇敤**銆�
+
+---
+
+## 2. 鍓嶇疆锛氬厛寤鸿〃銆佸啀褰曡溅杈�
+
+### 2.1 DDL 蹇呴』鍏堟墽琛�
+
+鏂囦欢锛歚doc/20260920_vehicle_and_residual_oil.sql`锛岀函鏂板銆佷笉鏀瑰姩浠讳綍鏃㈡湁鍒楋細
+
+- 鏂板缓 `vehicle` 琛紙鍚敮涓�绱㈠紩 `uk_plate_number`锛�
+- `stock_out_record` 鍔� 3 鍒楋細`vehicle_id`銆乣residual_oil`銆乣deduct_quantity`
+
+**璇� DDL 宸蹭簬 2026-09-20 鍦ㄥ紑鍙戝簱 `product-inventory-management-rbhb` 鎵ц瀹屾瘯**锛坄vehicle` 琛� 13 鍒� + `plate_number` 鍞竴绱㈠紩銆乣stock_out_record` 涓夊垪鍧囧凡鏍稿锛夈�傚叾浠栫幆澧冧笂绾挎椂鎸夊悓涓�涓枃浠舵墽琛屽嵆鍙��
+
+鏂囦欢绗� 3 娈电殑鑿滃崟 SQL锛坄杞﹁締淇℃伅`锛宍parent_id=2000`锛宍component=basicData/vehicle/index`锛�**宸蹭簬 2026-09-20 鍦ㄥ紑鍙戝簱鎵ц**锛坄menu_id=2377`锛宍order_num=4`锛屽苟宸叉巿鏉冪粰銆屾妧鏈川閲忋�嶈鑹� `role_id=2`锛屼笌鍚岀骇鐨勬补搴撲俊鎭�/鍌ㄧ綈淇℃伅涓�鑷达級銆�
+
+> 鈿狅笍 **鑿滃崟宸插氨浣嶏紝浣嗗墠绔〉闈㈣繕娌″缓**锛氳瀺閭﹀墠绔垎鏀� `dev_鏂扮枂_铻嶉偊鐜繚` 涓嬫殏鏃�**娌℃湁** `src/views/basicData/vehicle/index.vue`锛屼篃娌℃湁 `src/api/basicData/vehicle.js`銆傛墍浠ョ幇鍦�**鑿滃崟閲岃兘鐪嬪埌銆岃溅杈嗕俊鎭�嶏紝鐐硅繘鍘绘槸 404**鈥斺�旇繖鏄鏈熺殑鍗犱綅鐘舵�侊紝椤甸潰鎸� `views/basicData/vehicle/index.vue` 寤哄ソ鍚庤嚜鍔ㄥ彲鐢紝**涓嶉渶瑕佸啀鏀硅彍鍗�**銆�
+>
+> 椤甸潰瑕佽皟鐨勫悗绔帴鍙h鏈枃妗g 2.2 鑺傦紙`/basic/vehicle/*`锛夈�傚悓绾х殑娌瑰簱淇℃伅 `basicData/oilDepot/index`銆佸偍缃愪俊鎭兘鍦� `parent_id=2000` 涓嬶紝鐓у畠浠殑褰㈡�佸缓鍗冲彲銆�
+
+> **DDL 鏈墽琛屽墠**锛氬嚭搴撹褰曠殑鏂板/淇敼銆佽溅杈嗙鐞嗘帴鍙i兘浼氱洿鎺ユ姤閿欙紙`Unknown column` / `Table doesn't exist`锛夈��
+
+### 2.2 杞﹁締绠$悊鎺ュ彛锛堝墠缂� `/basic/vehicle`锛岀収 `oilDepot` 褰㈡�侊級
+
+| 鎺ュ彛 | 鏂规硶 | 璇存槑 |
+| --- | --- | --- |
+| `/basic/vehicle/list` | GET | 鍒嗛〉鏌ヨ锛岃繑鍥� `R.data.records`锛涙敮鎸� `plateNumber`銆乣driverName` 妯$硦鏌ャ�乣status` 绮剧‘鏌� |
+| `/basic/vehicle/all` | GET | **渚涘嚭搴撹〃鍗曚笅鎷�**锛屽彧杩斿洖鏈仠鐢ㄧ殑杞﹁締锛屾寜 id 鍗囧簭 |
+| `/basic/vehicle/{id}` | GET | 璇︽儏 |
+| `/basic/vehicle/add` | POST | 鏂板锛岃溅鐗屽彿蹇呭~涓斿敮涓� |
+| `/basic/vehicle/update` | POST | 淇敼锛屽繀椤诲甫 `id` |
+| `/basic/vehicle/del` | DELETE | 鎵归噺鍒犻櫎锛宐ody 涓� id 鏁扮粍 |
+
+杞﹁締瀛楁锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+| --- | --- | --- |
+| `id` | Long | 涓婚敭 |
+| `plateNumber` | String | **杞︾墝鍙凤紝杞﹁締鍞竴鏍囪瘑**锛屽繀濉�佷笉鍙噸澶� |
+| `driverName` | String | 榛樿鍙告満锛堝彲绌猴紝鍑哄簱鏃跺彲瑕嗙洊锛� |
+| `driverPhone` | String | 鍙告満鐢佃瘽 |
+| `loadCapacity` | BigDecimal | 鏍稿畾杞介噸(鍚�) |
+| `status` | String | `鍚敤` / `鍋滅敤`锛岄粯璁� `鍚敤`锛�**鍋滅敤鍚庝笉鍐嶅嚭鐜板湪涓嬫媺閲岋紝鍘嗗彶璁板綍涓嶅彈褰卞搷** |
+| `remark` | String | 澶囨敞 |
+
+> 杞﹁締鏄叕鍙哥骇涓绘暟鎹細`vehicle` 宸插姞鍏� `IgnoreTableConfig`锛�**涓嶅仛閮ㄩ棬/鍒涘缓浜鸿繃婊�**锛屽嚭搴撹〃鍗曠殑涓嬫媺鑳界湅鍒板叏閮ㄨ溅杈嗐��
+> 寤鸿鐢ㄣ�屽仠鐢ㄣ�嶈�屼笉鏄�屽垹闄ゃ�嶆潵涓嬬嚎涓�杈嗚溅鈥斺�斿垹闄や笉浼氭牎楠屾槸鍚﹀凡琚嚭搴撹褰曞紩鐢ㄣ��
+
+璇锋眰 / 鍝嶅簲绀轰緥锛�**娉ㄦ剰杩欎釜妯″潡杩斿洖鐨勬槸 `R`锛屼笌鍑哄簱妯″潡鐨� `AjaxResult` 缁撴瀯涓�鏍烽兘鏄� `{code, msg, data}`**锛夛細
+
+```json
+POST /basic/vehicle/add
+{
+  "plateNumber": "鑻廇12345",
+  "driverName": "寮犱笁",
+  "driverPhone": "13800000000",
+  "loadCapacity": 30,
+  "status": "鍚敤",
+  "remark": ""
+}
+```
+
+```json
+// 鎴愬姛
+{ "code": 200, "msg": "鎿嶄綔鎴愬姛", "data": true }
+// 杞︾墝鍙烽噸澶�
+{ "code": 500, "msg": "杞︾墝鍙峰凡瀛樺湪:鑻廇12345", "data": null }
+// 杞︾墝鍙蜂负绌�
+{ "code": 500, "msg": "杞︾墝鍙蜂笉鑳戒负绌�", "data": null }
+```
+
+```json
+GET /basic/vehicle/all
+{ "code": 200, "msg": "鎿嶄綔鎴愬姛",
+  "data": [ { "id": 12, "plateNumber": "鑻廇12345", "driverName": "寮犱笁", "status": "鍚敤" } ] }
+```
+
+```json
+DELETE /basic/vehicle/del
+[12, 13]
+```
+
+`GET /basic/vehicle/list` 鐨勫垎椤电粨鏋滃湪 `data.records`锛圡yBatis-Plus 鐨� `IPage`锛夛紝鍓嶇鎸夌幇鏈夊熀纭�鏁版嵁椤甸潰鍚屾牱鐨勬柟寮忓彇鍗冲彲銆�
+
+### 2.3 鈿狅笍 杞﹁締蹇呴』鍏堢櫥璁�
+
+閿�鍞被鍑哄簱浼氭寜杞︾墝鍙�/杞﹁締id 瀹氫綅銆屼笂娆″墿浣欐补閲忋�嶃��**娌″湪杞﹁締绠$悊閲岀櫥璁拌繃鐨勮溅鐗屽彿锛屾柊澧炲嚭搴撴椂浼氱洿鎺ユ姤閿�**锛�
+
+```
+杞︾墝鍙� 鑻廇12345 涓嶅湪杞﹁締绠$悊涓紝璇峰厛鍒拌溅杈嗙鐞嗘坊鍔�
+```
+
+鎵�浠ヤ笂绾块『搴忔槸锛�**鎵ц DDL 鈫� 褰曞叆杞﹁締 鈫� 鎵嶈兘璧板嚭搴�**銆�
+
+---
+
+## 3. 鏂板鍑哄簱锛氬厛鎼炴竻璧板摢涓帴鍙o紙**蹇呯湅**锛�
+
+娌瑰搧鍑哄簱琛ㄥ崟瑕佽皟鐨勬槸 `POST /stockInventory/addOilOutRecordOnly`銆傚畠鍦ㄨ繘涓氬姟鏍¢獙鍓嶄細**寮哄埗鏀瑰啓 4 涓瓧娈�**锛屽墠绔紶浠�涔堥兘浼氳瑕嗙洊锛�
+
+| 瀛楁 | 鍚庣鍐欐垚 | 璇存槑 |
+| --- | --- | --- |
+| `stockOutNum` | = 鍓嶇浼犵殑 `outTonnage` | **鍓嶇濉殑鏄� `outTonnage`锛堟湰娆¤杞︽�婚噺锛�**锛屼笉鐢ㄤ紶 `stockOutNum`锛涘彲鐢ㄩ噺鏍¢獙姣旂殑涔熸槸瀹� |
+| `approvalStatus` | `0`锛堝緟瀹℃壒锛� | 瑕嗙洊鍓嶇浼犲�� |
+| `recordType` | `'1'`锛堝悎鏍艰嚜瀹氫箟鍑哄簱锛� | 瑕嗙洊鍓嶇浼犲�硷紱鍑哄簱鍙拌处鎸� `recordType=1` 绛涖�屽悎鏍艰嚜瀹氫箟鍑哄簱銆� |
+| `recordId` | `0` | 鍓嶇涓嶇敤绠� |
+
+鍙﹀涓夋潯锛�
+
+- **涓嶅姩搴撳瓨**锛氳繖涓帴鍙e彧寤� `stock_out_record` 璁板綍锛屽簱瀛樻墸鍑忓彂鐢熷湪**瀹℃壒閫氳繃鏃�**锛堣 4.3锛�
+- **`outCategory` 蹇呴』鏄� `閿�鍞甡**锛氳溅杈嗘牎楠屻�佸彲鐢ㄩ噺鏍¢獙銆佹畫娌瑰彛寰�**鍏ㄩ儴鍙 `outCategory = '閿�鍞�'` 鐢熸晥**銆備紶銆屽瀛樸�嶃�屼唬鍌ㄣ�嶄笉鏍¢獙銆佸鎵规椂涔熶笉鎵e簱瀛�
+- 鍑哄簱缂栫爜 `outboundBatches`锛坄CK` 寮�澶达級鐢卞悗绔嚜鍔ㄧ敓鎴愶紝鍓嶇涓嶇敤浼�
+
+璇锋眰绀轰緥锛�
+
+```json
+POST /stockInventory/addOilOutRecordOnly
+{
+  "outCategory": "閿�鍞�",
+  "vehicleId": 12,
+  "truckPlateNo": "鑻廇12345",
+  "productModelId": 407,
+  "batchNo": "20260917-CY001-001",
+  "outTonnage": 15,
+  "outDate": "2026-09-20",
+  "oilDepotId": 1,
+  "tankId": 3,
+  "customerId": 88,
+  "customerName": "鏌愭煇鍖栧伐",
+  "driverName": "寮犱笁",
+  "driverPhone": "13800000000",
+  "remark": ""
+}
+```
+
+> 鍙︿竴涓叆鍙� `POST /stockOutRecord`锛堝嚭搴撶鐞�-鏂板鍑哄簱锛�**涓嶅仛** `outTonnage 鈫� stockOutNum` 鐨勬惉杩愶紝璧板畠鐨勫墠绔鑷繁浼� `stockOutNum`銆備袱涓叆鍙i兘浼氭墽琛屾湰娆℃柊澧炵殑杞﹁締鏍¢獙涓庡彲鐢ㄩ噺鏍¢獙銆�
+
+---
+
+## 4. 鍑哄簱琛ㄥ崟鏀归�狅紙閿�鍞被娌瑰搧鍑哄簱锛�
+
+### 4.1 杞﹁締鏀逛笅鎷�
+
+- 鏁版嵁婧愶細`GET /basic/vehicle/all`
+- 鎻愪氦鏃跺甫 `vehicleId`锛涗篃鍙互鍙甫 `truckPlateNo`锛堝悗绔細鎸夎溅鐗屽彿鍙嶆煡鍥炲~ `vehicleId`锛�
+- 涓よ�呴兘涓虹┖ 鈫� 鎶ラ敊 `閿�鍞被鍑哄簱蹇呴』閫夋嫨杞﹁締`
+
+> 鍏煎閫昏緫鐨勫瓨鍦ㄦ槸涓轰簡鍓嶇娌¤窡涓婃椂涓嶈嚦浜庣洿鎺ユ姤閿欙紝**寤鸿鍓嶇鐩存帴浼� `vehicleId`**銆�
+
+### 4.2 鍙敤閲忔彁绀�
+
+```
+GET /stockOutRecord/oilOutAvailableQty?vehicleId=&productModelId=&batchNo=
+```
+
+- `vehicleId`銆乣productModelId` 蹇呭~锛沗batchNo` 鍙�夛紝涓嶄紶鍒欏簱瀛樻寜璇ヨ鏍�**鍏ㄩ儴鎵规鍚堣**
+- 杩斿洖锛�
+
+```json
+{
+  "code": 200,
+  "data": {
+    "lastResidualOil": 5.0000,
+    "stockQuantity": 10.0000,
+    "availableQty": 15.0000
+  }
+}
+```
+
+- 灞曠ず寤鸿锛歚涓婃鍓╀綑(鍚�) 5 / 褰撳墠搴撳瓨(鍚�) 10 / 鍙敤閲�(鍚�) 15`锛�**骞舵妸杩欏彞鍙e緞鏄剧ず鍑烘潵**锛氬彲鐢ㄩ噺 = 涓婃鍓╀綑 + 褰撳墠搴撳瓨
+
+銆屼笂娆″墿浣欐补閲忋�嶇殑鍙栨暟瑙勫垯锛堝墠绔笉鐢ㄧ锛屽嚭闂鏃舵寜杩欎釜鎺掓煡锛夛細
+`stock_out_record` 涓悓 `vehicle_id` + `product_model_id`銆乣residual_oil` 闈炵┖銆乣approval_status=1` 鐨�**鏈�杩戜竴鏉�**銆�**娌″~娈嬫补鐨勮褰曚細琚烦杩�**锛岀户缁線鍓嶆壘銆�
+
+### 4.3 瀹℃壒锛堝簱瀛樺氨鏄湪杩欐墸鐨勶級
+
+```
+POST /stockOutRecord/approve
+{ "ids": [255], "approvalStatus": 1 }
+```
+
+- `approvalStatus`锛歚1` = 閫氳繃銆乣2` = 椹冲洖
+- 鎵归噺鎺ュ彛锛�**涓�鏉′笉杩囧垯鍏ㄦ壒鍥炴粴**
+- 鍙湁 `approvalStatus = 0`锛堝緟瀹℃壒锛夌殑璁板綍鑳藉鎵癸紱`addOilOutRecordOnly` 寤哄嚭鏉ョ殑璁板綍姝eソ鏄� 0
+- 鎵e噺閲� `deduct_quantity` 鍦ㄩ�氳繃杩欎竴姝ヨ惤搴�
+
+### 4.4 鏍¢獙涓庢姤閿欐枃妗�
+
+閿�鍞被鍑哄簱鍦�**鏂板**鍜�**瀹℃壒閫氳繃**涓ゆ閮芥牎楠屻��
+
+| 鍦烘櫙 | 鎶ラ敊 |
+| --- | --- |
+| 娌¢�夎溅杈嗐�佷篃娌″~杞︾墝鍙� | `閿�鍞被鍑哄簱蹇呴』閫夋嫨杞﹁締` |
+| 杞︾墝鍙蜂笉鍦ㄨ溅杈嗙鐞嗛噷 | `杞︾墝鍙� X 涓嶅湪杞﹁締绠$悊涓紝璇峰厛鍒拌溅杈嗙鐞嗘坊鍔燻 |
+| 娌¢�夊嚭搴撴壒娆� | `閿�鍞被鍑哄簱蹇呴』閫夋嫨鍑哄簱鎵规` |
+| 鏁伴噺涓虹┖鎴� 鈮� 0 | `閿�鍞被鍑哄簱鏁伴噺蹇呴』澶т簬0` |
+| 鏁伴噺 < 涓婃鍓╀綑娌归噺 | `鍑哄簱鏁伴噺涓嶈兘灏忎簬璇ヨ溅涓婃鍓╀綑娌归噺5` |
+| 鏁伴噺 > 涓婃鍓╀綑 + 搴撳瓨 | `鍑哄簱鏁伴噺瓒呭嚭鍙敤閲�(涓婃鍓╀綑5,搴撳瓨10,鍙敤15)` |
+| 瀹℃壒鏃跺簱瀛樿鍒鎵f帀浜� | `搴撳瓨涓嶈冻鏃犳硶鍑哄簱` |
+
+**閿欒鍝嶅簲褰㈡��**锛坄BaseException` 琚叏灞�寮傚父澶勭悊鍣ㄥ厹鎴� `AjaxResult`锛屾敞鎰� **HTTP 鐘舵�佺爜鏄� 200**锛夛細
+
+```json
+{ "code": 500, "msg": "閿�鍞被鍑哄簱蹇呴』閫夋嫨杞﹁締", "data": null }
+```
+
+鎵�浠ュ墠绔�**鍒ゅけ璐ヨ鐪� `code`锛屼笉鑳藉彧鐪� HTTP 鐘舵�佺爜**锛屽苟涓旇鎶� `msg` 鍘熸牱閫忓嚭鈥斺�旇繖浜涙枃妗堥噷甯︿簡鍏蜂綋鏁板瓧锛屾槸鐜板満鎺掓煡鐨勪富瑕佺嚎绱€��
+
+---
+
+## 5. 娈嬫补鎬庝箞褰曪紙閫佸畬璐у悗鍥炲~锛�
+
+```
+PUT /stockOutRecord/{id}/residualOil
+Content-Type: application/json
+
+{ "residualOil": 4.5 }
+```
+
+- `residualOil` 鍗曚綅鍚紝**鍙洿鏂拌繖涓�鍒�**锛屽叾浠栧垪涓嶅姩
+- 鏍¢獙锛氫笉鑳戒负绌恒�佷笉鑳藉皬浜� 0銆佷笉鑳藉ぇ浜庤璁板綍鐨勫嚭搴撴暟閲�
+- 寤鸿鍦�**鍑哄簱瀹℃壒閫氳繃鍚�**褰曞叆锛堣繖鏃惰揣宸茬粡閫佸埌浜嗭紝鎵嶇煡閬撳墿澶氬皯锛�
+
+> 鈿狅笍 **涓嶈鐢� `PUT /stockOutRecord/{id}` 鏉ュ綍娈嬫补**銆傞偅涓帴鍙h蛋 `BeanUtils.copyProperties`锛屼細鎶婃病浼犵殑瀛楁**鍏ㄩ儴缃┖**鈥斺�斿彧浼� `{id, residualOil}` 浼氭妸杩欐潯鍑哄簱璁板綍鐨勫鎴枫�佹壒娆°�佹暟閲忕瓑鍏ㄩ儴娓呮帀銆傛墍浠ユ湰娆′笓闂ㄥ紑浜� `/residualOil` 杩欎釜鍗曞瓧娈垫帴鍙c��
+
+---
+
+## 6. 鎵e簱瀛樼殑瀹為檯鍙e緞锛堟渶鍏抽敭鐨勪竴鏉★級
+
+瀹℃壒閫氳繃鏃讹細
+
+```
+deduct_quantity锛堢湡姝f墸搴撳瓨鐨勯噺锛� = 鍑哄簱鏁伴噺 鈭� 涓婃鍓╀綑娌归噺
+```
+
+- 涓婃鍓╀綑 5銆佹湰娆¤杞� 15锛堣溅涓� 5 + 鏂拌 10锛夆啋 鍙粠搴撳瓨鎵� **10**锛宍deduct_quantity` 钀藉簱涓� 10
+- 杞︿笂鐨勫墿娌瑰垰濂藉锛堝嚭搴撴暟閲� = 涓婃鍓╀綑锛夆啋 **涓嶇搴撳瓨**锛宍deduct_quantity` = 0
+- 鍑哄簱鏁伴噺 < 涓婃鍓╀綑娌归噺 鈫� 鎶ラ敊锛堟补涓嶅彲鑳藉嚟绌哄彉澶氾級
+- `deduct_quantity` 钀藉簱鐨勪綔鐢細**鍒犻櫎杩欐潯鍑哄簱璁板綍鏃舵寜瀹冨綊杩樺簱瀛�**锛屾墍浠ュ彧鑳藉綊杩樺綋鍒濈湡姝f墸鎺夌殑閲�
+
+---
+
+## 7. 鍑哄簱鍙拌处鍒楄〃鏂板瀛楁
+
+`GET /stockOutRecord/listPage`锛堜互鍙婂鍑� Excel銆佸垪琛ㄨ鎯咃級鏂板杩斿洖锛�
+
+| 瀛楁 | 璇存槑 |
+| --- | --- |
+| `vehicleId` | 杞﹁締id锛堣溅杈嗕富鏁版嵁锛� |
+| `residualOil` | 鏈閫佸畬璐у悗鍓╀綑娌归噺(鍚�) |
+| `deductQuantity` | 瀹℃壒閫氳繃鏃跺疄闄呬粠搴撳瓨鎵e噺鐨勬暟閲� |
+
+鍒楄〃 XML 鐢ㄧ殑鏄� `select sor.*`锛屾墍浠ヨ繖涓変釜瀛楁鏄嚜鍔ㄥ甫鍑虹殑锛屾棤闇�棰濆澶勭悊銆�
+
+---
+
+## 8. 宸茬煡褰卞搷涓庢敞鎰忕偣
+
+1. **閿�鍞被鍑哄簱澶氫簡涓や釜鍓嶇疆鏉′欢**锛氬繀椤绘湁杞﹁締銆佸繀椤婚�夋壒娆°�傚巻鍙叉暟鎹笉鍙楀奖鍝嶁�斺�擿vehicleId` 涓虹┖鐨勮�佽褰曪紝鍏躲�屼笂娆″墿浣欐补閲忋�嶆寜 0 澶勭悊锛屾墸鍑忛噺閫�鍖栦负銆屽嚭搴撴暟閲忋�嶏紝涓庢敼閫犲墠琛屼负涓�鑷淬��
+2. **鍘嗗彶宸插鎵硅褰曚笉琛ョ畻**锛氫緥濡� `CK20260920002`锛堝凡瀹℃壒銆佸綋鍒濇墸浜嗗叏棰濓級`deduct_quantity` 涓� null锛屽垹闄ゆ椂鎸夊叏棰濆綊杩橈紝涓庡畠褰撳垵鐨勬墸鍑忎竴鑷达紱瀹冩病鏈夎溅杈嗭紝涓嶅弬涓庢畫娌归摼銆�
+3. **涓�杈嗚溅鐨勬畫娌规寜娌瑰搧瑙勬牸鍒嗗紑绠�**锛氬悓涓�杈嗚溅鎷変笉鍚屾补鍝佷簰涓嶅共鎵般��
+4. **瀹℃壒閫氳繃鍚庡啀鏀� `residualOil` 涓嶄細閲嶇畻 `deduct_quantity`**锛堜笌骞冲彴鏃㈡湁鐨勩�屾敼鏁伴噺涓嶅悓姝ュ簱瀛樸�嶆槸鍚屼竴绫婚棶棰橈紝鏈涓嶅悓姝ワ級銆�
+5. **搴撳瓨瀹堝崼鍙湅 `qualitity`锛屼笉鑰冭檻 `locked_quantity`锛堝喕缁撻噺锛�**锛屼笌鏃㈡湁 `type='0'/'1'` 鍒嗘敮琛屼负涓�鑷淬��
+6. **鍙敤閲忔煡璇㈢殑搴撳瓨鍚堣浼氬彈鏁版嵁鏉冮檺褰卞搷**锛歚stock_inventory` 鏈姞鍏ュ拷鐣ヨ〃锛岄潪绠$悊鍛樹笖鏁版嵁鑼冨洿涓恒�屾湰閮ㄩ棬/鏈儴闂ㄥ強浠ヤ笅/浠呮湰浜恒�嶇殑璐﹀彿锛屾煡璇細琚拷鍔� `dept_id`/`create_user` 鏉′欢锛�**绠楀嚭鏉ョ殑銆屽綋鍓嶅簱瀛樸�嶅彲鑳藉彧瑕嗙洊鑷繁閮ㄩ棬鐨勯偅閮ㄥ垎**锛屼粠鑰屽亸灏忚�岃鎶ャ�岃秴鍑哄彲鐢ㄩ噺銆嶃�傝繖鏄钩鍙版棦鏈夎涓猴紙`type='0'/'1'` 鍒嗘敮鍚屾牱鍙楀奖鍝嶏級銆傝嫢瀹為檯浣跨敤涓亣鍒拌鎶ワ紝闇�瑕佸崟鐙‘璁� `stock_inventory` 鏄惁搴斿姞鍏ュ拷鐣ヨ〃銆�
+
+---
+
+## 9. 鍓嶇鑷煡娓呭崟
+
+- [ ] DDL 宸叉墽琛岋紙`vehicle` 琛ㄥ瓨鍦ㄣ�乣stock_out_record` 鏈� `vehicle_id`/`residual_oil`/`deduct_quantity` 涓夊垪锛�
+- [ ] 杞﹁締绠$悊椤甸潰宸插缓濂斤紙`src/views/basicData/vehicle/index.vue`锛夆�斺��**鑿滃崟宸查厤濂斤紝椤甸潰鏄敮涓�缂虹殑閮ㄥ垎锛屽缓濂藉悗鑿滃崟鐐硅繘鍘诲氨涓嶅啀 404**
+- [ ] 杞﹁締绠$悊鑳芥柊澧烇紝閲嶅杞︾墝鍙蜂細鎶� `杞︾墝鍙峰凡瀛樺湪:X`
+- [ ] 鑷冲皯褰曞叆浜嗕竴杈嗘祴璇曡溅
+- [ ] 鍑哄簱琛ㄥ崟鐨勩�岃溅杈嗐�嶆槸涓嬫媺锛屾暟鎹潵鑷� `/basic/vehicle/all`锛屾彁浜ゅ甫 `vehicleId`
+- [ ] 鍑哄簱琛ㄥ崟鎻愪氦鐨勬槸 `outTonnage`锛堣杞︽�婚噺锛夛紝涓� `outCategory` 浼犵殑鏄� `閿�鍞甡
+- [ ] 閫夊畬 杞﹁締 + 瑙勬牸 + 鎵规 鍚庤皟 `/stockOutRecord/oilOutAvailableQty`锛屽睍绀恒�屼笂娆″墿浣� / 褰撳墠搴撳瓨 / 鍙敤閲忋��
+- [ ] 閫佸嚭搴撴暟閲忚秴鍑哄彲鐢ㄩ噺鏃讹紝椤甸潰鑳芥樉绀哄悗绔繑鍥炵殑 `鍑哄簱鏁伴噺瓒呭嚭鍙敤閲�(...)` 鏂囨
+- [ ] 鎺ュ彛鎶ラ敊鍒ゅ畾鐢ㄧ殑鏄� `code` 鑰屼笉鏄� HTTP 鐘舵�佺爜锛堝け璐ユ椂 HTTP 浠嶆槸 200锛�
+- [ ] 瀹℃壒璧� `POST /stockOutRecord/approve`锛坄approvalStatus` 1/2锛夛紝瀹℃壒鍚庢墠鎵e簱瀛�
+- [ ] 閫佸畬璐у悗鏈夊湴鏂瑰綍娈嬫补锛岃蛋鐨勬槸 `PUT /stockOutRecord/{id}/residualOil`锛堜笉鏄� `PUT /stockOutRecord/{id}`锛�
+- [ ] 鍑哄簱鍙拌处鍒楄〃鑳界湅鍒� `residualOil` / `deductQuantity`
diff --git a/src/main/java/com/ruoyi/account/controller/PaymentRecordController.java b/src/main/java/com/ruoyi/account/controller/PaymentRecordController.java
index 6e30ae6..a858efe 100644
--- a/src/main/java/com/ruoyi/account/controller/PaymentRecordController.java
+++ b/src/main/java/com/ruoyi/account/controller/PaymentRecordController.java
@@ -2,6 +2,7 @@
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.account.enums.PartyTypeEnum;
 import com.ruoyi.account.pojo.PaymentRecord;
 import com.ruoyi.account.service.PaymentRecordService;
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
@@ -72,6 +73,9 @@
         if (paymentRecord.getPartyType() == null || paymentRecord.getPartyId() == null) {
             throw new RuntimeException("璇烽�夋嫨寰�鏉ユ柟");
         }
+        if (PartyTypeEnum.getByCode(paymentRecord.getPartyType()) == null) {
+            throw new RuntimeException("寰�鏉ユ柟绫诲瀷涓嶅悎娉�");
+        }
         if (paymentRecord.getDirection() == null) {
             throw new RuntimeException("璇烽�夋嫨鏂瑰悜锛堜粯娆�/鍥炴锛�");
         }
diff --git a/src/main/java/com/ruoyi/account/enums/PartyTypeEnum.java b/src/main/java/com/ruoyi/account/enums/PartyTypeEnum.java
new file mode 100644
index 0000000..3687ccb
--- /dev/null
+++ b/src/main/java/com/ruoyi/account/enums/PartyTypeEnum.java
@@ -0,0 +1,47 @@
+package com.ruoyi.account.enums;
+
+import com.ruoyi.common.enums.BaseEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 寰�鏉ユ柟绫诲瀷鏋氫妇锛屽搴� payment_record.party_type
+ */
+@Getter
+@AllArgsConstructor
+public enum PartyTypeEnum implements BaseEnum<Integer> {
+
+    CUSTOMER(1, "瀹㈡埛"),
+    SUPPLIER(2, "渚涘簲鍟�"),
+    OUTSOURCING_PARTNER(3, "鍚堜綔鍟�");
+
+    private final Integer value;
+    private final String label;
+
+    @Override
+    public Integer getCode() {
+        return value;
+    }
+
+    @Override
+    public String getValue() {
+        return label;
+    }
+
+    public static PartyTypeEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (PartyTypeEnum partyType : values()) {
+            if (partyType.getCode().equals(code)) {
+                return partyType;
+            }
+        }
+        return null;
+    }
+
+    public static String getLabelByCode(Integer code) {
+        PartyTypeEnum partyType = getByCode(code);
+        return partyType == null ? "鏈煡绫诲瀷" : partyType.getValue();
+    }
+}
diff --git a/src/main/java/com/ruoyi/account/pojo/PaymentRecord.java b/src/main/java/com/ruoyi/account/pojo/PaymentRecord.java
index a5118ed..f155934 100644
--- a/src/main/java/com/ruoyi/account/pojo/PaymentRecord.java
+++ b/src/main/java/com/ruoyi/account/pojo/PaymentRecord.java
@@ -31,10 +31,10 @@
     @Schema(description = "涓婚敭id")
     private Long id;
 
-    @Schema(description = "寰�鏉ユ柟绫诲瀷 1瀹㈡埛 2渚涘簲鍟�")
+    @Schema(description = "寰�鏉ユ柟绫诲瀷 1瀹㈡埛 2渚涘簲鍟� 3鍚堜綔鍟�")
     private Integer partyType;
 
-    @Schema(description = "瀹㈡埛id鎴栦緵搴斿晢id")
+    @Schema(description = "瀹㈡埛id鎴栦緵搴斿晢id鎴栧悎浣滃晢id")
     private Long partyId;
 
     @Schema(description = "鏂瑰悜 1浠樻 2鍥炴")
diff --git a/src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java
index 41ad4e4..43277b7 100644
--- a/src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java
+++ b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java
@@ -33,5 +33,8 @@
     @Schema(description = "鍙戣揣鍗曞彿")
     private String shippingNo;
 
+    @Schema(description = "閿�鍞悎鍚屽彿")
+    private String salesContractNo;
+
     private List<StorageBlobVO> storageBlobVOList;
 }
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
index 62d083d..0e8825b 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
@@ -40,9 +40,11 @@
 import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.quality.utils.QualityInspectHelper;
+import com.ruoyi.sales.mapper.SalesLedgerMapper;
 import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
 import com.ruoyi.sales.mapper.SalesQuotationMapper;
 import com.ruoyi.sales.mapper.ShippingInfoMapper;
+import com.ruoyi.sales.pojo.SalesLedger;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.pojo.SalesQuotation;
 import com.ruoyi.sales.pojo.ShippingInfo;
@@ -84,6 +86,7 @@
     private final SalesLedgerProductMapper salesLedgerProductMapper;
     private final StockUtils stockUtils;
     private final SalesQuotationMapper salesQuotationMapper;
+    private final SalesLedgerMapper salesLedgerMapper;
     private final ShippingInfoMapper shippingInfoMapper;
     private final QualityInspectHelper qualityInspectHelper;
     private final EnterpriseNewsScopeUserMapper enterpriseNewsScopeUserMapper;
@@ -125,6 +128,12 @@
                     ShippingInfo shippingInfo = shippingInfoMapper.selectById(vo.getBusinessId());
                     if (shippingInfo != null) {
                         vo.setShippingNo(shippingInfo.getShippingNo());
+                    }
+                } else if (TypeEnums.SALES_LEDGER_APPROVAL.getCode().equals(vo.getBusinessType())) {
+                    // 閿�鍞彴璐﹀鎵� - 鏌ヨ閿�鍞悎鍚屽彿
+                    SalesLedger salesLedger = salesLedgerMapper.selectById(vo.getBusinessId());
+                    if (salesLedger != null) {
+                        vo.setSalesContractNo(salesLedger.getSalesContractNo());
                     }
                 }
             }
@@ -536,6 +545,10 @@
             handleShippingApprovalFinished(instance, status);
             return;
         }
+        if (TypeEnums.SALES_LEDGER_APPROVAL.getCode().equals(businessType)) {
+            handleSalesLedgerApprovalFinished(instance, status);
+            return;
+        }
         if (TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode().equals(businessType)
                 || TypeEnums.EXPENSE_APPROVAL.getCode().equals(businessType)) {
             handleReimbursementApprovalFinished(instance, status);
@@ -666,6 +679,25 @@
         salesQuotationMapper.updateById(salesQuote);
     }
 
+    /**
+     * 閿�鍞彴璐﹀鎵圭粨鏉燂細鍙洖鍐欏彴璐︾殑瀹℃壒鐘舵�侊紝涓嶉攣瀹氬崟鎹�佷笉鑷姩鐢熸垚涓嬫父鍗曟嵁
+     */
+    private void handleSalesLedgerApprovalFinished(ApprovalInstance instance, String status) {
+        SalesLedger salesLedger = salesLedgerMapper.selectById(instance.getBusinessId());
+        if (salesLedger == null) {
+            return;
+        }
+
+        if ("APPROVED".equals(status)) {
+            salesLedger.setApprovalStatus(SalesQuotationStatusEnum.APPROVED.getCode());
+        } else if ("REJECTED".equals(status)) {
+            salesLedger.setApprovalStatus(SalesQuotationStatusEnum.REJECTED.getCode());
+        } else if ("PENDING".equals(status)) {
+            salesLedger.setApprovalStatus(SalesQuotationStatusEnum.IN_PROGRESS.getCode());
+        }
+        salesLedgerMapper.updateById(salesLedger);
+    }
+
     private void handleShippingApprovalFinished(ApprovalInstance instance, String status) {
         ShippingInfo shippingInfo = shippingInfoMapper.selectOne(
                 new LambdaQueryWrapper<ShippingInfo>()
diff --git a/src/main/java/com/ruoyi/basic/controller/VehicleController.java b/src/main/java/com/ruoyi/basic/controller/VehicleController.java
new file mode 100644
index 0000000..16fea0b
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/controller/VehicleController.java
@@ -0,0 +1,85 @@
+package com.ruoyi.basic.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.basic.pojo.Vehicle;
+import com.ruoyi.basic.service.IVehicleService;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.R;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 杞﹁締淇℃伅Controller
+ *
+ * @author ruoyi
+ * @date 2026-09-20
+ */
+@RestController
+@RequestMapping("/basic/vehicle")
+@AllArgsConstructor
+public class VehicleController {
+    private IVehicleService vehicleService;
+
+    /**
+     * 鍒嗛〉鏌ヨ杞﹁締淇℃伅鍒楄〃
+     */
+    @GetMapping("/list")
+    public R list(Page<Vehicle> page, Vehicle vehicle) {
+        IPage<Vehicle> result = vehicleService.selectVehicleList(page, vehicle);
+        return R.ok(result);
+    }
+
+    /**
+     * 鏌ヨ鍏ㄩ儴鍚敤涓殑杞﹁締锛堜緵鍑哄簱琛ㄥ崟涓嬫媺閫夋嫨锛�
+     */
+    @GetMapping("/all")
+    public R all() {
+        return R.ok(vehicleService.selectAll());
+    }
+
+    /**
+     * 鑾峰彇杞﹁締淇℃伅璇︾粏淇℃伅
+     */
+    @GetMapping(value = "/{id}")
+    public R getInfo(@PathVariable("id") Long id) {
+        return R.ok(vehicleService.selectVehicleById(id));
+    }
+
+    /**
+     * 鏂板杞﹁締淇℃伅
+     */
+    @Log(title = "杞﹁締淇℃伅", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public R add(@RequestBody Vehicle vehicle) {
+        return R.ok(vehicleService.insertVehicle(vehicle));
+    }
+
+    /**
+     * 淇敼杞﹁締淇℃伅
+     */
+    @Log(title = "杞﹁締淇℃伅", businessType = BusinessType.UPDATE)
+    @PostMapping("/update")
+    public R edit(@RequestBody Vehicle vehicle) {
+        return R.ok(vehicleService.updateVehicle(vehicle));
+    }
+
+    /**
+     * 鍒犻櫎杞﹁締淇℃伅
+     */
+    @Log(title = "杞﹁締淇℃伅", businessType = BusinessType.DELETE)
+    @DeleteMapping("/del")
+    public R remove(@RequestBody Long[] ids) {
+        if (ids == null || ids.length == 0) {
+            return R.fail("璇蜂紶鍏ヨ鍒犻櫎鐨処D");
+        }
+        return R.ok(vehicleService.deleteVehicleByIds(ids));
+    }
+}
diff --git a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
index b0c01bb..189a31b 100644
--- a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -202,7 +202,9 @@
     APPROVAL_INSTANCE("approval_instance"),
     ACCOUNT_INVOICE_APPLICATION("account_invoice_application"),
     ACCOUNT_PURCHASE_INVOICE("account_purchase_invoice"),
-    APPROVAL_TEMPLATE("approval_template");
+    APPROVAL_TEMPLATE("approval_template"),
+    // Outsourcing
+    OUTSOURCING_PARTNER("outsourcing_partner");
 
     private final String type;
     RecordTypeEnum(String type) { this.type = type; }
diff --git a/src/main/java/com/ruoyi/basic/enums/TankUsageTypeEnum.java b/src/main/java/com/ruoyi/basic/enums/TankUsageTypeEnum.java
new file mode 100644
index 0000000..a520280
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/enums/TankUsageTypeEnum.java
@@ -0,0 +1,29 @@
+package com.ruoyi.basic.enums;
+
+import com.ruoyi.common.enums.BaseEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鍌ㄧ綈鐢ㄩ�旀灇涓撅紝瀵瑰簲 tank_info.usage_type
+ */
+@Getter
+@AllArgsConstructor
+public enum TankUsageTypeEnum implements BaseEnum<Integer> {
+
+    LEASE(1, "绉熻祦"),
+    SELF_USE(2, "鑷敤");
+
+    private final Integer value;
+    private final String label;
+
+    @Override
+    public Integer getCode() {
+        return value;
+    }
+
+    @Override
+    public String getValue() {
+        return label;
+    }
+}
diff --git a/src/main/java/com/ruoyi/basic/mapper/VehicleMapper.java b/src/main/java/com/ruoyi/basic/mapper/VehicleMapper.java
new file mode 100644
index 0000000..a4dc13e
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/mapper/VehicleMapper.java
@@ -0,0 +1,15 @@
+package com.ruoyi.basic.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.basic.pojo.Vehicle;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 杞﹁締淇℃伅Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2026-09-20
+ */
+@Mapper
+public interface VehicleMapper extends BaseMapper<Vehicle> {
+}
diff --git a/src/main/java/com/ruoyi/basic/pojo/Customer.java b/src/main/java/com/ruoyi/basic/pojo/Customer.java
index 6a8e302..768d1b6 100644
--- a/src/main/java/com/ruoyi/basic/pojo/Customer.java
+++ b/src/main/java/com/ruoyi/basic/pojo/Customer.java
@@ -1,6 +1,7 @@
 package com.ruoyi.basic.pojo;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Date;
@@ -134,4 +135,8 @@
 
     @Schema(description = "鏄惁琚垎閰嶏細0-鏈垎閰嶏紝1-宸插垎閰�")
     private Integer isAssigned;
+
+    @Schema(description = "淇℃墭鍩洪噾(鍏�)")
+    @Excel(name = "淇℃墭鍩洪噾")
+    private BigDecimal trustFund;
 }
diff --git a/src/main/java/com/ruoyi/basic/pojo/TankInfo.java b/src/main/java/com/ruoyi/basic/pojo/TankInfo.java
index 9933bc4..78730ac 100644
--- a/src/main/java/com/ruoyi/basic/pojo/TankInfo.java
+++ b/src/main/java/com/ruoyi/basic/pojo/TankInfo.java
@@ -54,7 +54,17 @@
     private BigDecimal remaining;
 
     /**
-     * 绉熷淇℃伅
+     * 鏄惁鍚敤 1鍚敤 0鏈惎鐢�
+     */
+    private Integer isEnabled;
+
+    /**
+     * 鐢ㄩ�� 1绉熻祦 2鑷敤锛屽惎鐢ㄥ悗鎵嶉渶瑕侀��
+     */
+    private Integer usageType;
+
+    /**
+     * 绉熷淇℃伅锛屼粎鐢ㄩ��=绉熻祦鏃跺~鍐�
      */
     private String tenantInfo;
 
diff --git a/src/main/java/com/ruoyi/basic/pojo/Vehicle.java b/src/main/java/com/ruoyi/basic/pojo/Vehicle.java
new file mode 100644
index 0000000..d18db49
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/pojo/Vehicle.java
@@ -0,0 +1,96 @@
+package com.ruoyi.basic.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 杞﹁締淇℃伅瀵硅薄 vehicle
+ *
+ * @author ruoyi
+ * @date 2026-09-20
+ */
+@TableName(value = "vehicle")
+@Data
+public class Vehicle implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 杞﹁締id
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 杞︾墝鍙凤紙杞﹁締鍞竴鏍囪瘑锛�
+     */
+    private String plateNumber;
+
+    /**
+     * 榛樿鍙告満
+     */
+    private String driverName;
+
+    /**
+     * 鍙告満鐢佃瘽
+     */
+    private String driverPhone;
+
+    /**
+     * 鏍稿畾杞介噸(鍚�)
+     */
+    private BigDecimal loadCapacity;
+
+    /**
+     * 鐘舵�侊細鍚敤/鍋滅敤
+     */
+    private String status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    /**
+     * 绉熸埛id
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    /**
+     * 閮ㄩ棬id
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/basic/service/IVehicleService.java b/src/main/java/com/ruoyi/basic/service/IVehicleService.java
new file mode 100644
index 0000000..78ebd79
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/service/IVehicleService.java
@@ -0,0 +1,65 @@
+package com.ruoyi.basic.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.basic.pojo.Vehicle;
+
+import java.util.List;
+
+/**
+ * 杞﹁締淇℃伅Service鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2026-09-20
+ */
+public interface IVehicleService extends IService<Vehicle> {
+
+    /**
+     * 鍒嗛〉鏌ヨ杞﹁締淇℃伅鍒楄〃
+     *
+     * @param page    鍒嗛〉鍙傛暟
+     * @param vehicle 鏌ヨ鏉′欢
+     * @return 杞﹁締淇℃伅闆嗗悎
+     */
+    IPage<Vehicle> selectVehicleList(Page<Vehicle> page, Vehicle vehicle);
+
+    /**
+     * 鏌ヨ鍏ㄩ儴鍚敤涓殑杞﹁締锛堜緵鍑哄簱琛ㄥ崟涓嬫媺閫夋嫨锛�
+     *
+     * @return 杞﹁締淇℃伅闆嗗悎
+     */
+    List<Vehicle> selectAll();
+
+    /**
+     * 鏌ヨ杞﹁締淇℃伅
+     *
+     * @param id 杞﹁締淇℃伅涓婚敭
+     * @return 杞﹁締淇℃伅
+     */
+    Vehicle selectVehicleById(Long id);
+
+    /**
+     * 鏂板杞﹁締淇℃伅
+     *
+     * @param vehicle 杞﹁締淇℃伅
+     * @return 缁撴灉
+     */
+    boolean insertVehicle(Vehicle vehicle);
+
+    /**
+     * 淇敼杞﹁締淇℃伅
+     *
+     * @param vehicle 杞﹁締淇℃伅
+     * @return 缁撴灉
+     */
+    boolean updateVehicle(Vehicle vehicle);
+
+    /**
+     * 鎵归噺鍒犻櫎杞﹁締淇℃伅
+     *
+     * @param ids 闇�瑕佸垹闄ょ殑杞﹁締淇℃伅涓婚敭闆嗗悎
+     * @return 缁撴灉
+     */
+    boolean deleteVehicleByIds(Long[] ids);
+}
diff --git a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
index 5a4d19a..78cb8ba 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -23,6 +23,7 @@
 import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
 import com.ruoyi.procurementrecord.pojo.ReturnManagement;
+import com.ruoyi.sales.dto.CustomerDepositAmountDto;
 import com.ruoyi.sales.mapper.SalesLedgerMapper;
 import com.ruoyi.sales.mapper.SalesQuotationMapper;
 import com.ruoyi.sales.pojo.SalesLedger;
@@ -38,6 +39,7 @@
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.math.BigDecimal;
 import java.time.ZoneId;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -117,7 +119,37 @@
             customerVo.setFollowUpList(followUpDtoList);
         }
 
+        fillDepositAmount(Collections.singletonList(customerVo));
+
         return customerVo;
+    }
+
+    /**
+     * 濉唬鍌ㄩ噾棰濓紙璇ュ鎴峰叏閮ㄣ�岀被鍨�=浠e偍銆嶇殑閿�鍞彴璐﹀悎鍚岄噾棰濈疮璁★級骞跺垽鏂槸鍚﹀凡瓒呬俊鎵樺熀閲�
+     */
+    private void fillDepositAmount(List<CustomerVo> records) {
+        if (CollectionUtils.isEmpty(records)) {
+            return;
+        }
+        List<Long> customerIds = records.stream()
+                .map(CustomerVo::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        Map<Long, BigDecimal> depositAmountMap = customerIds.isEmpty()
+                ? Collections.emptyMap()
+                : salesLedgerMapper.selectDepositAmountByCustomerIds(customerIds).stream()
+                .filter(item -> item.getCustomerId() != null)
+                .collect(Collectors.toMap(
+                        CustomerDepositAmountDto::getCustomerId,
+                        item -> item.getDepositTotalAmount() == null ? BigDecimal.ZERO : item.getDepositTotalAmount(),
+                        (existing, replacement) -> existing));
+
+        records.forEach(customer -> {
+            BigDecimal depositTotalAmount = depositAmountMap.getOrDefault(customer.getId(), BigDecimal.ZERO);
+            customer.setDepositTotalAmount(depositTotalAmount);
+            customer.setExceedsTrustFund(customer.getTrustFund() != null
+                    && depositTotalAmount.compareTo(customer.getTrustFund()) > 0);
+        });
     }
 
     /**
@@ -168,6 +200,8 @@
                     c.setUserIds(userIds);
                 }
             });
+
+            fillDepositAmount(records);
         }
 
         return customerPage;
diff --git a/src/main/java/com/ruoyi/basic/service/impl/TankInfoServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/TankInfoServiceImpl.java
index fa909e7..ece224e 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/TankInfoServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/TankInfoServiceImpl.java
@@ -2,11 +2,14 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.enums.TankUsageTypeEnum;
 import com.ruoyi.basic.mapper.TankInfoMapper;
 import com.ruoyi.basic.pojo.TankInfo;
 import com.ruoyi.basic.service.ITankInfoService;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import org.springframework.stereotype.Service;
 
@@ -21,19 +24,30 @@
 @Service
 public class TankInfoServiceImpl extends ServiceImpl<TankInfoMapper, TankInfo> implements ITankInfoService {
 
+    /**
+     * 鏄惁鍚敤锛氬惎鐢ㄣ�備笌 tank_info.is_enabled 鐨勯粯璁ゅ�间竴鑷�
+     */
+    private static final Integer TANK_ENABLED = 1;
+
     @Override
     public IPage<TankInfo> selectTankInfoList(Page<TankInfo> page, TankInfo tankInfo) {
         LambdaQueryWrapper<TankInfo> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.like(StringUtils.isNotBlank(tankInfo.getTankNo()), TankInfo::getTankNo, tankInfo.getTankNo())
                 .like(StringUtils.isNotBlank(tankInfo.getCategory()), TankInfo::getCategory, tankInfo.getCategory())
+                .eq(tankInfo.getIsEnabled() != null, TankInfo::getIsEnabled, tankInfo.getIsEnabled())
+                .eq(tankInfo.getUsageType() != null, TankInfo::getUsageType, tankInfo.getUsageType())
                 .orderByAsc(TankInfo::getId);
         return this.page(page, queryWrapper);
     }
 
+    /**
+     * 涓嬫媺鐢細鍙繑鍥炲凡鍚敤鐨勫偍缃愩�傛湭鍚敤琛ㄧず杩樻病鎶曠敤锛屼笉搴旇鍏ュ簱绛変笟鍔¢�変腑
+     */
     @Override
     public List<TankInfo> selectAll() {
         LambdaQueryWrapper<TankInfo> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.orderByAsc(TankInfo::getId);
+        queryWrapper.eq(TankInfo::getIsEnabled, TANK_ENABLED)
+                .orderByAsc(TankInfo::getId);
         return this.list(queryWrapper);
     }
 
@@ -44,12 +58,62 @@
 
     @Override
     public boolean insertTankInfo(TankInfo tankInfo) {
+        normalizeUsage(tankInfo);
         return this.save(tankInfo);
     }
 
     @Override
     public boolean updateTankInfo(TankInfo tankInfo) {
-        return this.updateById(tankInfo);
+        if (tankInfo.getId() == null) {
+            throw new ServiceException("缂哄皯鍌ㄧ綈id");
+        }
+        normalizeUsage(tankInfo);
+        boolean updated = this.updateById(tankInfo);
+        if (updated) {
+            // usageType / tenantInfo 鏄厑璁歌娓呯┖鐨勶紝鑰� updateById 榛樿璺宠繃 null 瀛楁锛團ieldStrategy.NOT_NULL锛夛紝
+            // 娓呯┖鍔ㄤ綔鏍规湰鍐欎笉杩涘簱锛屾墍浠ヨ繖涓ゅ垪鍗曠嫭琛ヤ竴鏉℃洿鏂�
+            updated = this.update(Wrappers.<TankInfo>lambdaUpdate()
+                    .eq(TankInfo::getId, tankInfo.getId())
+                    .set(TankInfo::getUsageType, tankInfo.getUsageType())
+                    .set(TankInfo::getTenantInfo, tankInfo.getTenantInfo()));
+        }
+        return updated;
+    }
+
+    /**
+     * 褰掍竴鍖栥�屾槸鍚﹀惎鐢� / 鐢ㄩ�� / 绉熷淇℃伅銆嶄笁鑰呯殑鍏崇郴锛�
+     * <ul>
+     *     <li>鏈惎鐢細鐢ㄩ�斾笌绉熷淇℃伅涓�寰嬫竻绌� 鈥斺�� 鍚敤涔嬪悗鎵嶈皥绉熻祦/鑷敤</li>
+     *     <li>鍚敤 + 绉熻祦锛氱瀹俊鎭繀濉�</li>
+     *     <li>鍚敤 + 鑷敤锛氱瀹俊鎭竻绌�</li>
+     *     <li>鍚敤 + 鐢ㄩ�斾负绌猴細瑙嗕负鏈�夋嫨锛屾棦涓嶆牎楠屼篃涓嶆竻绌恒�傝縼绉诲悗鐨勫瓨閲忓偍缃愬氨鏄繖涓姸鎬侊紝
+     *         杩欐牱鑰佸墠绔笉鏀逛篃涓嶄細琚嫤锛屾洿涓嶄細鎶婂凡鏈夌殑绉熷淇℃伅鎶规帀</li>
+     * </ul>
+     */
+    private void normalizeUsage(TankInfo tankInfo) {
+        if (tankInfo.getIsEnabled() == null) {
+            tankInfo.setIsEnabled(TANK_ENABLED);
+        }
+        if (!TANK_ENABLED.equals(tankInfo.getIsEnabled())) {
+            tankInfo.setUsageType(null);
+            tankInfo.setTenantInfo(null);
+            return;
+        }
+        Integer usageType = tankInfo.getUsageType();
+        if (usageType == null) {
+            return;
+        }
+        if (TankUsageTypeEnum.LEASE.getCode().equals(usageType)) {
+            if (StringUtils.isBlank(tankInfo.getTenantInfo())) {
+                throw new ServiceException("绉熻祦鐨勫偍缃愬繀椤诲~鍐欑瀹俊鎭�");
+            }
+            return;
+        }
+        if (TankUsageTypeEnum.SELF_USE.getCode().equals(usageType)) {
+            tankInfo.setTenantInfo(null);
+            return;
+        }
+        throw new ServiceException("鍌ㄧ綈鐢ㄩ�斿彧鑳芥槸绉熻祦鎴栬嚜鐢�");
     }
 
     @Override
diff --git a/src/main/java/com/ruoyi/basic/service/impl/VehicleServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/VehicleServiceImpl.java
new file mode 100644
index 0000000..7b0c8d9
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/service/impl/VehicleServiceImpl.java
@@ -0,0 +1,89 @@
+package com.ruoyi.basic.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.basic.mapper.VehicleMapper;
+import com.ruoyi.basic.pojo.Vehicle;
+import com.ruoyi.basic.service.IVehicleService;
+import com.ruoyi.common.exception.base.BaseException;
+import com.ruoyi.common.utils.StringUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 杞﹁締淇℃伅Service涓氬姟灞傚鐞�
+ *
+ * @author ruoyi
+ * @date 2026-09-20
+ */
+@Service
+public class VehicleServiceImpl extends ServiceImpl<VehicleMapper, Vehicle> implements IVehicleService {
+
+    /**
+     * 杞﹁締鐘舵�侊細鍋滅敤銆傚仠鐢ㄧ殑杞﹁締涓嶅啀鍑虹幇鍦ㄥ嚭搴撹〃鍗曠殑涓嬫媺閲�
+     */
+    private static final String STATUS_DISABLED = "鍋滅敤";
+
+    @Override
+    public IPage<Vehicle> selectVehicleList(Page<Vehicle> page, Vehicle vehicle) {
+        LambdaQueryWrapper<Vehicle> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.like(StringUtils.isNotBlank(vehicle.getPlateNumber()), Vehicle::getPlateNumber, vehicle.getPlateNumber())
+                .like(StringUtils.isNotBlank(vehicle.getDriverName()), Vehicle::getDriverName, vehicle.getDriverName())
+                .eq(StringUtils.isNotBlank(vehicle.getStatus()), Vehicle::getStatus, vehicle.getStatus())
+                .orderByAsc(Vehicle::getId);
+        return this.page(page, queryWrapper);
+    }
+
+    @Override
+    public List<Vehicle> selectAll() {
+        LambdaQueryWrapper<Vehicle> queryWrapper = new LambdaQueryWrapper<>();
+        // status 鍏佽涓虹┖锛岃涓哄惎鐢�
+        queryWrapper.and(w -> w.isNull(Vehicle::getStatus).or().ne(Vehicle::getStatus, STATUS_DISABLED))
+                .orderByAsc(Vehicle::getId);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public Vehicle selectVehicleById(Long id) {
+        return this.getById(id);
+    }
+
+    @Override
+    public boolean insertVehicle(Vehicle vehicle) {
+        if (StringUtils.isBlank(vehicle.getPlateNumber())) {
+            throw new BaseException("杞︾墝鍙蜂笉鑳戒负绌�");
+        }
+        checkPlateNumberUnique(vehicle);
+        return this.save(vehicle);
+    }
+
+    @Override
+    public boolean updateVehicle(Vehicle vehicle) {
+        if (StringUtils.isNotBlank(vehicle.getPlateNumber())) {
+            checkPlateNumberUnique(vehicle);
+        }
+        return this.updateById(vehicle);
+    }
+
+    @Override
+    public boolean deleteVehicleByIds(Long[] ids) {
+        return this.removeByIds(List.of(ids));
+    }
+
+    /**
+     * 杞︾墝鍙锋槸杞﹁締鐨勫敮涓�鏍囪瘑锛屼笉鑳介噸澶�
+     */
+    private void checkPlateNumberUnique(Vehicle vehicle) {
+        LambdaQueryWrapper<Vehicle> queryWrapper = new LambdaQueryWrapper<Vehicle>()
+                .eq(Vehicle::getPlateNumber, vehicle.getPlateNumber());
+        if (vehicle.getId() != null) {
+            queryWrapper.ne(Vehicle::getId, vehicle.getId());
+        }
+        if (this.count(queryWrapper) > 0) {
+            throw new BaseException("杞︾墝鍙峰凡瀛樺湪:" + vehicle.getPlateNumber());
+        }
+    }
+}
diff --git a/src/main/java/com/ruoyi/basic/vo/CustomerVo.java b/src/main/java/com/ruoyi/basic/vo/CustomerVo.java
index 0f72a2f..e99002e 100644
--- a/src/main/java/com/ruoyi/basic/vo/CustomerVo.java
+++ b/src/main/java/com/ruoyi/basic/vo/CustomerVo.java
@@ -5,6 +5,7 @@
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 @Data
@@ -30,4 +31,10 @@
      * 鏄惁琚攢鍞ā鍧楀紩鐢細0-鍚︼紝1-鏄�
      */
     private Integer salesReferenceFlag;
+
+    @ApiModelProperty(value = "璇ュ鎴蜂唬鍌ㄩ噾棰濆悎璁★紙鎵�鏈夌被鍨�=浠e偍鐨勯攢鍞彴璐﹀悎鍚岄噾棰濈疮璁★級")
+    private BigDecimal depositTotalAmount;
+
+    @ApiModelProperty(value = "浠e偍閲戦鏄惁宸茶秴鍑轰俊鎵樺熀閲�")
+    private Boolean exceedsTrustFund;
 }
diff --git a/src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java b/src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java
index 9f6a258..36396f3 100644
--- a/src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java
+++ b/src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java
@@ -39,6 +39,8 @@
         IGNORE_TABLES.add("product_model");
         IGNORE_TABLES.add("ai_chat_session");
         IGNORE_TABLES.add("product");
+        // 杞﹁締鏄叕鍙哥骇涓绘暟鎹紝鍑哄簱琛ㄥ崟鐨勪笅鎷夊繀椤昏兘鐪嬪埌鍏ㄩ儴杞﹁締锛屼笉鍋氶儴闂�/鍒涘缓浜鸿繃婊�
+        IGNORE_TABLES.add("vehicle");
 
     }
 }
diff --git a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
index a27680a..0099a20 100644
--- a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
@@ -11,7 +11,8 @@
     CUSTOMIZATION_UNSTOCK_OUT("10", "涓嶅悎鏍艰嚜瀹氫箟鍑哄簱"),
     SALE_SHIP_STOCK_OUT("13", "閿�鍞�-鍙戣揣鍑哄簱"),
     PICK_STOCK_OUT("14", "鐢熶骇棰嗘枡鍑哄簱"),
-    FEED_STOCK_OUT("15", "鐢熶骇琛ユ枡鍑哄簱");
+    FEED_STOCK_OUT("15", "鐢熶骇琛ユ枡鍑哄簱"),
+    OUTSOURCING_SCRAP_OUT("30", "濮斿-鎶ュ簾鍝佸嚭搴�");
 
     private final String code;
     private final String value;
diff --git a/src/main/java/com/ruoyi/common/enums/TypeEnums.java b/src/main/java/com/ruoyi/common/enums/TypeEnums.java
index ba1c1f3..1a0b6f6 100644
--- a/src/main/java/com/ruoyi/common/enums/TypeEnums.java
+++ b/src/main/java/com/ruoyi/common/enums/TypeEnums.java
@@ -24,7 +24,8 @@
     OVERTIME_APPROVAL(15L, "鍔犵彮瀹℃壒"),
     TRAVEL_REIMBURSEMENT_APPROVAL(16L, "鍑哄樊鎶ラ攢瀹℃壒"),
     EXPENSE_APPROVAL(17L, "璐圭敤瀹℃壒"),
-    ENTERPRISE_NEWS_APPROVAL(18L, "浼佷笟鏂伴椈瀹℃壒");
+    ENTERPRISE_NEWS_APPROVAL(18L, "浼佷笟鏂伴椈瀹℃壒"),
+    SALES_LEDGER_APPROVAL(19L, "閿�鍞彴璐﹀鎵�");
 
 
 
diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
index a2f613f..c9c70ce 100644
--- a/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
@@ -95,6 +95,8 @@
                         outRecord.setQuantity(new BigDecimal(sparePartUse.getQuantity()));
                         outRecord.setSourceType(3);
                         outRecord.setSourceId(deviceMaintenance.getId());
+                        // 淇濆吇鍗曟病鏈夊崟鍙凤紝鏉ユ簮鍗曞彿鐢ㄨ澶囧悕绉帮紝娴佹按椤垫墠鐪嬪緱鍑哄浠剁敤鍦ㄥ摢鍙拌澶囦笂
+                        outRecord.setSourceNo(oldDeviceMaintenance.getDeviceName());
                         sparePartsRecordService.save(outRecord);
                     } else {
                         return AjaxResult.error("澶囦欢 " + spareParts.getName() + " 鏁伴噺涓嶈冻");
diff --git a/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
index a74e327..8af7912 100644
--- a/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
@@ -125,6 +125,8 @@
                         outRecord.setQuantity(new BigDecimal(sparePartUse.getQuantity()));
                         outRecord.setSourceType(2);
                         outRecord.setSourceId(deviceRepairDto.getId());
+                        // 鎶ヤ慨鍗曟病鏈夊崟鍙凤紝鏉ユ簮鍗曞彿鐢ㄨ澶囧悕绉帮紝娴佹按椤垫墠鐪嬪緱鍑哄浠剁敤鍦ㄥ摢鍙拌澶囦笂
+                        outRecord.setSourceNo(oldDeviceRepair.getDeviceName());
                         sparePartsRecordService.save(outRecord);
                     } else {
                         return AjaxResult.error("澶囦欢 " + spareParts.getName() + " 鏁伴噺涓嶈冻");
diff --git a/src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java b/src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java
index 92b7cb8..6e4ba12 100644
--- a/src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java
+++ b/src/main/java/com/ruoyi/mock/service/impl/DataGenerateServiceImpl.java
@@ -557,6 +557,8 @@
                 dto.setSalesman(item.getString("salesman"));
                 dto.setPaymentMethod(item.getString("paymentMethod"));
                 dto.setType(item.getInteger("type"));
+                // 妯℃嫙鏁版嵁鐢熸垚鐨勫彴璐﹂兘甯︿骇鍝佹槑缁嗭紝鎸夊崠娌瑰鐞嗭紙鍙拌处鏂板宸茶姹傚繀濉攢鍞被鍨嬶級
+                dto.setLedgerType("鍗栨补");
 
                 // 褰曞叆浜猴細浼樺厛鐢ˋI杩斿洖鐨勶紝鍚﹀垯浠�"閿�鍞�"瑙掕壊/閮ㄩ棬闅忔満閫�
                 String entryPerson = item.getString("entryPerson");
diff --git a/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingOrderController.java b/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingOrderController.java
new file mode 100644
index 0000000..6fdf217
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingOrderController.java
@@ -0,0 +1,141 @@
+package com.ruoyi.outsourcing.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.controller.BaseController;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.page.TableDataInfo;
+import com.ruoyi.outsourcing.dto.OutsourcingOrderDto;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrder;
+import com.ruoyi.outsourcing.service.IOutsourcingOrderService;
+import com.ruoyi.stock.dto.StockInventoryDto;
+import com.ruoyi.stock.service.StockInventoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 濮斿璁㈠崟 Controller
+ */
+@RestController
+@RequestMapping("/outsourcing/order")
+@AllArgsConstructor
+@Tag(name = "濮斿璁㈠崟")
+public class OutsourcingOrderController extends BaseController {
+
+    private final IOutsourcingOrderService outsourcingOrderService;
+    private final StockInventoryService stockInventoryService;
+
+    /**
+     * 濮斿璁㈠崟鍒楄〃
+     */
+    @Operation(summary = "濮斿璁㈠崟鍒楄〃")
+    @GetMapping("/list")
+    public TableDataInfo list(OutsourcingOrderDto dto) {
+        startPage();
+        List<OutsourcingOrder> list = outsourcingOrderService.selectOrderList(dto);
+        return getDataTable(list);
+    }
+
+    /**
+     * 濮斿璁㈠崟璇︽儏锛堝惈浜у搧鏄庣粏锛�
+     */
+    @Operation(summary = "濮斿璁㈠崟璇︽儏")
+    @GetMapping("/getById")
+    public AjaxResult getById(@RequestParam Long id) {
+        return AjaxResult.success(outsourcingOrderService.getDetail(id));
+    }
+
+    /**
+     * 鏂板濮斿璁㈠崟锛堣崏绋匡級
+     */
+    @Operation(summary = "鏂板濮斿璁㈠崟")
+    @Log(title = "濮斿璁㈠崟", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody OutsourcingOrderDto dto) {
+        return AjaxResult.success(outsourcingOrderService.saveOrder(dto));
+    }
+
+    /**
+     * 淇敼濮斿璁㈠崟锛屼粎鑽夌鐘舵�佸厑璁�
+     */
+    @Operation(summary = "淇敼濮斿璁㈠崟")
+    @Log(title = "濮斿璁㈠崟", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    public AjaxResult edit(@RequestBody OutsourcingOrderDto dto) {
+        return toAjax(outsourcingOrderService.updateOrder(dto));
+    }
+
+    /**
+     * 鍒犻櫎濮斿璁㈠崟锛屼粎鑽夌鐘舵�佸厑璁�
+     */
+    @Operation(summary = "鍒犻櫎濮斿璁㈠崟")
+    @Log(title = "濮斿璁㈠崟", businessType = BusinessType.DELETE)
+    @DeleteMapping("/delete")
+    public AjaxResult delete(@RequestBody Long[] ids) {
+        return toAjax(outsourcingOrderService.deleteOrders(ids == null ? List.of() : List.of(ids)));
+    }
+
+    /**
+     * 鎻愪氦濮斿璁㈠崟锛氳崏绋� -> 宸茬‘璁�
+     */
+    @Operation(summary = "鎻愪氦濮斿璁㈠崟")
+    @Log(title = "濮斿璁㈠崟鎻愪氦", businessType = BusinessType.OTHER)
+    @PostMapping("/submit")
+    public AjaxResult submit(@RequestParam Long id) {
+        return toAjax(outsourcingOrderService.submit(id));
+    }
+
+    /**
+     * 瀹屾垚濮斿璁㈠崟锛氬凡纭 -> 宸插畬鎴�
+     */
+    @Operation(summary = "瀹屾垚濮斿璁㈠崟")
+    @Log(title = "濮斿璁㈠崟瀹屾垚", businessType = BusinessType.OTHER)
+    @PostMapping("/complete")
+    public AjaxResult complete(@RequestParam Long id) {
+        return toAjax(outsourcingOrderService.complete(id));
+    }
+
+    /**
+     * 浣滃簾濮斿璁㈠崟锛氳崏绋�/宸茬‘璁� -> 宸蹭綔搴�
+     */
+    @Operation(summary = "浣滃簾濮斿璁㈠崟")
+    @Log(title = "濮斿璁㈠崟浣滃簾", businessType = BusinessType.OTHER)
+    @PostMapping("/cancel")
+    public AjaxResult cancel(@RequestParam Long id,
+                             @RequestParam(required = false) String cancelReason) {
+        return toAjax(outsourcingOrderService.cancel(id, cancelReason));
+    }
+
+    /**
+     * 濮斿璁㈠崟瀵煎嚭
+     */
+    @Operation(summary = "濮斿璁㈠崟瀵煎嚭")
+    @Log(title = "濮斿璁㈠崟", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, OutsourcingOrderDto dto) {
+        outsourcingOrderService.exportOrders(response, dto);
+    }
+
+    /**
+     * 鎶ュ簾鍝侀�夋枡鍒嗛〉锛堝悎鏍煎簱瀛橈紝鍙锛屼笉鍗犵敤銆佷笉鍥炲啓搴撳瓨锛�
+     */
+    @Operation(summary = "鎶ュ簾鍝侀�夋枡鍒嗛〉")
+    @GetMapping("/scrapStockPage")
+    public AjaxResult scrapStockPage(Page page, StockInventoryDto dto) {
+        IPage<StockInventoryDto> result = stockInventoryService.pageScrapStock(page, dto);
+        return AjaxResult.success(result);
+    }
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerController.java b/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerController.java
new file mode 100644
index 0000000..bc5c5e7
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerController.java
@@ -0,0 +1,133 @@
+package com.ruoyi.outsourcing.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.controller.BaseController;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.outsourcing.dto.OutsourcingPartnerDto;
+import com.ruoyi.outsourcing.excel.OutsourcingPartnerExcelDto;
+import com.ruoyi.outsourcing.service.IOutsourcingPartnerService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗� Controller
+ */
+@RestController
+@RequestMapping("/outsourcing/partner")
+@AllArgsConstructor
+@Tag(name = "濮斿鍚堜綔鍟嗘。妗�")
+public class OutsourcingPartnerController extends BaseController {
+
+    private final IOutsourcingPartnerService outsourcingPartnerService;
+
+    /**
+     * 鏂板鍚堜綔鍟嗭紙鍚檮浠讹級
+     */
+    @Operation(summary = "鏂板鍚堜綔鍟�")
+    @Log(title = "鍚堜綔鍟嗘。妗�", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    public AjaxResult add(@RequestBody OutsourcingPartnerDto dto) {
+        return AjaxResult.success(outsourcingPartnerService.savePartner(dto));
+    }
+
+    /**
+     * 淇敼鍚堜綔鍟嗭紙鍚檮浠讹級
+     */
+    @Operation(summary = "淇敼鍚堜綔鍟�")
+    @Log(title = "鍚堜綔鍟嗘。妗�", businessType = BusinessType.UPDATE)
+    @PostMapping("/update")
+    public AjaxResult update(@RequestBody OutsourcingPartnerDto dto) {
+        return toAjax(outsourcingPartnerService.updatePartner(dto));
+    }
+
+    /**
+     * 鍒犻櫎鍚堜綔鍟�
+     */
+    @Operation(summary = "鍒犻櫎鍚堜綔鍟�")
+    @Log(title = "鍚堜綔鍟嗘。妗�", businessType = BusinessType.DELETE)
+    @DeleteMapping("/del")
+    public AjaxResult del(@RequestBody List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        return toAjax(outsourcingPartnerService.delPartner(ids));
+    }
+
+    /**
+     * 鍚堜綔鍟嗚鎯咃紙鍚檮浠跺洖鏄撅級
+     */
+    @Operation(summary = "鍚堜綔鍟嗚鎯�")
+    @GetMapping("/{id}")
+    public AjaxResult detail(@PathVariable("id") Long id) {
+        return AjaxResult.success(outsourcingPartnerService.partnerDetail(id));
+    }
+
+    /**
+     * 鍚堜綔鍟嗗垎椤垫煡璇�
+     */
+    @Operation(summary = "鍚堜綔鍟嗗垎椤垫煡璇�")
+    @GetMapping("/listPage")
+    public AjaxResult listPage(Page page, OutsourcingPartnerDto dto) {
+        IPage<OutsourcingPartnerDto> result = outsourcingPartnerService.partnerListPage(page, dto);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 鍚堜綔鍟嗗鍑�
+     */
+    @Operation(summary = "鍚堜綔鍟嗗鍑�")
+    @Log(title = "鍚堜綔鍟嗘。妗�", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, OutsourcingPartnerDto dto) {
+        outsourcingPartnerService.partnerExport(response, dto);
+    }
+
+    /**
+     * 涓嬭浇瀵煎叆妯℃澘
+     */
+    @Operation(summary = "涓嬭浇鍚堜綔鍟嗗鍏ユā鏉�")
+    @Log(title = "鍚堜綔鍟嗘。妗�-涓嬭浇妯℃澘", businessType = BusinessType.EXPORT)
+    @PostMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        ExcelUtil<OutsourcingPartnerExcelDto> util = new ExcelUtil<>(OutsourcingPartnerExcelDto.class);
+        util.importTemplateExcel(response, "鍚堜綔鍟嗘。妗堟ā鏉�");
+    }
+
+    /**
+     * 鍚堜綔鍟嗗鍏�
+     */
+    @Operation(summary = "鍚堜綔鍟嗗鍏�")
+    @Log(title = "鍚堜綔鍟嗘。妗堝鍏�", businessType = BusinessType.IMPORT)
+    @PostMapping("/import")
+    public AjaxResult importData(MultipartFile file) {
+        return outsourcingPartnerService.importData(file)
+                ? AjaxResult.success("瀵煎叆鎴愬姛")
+                : AjaxResult.error("瀵煎叆澶辫触");
+    }
+
+    /**
+     * 鍚堜綔鍟嗕笅鎷夐�夐」
+     */
+    @Operation(summary = "鍚堜綔鍟嗕笅鎷夐�夐」")
+    @GetMapping("/getOptions")
+    public AjaxResult getOptions() {
+        return AjaxResult.success(outsourcingPartnerService.options());
+    }
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerReportController.java b/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerReportController.java
new file mode 100644
index 0000000..102a8d9
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/controller/OutsourcingPartnerReportController.java
@@ -0,0 +1,53 @@
+package com.ruoyi.outsourcing.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.outsourcing.service.IOutsourcingPartnerService;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsDetailsVo;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsVo;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 鍚堜綔鍟嗗線鏉ユ姤琛� Controller
+ *
+ * <p>搴斾粯鍙e緞锛氬澶栬鍗曢噾棰濓紙status in (1,2)锛夆垝 宸蹭粯娆撅紙payment_record party_type=3 and direction=1锛夈��
+ * 鑽夌涓庡凡浣滃簾璁㈠崟涓嶈鍏ュ簲浠樸��</p>
+ */
+@RestController
+@RequestMapping("/outsourcing/report")
+@AllArgsConstructor
+@Tag(name = "鍚堜綔鍟嗗線鏉�")
+public class OutsourcingPartnerReportController {
+
+    private final IOutsourcingPartnerService outsourcingPartnerService;
+
+    /**
+     * 鍚堜綔鍟嗗線鏉ユ眹鎬诲垪琛�
+     */
+    @Operation(summary = "鍚堜綔鍟嗗線鏉ユ眹鎬�")
+    @GetMapping("/partnerTransactions")
+    public AjaxResult partnerTransactions(Page page,
+                                         @RequestParam(required = false) String partnerName) {
+        IPage<OutsourcingPartnerTransactionsVo> result =
+                outsourcingPartnerService.partnerTransactions(page, partnerName);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 鍚堜綔鍟嗗線鏉ユ槑缁嗭紙閫愬澶栬鍗曪級
+     */
+    @Operation(summary = "鍚堜綔鍟嗗線鏉ユ槑缁�")
+    @GetMapping("/partnerTransactionsDetails")
+    public AjaxResult partnerTransactionsDetails(Page page, @RequestParam Long partnerId) {
+        IPage<OutsourcingPartnerTransactionsDetailsVo> result =
+                outsourcingPartnerService.partnerTransactionsDetails(page, partnerId);
+        return AjaxResult.success(result);
+    }
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/dto/OutsourcingOrderDto.java b/src/main/java/com/ruoyi/outsourcing/dto/OutsourcingOrderDto.java
new file mode 100644
index 0000000..5cd9937
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/dto/OutsourcingOrderDto.java
@@ -0,0 +1,67 @@
+package com.ruoyi.outsourcing.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrderProduct;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 濮斿璁㈠崟 DTO
+ */
+@Data
+public class OutsourcingOrderDto {
+
+    @Schema(description = "璁㈠崟涓婚敭")
+    private Long id;
+
+    @Schema(description = "濮斿璁㈠崟鍙�")
+    private String orderNo;
+
+    @Schema(description = "鍚堜綔鍟唅d")
+    private Long partnerId;
+
+    @Schema(description = "鍚堜綔鍟嗗悕绉�")
+    private String partnerName;
+
+    @Schema(description = "鐢宠鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate applyDate;
+
+    @Schema(description = "鏈熸湜浜や粯鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate expectedDeliveryDate;
+
+    @Schema(description = "椤圭洰鍚嶇О")
+    private String projectName;
+
+    @Schema(description = "濮斿閲戦锛堝惈绋庢�讳环锛�")
+    private BigDecimal contractAmount;
+
+    @Schema(description = "浠樻鏂瑰紡")
+    private String paymentMethod;
+
+    @Schema(description = "澶囨敞")
+    private String remarks;
+
+    @Schema(description = "鐘舵�� 0鑽夌 1宸茬‘璁� 2宸插畬鎴� 3宸蹭綔搴�")
+    private Integer status;
+
+    @Schema(description = "浣滃簾鍘熷洜")
+    private String cancelReason;
+
+    @Schema(description = "鐢宠鏃ユ湡鏌ヨ寮�濮嬶紙yyyy-MM-dd锛�")
+    private String startDate;
+
+    @Schema(description = "鐢宠鏃ユ湡鏌ヨ缁撴潫锛坹yyy-MM-dd锛�")
+    private String endDate;
+
+    @Schema(description = "濮斿浜у搧鏄庣粏")
+    private List<OutsourcingOrderProduct> productData;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/dto/OutsourcingPartnerDto.java b/src/main/java/com/ruoyi/outsourcing/dto/OutsourcingPartnerDto.java
new file mode 100644
index 0000000..df4c8e4
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/dto/OutsourcingPartnerDto.java
@@ -0,0 +1,23 @@
+package com.ruoyi.outsourcing.dto;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.outsourcing.pojo.OutsourcingPartner;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗� DTO锛堟煡璇㈡潯浠� + 鏂板/淇敼鍏ュ弬锛�
+ */
+@Data
+public class OutsourcingPartnerDto extends OutsourcingPartner {
+
+    @Schema(description = "缁存姢浜哄憳鍚嶇О")
+    private String maintainUserName;
+
+    @TableField(exist = false)
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobDTO> storageBlobDTOS;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/enums/OutsourcingOrderStatusEnum.java b/src/main/java/com/ruoyi/outsourcing/enums/OutsourcingOrderStatusEnum.java
new file mode 100644
index 0000000..9056120
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/enums/OutsourcingOrderStatusEnum.java
@@ -0,0 +1,43 @@
+package com.ruoyi.outsourcing.enums;
+
+import com.ruoyi.common.enums.BaseEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 濮斿璁㈠崟鐘舵�佹灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum OutsourcingOrderStatusEnum implements BaseEnum<Integer> {
+
+    DRAFT(0, "鑽夌"),
+    CONFIRMED(1, "宸茬‘璁�"),
+    COMPLETED(2, "宸插畬鎴�"),
+    CANCELLED(3, "宸蹭綔搴�");
+
+    private final Integer value;
+    private final String label;
+
+    @Override
+    public Integer getCode() {
+        return value;
+    }
+
+    @Override
+    public String getValue() {
+        return label;
+    }
+
+    public static String getLabelByValue(Integer value) {
+        if (value == null) {
+            return "鏈煡鐘舵��";
+        }
+        for (OutsourcingOrderStatusEnum status : values()) {
+            if (status.getCode().equals(value)) {
+                return status.getValue();
+            }
+        }
+        return "鏈煡鐘舵��";
+    }
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/excel/OutsourcingOrderExcelDto.java b/src/main/java/com/ruoyi/outsourcing/excel/OutsourcingOrderExcelDto.java
new file mode 100644
index 0000000..f50b290
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/excel/OutsourcingOrderExcelDto.java
@@ -0,0 +1,44 @@
+package com.ruoyi.outsourcing.excel;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 濮斿璁㈠崟瀵煎嚭瀵硅薄
+ */
+@Data
+public class OutsourcingOrderExcelDto {
+
+    @Excel(name = "濮斿璁㈠崟鍙�")
+    private String orderNo;
+
+    @Excel(name = "鍚堜綔鍟嗗悕绉�")
+    private String partnerName;
+
+    @Excel(name = "鐢宠鏃ユ湡", dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate applyDate;
+
+    @Excel(name = "鏈熸湜浜や粯鏃ユ湡", dateFormat = "yyyy-MM-dd")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate expectedDeliveryDate;
+
+    @Excel(name = "椤圭洰鍚嶇О")
+    private String projectName;
+
+    @Excel(name = "濮斿閲戦")
+    private BigDecimal contractAmount;
+
+    @Excel(name = "浠樻鏂瑰紡")
+    private String paymentMethod;
+
+    @Excel(name = "鐘舵��")
+    private String statusLabel;
+
+    @Excel(name = "澶囨敞")
+    private String remarks;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/excel/OutsourcingPartnerExcelDto.java b/src/main/java/com/ruoyi/outsourcing/excel/OutsourcingPartnerExcelDto.java
new file mode 100644
index 0000000..8759bda
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/excel/OutsourcingPartnerExcelDto.java
@@ -0,0 +1,41 @@
+package com.ruoyi.outsourcing.excel;
+
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗堝鍏ュ鍑哄璞�
+ */
+@Data
+public class OutsourcingPartnerExcelDto {
+
+    @Excel(name = "鍚堜綔鍟嗗悕绉�")
+    private String partnerName;
+
+    @Excel(name = "绾崇◣浜鸿瘑鍒彿")
+    private String taxpayerIdentificationNum;
+
+    @Excel(name = "鍏徃鍦板潃")
+    private String companyAddress;
+
+    @Excel(name = "鍏徃鐢佃瘽")
+    private String companyPhone;
+
+    @Excel(name = "寮�鎴疯")
+    private String bankAccountName;
+
+    @Excel(name = "璐﹀彿")
+    private String bankAccountNum;
+
+    @Excel(name = "鑱旂郴浜�")
+    private String contactUserName;
+
+    @Excel(name = "鑱旂郴鐢佃瘽")
+    private String contactUserPhone;
+
+    @Excel(name = "鍚堜綔鍟嗙被鍨�")
+    private String partnerType;
+
+    @Excel(name = "缁存姢浜�")
+    private String maintainUserName;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderMapper.java b/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderMapper.java
new file mode 100644
index 0000000..e92d6b8
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderMapper.java
@@ -0,0 +1,12 @@
+package com.ruoyi.outsourcing.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrder;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 濮斿璁㈠崟 Mapper
+ */
+@Mapper
+public interface OutsourcingOrderMapper extends BaseMapper<OutsourcingOrder> {
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderProductMapper.java b/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderProductMapper.java
new file mode 100644
index 0000000..a93edd3
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingOrderProductMapper.java
@@ -0,0 +1,12 @@
+package com.ruoyi.outsourcing.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrderProduct;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 濮斿璁㈠崟浜у搧鏄庣粏 Mapper
+ */
+@Mapper
+public interface OutsourcingOrderProductMapper extends BaseMapper<OutsourcingOrderProduct> {
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingPartnerMapper.java b/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingPartnerMapper.java
new file mode 100644
index 0000000..896065e
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/mapper/OutsourcingPartnerMapper.java
@@ -0,0 +1,29 @@
+package com.ruoyi.outsourcing.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.outsourcing.dto.OutsourcingPartnerDto;
+import com.ruoyi.outsourcing.excel.OutsourcingPartnerExcelDto;
+import com.ruoyi.outsourcing.pojo.OutsourcingPartner;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsDetailsVo;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsVo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗� Mapper
+ */
+@Mapper
+public interface OutsourcingPartnerMapper extends BaseMapper<OutsourcingPartner> {
+
+    IPage<OutsourcingPartnerDto> partnerListPage(Page page, @Param("dto") OutsourcingPartnerDto dto);
+
+    List<OutsourcingPartnerExcelDto> partnerExportList(@Param("dto") OutsourcingPartnerDto dto);
+
+    IPage<OutsourcingPartnerTransactionsVo> partnerTransactions(Page page, @Param("partnerName") String partnerName);
+
+    IPage<OutsourcingPartnerTransactionsDetailsVo> partnerTransactionsDetails(Page page, @Param("partnerId") Long partnerId);
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrder.java b/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrder.java
new file mode 100644
index 0000000..528d59f
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrder.java
@@ -0,0 +1,96 @@
+package com.ruoyi.outsourcing.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 濮斿璁㈠崟瀵硅薄 outsourcing_order
+ */
+@Getter
+@Setter
+@TableName("outsourcing_order")
+@Schema(name = "OutsourcingOrder", description = "濮斿璁㈠崟")
+public class OutsourcingOrder implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "涓婚敭id")
+    private Long id;
+
+    @Schema(description = "濮斿璁㈠崟鍙�")
+    private String orderNo;
+
+    @Schema(description = "鍚堜綔鍟唅d")
+    private Long partnerId;
+
+    @Schema(description = "鍚堜綔鍟嗗悕绉�")
+    private String partnerName;
+
+    @Schema(description = "鐢宠鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate applyDate;
+
+    @Schema(description = "鏈熸湜浜や粯鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate expectedDeliveryDate;
+
+    @Schema(description = "椤圭洰鍚嶇О")
+    private String projectName;
+
+    @Schema(description = "濮斿閲戦锛堝惈绋庢�讳环锛�")
+    private BigDecimal contractAmount;
+
+    @Schema(description = "浠樻鏂瑰紡")
+    private String paymentMethod;
+
+    @Schema(description = "澶囨敞")
+    private String remarks;
+
+    @Schema(description = "鐘舵�� 0鑽夌 1宸茬‘璁� 2宸插畬鎴� 3宸蹭綔搴�")
+    private Integer status;
+
+    @Schema(description = "纭鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime confirmTime;
+
+    @Schema(description = "浣滃簾鍘熷洜")
+    private String cancelReason;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+
+    @TableField(exist = false)
+    @Schema(description = "濮斿浜у搧鏄庣粏")
+    private List<OutsourcingOrderProduct> productData;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrderProduct.java b/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrderProduct.java
new file mode 100644
index 0000000..a866b1a
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingOrderProduct.java
@@ -0,0 +1,99 @@
+package com.ruoyi.outsourcing.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 濮斿璁㈠崟浜у搧鏄庣粏瀵硅薄 outsourcing_order_product
+ */
+@Getter
+@Setter
+@TableName("outsourcing_order_product")
+@Schema(name = "OutsourcingOrderProduct", description = "濮斿璁㈠崟浜у搧鏄庣粏")
+public class OutsourcingOrderProduct implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "涓婚敭id")
+    private Long id;
+
+    @Schema(description = "濮斿璁㈠崟id")
+    private Long outsourcingOrderId;
+
+    @Schema(description = "浜у搧id")
+    private Long productId;
+
+    @Schema(description = "浜у搧瑙勬牸id")
+    private Long productModelId;
+
+    @Schema(description = "浜у搧澶х被")
+    private String productCategory;
+
+    @Schema(description = "瑙勬牸鍨嬪彿")
+    private String specificationModel;
+
+    @Schema(description = "鍗曚綅")
+    private String unit;
+
+    @Schema(description = "鏁伴噺")
+    private BigDecimal quantity;
+
+    @Schema(description = "绋庣巼")
+    private BigDecimal taxRate;
+
+    @Schema(description = "鍚◣鍗曚环")
+    private BigDecimal taxInclusiveUnitPrice;
+
+    @Schema(description = "鍚◣鎬讳环")
+    private BigDecimal taxInclusiveTotalPrice;
+
+    @Schema(description = "涓嶅惈绋庢�讳环")
+    private BigDecimal taxExclusiveTotalPrice;
+
+    @Schema(description = "鍙戠エ绫诲瀷")
+    private String invoiceType;
+
+    @Schema(description = "鏄惁鎶ュ簾鍝侀�夋枡 0鍚� 1鏄�")
+    private Boolean isScrap;
+
+    @Schema(description = "閫夌敤鐨勬姤搴熷搧鍚堟牸搴撳瓨id锛坰tock_inventory.id锛屽彧璇诲紩鐢紝涓嶅洖鍐欏簱瀛橈級")
+    private Long stockInventoryId;
+
+    @Schema(description = "鎶ュ簾鍝佹壒鍙�")
+    private String batchNo;
+
+    @Schema(description = "涓嬪崟鏃惰鎵规鍙敤閲忓揩鐓�")
+    private BigDecimal availableQuantity;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingPartner.java b/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingPartner.java
new file mode 100644
index 0000000..36ee269
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/pojo/OutsourcingPartner.java
@@ -0,0 +1,86 @@
+package com.ruoyi.outsourcing.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗堝璞� outsourcing_partner
+ */
+@Data
+@TableName("outsourcing_partner")
+@Schema(name = "OutsourcingPartner", description = "濮斿鍚堜綔鍟嗘。妗�")
+public class OutsourcingPartner implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    @Schema(description = "涓婚敭id")
+    private Long id;
+
+    @Schema(description = "鍚堜綔鍟嗗悕绉�")
+    private String partnerName;
+
+    @Schema(description = "绾崇◣浜鸿瘑鍒彿")
+    private String taxpayerIdentificationNum;
+
+    @Schema(description = "鍏徃鍦板潃")
+    private String companyAddress;
+
+    @Schema(description = "鍏徃鐢佃瘽")
+    private String companyPhone;
+
+    @Schema(description = "寮�鎴疯鍚嶇О")
+    private String bankAccountName;
+
+    @Schema(description = "閾惰璐﹀彿")
+    private String bankAccountNum;
+
+    @Schema(description = "鑱旂郴浜哄鍚�")
+    private String contactUserName;
+
+    @Schema(description = "鑱旂郴浜虹數璇�")
+    private String contactUserPhone;
+
+    @Schema(description = "缁存姢浜篿d")
+    private Long maintainUserId;
+
+    @Schema(description = "缁存姢鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate maintainTime;
+
+    @Schema(description = "鍚堜綔鍟嗙被鍨�")
+    private String partnerType;
+
+    @Schema(description = "鏄惁鐧藉悕鍗� 1鏄� 0鍚�")
+    private Integer isWhite;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/service/IOutsourcingOrderService.java b/src/main/java/com/ruoyi/outsourcing/service/IOutsourcingOrderService.java
new file mode 100644
index 0000000..556cbac
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/service/IOutsourcingOrderService.java
@@ -0,0 +1,59 @@
+package com.ruoyi.outsourcing.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.outsourcing.dto.OutsourcingOrderDto;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrder;
+import jakarta.servlet.http.HttpServletResponse;
+
+import java.util.List;
+
+/**
+ * 濮斿璁㈠崟 Service
+ */
+public interface IOutsourcingOrderService extends IService<OutsourcingOrder> {
+
+    /**
+     * 濮斿璁㈠崟鍒楄〃锛堥厤鍚� startPage 鍒嗛〉锛�
+     */
+    List<OutsourcingOrder> selectOrderList(OutsourcingOrderDto dto);
+
+    /**
+     * 濮斿璁㈠崟璇︽儏锛堝惈浜у搧鏄庣粏锛�
+     */
+    OutsourcingOrderDto getDetail(Long id);
+
+    /**
+     * 鏂板濮斿璁㈠崟锛堣崏绋匡級
+     */
+    Long saveOrder(OutsourcingOrderDto dto);
+
+    /**
+     * 淇敼濮斿璁㈠崟锛屼粎鑽夌鐘舵�佸厑璁�
+     */
+    boolean updateOrder(OutsourcingOrderDto dto);
+
+    /**
+     * 鍒犻櫎濮斿璁㈠崟锛屼粎鑽夌鐘舵�佸厑璁�
+     */
+    boolean deleteOrders(List<Long> ids);
+
+    /**
+     * 鎻愪氦锛氳崏绋� -> 宸茬‘璁�
+     */
+    boolean submit(Long id);
+
+    /**
+     * 瀹屾垚锛氬凡纭 -> 宸插畬鎴�
+     */
+    boolean complete(Long id);
+
+    /**
+     * 浣滃簾锛氳崏绋�/宸茬‘璁� -> 宸蹭綔搴�
+     */
+    boolean cancel(Long id, String cancelReason);
+
+    /**
+     * 濮斿璁㈠崟瀵煎嚭
+     */
+    void exportOrders(HttpServletResponse response, OutsourcingOrderDto dto);
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/service/IOutsourcingPartnerService.java b/src/main/java/com/ruoyi/outsourcing/service/IOutsourcingPartnerService.java
new file mode 100644
index 0000000..0747e5d
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/service/IOutsourcingPartnerService.java
@@ -0,0 +1,70 @@
+package com.ruoyi.outsourcing.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.outsourcing.dto.OutsourcingPartnerDto;
+import com.ruoyi.outsourcing.pojo.OutsourcingPartner;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsDetailsVo;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsVo;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerVo;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗� Service
+ */
+public interface IOutsourcingPartnerService extends IService<OutsourcingPartner> {
+
+    /**
+     * 鏂板鍚堜綔鍟嗭紙鍚檮浠剁粦瀹氾級
+     */
+    Long savePartner(OutsourcingPartnerDto dto);
+
+    /**
+     * 淇敼鍚堜綔鍟嗭紙鍚檮浠剁粦瀹氾級
+     */
+    boolean updatePartner(OutsourcingPartnerDto dto);
+
+    /**
+     * 鍒犻櫎鍚堜綔鍟嗭紙鍏堟牎楠屽紩鐢紝鍐嶆竻鐞嗛檮浠讹級
+     */
+    boolean delPartner(List<Long> ids);
+
+    /**
+     * 鍚堜綔鍟嗚鎯咃紙鍚檮浠跺洖鏄撅級
+     */
+    OutsourcingPartnerVo partnerDetail(Long id);
+
+    /**
+     * 鍚堜綔鍟嗗垎椤垫煡璇�
+     */
+    IPage<OutsourcingPartnerDto> partnerListPage(Page page, OutsourcingPartnerDto dto);
+
+    /**
+     * 鍚堜綔鍟嗗鍑�
+     */
+    void partnerExport(HttpServletResponse response, OutsourcingPartnerDto dto);
+
+    /**
+     * 鍚堜綔鍟嗗鍏�
+     */
+    Boolean importData(MultipartFile file);
+
+    /**
+     * 鍚堜綔鍟嗕笅鎷夐�夐」
+     */
+    List<OutsourcingPartner> options();
+
+    /**
+     * 鍚堜綔鍟嗗線鏉ユ眹鎬�
+     */
+    IPage<OutsourcingPartnerTransactionsVo> partnerTransactions(Page page, String partnerName);
+
+    /**
+     * 鍚堜綔鍟嗗線鏉ユ槑缁�
+     */
+    IPage<OutsourcingPartnerTransactionsDetailsVo> partnerTransactionsDetails(Page page, Long partnerId);
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingOrderServiceImpl.java b/src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingOrderServiceImpl.java
new file mode 100644
index 0000000..b95657c
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingOrderServiceImpl.java
@@ -0,0 +1,427 @@
+package com.ruoyi.outsourcing.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.mapper.ProductMapper;
+import com.ruoyi.basic.mapper.ProductModelMapper;
+import com.ruoyi.basic.pojo.Product;
+import com.ruoyi.basic.pojo.ProductModel;
+import com.ruoyi.common.enums.ReviewStatusEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.OrderUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.outsourcing.dto.OutsourcingOrderDto;
+import com.ruoyi.outsourcing.enums.OutsourcingOrderStatusEnum;
+import com.ruoyi.outsourcing.excel.OutsourcingOrderExcelDto;
+import com.ruoyi.outsourcing.mapper.OutsourcingOrderMapper;
+import com.ruoyi.outsourcing.mapper.OutsourcingOrderProductMapper;
+import com.ruoyi.outsourcing.mapper.OutsourcingPartnerMapper;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrder;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrderProduct;
+import com.ruoyi.outsourcing.pojo.OutsourcingPartner;
+import com.ruoyi.outsourcing.service.IOutsourcingOrderService;
+import com.ruoyi.stock.dto.StockInventoryDto;
+import com.ruoyi.stock.pojo.StockOutRecord;
+import com.ruoyi.stock.service.StockInventoryService;
+import com.ruoyi.stock.service.StockOutRecordService;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 濮斿璁㈠崟 Service 瀹炵幇
+ */
+@Service
+@RequiredArgsConstructor
+public class OutsourcingOrderServiceImpl extends ServiceImpl<OutsourcingOrderMapper, OutsourcingOrder>
+        implements IOutsourcingOrderService {
+
+    private static final String ORDER_NO_PREFIX = "WWDD";
+
+    private final OutsourcingOrderMapper outsourcingOrderMapper;
+    private final OutsourcingOrderProductMapper outsourcingOrderProductMapper;
+    private final OutsourcingPartnerMapper outsourcingPartnerMapper;
+    private final ProductMapper productMapper;
+    private final ProductModelMapper productModelMapper;
+    private final StockInventoryService stockInventoryService;
+    private final StockOutRecordService stockOutRecordService;
+
+    @Override
+    public List<OutsourcingOrder> selectOrderList(OutsourcingOrderDto dto) {
+        LambdaQueryWrapper<OutsourcingOrder> wrapper = new LambdaQueryWrapper<>();
+        wrapper.like(StringUtils.isNotBlank(dto.getOrderNo()), OutsourcingOrder::getOrderNo, dto.getOrderNo())
+                .eq(dto.getPartnerId() != null, OutsourcingOrder::getPartnerId, dto.getPartnerId())
+                .like(StringUtils.isNotBlank(dto.getPartnerName()), OutsourcingOrder::getPartnerName, dto.getPartnerName())
+                .eq(dto.getStatus() != null, OutsourcingOrder::getStatus, dto.getStatus())
+                .like(StringUtils.isNotBlank(dto.getProjectName()), OutsourcingOrder::getProjectName, dto.getProjectName())
+                .ge(StringUtils.isNotBlank(dto.getStartDate()), OutsourcingOrder::getApplyDate, dto.getStartDate())
+                .le(StringUtils.isNotBlank(dto.getEndDate()), OutsourcingOrder::getApplyDate, dto.getEndDate())
+                .orderByDesc(OutsourcingOrder::getId);
+        return this.list(wrapper);
+    }
+
+    @Override
+    public OutsourcingOrderDto getDetail(Long id) {
+        OutsourcingOrder order = outsourcingOrderMapper.selectById(id);
+        if (order == null) {
+            throw new ServiceException("濮斿璁㈠崟涓嶅瓨鍦�");
+        }
+        OutsourcingOrderDto dto = new OutsourcingOrderDto();
+        BeanUtils.copyProperties(order, dto);
+        dto.setProductData(listProducts(id));
+        return dto;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveOrder(OutsourcingOrderDto dto) {
+        OutsourcingOrder order = new OutsourcingOrder();
+        BeanUtils.copyProperties(dto, order);
+        order.setId(null);
+        order.setStatus(OutsourcingOrderStatusEnum.DRAFT.getCode());
+        order.setOrderNo(OrderUtils.countTodayByCreateTime(
+                outsourcingOrderMapper, ORDER_NO_PREFIX, "order_no", LocalDateTime.now()));
+        order.setConfirmTime(null);
+        order.setCancelReason(null);
+        fillPartnerName(order);
+        if (order.getApplyDate() == null) {
+            order.setApplyDate(LocalDate.now());
+        }
+        order.setContractAmount(sumProducts(dto.getProductData()));
+        this.save(order);
+
+        saveProducts(order.getId(), dto.getProductData());
+        return order.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateOrder(OutsourcingOrderDto dto) {
+        if (dto.getId() == null) {
+            throw new ServiceException("濮斿璁㈠崟id涓嶈兘涓虹┖");
+        }
+        OutsourcingOrder existing = outsourcingOrderMapper.selectById(dto.getId());
+        if (existing == null) {
+            throw new ServiceException("濮斿璁㈠崟涓嶅瓨鍦�");
+        }
+        if (!OutsourcingOrderStatusEnum.DRAFT.getCode().equals(existing.getStatus())) {
+            throw new ServiceException("浠呰崏绋跨姸鎬佺殑濮斿璁㈠崟鍏佽淇敼");
+        }
+
+        OutsourcingOrder order = new OutsourcingOrder();
+        BeanUtils.copyProperties(dto, order);
+        // 鍗曞彿銆佺姸鎬併�佺‘璁や俊鎭敱绯荤粺缁存姢锛岀疆绌哄悗 MyBatis-Plus 涓嶄細鏇存柊杩欎簺鍒�
+        order.setOrderNo(null);
+        order.setStatus(null);
+        order.setConfirmTime(null);
+        order.setCancelReason(null);
+        order.setTenantId(null);
+        order.setDeptId(null);
+        order.setCreateUser(null);
+        order.setCreateTime(null);
+        order.setUpdateTime(null);
+        fillPartnerName(order);
+        order.setContractAmount(sumProducts(dto.getProductData()));
+        boolean updated = this.updateById(order);
+
+        if (updated) {
+            outsourcingOrderProductMapper.delete(Wrappers.<OutsourcingOrderProduct>lambdaQuery()
+                    .eq(OutsourcingOrderProduct::getOutsourcingOrderId, dto.getId()));
+            saveProducts(dto.getId(), dto.getProductData());
+        }
+        return updated;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean deleteOrders(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            throw new ServiceException("璇烽�夋嫨瑕佸垹闄ょ殑濮斿璁㈠崟");
+        }
+        List<String> notDraftNos = this.listByIds(ids).stream()
+                .filter(order -> !OutsourcingOrderStatusEnum.DRAFT.getCode().equals(order.getStatus()))
+                .map(OutsourcingOrder::getOrderNo)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (!notDraftNos.isEmpty()) {
+            throw new ServiceException("浠呰崏绋跨姸鎬佺殑濮斿璁㈠崟鍏佽鍒犻櫎锛�" + String.join("銆�", notDraftNos));
+        }
+
+        outsourcingOrderProductMapper.delete(Wrappers.<OutsourcingOrderProduct>lambdaQuery()
+                .in(OutsourcingOrderProduct::getOutsourcingOrderId, ids));
+        return this.removeByIds(ids);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean submit(Long id) {
+        OutsourcingOrder order = requireOrder(id);
+        if (!OutsourcingOrderStatusEnum.DRAFT.getCode().equals(order.getStatus())) {
+            throw new ServiceException("浠呰崏绋跨姸鎬佺殑濮斿璁㈠崟鍏佽鎻愪氦");
+        }
+        if (order.getPartnerId() == null
+                || outsourcingPartnerMapper.selectById(order.getPartnerId()) == null) {
+            throw new ServiceException("璇峰厛閫夋嫨鏈夋晥鍚堜綔鍟嗗啀鎻愪氦");
+        }
+        List<OutsourcingOrderProduct> products = listProducts(id);
+        if (products.isEmpty()) {
+            throw new ServiceException("濮斿璁㈠崟娌℃湁浜у搧鏄庣粏锛屾棤娉曟彁浜�");
+        }
+        if (order.getContractAmount() == null
+                || order.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("濮斿璁㈠崟閲戦蹇呴』澶т簬 0 鎵嶈兘鎻愪氦");
+        }
+
+        // 鏉′欢鏇存柊鎶㈠崰鑽夌鐘舵�侊紝閬垮厤骞跺彂涓嬮噸澶嶆彁浜�
+        boolean submitted = this.update(Wrappers.<OutsourcingOrder>lambdaUpdate()
+                .eq(OutsourcingOrder::getId, id)
+                .eq(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.DRAFT.getCode())
+                .set(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.CONFIRMED.getCode())
+                .set(OutsourcingOrder::getConfirmTime, LocalDateTime.now()));
+        if (!submitted) {
+            throw new ServiceException("璇ュ澶栬鍗曞凡鎻愪氦锛岃鍕块噸澶嶆搷浣�");
+        }
+        occupyScrapStock(products);
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean complete(Long id) {
+        OutsourcingOrder order = requireOrder(id);
+        if (!OutsourcingOrderStatusEnum.CONFIRMED.getCode().equals(order.getStatus())) {
+            throw new ServiceException("浠呭凡纭鐘舵�佺殑濮斿璁㈠崟鍏佽瀹屾垚");
+        }
+        requireScrapStockOutApproved(order.getId());
+        boolean completed = this.update(Wrappers.<OutsourcingOrder>lambdaUpdate()
+                .eq(OutsourcingOrder::getId, id)
+                .eq(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.CONFIRMED.getCode())
+                .set(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.COMPLETED.getCode()));
+        if (!completed) {
+            throw new ServiceException("浠呭凡纭鐘舵�佺殑濮斿璁㈠崟鍏佽瀹屾垚");
+        }
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean cancel(Long id, String cancelReason) {
+        OutsourcingOrder order = requireOrder(id);
+        Integer status = order.getStatus();
+        if (!OutsourcingOrderStatusEnum.DRAFT.getCode().equals(status)
+                && !OutsourcingOrderStatusEnum.CONFIRMED.getCode().equals(status)) {
+            throw new ServiceException("璇ュ澶栬鍗曞凡瀹屾垚鎴栧凡浣滃簾锛屼笉鍏佽閲嶅浣滃簾");
+        }
+        boolean cancelled = this.update(Wrappers.<OutsourcingOrder>lambdaUpdate()
+                .eq(OutsourcingOrder::getId, id)
+                .in(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.DRAFT.getCode(),
+                        OutsourcingOrderStatusEnum.CONFIRMED.getCode())
+                .set(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.CANCELLED.getCode())
+                .set(OutsourcingOrder::getCancelReason, cancelReason));
+        if (!cancelled) {
+            throw new ServiceException("璇ュ澶栬鍗曞凡瀹屾垚鎴栧凡浣滃簾锛屼笉鍏佽閲嶅浣滃簾");
+        }
+        if (OutsourcingOrderStatusEnum.CONFIRMED.getCode().equals(status)) {
+            // 鎻愪氦鏃跺崰鐢ㄧ殑鎶ュ簾鍝佸簱瀛樿閲婃斁锛氭湭瀹℃壒鐨勮褰曞垹鎺夊嵆閲婃斁锛屽凡瀹℃壒閫氳繃鐨勬寜鎵规褰掕繕搴撳瓨
+            stockOutRecordService.releaseByRecordIds(scrapLineIds(id),
+                    StockOutQualifiedRecordTypeEnum.OUTSOURCING_SCRAP_OUT.getCode());
+        }
+        return true;
+    }
+
+    @Override
+    public void exportOrders(HttpServletResponse response, OutsourcingOrderDto dto) {
+        List<OutsourcingOrder> orders = selectOrderList(dto);
+        List<OutsourcingOrderExcelDto> excelList = orders.stream().map(order -> {
+            OutsourcingOrderExcelDto excel = new OutsourcingOrderExcelDto();
+            BeanUtils.copyProperties(order, excel);
+            excel.setStatusLabel(OutsourcingOrderStatusEnum.getLabelByValue(order.getStatus()));
+            return excel;
+        }).collect(Collectors.toList());
+        ExcelUtil<OutsourcingOrderExcelDto> util = new ExcelUtil<>(OutsourcingOrderExcelDto.class);
+        util.exportExcel(response, excelList, "濮斿璁㈠崟");
+    }
+
+    private OutsourcingOrder requireOrder(Long id) {
+        if (id == null) {
+            throw new ServiceException("濮斿璁㈠崟id涓嶈兘涓虹┖");
+        }
+        OutsourcingOrder order = outsourcingOrderMapper.selectById(id);
+        if (order == null) {
+            throw new ServiceException("濮斿璁㈠崟涓嶅瓨鍦�");
+        }
+        return order;
+    }
+
+    /**
+     * 鎶ュ簾鍝侀�夋枡琛岀殑 id锛屼綔涓哄嚭搴撹褰曠殑 record_id銆�
+     * 鍑哄簱璁板綍涓�鏉″搴斾竴琛屾槑缁嗭紙涓�涓鏍�+鎵瑰彿锛夛紝涓庨噰璐��璐у嚭搴撶殑璁版硶涓�鑷�
+     */
+    private List<Long> scrapLineIds(Long orderId) {
+        return listProducts(orderId).stream()
+                .filter(product -> Boolean.TRUE.equals(product.getIsScrap()))
+                .map(OutsourcingOrderProduct::getId)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 鎶ュ簾鍝侀�夋枡鍗犵敤搴撳瓨锛氭瘡鏉¢�夋枡琛屽啓涓�鏉″緟瀹℃壒鍑哄簱璁板綍銆�
+     *
+     * <p>鎶ュ簾鍝佸彇鐨勬槸<b>鍚堟牸搴撳瓨</b>锛坰tock_inventory锛屽嚭搴撹褰� type=0锛夈��
+     * 璁板綍涓�钀藉簱灏遍�氳繃 stock_out_record 鐨勫緟瀹℃壒閲忔妸鍙敤閲忓崰浣�
+     * 锛堝彲鐢ㄩ噺 = qualitity 鈭� locked_quantity 鈭� 寰呭鎵瑰嚭搴撻噺锛夛紝鍥犳鍚屼竴鎵规涓嶄細琚寮犲澶栬鍗曡秴閫夛紝
+     * 浣� qualitity 鏈韩瑕佺瓑鍑哄簱绠$悊瀹℃壒閫氳繃鎵嶇湡姝f墸鍑� 鈥斺�� 涔熷氨鏄�屾彁浜ゅ嵆鍗犵敤銆佸鎵规墠瀹炴墸銆嶃��</p>
+     */
+    private void occupyScrapStock(List<OutsourcingOrderProduct> products) {
+        for (OutsourcingOrderProduct product : products) {
+            if (!Boolean.TRUE.equals(product.getIsScrap())) {
+                continue;
+            }
+            if (product.getProductModelId() == null || StringUtils.isEmpty(product.getBatchNo())) {
+                throw new ServiceException("鎶ュ簾鍝侀�夋枡琛岀己灏戜骇鍝佽鏍兼垨鎵瑰彿锛屾棤娉曟彁浜�");
+            }
+            BigDecimal quantity = product.getQuantity();
+            if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+                throw new ServiceException("鎶ュ簾鍝侀�夋枡琛屾暟閲忓繀椤诲ぇ浜� 0");
+            }
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setRecordId(product.getId());
+            stockInventoryDto.setRecordType(StockOutQualifiedRecordTypeEnum.OUTSOURCING_SCRAP_OUT.getCode());
+            stockInventoryDto.setProductModelId(product.getProductModelId());
+            stockInventoryDto.setBatchNo(product.getBatchNo());
+            stockInventoryDto.setQualitity(quantity);
+            try {
+                stockInventoryService.addStockOutRecordOnly(stockInventoryDto);
+            } catch (RuntimeException e) {
+                // 鍚堟牸搴撳瓨涓嶈冻鎶涚殑鏄� ServiceException锛屼笉鍚堟牸閭g BaseException 涔熶竴骞跺厹浣�
+                throw new ServiceException("鎶ュ簾鍝併��" + product.getSpecificationModel() + "銆嶆壒鍙枫��"
+                        + product.getBatchNo() + "銆�" + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 鎶ュ簾鍝侀�夋枡鐨勫嚭搴撹褰曞繀椤诲叏閮ㄥ鎵归�氳繃鎵嶅厑璁稿畬鎴愯鍗曪紝
+     * 鍚﹀垯浼氬嚭鐜般�屽澶栧凡瀹屾垚銆佸簱瀛樻病鎵c�嶇殑璐﹀疄涓嶇
+     */
+    private void requireScrapStockOutApproved(Long orderId) {
+        List<Long> lineIds = scrapLineIds(orderId);
+        if (lineIds.isEmpty()) {
+            return;
+        }
+        List<StockOutRecord> records = stockOutRecordService.listByRecordIds(
+                lineIds, StockOutQualifiedRecordTypeEnum.OUTSOURCING_SCRAP_OUT.getCode());
+        if (records.size() < lineIds.size()) {
+            throw new ServiceException("鎶ュ簾鍝侀�夋枡缂哄皯鍑哄簱璁板綍锛岃鍏堜綔搴熷悗閲嶆柊鎻愪氦");
+        }
+        List<String> pendingBatches = records.stream()
+                .filter(record -> !ReviewStatusEnum.APPROVED.getCode().equals(record.getApprovalStatus()))
+                .map(StockOutRecord::getOutboundBatches)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (!pendingBatches.isEmpty()) {
+            throw new ServiceException("鎶ュ簾鍝佸嚭搴撴湭瀹℃壒閫氳繃锛岃鍏堝埌鍑哄簱绠$悊瀹℃壒鍑哄簱鎵规锛�"
+                    + String.join("銆�", pendingBatches));
+        }
+    }
+
+    private List<OutsourcingOrderProduct> listProducts(Long orderId) {
+        return outsourcingOrderProductMapper.selectList(Wrappers.<OutsourcingOrderProduct>lambdaQuery()
+                .eq(OutsourcingOrderProduct::getOutsourcingOrderId, orderId)
+                .orderByAsc(OutsourcingOrderProduct::getId));
+    }
+
+    private void saveProducts(Long orderId, List<OutsourcingOrderProduct> products) {
+        if (products == null || products.isEmpty()) {
+            return;
+        }
+        fillProductNames(products);
+        for (OutsourcingOrderProduct product : products) {
+            product.setId(null);
+            product.setOutsourcingOrderId(orderId);
+            product.setTenantId(null);
+            product.setDeptId(null);
+            product.setCreateUser(null);
+            product.setCreateTime(null);
+            product.setUpdateTime(null);
+            outsourcingOrderProductMapper.insert(product);
+        }
+    }
+
+    /**
+     * 濮斿閲戦浠ユ槑缁嗗惈绋庢�讳环鍚堣涓哄噯锛岄伩鍏嶅墠绔紶鍏ラ噾棰濅笌鏄庣粏瀵逛笉涓婂鑷村線鏉ュ簲浠橀敊涔�
+     */
+    private BigDecimal sumProducts(List<OutsourcingOrderProduct> products) {
+        if (products == null || products.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        return products.stream()
+                .map(OutsourcingOrderProduct::getTaxInclusiveTotalPrice)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+    /**
+     * 琛ュ叏浜у搧澶х被涓庤鏍煎瀷鍙凤紝渚夸簬璁㈠崟鐩存帴灞曠ず浜у搧鍚嶇О
+     */
+    private void fillProductNames(List<OutsourcingOrderProduct> products) {
+        Set<Long> productIds = products.stream()
+                .map(OutsourcingOrderProduct::getProductId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Set<Long> modelIds = products.stream()
+                .map(OutsourcingOrderProduct::getProductModelId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        Map<Long, String> productMap = new HashMap<>();
+        if (!productIds.isEmpty()) {
+            for (Product product : productMapper.selectBatchIds(productIds)) {
+                productMap.put(product.getId(), product.getProductName());
+            }
+        }
+        Map<Long, String> modelMap = new HashMap<>();
+        if (!modelIds.isEmpty()) {
+            for (ProductModel model : productModelMapper.selectBatchIds(modelIds)) {
+                modelMap.put(model.getId(), model.getModel());
+            }
+        }
+
+        for (OutsourcingOrderProduct product : products) {
+            String productName = productMap.get(product.getProductId());
+            if (productName != null) {
+                product.setProductCategory(productName);
+            }
+            String model = modelMap.get(product.getProductModelId());
+            if (model != null) {
+                product.setSpecificationModel(model);
+            }
+        }
+    }
+
+    private void fillPartnerName(OutsourcingOrder order) {
+        if (order.getPartnerId() == null) {
+            return;
+        }
+        OutsourcingPartner partner = outsourcingPartnerMapper.selectById(order.getPartnerId());
+        if (partner == null) {
+            throw new ServiceException("鍚堜綔鍟嗕笉瀛樺湪锛岃閲嶆柊閫夋嫨");
+        }
+        order.setPartnerName(partner.getPartnerName());
+    }
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingPartnerServiceImpl.java b/src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingPartnerServiceImpl.java
new file mode 100644
index 0000000..d630cdc
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/service/impl/OutsourcingPartnerServiceImpl.java
@@ -0,0 +1,211 @@
+package com.ruoyi.outsourcing.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.account.mapper.PaymentRecordMapper;
+import com.ruoyi.account.pojo.PaymentRecord;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.outsourcing.dto.OutsourcingPartnerDto;
+import com.ruoyi.outsourcing.excel.OutsourcingPartnerExcelDto;
+import com.ruoyi.outsourcing.mapper.OutsourcingPartnerMapper;
+import com.ruoyi.outsourcing.mapper.OutsourcingOrderMapper;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrder;
+import com.ruoyi.outsourcing.pojo.OutsourcingPartner;
+import com.ruoyi.outsourcing.service.IOutsourcingPartnerService;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsDetailsVo;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsVo;
+import com.ruoyi.outsourcing.vo.OutsourcingPartnerVo;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDate;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗� Service 瀹炵幇
+ */
+@Service
+@RequiredArgsConstructor
+public class OutsourcingPartnerServiceImpl extends ServiceImpl<OutsourcingPartnerMapper, OutsourcingPartner>
+        implements IOutsourcingPartnerService {
+
+    private final OutsourcingPartnerMapper partnerMapper;
+    private final OutsourcingOrderMapper outsourcingOrderMapper;
+    private final PaymentRecordMapper paymentRecordMapper;
+    private final FileUtil fileUtil;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long savePartner(OutsourcingPartnerDto dto) {
+        if (dto.getPartnerName() == null || dto.getPartnerName().trim().isEmpty()) {
+            throw new ServiceException("璇峰~鍐欏悎浣滃晢鍚嶇О");
+        }
+        checkPartnerNameDuplicate(dto.getPartnerName(), null);
+
+        OutsourcingPartner partner = new OutsourcingPartner();
+        BeanUtils.copyProperties(dto, partner);
+        partner.setId(null);
+        partner.setTenantId(null);
+        partner.setDeptId(null);
+        partner.setCreateUser(null);
+        partner.setCreateTime(null);
+        partner.setUpdateTime(null);
+        if (partner.getMaintainTime() == null) {
+            partner.setMaintainTime(LocalDate.now());
+        }
+        if (partner.getMaintainUserId() == null) {
+            partner.setMaintainUserId(SecurityUtils.getUserId());
+        }
+        partnerMapper.insert(partner);
+
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.OUTSOURCING_PARTNER,
+                partner.getId(), dto.getStorageBlobDTOS());
+        return partner.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updatePartner(OutsourcingPartnerDto dto) {
+        if (dto.getId() == null) {
+            throw new ServiceException("鍚堜綔鍟唅d涓嶈兘涓虹┖");
+        }
+        if (partnerMapper.selectById(dto.getId()) == null) {
+            throw new ServiceException("鍚堜綔鍟嗕笉瀛樺湪");
+        }
+        checkPartnerNameDuplicate(dto.getPartnerName(), dto.getId());
+
+        OutsourcingPartner partner = new OutsourcingPartner();
+        BeanUtils.copyProperties(dto, partner);
+        // 绉熸埛銆佸垱寤轰俊鎭敱绯荤粺缁存姢锛岀疆绌哄悗 MyBatis-Plus 涓嶄細鏇存柊杩欎簺鍒�
+        partner.setTenantId(null);
+        partner.setDeptId(null);
+        partner.setCreateUser(null);
+        partner.setCreateTime(null);
+        partner.setUpdateTime(null);
+        boolean updated = partnerMapper.updateById(partner) > 0;
+
+        if (updated) {
+            fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.OUTSOURCING_PARTNER,
+                    dto.getId(), dto.getStorageBlobDTOS());
+        }
+        return updated;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean delPartner(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            throw new ServiceException("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        Long orderCount = outsourcingOrderMapper.selectCount(Wrappers.<OutsourcingOrder>lambdaQuery()
+                .in(OutsourcingOrder::getPartnerId, ids));
+        if (orderCount != null && orderCount > 0) {
+            throw new ServiceException("鍚堜綔鍟嗗凡瀛樺湪濮斿璁㈠崟锛屼笉鍏佽鍒犻櫎");
+        }
+        Long paymentCount = paymentRecordMapper.selectCount(Wrappers.<PaymentRecord>lambdaQuery()
+                .eq(PaymentRecord::getPartyType, 3)
+                .in(PaymentRecord::getPartyId, ids));
+        if (paymentCount != null && paymentCount > 0) {
+            throw new ServiceException("鍚堜綔鍟嗗凡瀛樺湪寰�鏉ユ敹浠樻璁板綍锛屼笉鍏佽鍒犻櫎");
+        }
+
+        fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(
+                ApplicationTypeEnum.FILE, RecordTypeEnum.OUTSOURCING_PARTNER, ids);
+        return this.removeByIds(ids);
+    }
+
+    @Override
+    public OutsourcingPartnerVo partnerDetail(Long id) {
+        OutsourcingPartner partner = partnerMapper.selectById(id);
+        if (partner == null) {
+            throw new ServiceException("鍚堜綔鍟嗕笉瀛樺湪");
+        }
+        OutsourcingPartnerVo vo = new OutsourcingPartnerVo();
+        BeanUtils.copyProperties(partner, vo);
+        vo.setStorageBlobVOS(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(
+                ApplicationTypeEnum.FILE, RecordTypeEnum.OUTSOURCING_PARTNER, id));
+        return vo;
+    }
+
+    @Override
+    public IPage<OutsourcingPartnerDto> partnerListPage(Page page, OutsourcingPartnerDto dto) {
+        return partnerMapper.partnerListPage(page, dto);
+    }
+
+    @Override
+    public void partnerExport(HttpServletResponse response, OutsourcingPartnerDto dto) {
+        List<OutsourcingPartnerExcelDto> list = partnerMapper.partnerExportList(dto);
+        ExcelUtil<OutsourcingPartnerExcelDto> util = new ExcelUtil<>(OutsourcingPartnerExcelDto.class);
+        util.exportExcel(response, list, "鍚堜綔鍟嗘。妗�");
+    }
+
+    @Override
+    public Boolean importData(MultipartFile file) {
+        try {
+            ExcelUtil<OutsourcingPartnerExcelDto> util = new ExcelUtil<>(OutsourcingPartnerExcelDto.class);
+            List<OutsourcingPartnerExcelDto> list = util.importExcel(file.getInputStream());
+            if (CollectionUtils.isEmpty(list)) {
+                return false;
+            }
+            List<OutsourcingPartner> partners = list.stream().map(excel -> {
+                OutsourcingPartner partner = new OutsourcingPartner();
+                BeanUtils.copyProperties(excel, partner);
+                partner.setMaintainUserId(SecurityUtils.getUserId());
+                partner.setMaintainTime(LocalDate.now());
+                return partner;
+            }).collect(Collectors.toList());
+            return this.saveOrUpdateBatch(partners);
+        } catch (Exception e) {
+            throw new ServiceException("鍚堜綔鍟嗗鍏ュけ璐ワ細" + e.getMessage());
+        }
+    }
+
+    @Override
+    public List<OutsourcingPartner> options() {
+        return partnerMapper.selectList(new LambdaQueryWrapper<OutsourcingPartner>()
+                .select(OutsourcingPartner::getId, OutsourcingPartner::getPartnerName)
+                .orderByDesc(OutsourcingPartner::getId));
+    }
+
+    @Override
+    public IPage<OutsourcingPartnerTransactionsVo> partnerTransactions(Page page, String partnerName) {
+        return partnerMapper.partnerTransactions(page, partnerName);
+    }
+
+    @Override
+    public IPage<OutsourcingPartnerTransactionsDetailsVo> partnerTransactionsDetails(Page page, Long partnerId) {
+        return partnerMapper.partnerTransactionsDetails(page, partnerId);
+    }
+
+    /**
+     * 鍚堜綔鍟嗗悕绉板湪绉熸埛鍐呭敮涓�
+     */
+    private void checkPartnerNameDuplicate(String partnerName, Long excludeId) {
+        if (partnerName == null || partnerName.trim().isEmpty()) {
+            return;
+        }
+        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
+        LambdaQueryWrapper<OutsourcingPartner> wrapper = Wrappers.<OutsourcingPartner>lambdaQuery()
+                .eq(tenantId != null, OutsourcingPartner::getTenantId, tenantId)
+                .isNull(tenantId == null, OutsourcingPartner::getTenantId)
+                .eq(OutsourcingPartner::getPartnerName, partnerName)
+                .ne(excludeId != null, OutsourcingPartner::getId, excludeId);
+        if (partnerMapper.selectCount(wrapper) > 0) {
+            throw new ServiceException("鍚堜綔鍟嗗凡瀛樺湪");
+        }
+    }
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsDetailsVo.java b/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsDetailsVo.java
new file mode 100644
index 0000000..4f29da4
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsDetailsVo.java
@@ -0,0 +1,44 @@
+package com.ruoyi.outsourcing.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+/**
+ * 鍚堜綔鍟嗗線鏉ユ槑缁嗭紙鎸夊澶栬鍗曢�愯锛�
+ */
+@Data
+@Schema(name = "OutsourcingPartnerTransactionsDetailsVo", description = "鍚堜綔鍟嗗線鏉ユ槑缁�")
+public class OutsourcingPartnerTransactionsDetailsVo {
+
+    @Schema(description = "濮斿璁㈠崟id")
+    private Long outsourcingOrderId;
+
+    @Schema(description = "濮斿璁㈠崟鍙�")
+    private String orderNo;
+
+    @Schema(description = "鍚堜綔鍟唅d")
+    private Long partnerId;
+
+    @Schema(description = "鍚堜綔鍟嗗悕绉�")
+    private String partnerName;
+
+    @Schema(description = "鐢宠鏃ユ湡")
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private LocalDate applyDate;
+
+    @Schema(description = "璁㈠崟閲戦锛堝惈绋庢�讳环锛�")
+    private BigDecimal contractAmount;
+
+    @Schema(description = "璁㈠崟鐘舵�� 0鑽夌 1宸茬‘璁� 2宸插畬鎴� 3宸蹭綔搴�")
+    private Integer status;
+
+    @Schema(description = "宸蹭粯娆惧悎璁★紙鍚堜綔鍟嗙骇鍚堣锛岄潪璁㈠崟绾э級")
+    private BigDecimal paidAmount;
+
+    @Schema(description = "搴斾粯浣欓 = 璁㈠崟閲戦 - 宸蹭粯娆�")
+    private BigDecimal payableAmount;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsVo.java b/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsVo.java
new file mode 100644
index 0000000..a11c429
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerTransactionsVo.java
@@ -0,0 +1,32 @@
+package com.ruoyi.outsourcing.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 鍚堜綔鍟嗗線鏉ュ垪琛紙鎸夊悎浣滃晢姹囨�伙級
+ */
+@Data
+@Schema(name = "OutsourcingPartnerTransactionsVo", description = "鍚堜綔鍟嗗線鏉ユ眹鎬�")
+public class OutsourcingPartnerTransactionsVo {
+
+    @Schema(description = "鍚堜綔鍟唅d")
+    private Long partnerId;
+
+    @Schema(description = "鍚堜綔鍟嗗悕绉�")
+    private String partnerName;
+
+    @Schema(description = "濮斿閲戦鍚堣锛堝凡纭+宸插畬鎴愶級")
+    private BigDecimal contractAmounts;
+
+    @Schema(description = "宸蹭粯娆惧悎璁�")
+    private BigDecimal paidAmount;
+
+    @Schema(description = "鍥炴鍚堣")
+    private BigDecimal receiptAmount;
+
+    @Schema(description = "搴斾粯浣欓 = 濮斿閲戦 - 宸蹭粯娆�")
+    private BigDecimal payableAmount;
+}
diff --git a/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerVo.java b/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerVo.java
new file mode 100644
index 0000000..0fdee27
--- /dev/null
+++ b/src/main/java/com/ruoyi/outsourcing/vo/OutsourcingPartnerVo.java
@@ -0,0 +1,23 @@
+package com.ruoyi.outsourcing.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.ruoyi.basic.dto.StorageBlobVO;
+import com.ruoyi.outsourcing.pojo.OutsourcingPartner;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 濮斿鍚堜綔鍟嗘。妗堝嚭鍙�
+ */
+@Data
+public class OutsourcingPartnerVo extends OutsourcingPartner {
+
+    @Schema(description = "缁存姢浜哄憳鍚嶇О")
+    private String maintainUserName;
+
+    @TableField(exist = false)
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobVO> storageBlobVOS;
+}
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
index a81fca7..553b2fa 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -105,6 +105,11 @@
      */
     private static final Integer LEDGER_TYPE_PURCHASE = 2;
 
+    /**
+     * 澶囦欢娴佹按鏉ユ簮锛氶噰璐叆搴撱�傝 spare_parts_record.source_type
+     */
+    private static final Integer SPARE_PARTS_SOURCE_PURCHASE = 1;
+
     private final PurchaseLedgerMapper purchaseLedgerMapper;
     private final SalesLedgerMapper salesLedgerMapper;
     private final SalesLedgerProductMapper salesLedgerProductMapper;
@@ -719,10 +724,13 @@
                 salesLedgerProduct.setRegisterDate(localDateTime);
                 salesLedgerProductMapper.insert(salesLedgerProduct);
             }
-            // 浠呮柊澧炴槑缁嗗洖鍐欏浠讹紝缂栬緫璧� update 鍒嗘敮澶╃劧涓嶉噸澶嶈鏁�
-            if (LEDGER_TYPE_PURCHASE.equals(ledgerType)) {
-                recordSparePartsInbound(insertList, purchaseLedger);
-            }
+        }
+        // 閲囪喘鏄庣粏鍥炲啓澶囦欢锛氬厛鎸夊彴璐﹀洖婊氫笂涓�娆$殑娴佹按锛屽啀鎸夊綋鍓嶆槑缁嗘暣浣撻噸寤恒��
+        // 缂栬緫鏃舵槑缁嗚鍙兘琚Щ闄わ紝鍙鏂板琛屼細婕忔帀绉婚櫎鐨勯偅浜涳紱鏁翠綋閲嶅缓澶╃劧涓庡綋鍓嶆槑缁嗗榻愩��
+        // 娉ㄦ剰杩欐蹇呴』鍦� insertList 涔嬪锛屽惁鍒欍�屽彧鍒犳槑缁嗐�佷笉鏂板銆嶇殑缂栬緫涓嶄細瑙﹀彂鍥炴粴
+        if (LEDGER_TYPE_PURCHASE.equals(ledgerType)) {
+            rollbackSparePartsInbound(salesLedgerId);
+            recordSparePartsInbound(products, purchaseLedger);
         }
 
         // 璁$畻鎬诲惈绋庨噾棰�
@@ -739,11 +747,54 @@
     }
 
     /**
-     * 閲囪喘鍙拌处鏂板鏄庣粏鎸夎鏍煎尮閰嶅浠讹細绱姞澶囦欢搴撳瓨骞惰涓�鏉″叆搴撴祦姘淬��
-     * 鏈淮鎶よ鏍肩殑澶囦欢涓嶅弬涓庯紝闈欓粯璺宠繃銆�
+     * 鎸夐噰璐彴璐﹀洖婊氬浠讹細鎶婅鍙拌处浜х敓鐨勫叆搴撴祦姘撮�愬浠跺噺鍥炲簱瀛橈紝鐒跺悗鍒犻櫎杩欎簺娴佹按銆�
+     *
+     * <p>澶囦欢鍏堢敱閲囪喘鍏ヨ处銆佸悗缁缁翠慨/淇濆吇棰嗙敤鍑鸿处锛岃嫢鍏ヨ处閲忓凡缁忚棰嗙敤鎺夛紝鍑忓洖浼氬嚭鐜拌礋搴撳瓨锛�
+     * 姝ゆ椂鐩存帴鎷︿綇涓嶈鍒狅紝閬垮厤鎶婂浠惰处鍋氳姳銆傚垹闄ゅ彴璐︿笌缂栬緫閲嶅缓閮借蛋杩欓噷锛屽彛寰勭粺涓�銆�</p>
      */
-    private void recordSparePartsInbound(List<SalesLedgerProduct> insertList, PurchaseLedger purchaseLedger) {
-        List<Long> modelIds = insertList.stream()
+    private void rollbackSparePartsInbound(Long ledgerId) {
+        List<SparePartsRecord> records = sparePartsRecordService.list(new LambdaQueryWrapper<SparePartsRecord>()
+                .eq(SparePartsRecord::getSourceType, SPARE_PARTS_SOURCE_PURCHASE)
+                .eq(SparePartsRecord::getSourceId, ledgerId));
+        if (CollectionUtils.isEmpty(records)) {
+            return;
+        }
+        Map<Long, BigDecimal> rollbackBySparePartsId = new HashMap<>();
+        for (SparePartsRecord record : records) {
+            BigDecimal quantity = record.getQuantity() == null ? BigDecimal.ZERO : record.getQuantity();
+            rollbackBySparePartsId.merge(record.getSparePartsId(), quantity, BigDecimal::add);
+        }
+        for (Map.Entry<Long, BigDecimal> entry : rollbackBySparePartsId.entrySet()) {
+            if (entry.getKey() == null) {
+                continue;
+            }
+            SpareParts spareParts = sparePartsMapper.selectById(entry.getKey());
+            if (spareParts == null) {
+                continue;
+            }
+            BigDecimal current = spareParts.getQuantity() == null ? BigDecimal.ZERO : spareParts.getQuantity();
+            if (current.compareTo(entry.getValue()) < 0) {
+                throw new BaseException("澶囦欢銆�" + spareParts.getName() + "銆嶅綋鍓嶅簱瀛� " + current
+                        + "锛屼笉瓒充互鍥炴粴鏈閲囪喘鍏ュ簱鐨� " + entry.getValue() + "锛岃鍏堝鐞嗛鐢ㄨ褰�");
+            }
+            spareParts.setQuantity(current.subtract(entry.getValue()));
+            sparePartsMapper.updateById(spareParts);
+        }
+        sparePartsRecordService.removeByIds(records.stream()
+                .map(SparePartsRecord::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList()));
+    }
+
+    /**
+     * 閲囪喘鍙拌处鏄庣粏鎸夎鏍煎尮閰嶅浠讹細绱姞澶囦欢搴撳瓨骞惰涓�鏉″叆搴撴祦姘淬��
+     * 鏈淮鎶よ鏍肩殑澶囦欢涓嶅弬涓庯紝闈欓粯璺宠繃銆�
+     *
+     * <p>璋冪敤鍓嶅繀椤诲厛 {@link #rollbackSparePartsInbound(Long)}锛屼袱鑰呮垚瀵瑰嚭鐜帮細
+     * 鍥炴粴鏃ф祦姘� + 鎸夊綋鍓嶆槑缁嗛噸寤猴紝鎵嶈兘淇濊瘉缂栬緫鍙拌处鍚庡浠舵暟閲忎笌鏄庣粏涓�鑷淬��</p>
+     */
+    private void recordSparePartsInbound(List<SalesLedgerProduct> products, PurchaseLedger purchaseLedger) {
+        List<Long> modelIds = products.stream()
                 .map(SalesLedgerProduct::getProductModelId)
                 .filter(Objects::nonNull)
                 .distinct()
@@ -758,7 +809,7 @@
         }
         Map<Long, SpareParts> sparePartsByModelId = sparePartsList.stream()
                 .collect(Collectors.toMap(SpareParts::getProductModelId, s -> s, (a, b) -> a));
-        for (SalesLedgerProduct product : insertList) {
+        for (SalesLedgerProduct product : products) {
             SpareParts spareParts = sparePartsByModelId.get(product.getProductModelId());
             if (spareParts == null || product.getQuantity() == null
                     || product.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
@@ -790,6 +841,9 @@
             if (purchaseLedger.getApprovalStatus().equals(3)) {
                 throw new BaseException(purchaseLedger.getPurchaseContractNumber()+"宸茬粡瀹℃壒閫氳繃锛屼笉鍏佽鍒犻櫎");
             }
+            // 鍙拌处鍒犻櫎瑕佹妸璇ュ彴璐﹂噰璐叆搴撴椂绱姞鍒板浠朵笂鐨勬暟閲忓噺鍥炲幓锛屽苟鍒犳帀瀵瑰簲娴佹按锛�
+            // 鍚﹀垯澶囦欢搴撳瓨铏氶珮銆佹祦姘存畫鐣欍�傚浠跺凡棰嗙敤瀵艰嚧鍑忓洖涓鸿礋鏃朵細鍦ㄨ繖閲屾嫤浣�
+            rollbackSparePartsInbound(id);
         }
         // 鎵归噺鍒犻櫎鍏宠仈鐨勯噰璐叆搴撹褰�
         LambdaQueryWrapper<SalesLedgerProduct> salesLedgerProductQueryWrapper = new LambdaQueryWrapper<>();
diff --git a/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java b/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java
index f5563ed..1012e81 100644
--- a/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java
+++ b/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java
@@ -27,8 +27,8 @@
     @Schema(description = "浠樻閲戦")
     private BigDecimal paymentAmount;
 
-    @Schema(description = "鍥炴閲戦")
-    private BigDecimal receiptAmount;
+    @Schema(description = "浠樻閲戦锛堝線鏉ユ祦姘达級")
+    private BigDecimal paymentRecordAmount;
 
     @Schema(description = "搴斾粯閲戦")
     private BigDecimal payableAmount;
diff --git a/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java b/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java
index 73af2b7..ea74715 100644
--- a/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java
+++ b/src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java
@@ -23,9 +23,9 @@
     //璇ヤ緵搴斿晢閲囪喘浠樻绱閲戦
     private BigDecimal paymentAmount;
 
-    @Schema(description = "鍥炴閲戦")
-    //璇ヤ緵搴斿晢寰�鏉ュ洖娆剧疮璁¢噾棰濓紙payment_record 鎵嬪伐娴佹按锛�
-    private BigDecimal receiptAmount;
+    @Schema(description = "浠樻閲戦锛堝線鏉ユ祦姘达級")
+    //璇ヤ緵搴斿晢寰�鏉ヤ粯娆剧疮璁¢噾棰濓紙payment_record 鎵嬪伐娴佹按锛宲arty_type=2 direction=2锛�
+    private BigDecimal paymentRecordAmount;
 
     @Schema(description = "搴斾粯閲戦")
     //璇ヤ緵搴斿晢閲囪喘搴斾粯绱閲戦=鍒濆閲戦-浠樻-閫�娆�
diff --git a/src/main/java/com/ruoyi/sales/dto/CustomerDepositAmountDto.java b/src/main/java/com/ruoyi/sales/dto/CustomerDepositAmountDto.java
new file mode 100644
index 0000000..170192a
--- /dev/null
+++ b/src/main/java/com/ruoyi/sales/dto/CustomerDepositAmountDto.java
@@ -0,0 +1,21 @@
+package com.ruoyi.sales.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 瀹㈡埛浠e偍閲戦姹囨�伙紙鐢ㄤ簬瀹㈡埛妗f鐨勪俊鎵樺熀閲戣仈鍔ㄦ彁閱掞級
+ *
+ * @author ruoyi
+ */
+@Data
+public class CustomerDepositAmountDto {
+
+    @Schema(description = "瀹㈡埛id")
+    private Long customerId;
+
+    @Schema(description = "浠e偍閲戦鍚堣锛堣瀹㈡埛鍏ㄩ儴绫诲瀷=浠e偍鐨勯攢鍞彴璐﹀悎鍚岄噾棰濅箣鍜岋級")
+    private BigDecimal depositTotalAmount;
+}
diff --git a/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java b/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
index a4fc57c..43f638e 100644
--- a/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
+++ b/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
@@ -9,6 +9,7 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.Date;
 import java.util.List;
@@ -63,4 +64,32 @@
 
     private List<StorageBlobDTO> storageBlobDTOs;
     private List<StorageBlobVO> StorageBlobVOs;
+
+    /**
+     * 閿�鍞被鍨嬶細鍗栨补 / 浠e偍
+     * 娉ㄦ剰锛氫笉瑕佸鐢ㄤ笂闈㈢殑 type锛岄偅涓槸瀛愯〃琛岀殑 SaleEnum(閿�鍞�/閲囪喘/椤圭洰)锛岃惤 sales_ledger_product.type
+     */
+    @Schema(description = "閿�鍞被鍨嬶細鍗栨补/浠e偍")
+    private String ledgerType;
+
+    @Schema(description = "娌瑰簱id锛堢被鍨�=浠e偍鏃朵娇鐢級")
+    private Long oilDepotId;
+
+    @Schema(description = "娌瑰簱鍚嶇О")
+    private String oilDepotName;
+
+    @Schema(description = "鍌ㄧ綈id锛堢被鍨�=浠e偍鏃朵娇鐢級")
+    private Long tankId;
+
+    @Schema(description = "鍌ㄧ綈鍙�")
+    private String tankNo;
+
+    @Schema(description = "瀹℃壒鐘舵�侊細寰呭鎵�/瀹℃牳涓�/閫氳繃/鎷掔粷")
+    private String approvalStatus;
+
+    /**
+     * 鍚堝悓閲戦銆傚崠娌圭殑閲戦鐢变骇鍝佹槑缁嗘眹鎬讳骇鐢燂紱浠e偍娌℃湁鏄庣粏锛岀敱鍓嶇鎵嬪伐濉啓
+     */
+    @Schema(description = "鍚堝悓閲戦")
+    private BigDecimal contractAmount;
 }
diff --git a/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java b/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
index 724473a..b390ec6 100644
--- a/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
+++ b/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
@@ -51,5 +51,38 @@
 
     private String templateName;
 
+    /**
+     * 琛屾潵婧愶細shipping=鍙戣揣鍗曪紝oilOut=閿�鍞被娌瑰搧鍑哄簱銆�
+     * 鍓嶇蹇呴』鎸夋瀛楁鍒ゅ埆锛宱ilOut 琛岀殑 id 涓� shipping_info.id 鍚屽煙浼氭挒鍙凤紝涓嶅彲鐢ㄤ簬鍙戣揣/鍒犻櫎/閫�璐ф帴鍙�
+     */
+    private String source;
+
+    /**
+     * 娌瑰搧鍑哄簱璁板綍id锛堜粎 source=oilOut 鏈夊�硷級
+     */
+    private Long stockOutRecordId;
+
+    /**
+     * 鍑哄簱渚у睍绀哄瓧娈碉紙浠� source=oilOut 鏈夊�硷級
+     */
+    private String outCategory;
+    private String tankNo;
+    private String oilProduct;
+    private String oilDepotName;
+    private String driverName;
+    private String contactPerson;
+    private String contactPhone;
+
+    /**
+     * 娌瑰搧鍑哄簱鐨勬壒鍙凤紙浠� source=oilOut 鏈夊�硷紝鍙栬嚜 stock_out_record.batch_no锛夈��
+     * 娉ㄦ剰涓� outboundBatches 鍖哄垎锛氬悗鑰呮槸 CK 鍑哄簱鍗曞彿锛屾瀛楁鏄壒娆″彿
+     */
+    private String outBatchNo;
+
+    /**
+     * 鎵瑰彿鏄庣粏锛堜粎 source=oilOut 鏈夊�硷級锛氫竴鏉℃补鍝佸嚭搴撳搴斾竴涓壒鍙凤紝
+     * 渚涘彂璐у彴璐﹁鎯呭脊妗嗙殑銆屾壒鍙枫�嶆槑缁嗚〃灞曠ず
+     */
+    private List<ShippingProductDetailDto> batchNoList;
 
 }
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
index 5518290..a730371 100644
--- a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -7,6 +7,7 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.home.dto.IncomeExpenseAnalysisDto;
 import com.ruoyi.purchase.vo.PurchaseReportVo;
+import com.ruoyi.sales.dto.CustomerDepositAmountDto;
 import com.ruoyi.sales.dto.SalesLedgerDto;
 import com.ruoyi.sales.dto.SalesTrendDto;
 import com.ruoyi.sales.dto.StatisticsTableDto;
@@ -90,4 +91,12 @@
 
     IPage<PurchaseReportVo> selectPurchaseReportVoPage(Page page, @Param("customerName") String customerName);
 
+    /**
+     * 鎸夊鎴锋壒閲忔眹鎬讳唬鍌ㄩ噾棰濓紙璇ュ鎴峰叏閮ㄣ�岀被鍨�=浠e偍銆嶇殑閿�鍞彴璐﹀悎鍚岄噾棰濅箣鍜岋級
+     *
+     * @param customerIds 瀹㈡埛id闆嗗悎
+     * @return 瀹㈡埛id + 浠e偍閲戦鍚堣锛屾棤浠e偍璁板綍鐨勫鎴蜂笉浼氬嚭鐜板湪缁撴灉閲�
+     */
+    List<CustomerDepositAmountDto> selectDepositAmountByCustomerIds(@Param("customerIds") List<Long> customerIds);
+
 }
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java b/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
index c8a0443..dff6269 100644
--- a/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
+++ b/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -143,5 +143,46 @@
     @TableField(exist = false)
     private Boolean hasProductionRecord;
 
+    /**
+     * 閿�鍞被鍨嬶細鍗栨补 / 浠e偍锛堝崠瀛樺偍浣嶇疆锛�
+     * 娉ㄦ剰锛氫笉瑕佷笌瀛愯〃琛岀殑 SaleEnum(type) 娣风敤锛岄偅鏄攢鍞�/閲囪喘/椤圭洰
+     */
+    @Schema(description = "閿�鍞被鍨嬶細鍗栨补/浠e偍")
+    @Excel(name = "閿�鍞被鍨�")
+    private String ledgerType;
+
+    @Schema(description = "娌瑰簱id锛堢被鍨�=浠e偍鏃朵娇鐢級")
+    private Long oilDepotId;
+
+    @Schema(description = "娌瑰簱鍚嶇О")
+    @Excel(name = "娌瑰簱")
+    private String oilDepotName;
+
+    @Schema(description = "鍌ㄧ綈id锛堢被鍨�=浠e偍鏃朵娇鐢級")
+    private Long tankId;
+
+    @Schema(description = "鍌ㄧ綈鍙�")
+    @Excel(name = "鍌ㄧ綈鍙�")
+    private String tankNo;
+
+    @Schema(description = "瀹℃壒鐘舵�侊細寰呭鎵�/瀹℃牳涓�/閫氳繃/鎷掔粷")
+    @Excel(name = "瀹℃壒鐘舵��")
+    private String approvalStatus;
+
+    /**
+     * 瀹㈡埛淇℃墭鍩洪噾锛堟煡璇㈡椂鐢辨湇鍔″眰鎸� customerId 鍥炲~锛屼笉鏄湰琛ㄥ瓧娈碉級
+     */
+    @Schema(description = "瀹㈡埛淇℃墭鍩洪噾")
+    @TableField(exist = false)
+    @Excel(name = "瀹㈡埛淇℃墭鍩洪噾")
+    private BigDecimal trustFund;
+
+    /**
+     * 鏈瑪鍚堝悓閲戦鏄惁瓒呭嚭瀹㈡埛淇℃墭鍩洪噾锛堣秴鍑哄垯鍓嶇鏍囩孩锛屼笉鏄湰琛ㄥ瓧娈碉級
+     */
+    @Schema(description = "鏈瑪鍚堝悓閲戦鏄惁瓒呭嚭瀹㈡埛淇℃墭鍩洪噾")
+    @TableField(exist = false)
+    private Boolean exceedsTrustFund;
+
 }
 
diff --git a/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java b/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
index ed9a268..6431297 100644
--- a/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
+++ b/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
@@ -91,4 +91,10 @@
 
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
+
+    /**
+     * 鏁版嵁鏉冮檺-閮ㄩ棬闆嗗悎锛岀敱 DataScopeAop 娉ㄥ叆锛屼粎渚� listPage 鐨勬煡璇㈡潯浠朵娇鐢�
+     */
+    @TableField(exist = false)
+    private Long[] deptIds;
 }
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
index 00e913f..07ab3af 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -12,15 +12,26 @@
 import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
 import com.ruoyi.account.pojo.sales.AccountInvoiceApplication;
 import com.ruoyi.account.pojo.sales.AccountSalesCollection;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import com.ruoyi.approve.service.ApprovalInstanceService;
 import com.ruoyi.basic.enums.ApplicationTypeEnum;
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.mapper.CustomerMapper;
 import com.ruoyi.basic.mapper.ProductMapper;
 import com.ruoyi.basic.mapper.ProductModelMapper;
+import com.ruoyi.basic.mapper.OilDepotMapper;
+import com.ruoyi.basic.mapper.TankInfoMapper;
 import com.ruoyi.basic.pojo.Customer;
+import com.ruoyi.basic.pojo.OilDepot;
+import com.ruoyi.basic.pojo.TankInfo;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.enums.FileNameType;
 import com.ruoyi.common.enums.SaleEnum;
+import com.ruoyi.common.enums.SalesQuotationStatusEnum;
+import com.ruoyi.common.enums.TypeEnums;
 import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.EnumUtil;
@@ -89,6 +100,10 @@
     private static final String LOCK_PREFIX = "contract_no_lock:";
     private static final long LOCK_WAIT_TIMEOUT = 10; // 閿佺瓑寰呰秴鏃舵椂闂达紙绉掞級
     private static final long LOCK_EXPIRE_TIME = 30;  // 閿佽嚜鍔ㄨ繃鏈熸椂闂达紙绉掞級
+    /** 閿�鍞被鍨嬶細鍗栨补锛堣蛋浜у搧鏄庣粏锛� */
+    private static final String LEDGER_TYPE_OIL = "鍗栨补";
+    /** 閿�鍞被鍨嬶細浠e偍锛堝崠瀛樺偍浣嶇疆锛岃蛋娌瑰簱+鍌ㄧ綈+鎵嬪伐鍚堝悓閲戦锛� */
+    private static final String LEDGER_TYPE_DEPOSIT = "浠e偍";
     private final SalesLedgerMapper salesLedgerMapper;
     private final CustomerMapper customerMapper;
     private final SalesLedgerProductMapper salesLedgerProductMapper;
@@ -106,6 +121,10 @@
     private final FileUtil fileUtil;
     private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper;
     private final AccountSalesCollectionMapper accountSalesCollectionMapper;
+    private final ApprovalTemplateMapper approvalTemplateMapper;
+    private final ApprovalInstanceService approvalInstanceService;
+    private final OilDepotMapper oilDepotMapper;
+    private final TankInfoMapper tankInfoMapper;
 
     @Autowired
     private SysDeptMapper sysDeptMapper;
@@ -123,7 +142,9 @@
 
     @Override
     public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
-        return salesLedgerMapper.selectSalesLedgerList(salesLedgerDto);
+        List<SalesLedger> list = salesLedgerMapper.selectSalesLedgerList(salesLedgerDto);
+        fillTrustFund(list);
+        return list;
     }
 
     public List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(Long relateId, SaleEnum type) {
@@ -303,7 +324,37 @@
 
     @Override
     public IPage<SalesLedgerVo> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto) {
-        return salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto);
+        IPage<SalesLedgerVo> result = salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto);
+        fillTrustFund(result.getRecords());
+        return result;
+    }
+
+    /**
+     * 鍥炲~瀹㈡埛鐨勪俊鎵樺熀閲戯紝骞跺垽鏂湰绗斿悎鍚岄噾棰濇槸鍚﹀凡瓒呭嚭銆�
+     * 瓒呭嚭涓庡惁鐢卞悗绔畻濂借繑鍥烇紝鍓嶇鍙礋璐f爣绾�
+     */
+    private void fillTrustFund(List<? extends SalesLedger> records) {
+        if (CollectionUtils.isEmpty(records)) {
+            return;
+        }
+        List<Long> customerIds = records.stream()
+                .map(SalesLedger::getCustomerId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (customerIds.isEmpty()) {
+            return;
+        }
+        Map<Long, BigDecimal> trustFundMap = customerMapper.selectBatchIds(customerIds).stream()
+                .filter(item -> item.getTrustFund() != null)
+                .collect(Collectors.toMap(Customer::getId, Customer::getTrustFund, (existing, replacement) -> existing));
+
+        records.forEach(ledger -> {
+            BigDecimal trustFund = trustFundMap.get(ledger.getCustomerId());
+            ledger.setTrustFund(trustFund);
+            ledger.setExceedsTrustFund(trustFund != null && ledger.getContractAmount() != null
+                    && ledger.getContractAmount().compareTo(trustFund) > 0);
+        });
     }
 
     @Override
@@ -536,6 +587,19 @@
         // 鍒犻櫎闄勪欢琛�
         commonFileService.deleteByBusinessIds(idList, FileNameType.SALE.getValue());
 
+        // 鍒犻櫎鍙拌处瀵瑰簲鐨勫鎵瑰疄渚嬶紙鍚屼竴鍙拌处鍙兘鍙嶅鎻愪氦/閲嶅锛屼細鏈夊鏉★級
+        List<ApprovalInstance> approvalInstances = approvalInstanceService.list(
+                new LambdaQueryWrapper<ApprovalInstance>()
+                        .in(ApprovalInstance::getBusinessId, idList)
+                        .eq(ApprovalInstance::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode())
+                        .eq(ApprovalInstance::getDeleted, 0));
+        if (CollectionUtils.isNotEmpty(approvalInstances)) {
+            approvalInstanceService.delete(approvalInstances.stream()
+                    .map(ApprovalInstance::getId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList()));
+        }
+
         // 鍒犻櫎鐢熶骇绠℃帶鏁版嵁
         //鏌ヨ鐢熶骇鎶ュ伐id
         ArrayList<Long> mainIdList = productionProductMainService.listMain(idList);
@@ -561,10 +625,13 @@
         SalesLedger salesLedger = convertToEntity(salesLedgerDto);
         salesLedger.setCustomerName(customer.getCustomerName());
         salesLedger.setTenantId(customer.getTenantId());
+        applyLedgerType(salesLedger, salesLedgerDto);
         // 褰曞叆浜猴細濡傛灉鏈紶鍒欎娇鐢ㄥ綋鍓嶇櫥褰曠敤鎴�
         if (StringUtils.isEmpty(salesLedger.getEntryPerson())) {
             salesLedger.setEntryPerson(String.valueOf(SecurityUtils.getUserId()));
         }
+        // 鏂板鍜屼慨鏀归兘瑕侀噸鏂拌蛋瀹℃壒
+        salesLedger.setApprovalStatus(SalesQuotationStatusEnum.PENDING.getCode());
         // 3. 鏂板鎴栨洿鏂颁富琛�
         if (salesLedger.getId() == null) {
             String contractNo = salesLedger.getSalesContractNo();
@@ -574,12 +641,19 @@
             salesLedger.setSalesContractNo(contractNo);
             salesLedgerMapper.insert(salesLedger);
         } else {
+            // 鏀瑰崟浼氶噸鏂拌蛋涓�閬嶅鎵癸細鍏堜綔搴熸鍓嶆湭缁撴潫鐨勫鎵瑰疄渚嬶紝浜嬪姟鎻愪氦鍓嶈繕浼氶噸鏂板彂璧�
+            approvalInstanceService.lambdaUpdate()
+                    .set(ApprovalInstance::getStatus, "REJECTED")
+                    .eq(ApprovalInstance::getBusinessId, salesLedger.getId())
+                    .eq(ApprovalInstance::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode())
+                    .update();
             salesLedgerMapper.updateById(salesLedger);
         }
 
 
-        // 4. 澶勭悊瀛愯〃鏁版嵁
-        List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
+        // 4. 澶勭悊瀛愯〃鏁版嵁銆備唬鍌ㄦ病鏈変骇鍝佹槑缁嗭紝鍚堝悓閲戦浠ユ墜宸ュ~鍐欑殑涓哄噯锛屼笉鑳借瀛愯〃姹囨�昏鐩�
+        List<SalesLedgerProduct> productList = LEDGER_TYPE_OIL.equals(salesLedger.getLedgerType())
+                ? salesLedgerDto.getProductData() : null;
         if (productList != null && !productList.isEmpty()) {
             handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
             updateMainContractAmount(
@@ -605,9 +679,100 @@
         // 6. 淇濆瓨閿�鍞彴璐﹂檮浠�
         fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, salesLedger.getId(), salesLedgerDto.getStorageBlobDTOs());
 
+        // 7. 鍙戣捣瀹℃壒銆傚悎鍚岄噾棰濊鍙栬惤搴撳悗鐨勫�硷紙鍗栨补鐨勯噾棰濈敱绗� 4 姝ョ殑浜у搧鏄庣粏姹囨�诲啓鍥烇級
+        SalesLedger savedLedger = salesLedgerMapper.selectById(salesLedger.getId());
+        startSalesLedgerApproval(savedLedger, customer.getTrustFund());
+
         return 1;
     }
 
+    /**
+     * 鏍¢獙閿�鍞被鍨嬪苟琛ラ綈浠e偍鐨勬补搴�/鍌ㄧ綈淇℃伅銆�
+     * 鍗栨补锛氬悎鍚岄噾棰濆彧鑳界敱浜у搧鏄庣粏姹囨�讳骇鐢燂紱浠e偍锛氭病鏈夋槑缁嗭紝蹇呴』閫夋补搴撱�佸偍缃愬苟鎵嬪伐濉悎鍚岄噾棰�
+     */
+    private void applyLedgerType(SalesLedger salesLedger, SalesLedgerDto salesLedgerDto) {
+        String ledgerType = salesLedgerDto.getLedgerType();
+        if (StringUtils.isEmpty(ledgerType)) {
+            throw new BaseException("閿�鍞被鍨嬩笉鑳戒负绌�");
+        }
+        if (LEDGER_TYPE_OIL.equals(ledgerType)) {
+            salesLedger.setOilDepotId(null);
+            salesLedger.setOilDepotName(null);
+            salesLedger.setTankId(null);
+            salesLedger.setTankNo(null);
+            // updateById 浼氳烦杩� null锛屽淇敼鏄┖鎿嶄綔锛涙柊澧炴椂涓庢敼閫犲墠涓�鑷达紙鏈氨涓� null锛�
+            salesLedger.setContractAmount(null);
+            return;
+        }
+        if (!LEDGER_TYPE_DEPOSIT.equals(ledgerType)) {
+            throw new BaseException("閿�鍞被鍨嬩笉鍚堟硶:" + ledgerType);
+        }
+        if (salesLedgerDto.getOilDepotId() == null) {
+            throw new BaseException("浠e偍蹇呴』閫夋嫨娌瑰簱");
+        }
+        if (salesLedgerDto.getTankId() == null) {
+            throw new BaseException("浠e偍蹇呴』閫夋嫨鍌ㄧ綈");
+        }
+        if (salesLedgerDto.getContractAmount() == null
+                || salesLedgerDto.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BaseException("浠e偍蹇呴』濉啓鍚堝悓閲戦");
+        }
+        OilDepot oilDepot = oilDepotMapper.selectById(salesLedgerDto.getOilDepotId());
+        if (oilDepot == null) {
+            throw new BaseException("娌瑰簱涓嶅瓨鍦�:" + salesLedgerDto.getOilDepotId());
+        }
+        TankInfo tankInfo = tankInfoMapper.selectById(salesLedgerDto.getTankId());
+        if (tankInfo == null) {
+            throw new BaseException("鍌ㄧ綈涓嶅瓨鍦�:" + salesLedgerDto.getTankId());
+        }
+        // 鍚嶇О浠ュ熀纭�鏁版嵁涓哄噯锛屼笉閲囦俊鍓嶇浼犲��
+        salesLedger.setOilDepotName(oilDepot.getDepotName());
+        salesLedger.setTankNo(tankInfo.getTankNo());
+    }
+
+    /**
+     * 鍙戣捣閿�鍞彴璐﹀鎵癸紝璧扮郴缁熷凡鏈夌殑 OA 瀹℃壒娴佺▼銆�
+     * 鍚堝悓閲戦瓒呭嚭瀹㈡埛淇℃墭鍩洪噾鏃讹紝瀹℃壒鏍囬甯︿笂鎻愰啋瀛楃溂
+     */
+    private void startSalesLedgerApproval(SalesLedger ledger, BigDecimal trustFund) {
+        if (ledger == null) {
+            return;
+        }
+        ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne(
+                new LambdaQueryWrapper<ApprovalTemplate>()
+                        .eq(ApprovalTemplate::getBusinessType, TypeEnums.SALES_LEDGER_APPROVAL.getCode())
+                        .eq(ApprovalTemplate::getDeleted, 0)
+                        .orderByDesc(ApprovalTemplate::getId)
+                        .last("LIMIT 1")
+        );
+        if (approvalTemplate == null) {
+            throw new RuntimeException("璇峰厛閰嶇疆閿�鍞彴璐﹀鎵规ā鏉�");
+        }
+        boolean exceedsTrustFund = trustFund != null && ledger.getContractAmount() != null
+                && ledger.getContractAmount().compareTo(trustFund) > 0;
+        String title = ledger.getSalesContractNo() + "瀹℃壒";
+        if (exceedsTrustFund) {
+            title += "锛堝悎鍚岄噾棰濊秴鍑哄鎴蜂俊鎵樺熀閲戯紝璇烽噸鐐瑰鏍革級";
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto();
+        approvalInstance.setTemplateId(approvalTemplate.getId());
+        approvalInstance.setTemplateName(approvalTemplate.getTemplateName());
+        approvalInstance.setBusinessId(ledger.getId());
+        approvalInstance.setBusinessType(TypeEnums.SALES_LEDGER_APPROVAL.getCode());
+        approvalInstance.setCurrentLevel(1);
+        approvalInstance.setTitle(title);
+        approvalInstance.setApplicantId(loginUser.getUserId());
+        approvalInstance.setApplicantName(loginUser.getNickName());
+        approvalInstance.setApplyTime(LocalDateTime.now());
+        try {
+            approvalInstanceService.add(approvalInstance);
+        } catch (Exception e) {
+            log.error("SalesLedgerServiceImpl approve error for salesContractNo: {}", ledger.getSalesContractNo(), e);
+            throw new RuntimeException("瀹℃壒澶辫触: " + e.getMessage(), e);
+        }
+    }
+
     @Override
     public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
         // 鎸塈D鍒嗙粍锛屽尯鍒嗘柊澧炲拰鏇存柊鐨勮褰�
diff --git a/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
index 351f1c9..9007342 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -36,6 +36,7 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDateTime;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -48,6 +49,10 @@
 @RequiredArgsConstructor
 public class ShippingInfoServiceImpl extends ServiceImpl<ShippingInfoMapper, ShippingInfo> implements ShippingInfoService {
 
+    /**
+     * listPage 鐨勮鏉ユ簮鏍囪锛歴hipping=鍙戣揣鍗曪紝oilOut=閿�鍞被娌瑰搧鍑哄簱
+     */
+    private static final String SOURCE_OIL_OUT = "oilOut";
 
     private final ShippingInfoMapper shippingInfoMapper;
 
@@ -69,10 +74,26 @@
         IPage<ShippingInfoDto> listPage = shippingInfoMapper.listPage(page, req);
         listPage.getRecords().forEach(item -> {
             item.setStorageBlobVOs(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.IMAGE, RecordTypeEnum.SHIPPING_INFO, item.getId()));
+            // 娌瑰搧鍑哄簱琛屾病鏈� shipping_product_detail锛屾壒鍙锋槑缁嗗彧鑳界敱鍑哄簱璁板綍鑷韩瀛楁鏋勯��
+            if (SOURCE_OIL_OUT.equals(item.getSource())) {
+                item.setBatchNoList(buildOilOutBatchNoList(item));
+            }
         });
         return listPage;
     }
 
+    /**
+     * 娌瑰搧鍑哄簱琛岀殑鎵瑰彿鏄庣粏锛屼緵鍙戣揣鍙拌处璇︽儏寮规鐨勩�屾壒鍙枫�嶆槑缁嗚〃灞曠ず
+     */
+    private List<ShippingProductDetailDto> buildOilOutBatchNoList(ShippingInfoDto item) {
+        ShippingProductDetailDto detail = new ShippingProductDetailDto();
+        detail.setBatchNo(item.getOutBatchNo());
+        detail.setProductName(item.getProductName());
+        detail.setSpecificationModel(item.getSpecificationModel());
+        detail.setDeliveryQuantity(item.getTotalQuantity());
+        return Collections.singletonList(detail);
+    }
+
     @Override
     public boolean deductStock(ShippingInfoDto req) {
         ShippingInfo byId = this.getById(req.getId());
diff --git a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
index b999a16..730c18e 100644
--- a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
+++ b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java
@@ -27,8 +27,8 @@
     @Schema(description = "鏀舵閲戦")
     private BigDecimal receiptPaymentAmount;
 
-    @Schema(description = "浠樻閲戦")
-    private BigDecimal paymentAmount;
+    @Schema(description = "鍥炴閲戦锛堝線鏉ユ祦姘达級")
+    private BigDecimal receiptRecordAmount;
 
     @Schema(description = "搴旀敹閲戦")
     private BigDecimal receiptableAmount;
diff --git a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java
index 5d79da4..a7b88d8 100644
--- a/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java
+++ b/src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java
@@ -23,9 +23,9 @@
     //璇ュ鎴烽攢鍞敹娆剧疮璁¢噾棰�
     private BigDecimal receiptPaymentAmount;
 
-    @Schema(description = "浠樻閲戦")
-    //璇ュ鎴峰線鏉ヤ粯娆剧疮璁¢噾棰濓紙payment_record 鎵嬪伐娴佹按锛�
-    private BigDecimal paymentAmount;
+    @Schema(description = "鍥炴閲戦锛堝線鏉ユ祦姘达級")
+    //璇ュ鎴峰線鏉ュ洖娆剧疮璁¢噾棰濓紙payment_record 鎵嬪伐娴佹按锛宲arty_type=1 direction=1锛�
+    private BigDecimal receiptRecordAmount;
 
     @Schema(description = "搴旀敹閲戦")
     //璇ュ鎴烽攢鍞簲鏀剁疮璁¢噾棰�=鍒濆閲戦-鍥炴-閫�娆�
diff --git a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
index 3911b43..c504265 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
@@ -43,6 +43,13 @@
         return R.ok(stockInventoryDtoIPage);
     }
 
+    @GetMapping("/pageScrapStock")
+    @Operation(summary = "鎶ュ簾鍝侀�夋枡鍒嗛〉锛堝彧璇伙紝甯︽壒鍙凤級")
+    public R pageScrapStock(Page page, StockInventoryDto stockInventoryDto) {
+        IPage<StockInventoryDto> result = stockInventoryService.pageScrapStock(page, stockInventoryDto);
+        return R.ok(result);
+    }
+
     @GetMapping("/pageListCombinedStockInventory")
     @Operation(summary = "鍒嗛〉鏌ヨ鑱斿悎搴撳瓨鍒楄〃")
     public R pageListCombinedStockInventory(Page page, StockInventoryDto stockInventoryDto) {
diff --git a/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java b/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
index 660cf31..5993ef2 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
@@ -98,4 +98,17 @@
         return AjaxResult.success();
     }
 
+    @GetMapping("/oilOutAvailableQty")
+    @Operation(summary = "娌瑰搧鍑哄簱鍙敤閲忥紙涓婃鍓╀綑娌归噺+褰撳墠搴撳瓨锛�")
+    public AjaxResult oilOutAvailableQty(StockOutRecordDto stockOutRecordDto) {
+        return AjaxResult.success(stockOutRecordService.getOilOutAvailableQty(stockOutRecordDto));
+    }
+
+    @PutMapping("/{id}/residualOil")
+    @Log(title = "鍑哄簱绠$悊-褰曞叆鍓╀綑娌归噺", businessType = BusinessType.UPDATE)
+    @Operation(summary = "褰曞叆鏈閫佸畬璐у悗鐨勫墿浣欐补閲忥紙鍙洿鏂拌瀛楁锛�")
+    public AjaxResult updateResidualOil(@PathVariable("id") Long id, @RequestBody StockOutRecordDto stockOutRecordDto) {
+        return AjaxResult.success(stockOutRecordService.updateResidualOil(id, stockOutRecordDto.getResidualOil()));
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/stock/dto/OilOutAvailableDto.java b/src/main/java/com/ruoyi/stock/dto/OilOutAvailableDto.java
new file mode 100644
index 0000000..2cae4b8
--- /dev/null
+++ b/src/main/java/com/ruoyi/stock/dto/OilOutAvailableDto.java
@@ -0,0 +1,25 @@
+package com.ruoyi.stock.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 娌瑰搧鍑哄簱鍙敤閲忥細涓嬫鍙戣揣 = 涓婃鍓╀綑娌归噺 + 搴撳瓨閲�
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-09-20
+ */
+@Data
+public class OilOutAvailableDto {
+
+    @Schema(description = "璇ヨ溅璇ヨ鏍间笂娆¢�佸畬璐у悗鍓╀綑娌归噺(鍚�)")
+    private BigDecimal lastResidualOil;
+
+    @Schema(description = "褰撳墠搴撳瓨閲�(鍚�)")
+    private BigDecimal stockQuantity;
+
+    @Schema(description = "鍙敤閲�(鍚�) = 涓婃鍓╀綑娌归噺 + 褰撳墠搴撳瓨閲�")
+    private BigDecimal availableQty;
+}
diff --git a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
index dbc271b..fdae5ec 100644
--- a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
+++ b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
@@ -29,6 +29,11 @@
 
     IPage<StockInventoryDto> pagestockInventory(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
 
+    /**
+     * 鎶ュ簾鍝侀�夋枡鍒嗛〉锛堝彧璇伙紝甯︽壒鍙凤紱涓� pagestockInventory 鐩镐簰鐙珛锛�
+     */
+    IPage<StockInventoryDto> selectScrapStockPage(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
+
     IPage<StockInventoryDto> pageListCombinedStockInventory(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
 
     int updateAddStockInventory(@Param("ew") StockInventoryDto stockInventoryDto);
diff --git a/src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java b/src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java
index c391587..52c9183 100644
--- a/src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java
+++ b/src/main/java/com/ruoyi/stock/mapper/StockOutRecordMapper.java
@@ -28,4 +28,14 @@
     List<StockOutRecordExportData> listStockOutRecordExportData(@Param("params") StockOutRecordDto stockOutRecordDto);
 
     IPage<SalesOutboundVo> listPageAccountSales(Page page, @Param("req") SalesOutboundDto salesOutboundDto);
+
+    /**
+     * 鎸夋潵婧愭槑缁唅d鎵归噺鏌ヨ鍑哄簱璁板綍銆�
+     *
+     * <p>stock_out_record 涓嶅湪 {@code IgnoreTableConfig.IGNORE_TABLES} 鐧藉悕鍗曢噷锛屾櫘閫氭煡璇細琚�
+     * DataScopeSqlInterceptor 杩藉姞 create_user/dept_id 鏉′欢銆傚澶栬鍗曡闈犺繖鎵硅褰曞仛銆屽畬鎴愬墠鐨勫嚭搴撳鎵规牎楠屻��
+     * 鍜屻�屼綔搴熸椂鐨勫崰鐢ㄩ噴鏀俱�嶏紝鎹釜璐﹀彿鏌ヤ笉鍒板氨浼氶潤榛樻紡鏍¢獙銆佸崰鐢ㄩ噴鏀句笉鎺夛紝鎵�浠ヨ繖閲屾樉寮忚烦杩囨暟鎹潈闄愩��</p>
+     */
+    List<StockOutRecord> selectByRecordIds(@Param("recordIds") List<Long> recordIds,
+                                           @Param("recordType") String recordType);
 }
diff --git a/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java b/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
index 2379658..fff787e 100644
--- a/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
+++ b/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
@@ -131,6 +131,15 @@
     @Schema(description = "杞︽寕鐗屽彿")
     private String trailerPlateNo;
 
+    @Schema(description = "杞﹁締id锛堣溅杈嗕富鏁版嵁锛�")
+    private Long vehicleId;
+
+    @Schema(description = "鏈閫佸畬璐у悗鍓╀綑娌归噺(鍚�)")
+    private BigDecimal residualOil;
+
+    @Schema(description = "瀹℃壒閫氳繃鏃跺疄闄呬粠搴撳瓨鎵e噺鐨勬暟閲忥紙鍑哄簱鏁伴噺-涓婃鍓╀綑娌归噺锛�")
+    private BigDecimal deductQuantity;
+
     @Schema(description = "浜у搧瑙勬牸id")
     private Long productModelId;
 
diff --git a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
index 0176b53..d69ecb5 100644
--- a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
@@ -25,6 +25,11 @@
 
     IPage<StockInventoryDto> pagestockInventory(Page page, StockInventoryDto stockInventoryDto);
 
+    /**
+     * 鎶ュ簾鍝侀�夋枡鍒嗛〉锛堝彧璇伙紝甯︽壒鍙凤級
+     */
+    IPage<StockInventoryDto> pageScrapStock(Page page, StockInventoryDto stockInventoryDto);
+
     IPage<StockInventoryDto> pageListCombinedStockInventory(Page page, StockInventoryDto stockInventoryDto);
 
     Boolean addstockInventory(StockInventoryDto stockInventoryDto);
diff --git a/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java b/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
index ca498b3..c77851d 100644
--- a/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
@@ -3,10 +3,12 @@
 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.OilOutAvailableDto;
 import com.ruoyi.stock.dto.StockOutRecordDto;
 import com.ruoyi.stock.pojo.StockOutRecord;
 
 import jakarta.servlet.http.HttpServletResponse;
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -33,4 +35,38 @@
     int batchApprove(List<Long> ids, Integer approvalStatus);
 
     int batchReAudit(List<Long> ids);
+
+    /**
+     * 褰曞叆銆屾湰娆¢�佸畬璐у悗鍓╀綑娌归噺銆嶃��
+     * 鍙洿鏂� residual_oil锛屼笉鍔ㄥ叾浠栧垪锛涜蛋 {@link #update} 浼氭妸鏈紶鐨勫瓧娈靛叏閮ㄧ疆绌猴紝鏁呭崟鐙紑涓�涓帴鍙�
+     *
+     * @param id          鍑哄簱璁板綍id
+     * @param residualOil 鍓╀綑娌归噺(鍚�)锛屼笉鑳戒负绌恒�佷笉鑳藉皬浜�0銆佷笉鑳藉ぇ浜庡嚭搴撴暟閲�
+     */
+    int updateResidualOil(Long id, BigDecimal residualOil);
+
+    /**
+     * 娌瑰搧鍑哄簱鍙敤閲忥細涓嬫鍙戣揣 = 涓婃鍓╀綑娌归噺 + 褰撳墠搴撳瓨閲�
+     *
+     * @param query 闇�甯� vehicleId銆乸roductModelId锛宐atchNo 鍙��
+     * @return 涓婃鍓╀綑娌归噺 / 褰撳墠搴撳瓨 / 鍙敤閲�
+     */
+    OilOutAvailableDto getOilOutAvailableQty(StockOutRecordDto query);
+
+    /**
+     * 鎸夋潵婧愭槑缁唅d鎵归噺鏌ヨ鍑哄簱璁板綍锛屼笉鍙楁暟鎹潈闄愮害鏉熴��
+     * 渚涗笟鍔″崟鎹仛銆屽嚭搴撴槸鍚﹀凡瀹℃壒銆嶈繖绫昏嚜韬姸鎬佹満鏍¢獙锛屾崲璐﹀彿鎵ц涔熷繀椤荤湅鍒板悓涓�鎵硅褰�
+     *
+     * @param recordIds  鏉ユ簮鏄庣粏id锛屼负绌鸿繑鍥炵┖闆嗗悎
+     * @param recordType 鍑哄簱绫诲瀷锛岃 StockInQualifiedRecordTypeEnum
+     */
+    List<StockOutRecord> listByRecordIds(List<Long> recordIds, String recordType);
+
+    /**
+     * 閲婃斁鏉ユ簮鏄庣粏鍏宠仈鐨勫嚭搴撹褰曪細瀹℃壒閫氳繃杩囩殑鎸夋壒娆″綊杩樺簱瀛橀噺锛岀劧鍚庡垹闄よ褰曘��
+     * 寰呭鎵�/椹冲洖鐨勮褰曟病鎵h繃搴撳瓨锛屽垹鎺夊嵆閲婃斁鍗犵敤
+     *
+     * @return 鍒犻櫎鐨勮褰曟暟
+     */
+    int releaseByRecordIds(List<Long> recordIds, String recordType);
 }
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 0f70cfe..ff5cd9e 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -80,6 +80,11 @@
     }
 
     @Override
+    public IPage<StockInventoryDto> pageScrapStock(Page page, StockInventoryDto stockInventoryDto) {
+        return stockInventoryMapper.selectScrapStockPage(page, stockInventoryDto);
+    }
+
+    @Override
     public IPage<StockInventoryDto> pageListCombinedStockInventory(Page page, StockInventoryDto stockInventoryDto) {
         return stockInventoryMapper.pageListCombinedStockInventory(page, stockInventoryDto);
     }
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
index 7138641..1445d45 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -2,10 +2,13 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.account.mapper.sales.AccountInvoiceApplicationMapper;
 import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
+import com.ruoyi.basic.pojo.Vehicle;
+import com.ruoyi.basic.service.IVehicleService;
 import com.ruoyi.common.enums.ReviewStatusEnum;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
@@ -15,8 +18,14 @@
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.outsourcing.enums.OutsourcingOrderStatusEnum;
+import com.ruoyi.outsourcing.mapper.OutsourcingOrderMapper;
+import com.ruoyi.outsourcing.mapper.OutsourcingOrderProductMapper;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrder;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrderProduct;
 
 import java.time.LocalDateTime;
+import com.ruoyi.stock.dto.OilOutAvailableDto;
 import com.ruoyi.stock.dto.StockInventoryDto;
 import com.ruoyi.stock.dto.StockOutRecordDto;
 import com.ruoyi.stock.dto.StockUninventoryDto;
@@ -34,7 +43,10 @@
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
+import java.math.BigDecimal;
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * <p>
@@ -47,11 +59,19 @@
 @Service
 @RequiredArgsConstructor
 public class StockOutRecordServiceImpl extends ServiceImpl<StockOutRecordMapper, StockOutRecord> implements StockOutRecordService {
+    /**
+     * 鍑哄簱绫诲埆锛氶攢鍞�傞攢鍞被娌瑰搧鍑哄簱鍦ㄥ鎵归�氳繃鏃舵寜鎵规鎵e噺搴撳瓨
+     */
+    private static final String OUT_CATEGORY_SALE = "閿�鍞�";
+
     private final StockOutRecordMapper stockOutRecordMapper;
+    private final IVehicleService vehicleService;
     private final StockInventoryMapper stockInventoryMapper;
     private final StockUninventoryMapper stockUninventoryMapper;
     private final AccountSalesCollectionMapper accountSalesCollectionMapper;
     private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper;
+    private final OutsourcingOrderProductMapper outsourcingOrderProductMapper;
+    private final OutsourcingOrderMapper outsourcingOrderMapper;
 
     @Override
     public IPage<StockOutRecordDto> listPage(Page page, StockOutRecordDto stockOutRecordDto) {
@@ -60,6 +80,14 @@
 
     @Override
     public int add(StockOutRecordDto stockOutRecordDto) {
+        if (OUT_CATEGORY_SALE.equals(stockOutRecordDto.getOutCategory())) {
+            // 閿�鍞被娌瑰搧鍑哄簱瀹℃壒閫氳繃鏃惰鎸夋壒娆℃墸鍑忓簱瀛橈紝鎵规涓嶈兘涓虹┖
+            if (StringUtils.isEmpty(stockOutRecordDto.getBatchNo())) {
+                throw new BaseException("閿�鍞被鍑哄簱蹇呴』閫夋嫨鍑哄簱鎵规");
+            }
+            resolveVehicle(stockOutRecordDto);
+            checkSaleOutAvailable(stockOutRecordDto);
+        }
         LocalDateTime createTime = stockOutRecordDto.getCreateTime();
         if (createTime == null) {
             createTime = LocalDateTime.now();
@@ -87,6 +115,7 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int batchDelete(List<Long> ids) {
         //todo 濡傛灉鍑哄簱涓庡紑绁ㄦ敹娆炬湁鍏宠仈鍒欐棤娉曞垹闄�
         if (accountSalesCollectionMapper.existsByStockOutRecordId(ids) ||
@@ -133,8 +162,35 @@
                     stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto);
                 }
             }
+            else if (OUT_CATEGORY_SALE.equals(stockOutRecord.getOutCategory())) {
+                // 閿�鍞被娌瑰搧鍑哄簱鍙湁瀹℃壒閫氳繃鏃舵墠鎵h繃搴撳瓨锛屽垹闄ゅ凡閫氳繃鐨勮褰曢渶鎸夊悓鎵规褰掕繕
+                returnSaleOilOutStock(stockOutRecord);
+            }
         }
         return stockOutRecordMapper.deleteBatchIds(ids);
+    }
+
+    /**
+     * 閿�鍞被娌瑰搧鍑哄簱褰掕繕搴撳瓨锛屼粎瀵瑰鎵归�氳繃锛堝嵆鎵h繃搴撳瓨锛夌殑璁板綍鐢熸晥銆�
+     * 褰掕繕閲忓彇 deduct_quantity锛堝疄闄呮墸鍑忛噺锛夛紱鏀归�犲墠瀹℃壒閫氳繃鐨勮褰曟病鏈夎鍊硷紝鎸夊叏棰濆綊杩�
+     */
+    private void returnSaleOilOutStock(StockOutRecord stockOutRecord) {
+        if (!ReviewStatusEnum.APPROVED.getCode().equals(stockOutRecord.getApprovalStatus())
+                || StringUtils.isEmpty(stockOutRecord.getBatchNo())
+                || stockOutRecord.getStockOutNum() == null) {
+            return;
+        }
+        BigDecimal returnQuantity = stockOutRecord.getDeductQuantity() != null
+                ? stockOutRecord.getDeductQuantity()
+                : stockOutRecord.getStockOutNum();
+        if (returnQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        StockInventoryDto stockInventoryDto = new StockInventoryDto();
+        stockInventoryDto.setProductModelId(stockOutRecord.getProductModelId());
+        stockInventoryDto.setBatchNo(stockOutRecord.getBatchNo());
+        stockInventoryDto.setQualitity(returnQuantity);
+        stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
     }
 
     @Override
@@ -183,9 +239,7 @@
             if (stockOutRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockOutRecord.getApprovalStatus())) {
                 throw new BaseException("鍙湁寰呭鎵圭姸鎬佺殑璁板綍鎵嶈兘瀹℃壒,鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches());
             }
-            stockOutRecord.setApprovalStatus(approvalStatus);
-            stockOutRecordMapper.updateById(stockOutRecord);
-            // 瀹℃壒閫氳繃鏃讹紝鎵e噺搴撳瓨
+            // 瀹℃壒閫氳繃鏃跺厛鎵e噺搴撳瓨锛屾墸鍑忛噺锛坉educt_quantity锛夎闅忓鎵圭姸鎬佷竴璧疯惤搴擄紝鎵�浠ユ斁鍦ㄦ洿鏂颁箣鍓�
             if (ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus)) {
                 if ("0".equals(stockOutRecord.getType())) {
                     // 鍚堟牸鍑哄簱 -> 鍏堟煡搴撳瓨鏄惁瀛樺湪锛屽瓨鍦ㄦ墠鎵e噺
@@ -209,10 +263,94 @@
                     stockUninventoryDto.setBatchNo(stockOutRecord.getBatchNo());
                     stockUninventoryDto.setQualitity(stockOutRecord.getStockOutNum());
                     stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
+                } else if (OUT_CATEGORY_SALE.equals(stockOutRecord.getOutCategory())) {
+                    // 閿�鍞被娌瑰搧鍑哄簱锛坱ype 涓虹┖锛夊鎵归�氳繃鏃舵寜鎵规鎵e噺搴撳瓨
+                    deductSaleOilOutStock(stockOutRecord);
                 }
+            }
+            stockOutRecord.setApprovalStatus(approvalStatus);
+            stockOutRecordMapper.updateById(stockOutRecord);
+            if (ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus)
+                    && StockOutQualifiedRecordTypeEnum.OUTSOURCING_SCRAP_OUT.getCode()
+                            .equals(stockOutRecord.getRecordType())) {
+                tryCompleteOutsourcingOrder(stockOutRecord);
             }
         }
         return ids.size();
+    }
+
+    /**
+     * 濮斿鎶ュ簾鍝佸嚭搴撳鎵归�氳繃鍚庯紝鑻ヨ濮斿璁㈠崟鐨勬姤搴熷搧閫夋枡琛屽凡<b>鍏ㄩ儴</b>鍑哄簱瀹℃壒閫氳繃锛�
+     * 鑷姩鎶婅鍗曚粠銆屽凡纭銆嶆帹鍒般�屽凡瀹屾垚銆嶏紝鐪佹帀浜哄伐鍐嶇偣涓�娆″畬鎴愩��
+     *
+     * <p>鐩存帴鐢� mapper 鑰屼笉鏄� {@code IOutsourcingOrderService#complete}锛氬悗鑰呬緷璧栨湰鏈嶅姟锛�
+     * 涓よ竟閮借蛋鏋勯�犲櫒娉ㄥ叆浼氬舰鎴愬惊鐜緷璧栥�傞┏鍥�/鍙嶅涓嶅洖閫�璁㈠崟鐘舵�侊紙澶勭悊閿欒瀹℃壒璧颁汉宸ラ噸鏂板鎵癸級銆�</p>
+     */
+    private void tryCompleteOutsourcingOrder(StockOutRecord record) {
+        if (record.getRecordId() == null) {
+            return;
+        }
+        OutsourcingOrderProduct line = outsourcingOrderProductMapper.selectById(record.getRecordId());
+        if (line == null || line.getOutsourcingOrderId() == null) {
+            return;
+        }
+        Long orderId = line.getOutsourcingOrderId();
+        // isScrap 鍦� Java 渚ц繃婊わ紝涓庡澶栨ā鍧楄嚜宸辩殑 scrapLineIds 涓�鑷达紝涓嶄緷璧� Boolean 瀛楁鐨勫垪鍚嶆帹鏂�
+        List<Long> lineIds = outsourcingOrderProductMapper.selectList(
+                        Wrappers.<OutsourcingOrderProduct>lambdaQuery()
+                                .eq(OutsourcingOrderProduct::getOutsourcingOrderId, orderId))
+                .stream()
+                .filter(item -> Boolean.TRUE.equals(item.getIsScrap()))
+                .map(OutsourcingOrderProduct::getId)
+                .toList();
+        if (lineIds.isEmpty()) {
+            return;
+        }
+        // 鍑哄簱璁板綍涓�鏉″搴斾竴琛屾槑缁嗭紱缂鸿褰曪紙琚弽瀹″垹闄ょ瓑锛夊氨涓嶅畬鎴愶紝涓庝汉宸ュ畬鎴愮殑鏍¢獙鍙e緞涓�鑷�
+        long approvedCount = listByRecordIds(lineIds, record.getRecordType()).stream()
+                .filter(item -> ReviewStatusEnum.APPROVED.getCode().equals(item.getApprovalStatus()))
+                .count();
+        if (approvedCount < lineIds.size()) {
+            return;
+        }
+        // 鏉′欢鏇存柊锛屽彧鏈夈�屽凡纭銆嶈兘鎺ㄨ繘锛涘凡浣滃簾/宸插畬鎴愮殑璁㈠崟涓嶅姩
+        outsourcingOrderMapper.update(null, Wrappers.<OutsourcingOrder>lambdaUpdate()
+                .eq(OutsourcingOrder::getId, orderId)
+                .eq(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.CONFIRMED.getCode())
+                .set(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.COMPLETED.getCode()));
+    }
+
+    /**
+     * 閿�鍞被娌瑰搧鍑哄簱鎵e噺搴撳瓨銆傚嚭搴撴暟閲忔槸鏈瑁呰溅鎬婚噺锛岃溅涓婁笂娆″墿鐨勬补鏈缁х画鐢紝
+     * 鎵�浠ュ彧浠庡簱瀛樿ˉ瓒炽�屽嚭搴撴暟閲� 鈭� 涓婃鍓╀綑娌归噺銆嶇殑宸銆�
+     * updateSubtractStockInventory 鑷甫 qualitity &gt;= 鏁伴噺 鐨勫畧鍗紝褰卞搷琛屾暟涓� 0 鍗冲簱瀛樹笉瓒筹紝
+     * 鎶涢敊鐢卞灞備簨鍔″洖婊�
+     */
+    private void deductSaleOilOutStock(StockOutRecord stockOutRecord) {
+        String suffix = ",鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches();
+        if (StringUtils.isEmpty(stockOutRecord.getBatchNo())) {
+            throw new BaseException("閿�鍞被鍑哄簱蹇呴』閫夋嫨鍑哄簱鎵规" + suffix);
+        }
+        BigDecimal quantity = stockOutRecord.getStockOutNum();
+        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BaseException("閿�鍞被鍑哄簱鏁伴噺蹇呴』澶т簬0" + suffix);
+        }
+        BigDecimal lastResidual = getLastResidualOil(stockOutRecord.getVehicleId(), stockOutRecord.getProductModelId(), stockOutRecord.getId());
+        if (lastResidual.compareTo(quantity) > 0) {
+            throw new BaseException("鍑哄簱鏁伴噺涓嶈兘灏忎簬璇ヨ溅涓婃鍓╀綑娌归噺" + toPlain(lastResidual) + suffix);
+        }
+        BigDecimal deduct = quantity.subtract(lastResidual);
+        if (deduct.compareTo(BigDecimal.ZERO) > 0) {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setProductModelId(stockOutRecord.getProductModelId());
+            stockInventoryDto.setBatchNo(stockOutRecord.getBatchNo());
+            stockInventoryDto.setQualitity(deduct);
+            if (stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto) == 0) {
+                throw new BaseException("搴撳瓨涓嶈冻鏃犳硶鍑哄簱" + suffix);
+            }
+        }
+        // 杞︿笂鍓╂补鍒氬ソ澶燂紙deduct=0锛夋椂涓嶆秹鍙婂簱瀛樻洿鏂帮紝鍚屾牱钀藉簱渚夸簬鍒犻櫎鏃舵寜瀹為檯鎵e噺閲忓綊杩�
+        stockOutRecord.setDeductQuantity(deduct);
     }
 
     @Override
@@ -235,6 +373,24 @@
         return ids.size();
     }
 
+    @Override
+    public int updateResidualOil(Long id, BigDecimal residualOil) {
+        StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
+        if (stockOutRecord == null) {
+            throw new BaseException("璇ュ嚭搴撹褰曚笉瀛樺湪,鏃犳硶鏇存柊!!!");
+        }
+        if (residualOil == null || residualOil.compareTo(BigDecimal.ZERO) < 0) {
+            throw new BaseException("鍓╀綑娌归噺涓嶈兘涓虹┖涓斾笉鑳藉皬浜�0");
+        }
+        if (stockOutRecord.getStockOutNum() != null && residualOil.compareTo(stockOutRecord.getStockOutNum()) > 0) {
+            throw new BaseException("鍓╀綑娌归噺涓嶈兘澶т簬鍑哄簱鏁伴噺" + toPlain(stockOutRecord.getStockOutNum()));
+        }
+        StockOutRecord update = new StockOutRecord();
+        update.setId(id);
+        update.setResidualOil(residualOil);
+        return stockOutRecordMapper.updateById(update);
+    }
+
     private StockInventory getStockInventory(Long productModelId, String batchNo) {
         LambdaQueryWrapper<StockInventory> eq = new LambdaQueryWrapper<>();
         eq.eq(StockInventory::getProductModelId, productModelId);
@@ -256,4 +412,145 @@
         }
         return stockUninventoryMapper.selectOne(eq);
     }
+
+    /**
+     * 杞﹁締鍥炲~锛氬墠绔彲浠ョ洿鎺ヤ紶 vehicleId锛屼篃鍙互鍙紶杞︾墝鍙凤紝
+     * 鍚庤�呮寜杞﹁締涓绘暟鎹弽鏌ュ洖濉�備袱鑰呴兘鎷夸笉鍒板氨鏃犳硶瀹氫綅銆屼笂娆″墿浣欐补閲忋�嶏紝鐩存帴鎶ラ敊
+     */
+    private void resolveVehicle(StockOutRecordDto stockOutRecordDto) {
+        if (stockOutRecordDto.getVehicleId() != null) {
+            return;
+        }
+        if (StringUtils.isEmpty(stockOutRecordDto.getTruckPlateNo())) {
+            throw new BaseException("閿�鍞被鍑哄簱蹇呴』閫夋嫨杞﹁締");
+        }
+        LambdaQueryWrapper<Vehicle> wrapper = new LambdaQueryWrapper<Vehicle>()
+                .eq(Vehicle::getPlateNumber, stockOutRecordDto.getTruckPlateNo());
+        Vehicle vehicle = vehicleService.getOne(wrapper, false);
+        if (vehicle == null) {
+            throw new BaseException("杞︾墝鍙� " + stockOutRecordDto.getTruckPlateNo() + " 涓嶅湪杞﹁締绠$悊涓紝璇峰厛鍒拌溅杈嗙鐞嗘坊鍔�");
+        }
+        stockOutRecordDto.setVehicleId(vehicle.getId());
+    }
+
+    /**
+     * 鍑哄簱鏁伴噺鏄湰娆¤杞︽�婚噺锛屽繀椤昏惤鍦ㄣ�屼笂娆″墿浣欐补閲� ~ 涓婃鍓╀綑娌归噺 + 搴撳瓨銆嶅尯闂村唴
+     */
+    private void checkSaleOutAvailable(StockOutRecordDto stockOutRecordDto) {
+        String suffix = ",鍑哄簱鎵规:" + stockOutRecordDto.getOutboundBatches();
+        BigDecimal quantity = stockOutRecordDto.getStockOutNum();
+        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BaseException("閿�鍞被鍑哄簱鏁伴噺蹇呴』澶т簬0" + suffix);
+        }
+        BigDecimal lastResidual = getLastResidualOil(stockOutRecordDto.getVehicleId(), stockOutRecordDto.getProductModelId(), null);
+        if (lastResidual.compareTo(quantity) > 0) {
+            throw new BaseException("鍑哄簱鏁伴噺涓嶈兘灏忎簬璇ヨ溅涓婃鍓╀綑娌归噺" + toPlain(lastResidual) + suffix);
+        }
+        BigDecimal stockQuantity = getStockQuantity(stockOutRecordDto.getProductModelId(), stockOutRecordDto.getBatchNo());
+        BigDecimal availableQty = lastResidual.add(stockQuantity);
+        if (quantity.compareTo(availableQty) > 0) {
+            throw new BaseException("鍑哄簱鏁伴噺瓒呭嚭鍙敤閲�(涓婃鍓╀綑" + toPlain(lastResidual)
+                    + ",搴撳瓨" + toPlain(stockQuantity) + ",鍙敤" + toPlain(availableQty) + ")" + suffix);
+        }
+    }
+
+    /**
+     * 璇ヨ溅璇ヨ鏍间笂娆¢�佸畬璐у悗鍓╀綑鐨勬补閲忥紝鍙栨渶杩戜竴鏉″~杩囨畫娌逛笖宸插鎵圭殑璁板綍銆�
+     * excludeId 鐢ㄤ簬鎺掗櫎銆屾湰娆¤瀹℃壒鐨勮繖鏉¤嚜宸便�嶁�斺�斿畠鑷繁鐨� residual_oil 鏄�佸畬涔嬪悗鐨勫�硷紝涓嶈兘鐢ㄦ潵绠楁湰娆℃墸鍑�
+     */
+    private BigDecimal getLastResidualOil(Long vehicleId, Long productModelId, Long excludeId) {
+        if (vehicleId == null || productModelId == null) {
+            return BigDecimal.ZERO;
+        }
+        LambdaQueryWrapper<StockOutRecord> wrapper = new LambdaQueryWrapper<StockOutRecord>()
+                .eq(StockOutRecord::getVehicleId, vehicleId)
+                .eq(StockOutRecord::getProductModelId, productModelId)
+                .isNotNull(StockOutRecord::getResidualOil)
+                .eq(StockOutRecord::getApprovalStatus, ReviewStatusEnum.APPROVED.getCode());
+        if (excludeId != null) {
+            wrapper.lt(StockOutRecord::getId, excludeId);
+        }
+        wrapper.orderByDesc(StockOutRecord::getId).last("limit 1");
+        StockOutRecord last = stockOutRecordMapper.selectOne(wrapper);
+        return last == null || last.getResidualOil() == null ? BigDecimal.ZERO : last.getResidualOil();
+    }
+
+    /**
+     * 褰撳墠搴撳瓨閲忥紝鎸夎鏍煎悎璁★紱浼犱簡鎵规鍙峰啀鍙犲姞鎵规鏉′欢
+     */
+    private BigDecimal getStockQuantity(Long productModelId, String batchNo) {
+        if (productModelId == null) {
+            return BigDecimal.ZERO;
+        }
+        LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<StockInventory>()
+                .eq(StockInventory::getProductModelId, productModelId);
+        if (!StringUtils.isEmpty(batchNo)) {
+            wrapper.eq(StockInventory::getBatchNo, batchNo);
+        }
+        return stockInventoryMapper.selectList(wrapper).stream()
+                .map(StockInventory::getQualitity)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+    @Override
+    public OilOutAvailableDto getOilOutAvailableQty(StockOutRecordDto query) {
+        if (query == null || query.getVehicleId() == null || query.getProductModelId() == null) {
+            throw new BaseException("璇峰厛閫夋嫨杞﹁締鍜屼骇鍝佽鏍�");
+        }
+        BigDecimal lastResidualOil = getLastResidualOil(query.getVehicleId(), query.getProductModelId(), query.getId());
+        BigDecimal stockQuantity = getStockQuantity(query.getProductModelId(), query.getBatchNo());
+        OilOutAvailableDto result = new OilOutAvailableDto();
+        result.setLastResidualOil(lastResidualOil);
+        result.setStockQuantity(stockQuantity);
+        result.setAvailableQty(lastResidualOil.add(stockQuantity));
+        return result;
+    }
+
+    private String toPlain(BigDecimal value) {
+        return value == null ? "0" : value.stripTrailingZeros().toPlainString();
+    }
+
+    @Override
+    public List<StockOutRecord> listByRecordIds(List<Long> recordIds, String recordType) {
+        if (CollectionUtils.isEmpty(recordIds) || StringUtils.isEmpty(recordType)) {
+            return Collections.emptyList();
+        }
+        return stockOutRecordMapper.selectByRecordIds(recordIds, recordType);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int releaseByRecordIds(List<Long> recordIds, String recordType) {
+        List<StockOutRecord> records = listByRecordIds(recordIds, recordType);
+        if (records.isEmpty()) {
+            return 0;
+        }
+        for (StockOutRecord record : records) {
+            if (!ReviewStatusEnum.APPROVED.getCode().equals(record.getApprovalStatus())) {
+                continue;
+            }
+            // type=0 褰掕繕鍚堟牸搴撳瓨锛坰tock_inventory锛夛紝type=1 褰掕繕涓嶅悎鏍煎簱瀛橈紙stock_uninventory锛�
+            if ("0".equals(record.getType())) {
+                StockInventoryDto stockInventoryDto = new StockInventoryDto();
+                stockInventoryDto.setProductModelId(record.getProductModelId());
+                stockInventoryDto.setBatchNo(record.getBatchNo());
+                stockInventoryDto.setQualitity(record.getStockOutNum());
+                if (stockInventoryMapper.updateAddStockInventory(stockInventoryDto) == 0) {
+                    throw new BaseException("搴撳瓨璁板綍涓病鏈夊搴旂殑浜у搧,鏃犳硶褰掕繕,鍑哄簱鎵规:" + record.getOutboundBatches());
+                }
+            } else if ("1".equals(record.getType())) {
+                StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
+                stockUninventoryDto.setProductModelId(record.getProductModelId());
+                stockUninventoryDto.setBatchNo(record.getBatchNo());
+                stockUninventoryDto.setQualitity(record.getStockOutNum());
+                if (stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto) == 0) {
+                    throw new BaseException("搴撳瓨璁板綍涓病鏈夊搴旂殑浜у搧,鏃犳硶褰掕繕,鍑哄簱鎵规:" + record.getOutboundBatches());
+                }
+            } else {
+                throw new BaseException("鍑哄簱璁板綍绫诲瀷寮傚父锛屾棤娉曞綊杩樺簱瀛�,鍑哄簱鎵规:" + record.getOutboundBatches());
+            }
+        }
+        return stockOutRecordMapper.deleteBatchIds(records.stream().map(StockOutRecord::getId).toList());
+    }
 }
diff --git a/src/main/resources/mapper/basic/CustomerMapper.xml b/src/main/resources/mapper/basic/CustomerMapper.xml
index 2f3e800..e1df0a6 100644
--- a/src/main/resources/mapper/basic/CustomerMapper.xml
+++ b/src/main/resources/mapper/basic/CustomerMapper.xml
@@ -128,12 +128,12 @@
                c.customer_name,
                T1.contractAmounts AS contractAmounts,
                IFNULL(T2.receiptPaymentAmount, 0) AS receiptPaymentAmount,
-               IFNULL(T5.paymentAmount, 0) AS paymentAmount,
+               IFNULL(T5.receiptRecordAmount, 0) AS receiptRecordAmount,
                T1.contractAmounts - IFNULL(T2.receiptPaymentAmount, 0) - IFNULL(T4.returnAmount, 0) AS receiptableAmount,
                IFNULL(T4.returnAmount, 0) AS returnAmount
         from (select customer_id, sum(contract_amount) as contractAmounts from sales_ledger group by customer_id) T1
         left join (select customer_id, sum(collection_amount) as receiptPaymentAmount from account_sales_collection group by customer_id) T2 on T1.customer_id = T2.customer_id
-        left join (select party_id, sum(amount) as paymentAmount from payment_record where party_type = 1 and direction = 1 group by party_id) T5 on T5.party_id = T1.customer_id
+        left join (select party_id, sum(amount) as receiptRecordAmount from payment_record where party_type = 1 and direction = 1 group by party_id) T5 on T5.party_id = T1.customer_id
         left join (
             select
                 sl.customer_id,
@@ -159,7 +159,7 @@
                sl.execution_date,
                sl.contract_amount AS contract_amount,
                IFNULL(T1.receiptPaymentAmount, 0) AS receiptPaymentAmount,
-               IFNULL(T5.paymentAmount, 0) AS paymentAmount,
+               IFNULL(T5.receiptRecordAmount, 0) AS receiptRecordAmount,
                sl.contract_amount - IFNULL(T1.receiptPaymentAmount, 0) - IFNULL(T3.returnAmount, 0) AS receiptableAmount,
                IFNULL(T3.returnAmount, 0) AS returnAmount
         from sales_ledger sl
@@ -185,7 +185,7 @@
             group by sl.id
         )T3 on T3.id = sl.id
         left join (
-            select party_id, sum(amount) as paymentAmount
+            select party_id, sum(amount) as receiptRecordAmount
             from payment_record
             where party_type = 1 and direction = 1
             group by party_id
diff --git a/src/main/resources/mapper/basic/SupplierManageMapper.xml b/src/main/resources/mapper/basic/SupplierManageMapper.xml
index 0c104c1..bcadd9e 100644
--- a/src/main/resources/mapper/basic/SupplierManageMapper.xml
+++ b/src/main/resources/mapper/basic/SupplierManageMapper.xml
@@ -74,12 +74,12 @@
                sm.supplier_name,
                T1.contractAmounts,
                IFNULL(T2.paymentAmount, 0) AS paymentAmount,
-               IFNULL(T5.receiptAmount, 0) AS receiptAmount,
+               IFNULL(T5.paymentRecordAmount, 0) AS paymentRecordAmount,
                T1.contractAmounts - IFNULL(T2.paymentAmount, 0) - IFNULL(T4.returnAmount, 0) AS payableAmount,
                IFNULL(T4.returnAmount, 0) AS returnAmount
         from (select supplier_id, sum(contract_amount) as contractAmounts from purchase_ledger group by supplier_id) T1
         left join (select supplier_id, sum(payment_amount) as paymentAmount from account_purchase_payment group by supplier_id) T2 on T1.supplier_id = T2.supplier_id
-        left join (select party_id, sum(amount) as receiptAmount from payment_record where party_type = 2 and direction = 2 group by party_id) T5 on T5.party_id = T1.supplier_id
+        left join (select party_id, sum(amount) as paymentRecordAmount from payment_record where party_type = 2 and direction = 2 group by party_id) T5 on T5.party_id = T1.supplier_id
         left join (
             select
                 supplier_id,
@@ -102,7 +102,7 @@
               pl.execution_date,
               pl.contract_amount AS contract_amount,
               IFNULL(T1.paymentAmount, 0) AS paymentAmount,
-              IFNULL(T5.receiptAmount, 0) AS receiptAmount,
+              IFNULL(T5.paymentRecordAmount, 0) AS paymentRecordAmount,
               pl.contract_amount - IFNULL(T1.paymentAmount, 0) - IFNULL(T3.returnAmount, 0) AS payableAmount,
               IFNULL(T3.returnAmount, 0) AS returnAmount
        from purchase_ledger pl
@@ -130,7 +130,7 @@
            group by pl.id
        )T3 on T3.id = pl.id
        left join (
-           select party_id, sum(amount) as receiptAmount
+           select party_id, sum(amount) as paymentRecordAmount
            from payment_record
            where party_type = 2 and direction = 2
            group by party_id
diff --git a/src/main/resources/mapper/outsourcing/OutsourcingPartnerMapper.xml b/src/main/resources/mapper/outsourcing/OutsourcingPartnerMapper.xml
new file mode 100644
index 0000000..a440e52
--- /dev/null
+++ b/src/main/resources/mapper/outsourcing/OutsourcingPartnerMapper.xml
@@ -0,0 +1,124 @@
+<?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.outsourcing.mapper.OutsourcingPartnerMapper">
+
+    <select id="partnerListPage" resultType="com.ruoyi.outsourcing.dto.OutsourcingPartnerDto">
+        SELECT
+        T1.id,
+        T1.partner_name,
+        T1.taxpayer_identification_num,
+        T1.company_address,
+        T1.company_phone,
+        T1.bank_account_name,
+        T1.bank_account_num,
+        T1.contact_user_name,
+        T1.contact_user_phone,
+        T1.maintain_user_id,
+        T1.maintain_time,
+        T1.partner_type,
+        T1.is_white,
+        T1.remark,
+        T1.create_time,
+        T1.create_user,
+        T1.update_time,
+        T1.tenant_id,
+        T2.nick_name AS maintainUserName
+        FROM outsourcing_partner T1
+        LEFT JOIN sys_user T2 ON T1.maintain_user_id = T2.user_id
+        <where>
+            <if test="dto.partnerName != null and dto.partnerName != ''">
+                AND T1.partner_name LIKE CONCAT('%',#{dto.partnerName},'%')
+            </if>
+            <if test="dto.partnerType != null and dto.partnerType != ''">
+                AND T1.partner_type = #{dto.partnerType}
+            </if>
+            <if test="dto.isWhite != null">
+                AND T1.is_white = #{dto.isWhite}
+            </if>
+        </where>
+        order by T1.id desc
+    </select>
+
+    <select id="partnerExportList" resultType="com.ruoyi.outsourcing.excel.OutsourcingPartnerExcelDto">
+        SELECT
+        T1.partner_name,
+        T1.taxpayer_identification_num,
+        T1.company_address,
+        T1.company_phone,
+        T1.bank_account_name,
+        T1.bank_account_num,
+        T1.contact_user_name,
+        T1.contact_user_phone,
+        T1.partner_type,
+        T2.nick_name AS maintainUserName
+        FROM outsourcing_partner T1
+        LEFT JOIN sys_user T2 ON T1.maintain_user_id = T2.user_id
+        <where>
+            <if test="dto.partnerName != null and dto.partnerName != ''">
+                AND T1.partner_name LIKE CONCAT('%',#{dto.partnerName},'%')
+            </if>
+            <if test="dto.partnerType != null and dto.partnerType != ''">
+                AND T1.partner_type = #{dto.partnerType}
+            </if>
+            <if test="dto.isWhite != null">
+                AND T1.is_white = #{dto.isWhite}
+            </if>
+        </where>
+        order by T1.id desc
+    </select>
+
+    <!-- 寰�鏉ュ垪琛細椤跺眰 from 鏄瓙鏌ヨ锛屾暟鎹潈闄愭嫤鎴櫒澶╃劧璺宠繃锛涗袱渚у彛寰勫繀椤讳竴鑷达紝鏄庣粏渚ф樉寮忔爣璁拌烦杩� -->
+    <select id="partnerTransactions" resultType="com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsVo">
+        select T1.partner_id,
+               op.partner_name,
+               T1.contractAmounts,
+               IFNULL(T2.paidAmount, 0) AS paidAmount,
+               IFNULL(T3.receiptAmount, 0) AS receiptAmount,
+               T1.contractAmounts - IFNULL(T2.paidAmount, 0) AS payableAmount
+        from (select partner_id, sum(contract_amount) as contractAmounts
+              from outsourcing_order
+              where status in (1, 2)
+              group by partner_id) T1
+        left join (select party_id, sum(amount) as paidAmount
+                   from payment_record
+                   where party_type = 3 and direction = 1
+                   group by party_id) T2 on T2.party_id = T1.partner_id
+        left join (select party_id, sum(amount) as receiptAmount
+                   from payment_record
+                   where party_type = 3 and direction = 2
+                   group by party_id) T3 on T3.party_id = T1.partner_id
+        inner join outsourcing_partner op on T1.partner_id = op.id
+        <where>
+            <if test="partnerName != null and partnerName != ''">
+                AND op.partner_name LIKE CONCAT('%',#{partnerName},'%')
+            </if>
+        </where>
+        order by T1.partner_id desc
+    </select>
+
+    <!-- 寰�鏉ユ槑缁嗭細椤跺眰 from 鏄� outsourcing_order锛屼細琚嫤鎴櫒杩藉姞 create_user/dept_id 鏉′欢锛�
+         涓庡垪琛ㄥ彛寰勪笉涓�鑷翠細瀵艰嚧銆屽垪琛ㄦ湁搴斾粯銆佹槑缁� 0 琛屻�嶏紝鏁呯敤 /*data_scope*/ 鏄惧紡璺宠繃 -->
+    <select id="partnerTransactionsDetails"
+            resultType="com.ruoyi.outsourcing.vo.OutsourcingPartnerTransactionsDetailsVo">
+        select /*data_scope*/ oo.id as outsourcingOrderId,
+               oo.order_no,
+               oo.partner_id,
+               oo.partner_name,
+               oo.apply_date,
+               oo.contract_amount,
+               oo.status,
+               IFNULL(T1.paidAmount, 0) AS paidAmount,
+               oo.contract_amount - IFNULL(T1.paidAmount, 0) AS payableAmount
+        from outsourcing_order oo
+        left join (select party_id, sum(amount) as paidAmount
+                   from payment_record
+                   where party_type = 3 and direction = 1
+                   group by party_id) T1 on T1.party_id = oo.partner_id
+        where oo.partner_id = #{partnerId}
+          and oo.status in (1, 2)
+        order by oo.id desc
+    </select>
+
+</mapper>
diff --git a/src/main/resources/mapper/sales/SalesLedgerMapper.xml b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
index 2d6b7f8..95cb482 100644
--- a/src/main/resources/mapper/sales/SalesLedgerMapper.xml
+++ b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -13,6 +13,22 @@
 
     </select>
 
+    <!-- 浠e偍閲戦锛氳瀹㈡埛鍏ㄩ儴銆岀被鍨�=浠e偍銆嶅彴璐﹀悎鍚岄噾棰濆悎璁°�傝法瀹㈡埛姹囨�伙紝鍔� /*data_scope*/ 璺宠繃鏁版嵁鏉冮檺锛�
+         鍚﹀垯闈炵鐞嗗憳鐪嬪埌鐨勬槸琚� dept_id 杩囨护杩囩殑閮ㄥ垎閲戦锛屼細鎶娿�屽凡瓒呬俊鎵樺熀閲戙�嶈鍒ゆ垚鏈秴 -->
+    <select id="selectDepositAmountByCustomerIds" resultType="com.ruoyi.sales.dto.CustomerDepositAmountDto">
+        select /*data_scope*/ customer_id as customerId,
+        IFNULL(sum(contract_amount), 0) as depositTotalAmount
+        from sales_ledger
+        where ledger_type = '浠e偍'
+        <if test="customerIds != null and customerIds.size() > 0">
+            and customer_id in
+            <foreach collection="customerIds" item="cid" open="(" separator="," close=")">
+                #{cid}
+            </foreach>
+        </if>
+        group by customer_id
+    </select>
+
     <select id="selectSalesLedgerList" resultType="com.ruoyi.sales.pojo.SalesLedger">
         SELECT
         T1.id,
@@ -23,6 +39,12 @@
         T1.salesman,
         T1.customer_id,
         T1.customer_name,
+        T1.ledger_type,
+        T1.oil_depot_id,
+        T1.oil_depot_name,
+        T1.tank_id,
+        T1.tank_no,
+        T1.approval_status,
         T1.entry_person,
         T1.remarks,
         T1.attachment_materials,
@@ -39,6 +61,12 @@
             <if test="salesLedgerDto.customerName != null and salesLedgerDto.customerName != '' ">
                 T1.customer_name LIKE CONCAT('%',#{salesLedgerDto.customerName},'%')
             </if>
+            <if test="salesLedgerDto.ledgerType != null and salesLedgerDto.ledgerType != '' ">
+                AND T1.ledger_type = #{salesLedgerDto.ledgerType}
+            </if>
+            <if test="salesLedgerDto.approvalStatus != null and salesLedgerDto.approvalStatus != '' ">
+                AND T1.approval_status = #{salesLedgerDto.approvalStatus}
+            </if>
         </where>
     </select>
 
@@ -51,6 +79,12 @@
         T1.salesman,
         T1.customer_id,
         T1.customer_name,
+        T1.ledger_type,
+        T1.oil_depot_id,
+        T1.oil_depot_name,
+        T1.tank_id,
+        T1.tank_no,
+        T1.approval_status,
         T1.entry_person,
         T1.remarks,
         T1.attachment_materials,
@@ -95,6 +129,12 @@
             <if test="salesLedgerDto.entryDateEnd != null and salesLedgerDto.entryDateEnd != '' ">
                 AND T1.entry_date &lt;= DATE_FORMAT(#{salesLedgerDto.entryDateEnd},'%Y-%m-%d')
             </if>
+            <if test="salesLedgerDto.ledgerType != null and salesLedgerDto.ledgerType != '' ">
+                AND T1.ledger_type = #{salesLedgerDto.ledgerType}
+            </if>
+            <if test="salesLedgerDto.approvalStatus != null and salesLedgerDto.approvalStatus != '' ">
+                AND T1.approval_status = #{salesLedgerDto.approvalStatus}
+            </if>
         </where>
         order by T1.entry_date desc
     </select>
diff --git a/src/main/resources/mapper/sales/ShippingInfoMapper.xml b/src/main/resources/mapper/sales/ShippingInfoMapper.xml
index 8775933..fe60cdc 100644
--- a/src/main/resources/mapper/sales/ShippingInfoMapper.xml
+++ b/src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -2,6 +2,11 @@
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.ruoyi.sales.mapper.ShippingInfoMapper">
 
+    <!--
+      鍙戣揣鍙拌处鍒楄〃 = 鍙戣揣鍗�(shipping_info) UNION ALL 閿�鍞被娌瑰搧鍑哄簱(stock_out_record)銆�
+      涓ゅ垎鏀垪椤哄簭蹇呴』涓ユ牸涓�鑷达紙鍏� 31 鍒楋級锛屽垪鍚嶄互鍒嗘敮 A 涓哄噯銆�
+      娉ㄦ剰锛氭暟鎹潈闄愭嫤鎴櫒鍙粰绗竴涓� from 鐨勮〃鍔犳潯浠讹紝鍒嗘敮 B 闇�鍦ㄦ湰 XML 閲岃嚜琛岃ˉ鍚岀瓑杩囨护銆�
+    -->
     <select id="listPage" resultType="com.ruoyi.sales.dto.ShippingInfoDto">
         SELECT
         s.id,
@@ -24,7 +29,17 @@
         p.product_name,
         sl.customer_name,
         spd.totalQuantity,
-        sor.outboundBatches
+        sor.outboundBatches,
+        'shipping' as source,
+        NULL as stockOutRecordId,
+        NULL as outCategory,
+        NULL as tankNo,
+        NULL as oilProduct,
+        NULL as oilDepotName,
+        NULL as driverName,
+        NULL as contactPerson,
+        NULL as contactPhone,
+        NULL as outBatchNo
         FROM shipping_info s
         LEFT JOIN (select shipping_info_id,sum(quantity) totalQuantity from shipping_product_detail GROUP BY shipping_info_id) spd ON spd.shipping_info_id = s.id
         LEFT JOIN sales_ledger sl ON s.sales_ledger_id = sl.id
@@ -48,6 +63,68 @@
         <if test="req.expressNumber != null and req.expressNumber != ''">
             AND s.express_number LIKE CONCAT('%',#{req.expressNumber},'%')
         </if>
+        UNION ALL
+        SELECT
+        o.id,
+        NULL as sales_ledger_id,
+        o.out_date as shipping_date,
+        o.truck_plate_no as shipping_car_number,
+        NULL as express_number,
+        NULL as express_company,
+        o.outbound_batches as shipping_no,
+        NULL as type,
+        '宸插彂璐�' as status,
+        o.create_time,
+        o.update_time,
+        o.create_user,
+        o.update_user,
+        NULL as tenant_id,
+        NULL as sales_contract_no,
+        pm2.model as specification_model,
+        pm2.unit,
+        p2.product_name,
+        o.customer_name,
+        o.stock_out_num as totalQuantity,
+        o.outbound_batches as outboundBatches,
+        'oilOut' as source,
+        o.id as stockOutRecordId,
+        o.out_category as outCategory,
+        o.tank_no as tankNo,
+        o.oil_product as oilProduct,
+        o.oil_depot_name as oilDepotName,
+        o.driver_name as driverName,
+        o.contact_person as contactPerson,
+        o.contact_phone as contactPhone,
+        o.batch_no as outBatchNo
+        FROM stock_out_record o
+        left join product_model pm2 on pm2.id = o.product_model_id
+        left join product p2 on p2.id = pm2.product_id
+        WHERE o.out_category = '閿�鍞�' AND o.approval_status = 1
+        <!-- 娌瑰搧鍑哄簱娌℃湁鍚堝悓鍙�/蹇�掑崟锛屾寜杩欎袱椤圭瓫閫夋椂涓嶅簲杩斿洖浠讳綍娌瑰搧琛� -->
+        <if test="req.salesContractNo != null and req.salesContractNo != ''">
+            AND 1=0
+        </if>
+        <if test="req.expressNumber != null and req.expressNumber != ''">
+            AND 1=0
+        </if>
+        <if test="req.shippingCarNumber != null and req.shippingCarNumber != ''">
+            AND o.truck_plate_no LIKE CONCAT('%',#{req.shippingCarNumber},'%')
+        </if>
+        <if test="req.shippingNo != null and req.shippingNo != ''">
+            AND o.outbound_batches LIKE CONCAT('%',#{req.shippingNo},'%')
+        </if>
+        <if test="req.createUser != null">
+            AND o.create_user = #{req.createUser}
+        </if>
+        <if test="req.deptId != null">
+            AND o.dept_id = #{req.deptId}
+        </if>
+        <if test="req.deptIds != null and req.deptIds.length > 0">
+            AND o.dept_id IN
+            <foreach collection="req.deptIds" item="deptId" open="(" separator="," close=")">
+                #{deptId}
+            </foreach>
+        </if>
         order by create_time DESC
     </select>
     <select id="listAll" resultType="com.ruoyi.sales.pojo.ShippingInfo">
diff --git a/src/main/resources/mapper/stock/StockInventoryMapper.xml b/src/main/resources/mapper/stock/StockInventoryMapper.xml
index 0068503..487000b 100644
--- a/src/main/resources/mapper/stock/StockInventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -86,6 +86,48 @@
         </if>
     </select>
 
+    <!-- 鎶ュ簾鍝侀�夋枡锛氬彧鏌ヤ骇鍝佹爲銆屽嵄搴熷缃鐢熷嵄搴熴�嶅ぇ绫伙紙鍚叾鎵�鏈夊瓙鑺傜偣锛変笅鐨勫悎鏍煎簱瀛橈紝甯︽壒鍙枫��
+         澶х被鍚嶆寜涓氬姟鍙e緞鍐欐鍦ㄨ繖閲岋紝鏀瑰彛寰勫彧闇�鏀硅繖涓�澶勩��
+         璇ユ煡璇互 WITH 寮�澶达紝鑰屾暟鎹潈闄愭嫤鎴櫒鍙鐞� select 寮�澶寸殑 SQL锛屽洜姝や笉浼氳拷鍔� dept_id/create_user,
+         涓庛�屽簱瀛樻槸鍏辩敤鍩虹鏁版嵁銆侀�夋枡涓嶅簲鍙楀缓鍗曚汉闄愬埗銆嶇殑鍙e緞涓�鑷达紱淇濈暀 /*data_scope*/ 鏍囪璁╄繖涓剰鍥炬樉寮忓彲璇汇��
+         涓嶅奖鍝嶅師 pagestockInventory -->
+    <select id="selectScrapStockPage" resultType="com.ruoyi.stock.dto.StockInventoryDto">
+        WITH RECURSIVE scrap_product_tree AS (
+            SELECT id
+            FROM product
+            WHERE product_name = '鍗卞簾澶勭疆娆$敓鍗卞簾'
+            UNION ALL
+            SELECT p.id
+            FROM product p
+            INNER JOIN scrap_product_tree t ON p.parent_id = t.id
+        )
+        select /*data_scope*/ si.id,
+        si.product_model_id,
+        si.batch_no,
+        si.qualitity,
+        COALESCE(si.locked_quantity, 0) as locked_quantity,
+        (si.qualitity - COALESCE(si.locked_quantity, 0)) as un_locked_quantity,
+        si.remark,
+        si.version,
+        pm.model,
+        pm.unit,
+        p.product_name
+        from stock_inventory si
+        left join product_model pm on si.product_model_id = pm.id
+        left join product p on pm.product_id = p.id
+        where pm.product_id in (select id from scrap_product_tree)
+        <if test="ew.productModelId != null">
+            and si.product_model_id = #{ew.productModelId}
+        </if>
+        <if test="ew.batchNo != null and ew.batchNo != ''">
+            and si.batch_no like concat('%',#{ew.batchNo},'%')
+        </if>
+        <if test="ew.productName != null and ew.productName != ''">
+            and p.product_name like concat('%',#{ew.productName},'%')
+        </if>
+        order by si.update_time desc
+    </select>
+
     <select id="pageListCombinedStockInventory" resultType="com.ruoyi.stock.dto.StockInventoryDto">
         WITH RECURSIVE product_tree AS (
         SELECT id
diff --git a/src/main/resources/mapper/stock/StockOutRecordMapper.xml b/src/main/resources/mapper/stock/StockOutRecordMapper.xml
index ad5976f..3808606 100644
--- a/src/main/resources/mapper/stock/StockOutRecordMapper.xml
+++ b/src/main/resources/mapper/stock/StockOutRecordMapper.xml
@@ -64,6 +64,18 @@
         </where>
         order by sor.id desc
     </select>
+    <!-- 鎸夋潵婧愭槑缁嗘煡鍑哄簱璁板綍锛氬姞 /*data_scope*/ 璺宠繃鏁版嵁鏉冮檺锛屽惁鍒欐崲璐﹀彿浼氭煡涓嶅埌璁板綍锛�
+         瀵艰嚧濮斿鐨勩�屽畬鎴愭牎楠屻�嶈闈欓粯缁曡繃銆併�屼綔搴熼噴鏀惧崰鐢ㄣ�嶅け鏁� -->
+    <select id="selectByRecordIds" resultType="com.ruoyi.stock.pojo.StockOutRecord">
+        select /*data_scope*/ *
+        from stock_out_record
+        where record_type = #{recordType}
+        and record_id in
+        <foreach collection="recordIds" item="recordId" open="(" separator="," close=")">
+            #{recordId}
+        </foreach>
+    </select>
+
     <select id="listStockOutRecordExportData" resultType="com.ruoyi.stock.execl.StockOutRecordExportData">
         SELECT
         sor.*,

--
Gitblit v1.9.3