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 >= 鏁伴噺 鐨勫畧鍗紝褰卞搷琛屾暟涓� 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 <= 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