From 558552aba0f99081a670dc29169989f186a7c7cd Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期四, 09 七月 2026 17:21:34 +0800
Subject: [PATCH] 银川 1.仓储物流页面开发、联调
---
src/views/wls/stocktaking/plan/components/index.ts | 0
src/views/stocktaking/plan/index.vue | 0
src/views/salesnotice/components/select.vue | 0
src/views/barcode/config/data.ts | 0
src/views/barcode/components/printer-label.vue | 0
src/views/warehouse/area/modules/form.vue | 0
src/views/warehouse/modules/form.vue | 0
src/views/wls/stocktaking/task/index.vue | 201
src/api/wls/batch/index.ts | 90
src/views/wls/returnissue/modules/detail-form.vue | 104
src/views/wls/returnvendor/modules/line-list.vue | 237
src/api/wls/returnsales/detail/index.ts | 55
src/views/returnvendor/modules/detail-list.vue | 0
src/views/returnsales/modules/detail-form.vue | 0
src/api/wls/productsales/line/index.ts | 56
src/views/packages/modules/package-line-list.vue | 0
src/api/wls/productissue/index.ts | 87
src/views/miscissue/index.vue | 0
src/api/wls/returnissue/line/index.ts | 58
src/views/wls/itemreceipt/modules/detail-list.vue | 0
src/views/arrivalnotice/components/select.vue | 0
src/views/wls/arrivalnotice/index.vue | 179
src/views/wls/productsales/modules/line-list.vue | 236
src/views/wls/warehouse/area/modules/form.vue | 149
src/views/wls/stocktaking/task/data.ts | 600 +
src/views/stocktaking/plan/modules/form.vue | 0
src/views/wls/arrivalnotice/modules/line-form.vue | 94
src/views/warehouse/data.ts | 0
src/api/wls/productreceipt/line/index.ts | 57
src/views/productissue/modules/form.vue | 0
src/views/wls/sn/data.ts | 0
src/views/wls/returnvendor/modules/detail-list.vue | 0
src/api/wls/productsales/index.ts | 96
src/views/wls/returnsales/modules/detail-list.vue | 0
src/views/productsales/modules/detail-form.vue | 0
src/views/stocktaking/task/index.vue | 0
src/views/wls/itemreceipt/modules/line-form.vue | 104
vite.config.ts | 2
src/views/erp/stock/warehouseMapping/modules/form.vue | 78
src/views/itemreceipt/modules/form.vue | 0
src/views/wls/returnissue/modules/line-form.vue | 95
src/views/stocktaking/plan/components/select-dialog.vue | 0
src/views/wls/transfer/modules/detail-form.vue | 103
src/views/packages/modules/form.vue | 0
src/views/productissue/modules/line-list.vue | 0
src/views/arrivalnotice/data.ts | 0
src/views/wls/outsourcereceipt/modules/detail-form.vue | 103
src/views/returnvendor/modules/line-form.vue | 0
src/views/miscreceipt/index.vue | 0
src/views/wls/batch/components/index.ts | 0
src/views/outsourceissue/index.vue | 0
src/views/wls/arrivalnotice/components/line-select-dialog.vue | 203
src/views/wls/salesnotice/data.ts | 362
src/views/erp/purchase/order/components/index.ts | 4
src/views/stocktaking/plan/modules/param-form.vue | 0
src/views/barcode/components/detail.vue | 0
src/views/wls/miscreceipt/modules/line-list.vue | 140
src/views/wls/outsourcereceipt/modules/line-form.vue | 98
src/views/wls/barcode/index.vue | 234
src/views/wls/barcode/config/modules/form.vue | 89
src/views/warehouse/components/index.ts | 0
src/api/wls/returnissue/index.ts | 78
src/views/erp/stock/warehouseMapping/data.ts | 143
src/views/productreceipt/modules/line-form.vue | 0
src/api/wls/returnsales/line/index.ts | 57
src/views/wls/salesnotice/index.vue | 168
src/views/productreceipt/modules/detail-list.vue | 0
src/views/arrivalnotice/modules/form.vue | 0
src/views/wls/productsales/modules/detail-list.vue | 0
src/views/wls/productsales/modules/form.vue | 270
src/api/wls/transfer/index.ts | 92
src/views/wls/stocktaking/plan/modules/param-list.vue | 131
src/views/returnsales/index.vue | 0
src/views/wls/sn/modules/form.vue | 62
src/views/wls/productreceipt/modules/line-list.vue | 254
src/views/wls/miscreceipt/modules/form.vue | 200
src/views/wls/transfer/index.vue | 214
src/views/outsourcereceipt/index.vue | 0
src/views/stocktaking/plan/components/index.ts | 0
src/views/materialstock/components/select.vue | 0
src/views/packages/modules/line-form.vue | 0
src/views/stocktaking/plan/components/select.vue | 0
src/views/wls/salesnotice/components/select.vue | 141
src/views/wls/transfer/modules/line-form.vue | 102
src/views/erp/stock/warehouseMapping/index.vue | 132
src/views/wls/materialstock/data.ts | 306
src/api/wls/productsales/detail/index.ts | 61
src/views/warehouse/location/data.ts | 0
src/api/wls/productproduce/line/index.ts | 37
src/views/arrivalnotice/components/select-dialog.vue | 0
src/api/wls/stocktaking/plan/param/index.ts | 49
src/views/wls/miscissue/modules/line-form.vue | 98
src/api/wls/outsourcereceipt/line/index.ts | 59
src/views/productissue/modules/detail-form.vue | 0
src/views/wls/salesnotice/modules/form.vue | 182
src/views/wls/warehouse/components/area-select.vue | 0
src/views/wls/warehouse/location/index.vue | 218
src/api/wls/miscissue/index.ts | 70
src/views/barcode/components/index.ts | 0
src/api/wls/barcode/index.ts | 70
src/api/wls/returnvendor/line/index.ts | 53
src/views/itemreceipt/modules/line-list.vue | 0
src/views/miscreceipt/modules/line-list.vue | 0
src/views/wls/productissue/modules/detail-form.vue | 104
src/views/wls/salesnotice/components/line-select.vue | 153
src/views/warehouse/components/area-select.vue | 0
src/views/warehouse/components/location-select.vue | 0
src/views/productsales/modules/line-form.vue | 0
src/views/wls/productreceipt/index.vue | 201
src/api/erp/warehouseMapping/index.ts | 68
src/api/wls/outsourcereceipt/detail/index.ts | 61
src/views/returnsales/modules/form.vue | 0
src/views/barcode/components/barcode.vue | 0
src/views/sn/modules/detail.vue | 0
src/views/transfer/modules/line-form.vue | 0
src/views/returnvendor/modules/line-list.vue | 0
src/views/wls/productreceipt/modules/form.vue | 243
src/views/returnvendor/modules/form.vue | 0
src/views/productsales/data.ts | 0
src/views/wls/itemreceipt/modules/detail-form.vue | 104
src/views/returnissue/modules/line-list.vue | 0
src/views/returnsales/modules/line-form.vue | 0
src/views/wls/productsales/modules/line-form.vue | 104
src/views/wls/outsourceissue/modules/line-list.vue | 227
src/api/wls/productreceipt/index.ts | 91
src/api/wls/warehouse/area/index.ts | 65
src/views/wls/salesnotice/components/select-dialog.vue | 320
src/views/wls/barcode/config/index.vue | 150
src/views/wls/warehouse/area/index.vue | 211
src/views/salesnotice/components/line-select-dialog.vue | 0
src/api/wls/materialstock/index.ts | 61
src/views/wls/sn/modules/detail.vue | 100
src/views/returnissue/modules/detail-form.vue | 0
src/views/packages/index.vue | 0
src/views/salesnotice/components/index.ts | 0
src/views/stocktaking/task/data.ts | 0
src/views/wls/outsourcereceipt/modules/line-list.vue | 236
src/views/wls/packages/modules/form.vue | 193
src/views/stocktaking/plan/modules/param-list.vue | 0
src/api/wls/salesnotice/index.ts | 65
src/views/wls/outsourcereceipt/index.vue | 201
src/views/outsourceissue/modules/detail-list.vue | 0
src/views/returnvendor/index.vue | 0
src/views/wls/transfer/modules/form.vue | 258
src/views/productreceipt/modules/line-list.vue | 0
src/views/returnissue/modules/form.vue | 0
src/views/wls/returnsales/modules/line-list.vue | 247
src/views/wls/warehouse/location/modules/form.vue | 184
src/views/miscissue/data.ts | 0
src/views/wls/barcode/components/index.ts | 0
src/views/productreceipt/modules/form.vue | 0
src/views/wls/itemreceipt/modules/line-list.vue | 241
src/api/wls/miscreceipt/line/index.ts | 57
src/api/wls/outsourceissue/line/index.ts | 55
src/views/wls/returnvendor/modules/detail-form.vue | 104
src/views/wls/arrivalnotice/components/select.vue | 141
src/views/wls/materialstock/index.vue | 175
src/api/wms/approval/index.ts | 69
src/views/wls/batch/components/select.vue | 149
src/views/warehouse/area/data.ts | 0
src/views/outsourcereceipt/modules/line-form.vue | 0
src/api/wls/miscreceipt/index.ts | 72
src/views/wls/salesnotice/components/line-select-dialog.vue | 198
src/views/warehouse/components/select.vue | 0
src/views/wls/salesnotice/components/index.ts | 0
src/views/wls/outsourcereceipt/modules/form.vue | 227
src/views/wls/barcode/config/data.ts | 114
src/views/wls/transfer/data.ts | 647 +
src/views/arrivalnotice/components/line-select-dialog.vue | 0
src/views/wls/packages/modules/line-form.vue | 92
src/views/wls/returnvendor/modules/form.vue | 236
src/api/wls/barcode/config/index.ts | 49
src/views/wls/productsales/modules/detail-form.vue | 110
src/views/wls/barcode/components/printer-label.vue | 30
src/views/wls/batch/components/select-dialog.vue | 0
src/views/wls/packages/components/index.ts | 0
src/views/productissue/data.ts | 0
src/views/packages/components/index.ts | 0
src/views/wls/stocktaking/plan/index.vue | 176
src/api/wls/returnvendor/index.ts | 88
src/views/wls/stocktaking/task/modules/result-form.vue | 106
src/views/wls/warehouse/components/select.vue | 0
src/views/wls/miscissue/index.vue | 187
src/views/miscreceipt/modules/line-form.vue | 0
src/views/wls/productissue/modules/line-form.vue | 94
src/api/wls/transfer/detail/index.ts | 59
src/views/wls/packages/data.ts | 641 +
src/views/batch/components/index.ts | 0
src/views/salesnotice/modules/form.vue | 0
src/views/warehouse/location/modules/form.vue | 0
src/views/itemreceipt/modules/line-form.vue | 0
src/views/wls/returnissue/modules/form.vue | 222
src/views/wls/outsourceissue/index.vue | 201
src/views/returnissue/index.vue | 0
src/views/wls/stocktaking/task/modules/form.vue | 226
src/api/wls/arrivalnotice/line/index.ts | 55
src/views/packages/data.ts | 0
src/views/wls/returnissue/index.vue | 213
src/views/wls/warehouse/components/index.ts | 0
src/views/sn/modules/form.vue | 0
src/api/wls/outsourcereceipt/index.ts | 80
src/views/wls/barcode/components/detail.vue | 169
src/views/productreceipt/data.ts | 0
src/views/returnissue/data.ts | 0
src/views/wls/approval/config/modules/process-config-form.vue | 113
src/views/wls/productissue/modules/form.vue | 232
src/views/wls/stocktaking/plan/modules/form.vue | 126
src/views/wls/returnissue/modules/detail-list.vue | 0
src/views/wls/stocktaking/plan/components/condition-value-input.vue | 204
src/views/wls/itemreceipt/data.ts | 527
src/views/itemreceipt/modules/detail-form.vue | 0
src/views/wls/outsourceissue/modules/form.vue | 236
src/views/transfer/data.ts | 0
src/views/wls/returnvendor/data.ts | 530
src/api/wls/stocktaking/task/index.ts | 86
src/views/transfer/modules/detail-form.vue | 0
src/views/outsourcereceipt/modules/form.vue | 0
src/views/transfer/modules/form.vue | 0
src/views/salesnotice/modules/line-list.vue | 0
src/api/wls/returnvendor/detail/index.ts | 59
src/views/arrivalnotice/components/line-select.vue | 0
src/views/barcode/config/index.vue | 0
src/views/wls/stocktaking/task/modules/line-list.vue | 151
src/views/outsourceissue/modules/detail-form.vue | 0
src/views/wls/productissue/index.vue | 219
src/views/batch/components/select.vue | 0
src/views/wls/arrivalnotice/components/line-select.vue | 153
src/api/mes/wm/approval/index.ts | 69
src/views/wls/outsourceissue/data.ts | 487
src/views/wls/barcode/data.ts | 735 +
src/views/wls/packages/components/select-dialog.vue | 231
src/views/productsales/modules/form.vue | 0
src/views/transfer/modules/line-list.vue | 0
src/views/outsourceissue/modules/form.vue | 0
src/views/arrivalnotice/modules/line-list.vue | 0
src/views/wls/miscissue/modules/line-list.vue | 140
src/views/warehouse/area/index.vue | 0
src/api/wls/packages/index.ts | 83
src/views/stocktaking/task/modules/form.vue | 0
src/views/barcode/index.vue | 0
src/views/productissue/modules/detail-list.vue | 0
src/views/wls/barcode/components/barcode.vue | 51
src/views/wls/arrivalnotice/data.ts | 354
src/views/productreceipt/index.vue | 0
src/views/productsales/index.vue | 0
src/views/sn/index.vue | 0
src/views/wls/stocktaking/task/modules/result-list.vue | 136
src/views/outsourcereceipt/modules/line-list.vue | 0
src/views/returnissue/modules/detail-list.vue | 0
src/views/wls/materialstock/components/index.ts | 0
src/views/outsourceissue/data.ts | 0
src/views/wls/outsourceissue/modules/detail-list.vue | 0
src/views/arrivalnotice/index.vue | 0
src/api/wls/approval/index.ts | 69
src/views/stocktaking/plan/components/condition-value-input.vue | 0
src/views/stocktaking/plan/data.ts | 0
src/views/packages/components/select.vue | 0
src/views/wls/materialstock/components/select.vue | 158
src/views/miscissue/modules/line-list.vue | 0
src/views/wls/miscreceipt/index.vue | 187
src/api/wls/outsourceissue/index.ts | 90
src/api/wls/stocktaking/task/line/index.ts | 72
src/views/wls/returnsales/modules/line-form.vue | 102
src/views/transfer/index.vue | 0
src/api/wls/sn/index.ts | 90
src/api/wls/stocktaking/task/result/index.ts | 66
src/api/wls/packages/line/index.ts | 54
src/views/returnsales/data.ts | 0
src/views/wls/packages/components/select.vue | 143
src/views/wls/barcode/modules/form.vue | 111
src/views/salesnotice/index.vue | 0
src/views/wls/outsourcereceipt/data.ts | 540
src/views/miscreceipt/modules/form.vue | 0
src/views/arrivalnotice/components/index.ts | 0
src/views/productissue/index.vue | 0
src/views/transfer/modules/detail-list.vue | 0
src/views/wls/approval/config/data.ts | 209
src/views/materialstock/index.vue | 0
src/views/wls/salesnotice/modules/line-list.vue | 140
src/views/wls/productreceipt/modules/detail-form.vue | 104
src/views/wls/stocktaking/plan/data.ts | 472
src/api/wls/itemreceipt/detail/index.ts | 58
src/views/returnsales/modules/detail-list.vue | 0
src/views/wls/warehouse/data.ts | 221
src/views/itemreceipt/data.ts | 0
src/views/outsourceissue/modules/line-form.vue | 0
src/views/sn/data.ts | 0
src/views/wls/productsales/index.vue | 215
src/views/materialstock/components/index.ts | 0
src/views/wls/miscreceipt/data.ts | 422
src/views/wls/returnvendor/index.vue | 201
src/views/wls/returnissue/data.ts | 567
src/views/itemreceipt/modules/detail-list.vue | 0
src/views/wls/warehouse/area/data.ts | 338
src/api/wls/itemreceipt/index.ts | 86
src/views/outsourcereceipt/modules/detail-list.vue | 0
src/views/materialstock/components/select-dialog.vue | 0
src/api/wls/miscissue/line/index.ts | 70
src/views/wls/materialstock/components/select-dialog.vue | 0
src/views/wls/batch/data.ts | 372
src/views/materialstock/data.ts | 0
src/views/stocktaking/task/modules/result-list.vue | 0
src/views/wls/outsourceissue/modules/line-form.vue | 97
src/views/batch/components/detail.vue | 0
src/views/wls/itemreceipt/modules/form.vue | 242
src/views/wls/warehouse/index.vue | 175
src/views/wls/arrivalnotice/components/select-dialog.vue | 288
src/views/productsales/modules/line-list.vue | 0
src/views/miscissue/modules/form.vue | 0
src/views/returnvendor/modules/detail-form.vue | 0
src/views/wls/arrivalnotice/modules/line-list.vue | 140
src/views/stocktaking/task/modules/line-list.vue | 0
src/views/miscissue/modules/line-form.vue | 0
src/api/wls/returnissue/detail/index.ts | 56
src/views/wls/warehouse/location/data.ts | 193
src/views/salesnotice/modules/line-form.vue | 0
src/views/wls/returnsales/modules/detail-form.vue | 104
src/views/erp/purchase/order/components/select-dialog.vue | 196
src/api/wls/outsourceissue/detail/index.ts | 61
src/views/productsales/modules/detail-list.vue | 0
src/views/packages/modules/sub-package-list.vue | 0
src/views/wls/returnvendor/modules/line-form.vue | 99
src/views/salesnotice/components/select-dialog.vue | 0
src/views/wls/itemreceipt/index.vue | 201
src/views/wls/outsourceissue/modules/detail-form.vue | 104
src/views/batch/data.ts | 0
src/views/itemreceipt/index.vue | 0
src/api/wls/itemconsume/line/index.ts | 37
src/views/wls/productreceipt/data.ts | 452
src/views/barcode/data.ts | 0
src/api/wls/transfer/line/index.ts | 55
src/api/wls/itemreceipt/line/index.ts | 63
src/views/wls/productreceipt/modules/line-form.vue | 95
src/api/wls/productissue/detail/index.ts | 59
src/views/wls/batch/components/detail.vue | 52
src/views/wls/returnsales/index.vue | 213
src/views/wls/miscreceipt/modules/line-form.vue | 98
src/views/wls/transfer/modules/detail-list.vue | 0
src/views/wls/productissue/modules/line-list.vue | 223
src/views/barcode/modules/form.vue | 0
src/views/wls/outsourcereceipt/modules/detail-list.vue | 0
src/views/wls/returnsales/data.ts | 515
src/api/wls/warehouse/index.ts | 55
src/views/wls/packages/index.vue | 160
src/views/wls/arrivalnotice/components/index.ts | 0
src/views/wls/salesnotice/modules/line-form.vue | 94
src/views/wls/warehouse/components/location-select.vue | 0
src/views/warehouse/location/index.vue | 0
src/views/wls/productsales/data.ts | 701 +
src/api/wls/returnsales/index.ts | 78
src/views/stocktaking/task/modules/result-form.vue | 0
src/views/salesnotice/data.ts | 0
src/views/wls/stocktaking/plan/components/select-dialog.vue | 213
src/views/outsourcereceipt/data.ts | 0
src/views/returnvendor/data.ts | 0
src/views/wls/sn/index.vue | 177
src/views/wls/stocktaking/plan/components/select.vue | 135
src/api/wls/productreceipt/detail/index.ts | 58
src/views/wls/productreceipt/modules/detail-list.vue | 0
src/views/wls/returnsales/modules/form.vue | 231
src/views/wls/packages/modules/sub-package-list.vue | 0
src/views/outsourceissue/modules/line-list.vue | 0
src/views/erp/purchase/order/components/select.vue | 138
src/views/productreceipt/modules/detail-form.vue | 0
src/views/wls/miscissue/modules/form.vue | 199
src/api/wls/warehouse/location/index.ts | 72
src/api/wls/salesnotice/line/index.ts | 55
src/views/productissue/modules/line-form.vue | 0
src/views/wls/productissue/data.ts | 475
src/views/batch/components/select-dialog.vue | 0
src/views/wls/returnissue/modules/line-list.vue | 250
src/api/wls/productissue/line/index.ts | 52
src/views/wls/miscissue/data.ts | 452
src/api/wls/stocktaking/plan/index.ts | 68
src/views/wls/productissue/modules/detail-list.vue | 0
src/views/barcode/config/modules/form.vue | 0
src/views/wls/stocktaking/plan/modules/param-form.vue | 108
src/api/wls/arrivalnotice/index.ts | 64
src/views/wls/approval/config/index.vue | 113
src/views/wls/arrivalnotice/modules/form.vue | 171
src/views/miscreceipt/data.ts | 0
src/views/salesnotice/components/line-select.vue | 0
src/views/packages/components/select-dialog.vue | 0
src/views/wls/transfer/modules/line-list.vue | 228
src/views/warehouse/index.vue | 0
src/views/wls/warehouse/modules/form.vue | 124
src/views/arrivalnotice/modules/line-form.vue | 0
src/views/wls/packages/modules/package-line-list.vue | 132
src/views/outsourcereceipt/modules/detail-form.vue | 0
src/views/returnissue/modules/line-form.vue | 0
src/views/returnsales/modules/line-list.vue | 0
391 files changed, 34,590 insertions(+), 1 deletions(-)
diff --git a/src/api/erp/warehouseMapping/index.ts b/src/api/erp/warehouseMapping/index.ts
new file mode 100644
index 0000000..245a938
--- /dev/null
+++ b/src/api/erp/warehouseMapping/index.ts
@@ -0,0 +1,68 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace ErpWarehouseMappingApi {
+ /** 浠撳簱鏄犲皠 */
+ export interface WarehouseMapping {
+ id?: number; // 鏄犲皠缂栧彿
+ erpWarehouseId: number; // ERP浠撳簱ID
+ erpWarehouseName?: string; // ERP浠撳簱鍚嶇О
+ mesWarehouseId: number; // MES浠撳簱ID
+ mesWarehouseName?: string; // MES浠撳簱鍚嶇О
+ status: number; // 鐘舵��(0:绂佺敤 1:鍚敤)
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鍒嗛〉鏌ヨ鏄犲皠鍒楄〃 */
+export function getMappingPage(params: PageParam) {
+ return requestClient.get<PageResult<ErpWarehouseMappingApi.WarehouseMapping>>(
+ '/erp/warehouse-mapping/page',
+ { params },
+ );
+}
+
+/** 鑾峰彇鍗曚釜鏄犲皠 */
+export function getMapping(id: number) {
+ return requestClient.get<ErpWarehouseMappingApi.WarehouseMapping>(
+ '/erp/warehouse-mapping/get',
+ {
+ params: { id },
+ },
+ );
+}
+
+/** 鑾峰彇鏄犲皠绠�鍗曞垪琛� */
+export function getMappingSimpleList() {
+ return requestClient.get<ErpWarehouseMappingApi.WarehouseMapping[]>(
+ '/erp/warehouse-mapping/simple-list',
+ );
+}
+
+/** 鏍规嵁ERP浠撳簱ID鑾峰彇MES浠撳簱鏄犲皠 */
+export function getMappingByErpWarehouseId(erpWarehouseId: number) {
+ return requestClient.get<ErpWarehouseMappingApi.WarehouseMapping>(
+ '/erp/warehouse-mapping/get-by-erp',
+ {
+ params: { erpWarehouseId },
+ },
+ );
+}
+
+/** 鏂板鏄犲皠 */
+export function createMapping(data: ErpWarehouseMappingApi.WarehouseMapping) {
+ return requestClient.post('/erp/warehouse-mapping/create', data);
+}
+
+/** 鏇存柊鏄犲皠 */
+export function updateMapping(data: ErpWarehouseMappingApi.WarehouseMapping) {
+ return requestClient.put('/erp/warehouse-mapping/update', data);
+}
+
+/** 鍒犻櫎鏄犲皠 */
+export function deleteMapping(id: number) {
+ return requestClient.delete('/erp/warehouse-mapping/delete', {
+ params: { id },
+ });
+}
diff --git a/src/api/mes/wm/approval/index.ts b/src/api/mes/wm/approval/index.ts
new file mode 100644
index 0000000..d9fb0fa
--- /dev/null
+++ b/src/api/mes/wm/approval/index.ts
@@ -0,0 +1,69 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WmsApprovalApi {
+ /** 瀹℃壒閰嶇疆 */
+ export interface ApprovalConfig {
+ id?: number;
+ warehouseId: number;
+ warehouseName?: string;
+ bizType: string;
+ approvalEnabled: boolean;
+ processDefinitionKey?: string;
+ processDefinitionName?: string;
+ categoryCode?: string;
+ createTime?: Date;
+ }
+
+ /** 娴佺▼瀹氫箟绠�鍗曚俊鎭� */
+ export interface ProcessSimpleInfo {
+ key: string;
+ name: string;
+ }
+
+ /** 涓氬姟绫诲瀷 */
+ export type BizType = 'INBOUND' | 'OUTBOUND' | 'TRANSFER' | 'CHECK';
+}
+
+/** 鑾峰彇鍗曚釜瀹℃壒閰嶇疆 */
+export function getApprovalConfig(warehouseId: number, bizType: string) {
+ return requestClient.get<WmsApprovalApi.ApprovalConfig>(
+ '/mes/wms/approval/config/get',
+ {
+ params: { warehouseId, bizType },
+ },
+ );
+}
+
+/** 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃� */
+export function getApprovalConfigList(warehouseId: number) {
+ return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+ '/mes/wms/approval/config/list',
+ {
+ params: { warehouseId },
+ },
+ );
+}
+
+/** 鑾峰彇鎵�鏈変粨搴撶殑瀹℃壒閰嶇疆鍒楄〃 */
+export function getApprovalConfigAllList() {
+ return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+ '/mes/wms/approval/config/all-list',
+ );
+}
+
+/** 淇濆瓨瀹℃壒閰嶇疆 */
+export function saveApprovalConfig(data: WmsApprovalApi.ApprovalConfig) {
+ return requestClient.post('/mes/wms/approval/config/save', data);
+}
+
+/** 鑾峰彇鍙敤瀹℃壒娴佺▼鍒楄〃 */
+export function getAvailableProcessList(warehouseId: number, bizType: string) {
+ return requestClient.get<WmsApprovalApi.ProcessSimpleInfo[]>(
+ '/mes/wms/approval/process-list',
+ {
+ params: { warehouseId, bizType },
+ },
+ );
+}
\ No newline at end of file
diff --git a/src/api/wls/approval/index.ts b/src/api/wls/approval/index.ts
new file mode 100644
index 0000000..020bb3e
--- /dev/null
+++ b/src/api/wls/approval/index.ts
@@ -0,0 +1,69 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsApprovalApi {
+ /** 瀹℃壒閰嶇疆 */
+ export interface ApprovalConfig {
+ id?: number;
+ warehouseId: number;
+ warehouseName?: string;
+ bizType: string;
+ approvalEnabled: boolean;
+ processDefinitionKey?: string;
+ processDefinitionName?: string;
+ categoryCode?: string;
+ createTime?: Date;
+ }
+
+ /** 娴佺▼瀹氫箟绠�鍗曚俊鎭� */
+ export interface ProcessSimpleInfo {
+ key: string;
+ name: string;
+ }
+
+ /** 涓氬姟绫诲瀷 */
+ export type BizType = 'INBOUND' | 'OUTBOUND' | 'TRANSFER' | 'CHECK';
+}
+
+/** 鑾峰彇鍗曚釜瀹℃壒閰嶇疆 */
+export function getApprovalConfig(warehouseId: number, bizType: string) {
+ return requestClient.get<WlsApprovalApi.ApprovalConfig>(
+ '/mes/wm/approval/config/get',
+ {
+ params: { warehouseId, bizType },
+ },
+ );
+}
+
+/** 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃� */
+export function getApprovalConfigList(warehouseId: number) {
+ return requestClient.get<WlsApprovalApi.ApprovalConfig[]>(
+ '/mes/wm/approval/config/list',
+ {
+ params: { warehouseId },
+ },
+ );
+}
+
+/** 鑾峰彇鎵�鏈変粨搴撶殑瀹℃壒閰嶇疆鍒楄〃 */
+export function getApprovalConfigAllList() {
+ return requestClient.get<WlsApprovalApi.ApprovalConfig[]>(
+ '/mes/wm/approval/config/all-list',
+ );
+}
+
+/** 淇濆瓨瀹℃壒閰嶇疆 */
+export function saveApprovalConfig(data: WlsApprovalApi.ApprovalConfig) {
+ return requestClient.post('/mes/wm/approval/config/save', data);
+}
+
+/** 鑾峰彇鍙敤瀹℃壒娴佺▼鍒楄〃 */
+export function getAvailableProcessList(warehouseId: number, bizType: string) {
+ return requestClient.get<WlsApprovalApi.ProcessSimpleInfo[]>(
+ '/mes/wm/approval/process-list',
+ {
+ params: { warehouseId, bizType },
+ },
+ );
+}
\ No newline at end of file
diff --git a/src/api/wls/arrivalnotice/index.ts b/src/api/wls/arrivalnotice/index.ts
new file mode 100644
index 0000000..5238aee
--- /dev/null
+++ b/src/api/wls/arrivalnotice/index.ts
@@ -0,0 +1,64 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsArrivalNoticeApi {
+ /** MES 鍒拌揣閫氱煡鍗� */
+ export interface ArrivalNotice {
+ id?: number; // 閫氱煡鍗曠紪鍙�
+ code?: string; // 閫氱煡鍗曠紪鍙�
+ name?: string; // 閫氱煡鍗曞悕绉�
+ purchaseOrderCode?: string; // 閲囪喘璁㈠崟缂栧彿
+ vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+ vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+ vendorName?: string; // 渚涘簲鍟嗗悕绉�
+ arrivalDate?: number; // 鍒拌揣鏃ユ湡
+ contactName?: string; // 鑱旂郴浜�
+ contactTelephone?: string; // 鑱旂郴鏂瑰紡
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曞垎椤� */
+export function getArrivalNoticePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsArrivalNoticeApi.ArrivalNotice>>(
+ '/mes/wm/arrival-notice/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曡鎯� */
+export function getArrivalNotice(id: number) {
+ return requestClient.get<WlsArrivalNoticeApi.ArrivalNotice>(
+ `/mes/wm/arrival-notice/get?id=${id}`,
+ );
+}
+
+/** 鏂板鍒拌揣閫氱煡鍗� */
+export function createArrivalNotice(data: WlsArrivalNoticeApi.ArrivalNotice) {
+ return requestClient.post<number>('/mes/wm/arrival-notice/create', data);
+}
+
+/** 淇敼鍒拌揣閫氱煡鍗� */
+export function updateArrivalNotice(data: WlsArrivalNoticeApi.ArrivalNotice) {
+ return requestClient.put('/mes/wm/arrival-notice/update', data);
+}
+
+/** 鍒犻櫎鍒拌揣閫氱煡鍗� */
+export function deleteArrivalNotice(id: number) {
+ return requestClient.delete(`/mes/wm/arrival-notice/delete?id=${id}`);
+}
+
+/** 鎻愪氦鍒拌揣閫氱煡鍗� */
+export function submitArrivalNotice(id: number) {
+ return requestClient.put(`/mes/wm/arrival-notice/submit?id=${id}`);
+}
+
+/** 瀵煎嚭鍒拌揣閫氱煡鍗� */
+export function exportArrivalNotice(params: any) {
+ return requestClient.download('/mes/wm/arrival-notice/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/arrivalnotice/line/index.ts b/src/api/wls/arrivalnotice/line/index.ts
new file mode 100644
index 0000000..1740b71
--- /dev/null
+++ b/src/api/wls/arrivalnotice/line/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsArrivalNoticeLineApi {
+ /** MES 鍒拌揣閫氱煡鍗曡 */
+ export interface ArrivalNoticeLine {
+ id?: number; // 琛岀紪鍙�
+ noticeId?: number; // 鍒拌揣閫氱煡鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ arrivalQuantity?: number; // 鍒拌揣鏁伴噺
+ qualifiedQuantity?: number; // 鍚堟牸鏁伴噺
+ iqcCheckFlag?: boolean; // 鏄惁妫�楠�
+ iqcId?: number; // 鏉ユ枡妫�楠屽崟缂栧彿
+ iqcCode?: string; // 鏉ユ枡妫�楠屽崟缂栫爜
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曡鍒嗛〉 */
+export function getArrivalNoticeLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsArrivalNoticeLineApi.ArrivalNoticeLine>
+ >('/mes/wm/arrival-notice-line/page', { params });
+}
+
+/** 鏌ヨ鍒拌揣閫氱煡鍗曡璇︽儏 */
+export function getArrivalNoticeLine(id: number) {
+ return requestClient.get<WlsArrivalNoticeLineApi.ArrivalNoticeLine>(
+ `/mes/wm/arrival-notice-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板鍒拌揣閫氱煡鍗曡 */
+export function createArrivalNoticeLine(
+ data: WlsArrivalNoticeLineApi.ArrivalNoticeLine,
+) {
+ return requestClient.post<number>('/mes/wm/arrival-notice-line/create', data);
+}
+
+/** 淇敼鍒拌揣閫氱煡鍗曡 */
+export function updateArrivalNoticeLine(
+ data: WlsArrivalNoticeLineApi.ArrivalNoticeLine,
+) {
+ return requestClient.put('/mes/wm/arrival-notice-line/update', data);
+}
+
+/** 鍒犻櫎鍒拌揣閫氱煡鍗曡 */
+export function deleteArrivalNoticeLine(id: number) {
+ return requestClient.delete(`/mes/wm/arrival-notice-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/barcode/config/index.ts b/src/api/wls/barcode/config/index.ts
new file mode 100644
index 0000000..0688d67
--- /dev/null
+++ b/src/api/wls/barcode/config/index.ts
@@ -0,0 +1,49 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsBarcodeConfigApi {
+ /** MES 鏉$爜閰嶇疆 */
+ export interface BarcodeConfig {
+ id?: number; // 缂栧彿
+ format?: number; // 鏉$爜鏍煎紡
+ bizType?: number; // 涓氬姟绫诲瀷
+ contentFormat?: string; // 鍐呭鏍煎紡妯℃澘
+ contentExample?: string; // 鍐呭鏍蜂緥
+ autoGenerateFlag?: boolean; // 鏄惁鑷姩鐢熸垚
+ defaultTemplate?: string; // 榛樿鎵撳嵃妯℃澘
+ status?: number; // 鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鏉$爜閰嶇疆鍒嗛〉 */
+export function getBarcodeConfigPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsBarcodeConfigApi.BarcodeConfig>>(
+ '/mes/wm/barcode-config/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鏉$爜閰嶇疆璇︽儏 */
+export function getBarcodeConfig(id: number) {
+ return requestClient.get<WlsBarcodeConfigApi.BarcodeConfig>(
+ `/mes/wm/barcode-config/get?id=${id}`,
+ );
+}
+
+/** 鏂板鏉$爜閰嶇疆 */
+export function createBarcodeConfig(data: WlsBarcodeConfigApi.BarcodeConfig) {
+ return requestClient.post('/mes/wm/barcode-config/create', data);
+}
+
+/** 淇敼鏉$爜閰嶇疆 */
+export function updateBarcodeConfig(data: WlsBarcodeConfigApi.BarcodeConfig) {
+ return requestClient.put('/mes/wm/barcode-config/update', data);
+}
+
+/** 鍒犻櫎鏉$爜閰嶇疆 */
+export function deleteBarcodeConfig(id: number) {
+ return requestClient.delete(`/mes/wm/barcode-config/delete?id=${id}`);
+}
diff --git a/src/api/wls/barcode/index.ts b/src/api/wls/barcode/index.ts
new file mode 100644
index 0000000..e7812c0
--- /dev/null
+++ b/src/api/wls/barcode/index.ts
@@ -0,0 +1,70 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsBarcodeApi {
+ /** MES 鏉$爜娓呭崟 */
+ export interface Barcode {
+ id?: number; // 鏉$爜缂栧彿
+ configId?: number; // 鏉$爜閰嶇疆缂栧彿
+ format?: number; // 鏉$爜鏍煎紡
+ bizType?: number; // 涓氬姟绫诲瀷
+ content?: string; // 鏉$爜鍐呭
+ bizId?: number; // 涓氬姟瀵硅薄缂栧彿
+ bizCode?: string; // 涓氬姟瀵硅薄缂栫爜
+ bizName?: string; // 涓氬姟瀵硅薄鍚嶇О
+ status?: number; // 鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鏉$爜鍒嗛〉 */
+export function getBarcodePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsBarcodeApi.Barcode>>(
+ '/mes/wm/barcode/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鏉$爜璇︽儏 */
+export function getBarcode(id: number) {
+ return requestClient.get<WlsBarcodeApi.Barcode>(
+ `/mes/wm/barcode/get?id=${id}`,
+ );
+}
+
+/** 鏍规嵁涓氬姟瀵硅薄鑾峰彇鏉$爜 */
+export function getBarcodeByBusiness(bizType: number, bizId: number) {
+ return requestClient.get<WlsBarcodeApi.Barcode>(
+ '/mes/wm/barcode/get-by-business',
+ { params: { bizId, bizType } },
+ );
+}
+
+/** 鏂板鏉$爜 */
+export function createBarcode(data: WlsBarcodeApi.Barcode) {
+ return requestClient.post('/mes/wm/barcode/create', data);
+}
+
+/** 淇敼鏉$爜 */
+export function updateBarcode(data: WlsBarcodeApi.Barcode) {
+ return requestClient.put('/mes/wm/barcode/update', data);
+}
+
+/** 鍒犻櫎鏉$爜 */
+export function deleteBarcode(id: number) {
+ return requestClient.delete(`/mes/wm/barcode/delete?id=${id}`);
+}
+
+/** 瀵煎嚭鏉$爜 */
+export function exportBarcode(params: any) {
+ return requestClient.download('/mes/wm/barcode/export-excel', { params });
+}
+
+/** 鐢熸垚鏉$爜鍐呭 */
+export function generateBarcodeContent(bizType: number, bizCode: string) {
+ return requestClient.get<string>('/mes/wm/barcode/generate-content', {
+ params: { bizCode, bizType },
+ });
+}
diff --git a/src/api/wls/batch/index.ts b/src/api/wls/batch/index.ts
new file mode 100644
index 0000000..fef1d78
--- /dev/null
+++ b/src/api/wls/batch/index.ts
@@ -0,0 +1,90 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsBatchApi {
+ /** MES 鎵规 */
+ export interface Batch {
+ id?: number; // 鎵规 ID
+ code?: string; // 鎵规缂栫爜
+ itemId?: number; // 鐗╂枡 ID
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ itemSpecification?: string; // 瑙勬牸鍨嬪彿
+ unitName?: string; // 鍗曚綅鍚嶇О
+ produceDate?: Date; // 鐢熶骇鏃ユ湡
+ expireDate?: Date; // 鏈夋晥鏈�
+ receiptDate?: Date; // 鍏ュ簱鏃ユ湡
+ vendorId?: number; // 渚涘簲鍟� ID
+ vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+ vendorName?: string; // 渚涘簲鍟嗗悕绉�
+ clientId?: number; // 瀹㈡埛 ID
+ clientCode?: string; // 瀹㈡埛缂栫爜
+ clientName?: string; // 瀹㈡埛鍚嶇О
+ purchaseOrderCode?: string; // 閲囪喘璁㈠崟缂栧彿
+ salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+ workOrderId?: number; // 鐢熶骇璁㈠崟 ID
+ workOrderCode?: string; // 鐢熶骇璁㈠崟缂栧彿
+ taskId?: number; // 鐢熶骇浠诲姟 ID
+ taskCode?: string; // 鐢熶骇浠诲姟缂栧彿
+ workstationId?: number; // 宸ヤ綔绔� ID
+ workstationCode?: string; // 宸ヤ綔绔欑紪鐮�
+ toolId?: number; // 宸ュ叿 ID
+ toolCode?: string; // 宸ュ叿缂栧彿
+ moldId?: number; // 妯″叿 ID
+ lotNumber?: string; // 鐢熶骇鎵瑰彿
+ qualityStatus?: number; // 璐ㄩ噺鐘舵��
+ remark?: string; // 澶囨敞
+ }
+
+ /** MES 鎵规鍒嗛〉鏌ヨ鍙傛暟 */
+ export interface PageParams extends PageParam {
+ code?: string; // 鎵规鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+ clientId?: number; // 瀹㈡埛缂栧彿
+ workOrderId?: number; // 璁㈠崟缂栧彿
+ taskId?: number; // 鐢熶骇浠诲姟缂栧彿
+ workstationId?: number; // 宸ヤ綔绔欑紪鍙�
+ toolId?: number; // 宸ュ叿缂栧彿
+ moldId?: number; // 妯″叿缂栧彿
+ salesOrderCode?: string; // 閿�鍞鍗曞彿
+ purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+ lotNumber?: string; // 鎵瑰彿
+ qualityStatus?: number; // 璐ㄩ噺鐘舵��
+ produceDate?: string[]; // 鐢熶骇鏃ユ湡
+ expireDate?: string[]; // 杩囨湡鏃ユ湡
+ receiptDate?: string[]; // 鍏ュ簱鏃ユ湡
+ }
+}
+
+/** 鏌ヨ鎵规璇︽儏 */
+export function getBatch(id: number) {
+ return requestClient.get<WlsBatchApi.Batch>(`/mes/wm/batch/get?id=${id}`);
+}
+
+/** 鏌ヨ鎵规鍒嗛〉 */
+export function getBatchPage(params: WlsBatchApi.PageParams) {
+ return requestClient.get<PageResult<WlsBatchApi.Batch>>(
+ '/mes/wm/batch/page',
+ { params },
+ );
+}
+
+/** 鎵规鍚戝墠杩芥函 */
+export function getForwardBatchList(code: string) {
+ return requestClient.get<WlsBatchApi.Batch[]>(
+ '/mes/wm/batch/forward-list',
+ {
+ params: { code },
+ },
+ );
+}
+
+/** 鎵规鍚戝悗杩芥函 */
+export function getBackwardBatchList(code: string) {
+ return requestClient.get<WlsBatchApi.Batch[]>(
+ '/mes/wm/batch/backward-list',
+ { params: { code } },
+ );
+}
diff --git a/src/api/wls/itemconsume/line/index.ts b/src/api/wls/itemconsume/line/index.ts
new file mode 100644
index 0000000..5c817ea
--- /dev/null
+++ b/src/api/wls/itemconsume/line/index.ts
@@ -0,0 +1,37 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemConsumeLineApi {
+ /** MES 鐗╂枡娑堣�楄 */
+ export interface ItemConsumeLine {
+ id?: number;
+ feedbackId?: number; // 鎶ュ伐缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╄祫缂栫爜
+ itemName?: string; // 鐗╄祫鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitId?: number; // 鍗曚綅缂栧彿
+ unitName?: string; // 鍗曚綅
+ quantity?: number; // 娑堣�楁暟閲�
+ batchCode?: string; // 鎵规鍙�
+ locationId?: number; // 搴撲綅缂栧彿
+ locationName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+
+ /** MES 鐗╂枡娑堣�楄鍒嗛〉鏌ヨ鍙傛暟 */
+ export interface PageParams extends PageParam {
+ feedbackId?: number;
+ }
+}
+
+/** 鏌ヨ鐗╂枡娑堣�楄鍒嗛〉 */
+export function getItemConsumeLinePage(
+ params: WlsItemConsumeLineApi.PageParams,
+) {
+ return requestClient.get<PageResult<WlsItemConsumeLineApi.ItemConsumeLine>>(
+ '/mes/wm/item-consume-line/page',
+ { params },
+ );
+}
diff --git a/src/api/wls/itemreceipt/detail/index.ts b/src/api/wls/itemreceipt/detail/index.ts
new file mode 100644
index 0000000..1311798
--- /dev/null
+++ b/src/api/wls/itemreceipt/detail/index.ts
@@ -0,0 +1,58 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemReceiptDetailApi {
+ /** MES 閲囪喘鍏ュ簱鏄庣粏 */
+ export interface ItemReceiptDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ lineId?: number; // 鍏ュ簱鍗曡缂栧彿
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getItemReceiptDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsItemReceiptDetailApi.ItemReceiptDetail[]>(
+ '/mes/wm/item-receipt-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鏄庣粏璇︽儏 */
+export function getItemReceiptDetail(id: number) {
+ return requestClient.get<WlsItemReceiptDetailApi.ItemReceiptDetail>(
+ `/mes/wm/item-receipt-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板閲囪喘鍏ュ簱鏄庣粏 */
+export function createItemReceiptDetail(
+ data: WlsItemReceiptDetailApi.ItemReceiptDetail,
+) {
+ return requestClient.post<number>('/mes/wm/item-receipt-detail/create', data);
+}
+
+/** 淇敼閲囪喘鍏ュ簱鏄庣粏 */
+export function updateItemReceiptDetail(
+ data: WlsItemReceiptDetailApi.ItemReceiptDetail,
+) {
+ return requestClient.put('/mes/wm/item-receipt-detail/update', data);
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鏄庣粏 */
+export function deleteItemReceiptDetail(id: number) {
+ return requestClient.delete(`/mes/wm/item-receipt-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/itemreceipt/index.ts b/src/api/wls/itemreceipt/index.ts
new file mode 100644
index 0000000..8c1265c
--- /dev/null
+++ b/src/api/wls/itemreceipt/index.ts
@@ -0,0 +1,86 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemReceiptApi {
+ /** MES 閲囪喘鍏ュ簱鍗� */
+ export interface ItemReceipt {
+ id?: number; // 鍏ュ簱鍗曠紪鍙�
+ code?: string; // 鍏ュ簱鍗曠紪鐮�
+ name?: string; // 鍏ュ簱鍗曞悕绉�
+ iqcId?: number; // 鏉ユ枡妫�楠屽崟缂栧彿
+ iqcCode?: string; // 鏉ユ枡妫�楠屽崟缂栫爜
+ noticeId?: number; // 鍒拌揣閫氱煡鍗曠紪鍙�
+ noticeCode?: string; // 鍒拌揣閫氱煡鍗曠紪鐮�
+ purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+ vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+ vendorName?: string; // 渚涘簲鍟嗗悕绉�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ receiptDate?: number; // 鍏ュ簱鏃ユ湡
+ status?: number; // 鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曞垎椤� */
+export function getItemReceiptPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsItemReceiptApi.ItemReceipt>>(
+ '/mes/wm/item-receipt/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曡鎯� */
+export function getItemReceipt(id: number) {
+ return requestClient.get<WlsItemReceiptApi.ItemReceipt>(
+ `/mes/wm/item-receipt/get?id=${id}`,
+ );
+}
+
+/** 鏂板閲囪喘鍏ュ簱鍗� */
+export function createItemReceipt(data: WlsItemReceiptApi.ItemReceipt) {
+ return requestClient.post<number>('/mes/wm/item-receipt/create', data);
+}
+
+/** 淇敼閲囪喘鍏ュ簱鍗� */
+export function updateItemReceipt(data: WlsItemReceiptApi.ItemReceipt) {
+ return requestClient.put('/mes/wm/item-receipt/update', data);
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鍗� */
+export function deleteItemReceipt(id: number) {
+ return requestClient.delete(`/mes/wm/item-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦閲囪喘鍏ュ簱鍗� */
+export function submitItemReceipt(id: number) {
+ return requestClient.put(`/mes/wm/item-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockItemReceipt(id: number) {
+ return requestClient.put(`/mes/wm/item-receipt/stock?id=${id}`);
+}
+
+/** 鎵ц鍏ュ簱 */
+export function finishItemReceipt(id: number) {
+ return requestClient.put(`/mes/wm/item-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷閲囪喘鍏ュ簱鍗� */
+export function cancelItemReceipt(id: number) {
+ return requestClient.put(`/mes/wm/item-receipt/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭閲囪喘鍏ュ簱鍗� */
+export function exportItemReceipt(params: any) {
+ return requestClient.download('/mes/wm/item-receipt/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/itemreceipt/line/index.ts b/src/api/wls/itemreceipt/line/index.ts
new file mode 100644
index 0000000..481e642
--- /dev/null
+++ b/src/api/wls/itemreceipt/line/index.ts
@@ -0,0 +1,63 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsItemReceiptLineApi {
+ /** MES 閲囪喘鍏ュ簱鍗曡 */
+ export interface ItemReceiptLine {
+ id?: number; // 琛岀紪鍙�
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ receiptCode?: string; // 鍏ュ簱鍗曠紪鐮�
+ arrivalNoticeLineId?: number; // 鍒拌揣閫氱煡鍗曡缂栧彿
+ purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ receivedQuantity?: number; // 鍏ュ簱鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ productionDate?: number; // 鐢熶骇鏃ユ湡
+ expireDate?: number; // 鏈夋晥鏈�
+ lotNumber?: string; // 鐢熶骇鎵瑰彿
+ iqcCheckFlag?: boolean; // 鏄惁妫�楠�
+ iqcId?: number; // 鏉ユ枡妫�楠屽崟缂栧彿
+ iqcCode?: string; // 鏉ユ枡妫�楠屽崟缂栫爜
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曡鍒嗛〉 */
+export function getItemReceiptLinePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsItemReceiptLineApi.ItemReceiptLine>>(
+ '/mes/wm/item-receipt-line/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ閲囪喘鍏ュ簱鍗曡璇︽儏 */
+export function getItemReceiptLine(id: number) {
+ return requestClient.get<WlsItemReceiptLineApi.ItemReceiptLine>(
+ `/mes/wm/item-receipt-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板閲囪喘鍏ュ簱鍗曡 */
+export function createItemReceiptLine(
+ data: WlsItemReceiptLineApi.ItemReceiptLine,
+) {
+ return requestClient.post<number>('/mes/wm/item-receipt-line/create', data);
+}
+
+/** 淇敼閲囪喘鍏ュ簱鍗曡 */
+export function updateItemReceiptLine(
+ data: WlsItemReceiptLineApi.ItemReceiptLine,
+) {
+ return requestClient.put('/mes/wm/item-receipt-line/update', data);
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鍗曡 */
+export function deleteItemReceiptLine(id: number) {
+ return requestClient.delete(`/mes/wm/item-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/materialstock/index.ts b/src/api/wls/materialstock/index.ts
new file mode 100644
index 0000000..2f03a8d
--- /dev/null
+++ b/src/api/wls/materialstock/index.ts
@@ -0,0 +1,61 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMaterialStockApi {
+ /** MES 搴撳瓨鍙拌处 */
+ export interface MaterialStock {
+ id?: number; // 缂栧彿
+ itemTypeId?: number; // 鐗╂枡鍒嗙被缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseCode?: string; // 浠撳簱缂栫爜
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+ vendorName?: string; // 渚涘簲鍟嗗悕绉�
+ quantity?: number; // 鍦ㄥ簱鏁伴噺
+ receiptTime?: string; // 鍏ュ簱鏃ユ湡
+ frozen?: boolean; // 鏄惁鍐荤粨
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ搴撳瓨鍙拌处鍒嗛〉 */
+export function getMaterialStockPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsMaterialStockApi.MaterialStock>>(
+ '/mes/wm/material-stock/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ搴撳瓨鍙拌处璇︽儏 */
+export function getMaterialStock(id: number) {
+ return requestClient.get<WlsMaterialStockApi.MaterialStock>(
+ `/mes/wm/material-stock/get?id=${id}`,
+ );
+}
+
+/** 鏇存柊搴撳瓨鍐荤粨鐘舵�� */
+export function updateMaterialStockFrozen(data: {
+ frozen: boolean;
+ id: number;
+}) {
+ return requestClient.put('/mes/wm/material-stock/update-frozen', data);
+}
+
+/** 瀵煎嚭搴撳瓨鍙拌处 */
+export function exportMaterialStock(params: any) {
+ return requestClient.download('/mes/wm/material-stock/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/miscissue/index.ts b/src/api/wls/miscissue/index.ts
new file mode 100644
index 0000000..f08625e
--- /dev/null
+++ b/src/api/wls/miscissue/index.ts
@@ -0,0 +1,70 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscIssueApi {
+ /** MES 鏉傞」鍑哄簱鍗� */
+ export interface MiscIssue {
+ id?: number; // 缂栧彿
+ code?: string; // 鍑哄簱鍗曠紪鍙�
+ name?: string; // 鍑哄簱鍗曞悕绉�
+ type?: number; // 涓氬姟绫诲瀷
+ sourceDocType?: string; // 鏉ユ簮鍗曟嵁绫诲瀷
+ sourceDocId?: number; // 鏉ユ簮鍗曟嵁缂栧彿
+ sourceDocCode?: string; // 鏉ユ簮鍗曟嵁缂栫爜
+ issueDate?: number; // 鍑哄簱鏃ユ湡
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曞垎椤� */
+export function getMiscIssuePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsMiscIssueApi.MiscIssue>>(
+ '/mes/wm/misc-issue/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡鎯� */
+export function getMiscIssue(id: number) {
+ return requestClient.get<WlsMiscIssueApi.MiscIssue>(
+ `/mes/wm/misc-issue/get?id=${id}`,
+ );
+}
+
+/** 鏂板鏉傞」鍑哄簱鍗� */
+export function createMiscIssue(data: WlsMiscIssueApi.MiscIssue) {
+ return requestClient.post<number>('/mes/wm/misc-issue/create', data);
+}
+
+/** 淇敼鏉傞」鍑哄簱鍗� */
+export function updateMiscIssue(data: WlsMiscIssueApi.MiscIssue) {
+ return requestClient.put('/mes/wm/misc-issue/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍑哄簱鍗� */
+export function deleteMiscIssue(id: number) {
+ return requestClient.delete(`/mes/wm/misc-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦鏉傞」鍑哄簱鍗� */
+export function submitMiscIssue(id: number) {
+ return requestClient.put(`/mes/wm/misc-issue/submit?id=${id}`);
+}
+
+/** 鎵ц鍑哄簱 */
+export function finishMiscIssue(id: number) {
+ return requestClient.put(`/mes/wm/misc-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷鏉傞」鍑哄簱鍗� */
+export function cancelMiscIssue(id: number) {
+ return requestClient.put(`/mes/wm/misc-issue/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭鏉傞」鍑哄簱鍗� */
+export function exportMiscIssue(params: any) {
+ return requestClient.download('/mes/wm/misc-issue/export-excel', { params });
+}
diff --git a/src/api/wls/miscissue/line/index.ts b/src/api/wls/miscissue/line/index.ts
new file mode 100644
index 0000000..09ebe1d
--- /dev/null
+++ b/src/api/wls/miscissue/line/index.ts
@@ -0,0 +1,70 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscIssueLineApi {
+ /** MES 鏉傞」鍑哄簱鍗曡 */
+ export interface MiscIssueLine {
+ id?: number; // 缂栧彿
+ issueId?: number; // 鍑哄簱鍗曠紪鍙�
+ sourceDocLineId?: number; // 鏉ユ簮鍗曟嵁琛岀紪鍙�
+ materialStockId?: number; // 搴撳瓨缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasure?: string; // 璁¢噺鍗曚綅
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 鍑哄簱鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseCode?: string; // 浠撳簱缂栫爜
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationCode?: string; // 搴撳尯缂栫爜
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaCode?: string; // 搴撲綅缂栫爜
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ quantityMax?: number; // 鍙嚭搴撴渶澶ф暟閲忥紙鍓嶇鐢ㄤ簬闄愬埗鍑哄簱鏁伴噺锛屽彇鑷簱瀛樺湪搴撴暟閲忥級
+ }
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡鍒嗛〉 */
+export function getMiscIssueLinePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsMiscIssueLineApi.MiscIssueLine>>(
+ '/mes/wm/misc-issue-line/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡鍒楄〃 */
+export function getMiscIssueLineListByIssueId(issueId: number) {
+ return requestClient.get<WlsMiscIssueLineApi.MiscIssueLine[]>(
+ `/mes/wm/misc-issue-line/list-by-issue-id?issueId=${issueId}`,
+ );
+}
+
+/** 鏌ヨ鏉傞」鍑哄簱鍗曡璇︽儏 */
+export function getMiscIssueLine(id: number) {
+ return requestClient.get<WlsMiscIssueLineApi.MiscIssueLine>(
+ `/mes/wm/misc-issue-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板鏉傞」鍑哄簱鍗曡 */
+export function createMiscIssueLine(data: WlsMiscIssueLineApi.MiscIssueLine) {
+ return requestClient.post('/mes/wm/misc-issue-line/create', data);
+}
+
+/** 淇敼鏉傞」鍑哄簱鍗曡 */
+export function updateMiscIssueLine(data: WlsMiscIssueLineApi.MiscIssueLine) {
+ return requestClient.put('/mes/wm/misc-issue-line/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍑哄簱鍗曡 */
+export function deleteMiscIssueLine(id: number) {
+ return requestClient.delete(`/mes/wm/misc-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/miscreceipt/index.ts b/src/api/wls/miscreceipt/index.ts
new file mode 100644
index 0000000..0253771
--- /dev/null
+++ b/src/api/wls/miscreceipt/index.ts
@@ -0,0 +1,72 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscReceiptApi {
+ /** MES 鏉傞」鍏ュ簱鍗� */
+ export interface MiscReceipt {
+ id?: number; // 缂栧彿
+ code?: string; // 鍏ュ簱鍗曠紪鍙�
+ name?: string; // 鍏ュ簱鍗曞悕绉�
+ type?: number; // 鏉傞」绫诲瀷
+ sourceDocType?: string; // 鏉ユ簮鍗曟嵁绫诲瀷
+ sourceDocId?: number; // 鏉ユ簮鍗曟嵁缂栧彿
+ sourceDocCode?: string; // 鏉ユ簮鍗曟嵁缂栫爜
+ receiptDate?: number; // 鍏ュ簱鏃ユ湡
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曞垎椤� */
+export function getMiscReceiptPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsMiscReceiptApi.MiscReceipt>>(
+ '/mes/wm/misc-receipt/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曡鎯� */
+export function getMiscReceipt(id: number) {
+ return requestClient.get<WlsMiscReceiptApi.MiscReceipt>(
+ `/mes/wm/misc-receipt/get?id=${id}`,
+ );
+}
+
+/** 鏂板鏉傞」鍏ュ簱鍗� */
+export function createMiscReceipt(data: WlsMiscReceiptApi.MiscReceipt) {
+ return requestClient.post<number>('/mes/wm/misc-receipt/create', data);
+}
+
+/** 淇敼鏉傞」鍏ュ簱鍗� */
+export function updateMiscReceipt(data: WlsMiscReceiptApi.MiscReceipt) {
+ return requestClient.put('/mes/wm/misc-receipt/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍏ュ簱鍗� */
+export function deleteMiscReceipt(id: number) {
+ return requestClient.delete(`/mes/wm/misc-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦鏉傞」鍏ュ簱鍗� */
+export function submitMiscReceipt(id: number) {
+ return requestClient.put(`/mes/wm/misc-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц鍏ュ簱 */
+export function finishMiscReceipt(id: number) {
+ return requestClient.put(`/mes/wm/misc-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷鏉傞」鍏ュ簱鍗� */
+export function cancelMiscReceipt(id: number) {
+ return requestClient.put(`/mes/wm/misc-receipt/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭鏉傞」鍏ュ簱鍗� */
+export function exportMiscReceipt(params: any) {
+ return requestClient.download('/mes/wm/misc-receipt/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/miscreceipt/line/index.ts b/src/api/wls/miscreceipt/line/index.ts
new file mode 100644
index 0000000..d8e915b
--- /dev/null
+++ b/src/api/wls/miscreceipt/line/index.ts
@@ -0,0 +1,57 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsMiscReceiptLineApi {
+ /** MES 鏉傞」鍏ュ簱鍗曡 */
+ export interface MiscReceiptLine {
+ id?: number; // 缂栧彿
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 鍏ュ簱鏁伴噺
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曡鍒楄〃 */
+export function getMiscReceiptLineListByReceiptId(receiptId: number) {
+ return requestClient.get<WlsMiscReceiptLineApi.MiscReceiptLine[]>(
+ `/mes/wm/misc-receipt-line/list-by-receipt-id?receiptId=${receiptId}`,
+ );
+}
+
+/** 鏌ヨ鏉傞」鍏ュ簱鍗曡璇︽儏 */
+export function getMiscReceiptLine(id: number) {
+ return requestClient.get<WlsMiscReceiptLineApi.MiscReceiptLine>(
+ `/mes/wm/misc-receipt-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板鏉傞」鍏ュ簱鍗曡 */
+export function createMiscReceiptLine(
+ data: WlsMiscReceiptLineApi.MiscReceiptLine,
+) {
+ return requestClient.post('/mes/wm/misc-receipt-line/create', data);
+}
+
+/** 淇敼鏉傞」鍏ュ簱鍗曡 */
+export function updateMiscReceiptLine(
+ data: WlsMiscReceiptLineApi.MiscReceiptLine,
+) {
+ return requestClient.put('/mes/wm/misc-receipt-line/update', data);
+}
+
+/** 鍒犻櫎鏉傞」鍏ュ簱鍗曡 */
+export function deleteMiscReceiptLine(id: number) {
+ return requestClient.delete(`/mes/wm/misc-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/outsourceissue/detail/index.ts b/src/api/wls/outsourceissue/detail/index.ts
new file mode 100644
index 0000000..c183aec
--- /dev/null
+++ b/src/api/wls/outsourceissue/detail/index.ts
@@ -0,0 +1,61 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceIssueDetailApi {
+ /** MES 澶栧崗鍙戞枡鍗曟槑缁� */
+ export interface OutsourceIssueDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ lineId?: number; // 琛岀紪鍙�
+ issueId?: number; // 鍙戞枡鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 鏁伴噺
+ materialStockId?: number; // 搴撳瓨缂栧彿
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规缂栫爜
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曟槑缁嗗垪琛� */
+export function getOutsourceIssueDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsOutsourceIssueDetailApi.OutsourceIssueDetail[]>(
+ '/mes/wm/outsource-issue-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曟槑缁嗚鎯� */
+export function getOutsourceIssueDetail(id: number) {
+ return requestClient.get<WlsOutsourceIssueDetailApi.OutsourceIssueDetail>(
+ `/mes/wm/outsource-issue-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板澶栧崗鍙戞枡鍗曟槑缁� */
+export function createOutsourceIssueDetail(
+ data: WlsOutsourceIssueDetailApi.OutsourceIssueDetail,
+) {
+ return requestClient.post('/mes/wm/outsource-issue-detail/create', data);
+}
+
+/** 淇敼澶栧崗鍙戞枡鍗曟槑缁� */
+export function updateOutsourceIssueDetail(
+ data: WlsOutsourceIssueDetailApi.OutsourceIssueDetail,
+) {
+ return requestClient.put('/mes/wm/outsource-issue-detail/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗曟槑缁� */
+export function deleteOutsourceIssueDetail(id: number) {
+ return requestClient.delete(`/mes/wm/outsource-issue-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/outsourceissue/index.ts b/src/api/wls/outsourceissue/index.ts
new file mode 100644
index 0000000..b1adf8e
--- /dev/null
+++ b/src/api/wls/outsourceissue/index.ts
@@ -0,0 +1,90 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceIssueApi {
+ /** MES 澶栧崗鍙戞枡鍗� */
+ export interface OutsourceIssue {
+ id?: number; // 鍙戞枡鍗曠紪鍙�
+ code?: string; // 鍙戞枡鍗曠紪鍙�
+ name?: string; // 鍙戞枡鍗曞悕绉�
+ vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+ vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+ vendorName?: string; // 渚涘簲鍟嗗悕绉�
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+ workOrderName?: string; // 鐢熶骇璁㈠崟鍚嶇О
+ issueDate?: number; // 鍙戞枡鏃ユ湡
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曞垎椤� */
+export function getOutsourceIssuePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsOutsourceIssueApi.OutsourceIssue>>(
+ '/mes/wm/outsource-issue/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曡鎯� */
+export function getOutsourceIssue(id: number) {
+ return requestClient.get<WlsOutsourceIssueApi.OutsourceIssue>(
+ `/mes/wm/outsource-issue/get?id=${id}`,
+ );
+}
+
+/** 鏂板澶栧崗鍙戞枡鍗� */
+export function createOutsourceIssue(
+ data: WlsOutsourceIssueApi.OutsourceIssue,
+) {
+ return requestClient.post<number>('/mes/wm/outsource-issue/create', data);
+}
+
+/** 淇敼澶栧崗鍙戞枡鍗� */
+export function updateOutsourceIssue(
+ data: WlsOutsourceIssueApi.OutsourceIssue,
+) {
+ return requestClient.put('/mes/wm/outsource-issue/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗� */
+export function deleteOutsourceIssue(id: number) {
+ return requestClient.delete(`/mes/wm/outsource-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦澶栧崗鍙戞枡鍗� */
+export function submitOutsourceIssue(id: number) {
+ return requestClient.put(`/mes/wm/outsource-issue/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockOutsourceIssue(id: number) {
+ return requestClient.put(`/mes/wm/outsource-issue/stock?id=${id}`);
+}
+
+/** 鎵ц棰嗗嚭 */
+export function finishOutsourceIssue(id: number) {
+ return requestClient.put(`/mes/wm/outsource-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷澶栧崗鍙戞枡鍗� */
+export function cancelOutsourceIssue(id: number) {
+ return requestClient.put(`/mes/wm/outsource-issue/cancel?id=${id}`);
+}
+
+/** 鏍¢獙澶栧崗鍙戞枡鍗曟嫞璐ф暟閲忔槸鍚︿笌鍙戞枡鏁伴噺涓�鑷� */
+export function checkOutsourceIssueQuantity(id: number) {
+ return requestClient.get<boolean>(
+ `/mes/wm/outsource-issue/check-quantity?id=${id}`,
+ );
+}
+
+/** 瀵煎嚭澶栧崗鍙戞枡鍗� */
+export function exportOutsourceIssue(params: any) {
+ return requestClient.download('/mes/wm/outsource-issue/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/outsourceissue/line/index.ts b/src/api/wls/outsourceissue/line/index.ts
new file mode 100644
index 0000000..4d373a1
--- /dev/null
+++ b/src/api/wls/outsourceissue/line/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceIssueLineApi {
+ /** MES 澶栧崗鍙戞枡鍗曡 */
+ export interface OutsourceIssueLine {
+ id?: number; // 琛岀紪鍙�
+ issueId?: number; // 鍙戞枡鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 鍙戞枡鏁伴噺
+ materialStockId?: number; // 搴撳瓨缂栧彿
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规缂栫爜
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曡鍒嗛〉 */
+export function getOutsourceIssueLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsOutsourceIssueLineApi.OutsourceIssueLine>
+ >('/mes/wm/outsource-issue-line/page', { params });
+}
+
+/** 鏌ヨ澶栧崗鍙戞枡鍗曡璇︽儏 */
+export function getOutsourceIssueLine(id: number) {
+ return requestClient.get<WlsOutsourceIssueLineApi.OutsourceIssueLine>(
+ `/mes/wm/outsource-issue-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板澶栧崗鍙戞枡鍗曡 */
+export function createOutsourceIssueLine(
+ data: WlsOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+ return requestClient.post('/mes/wm/outsource-issue-line/create', data);
+}
+
+/** 淇敼澶栧崗鍙戞枡鍗曡 */
+export function updateOutsourceIssueLine(
+ data: WlsOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+ return requestClient.put('/mes/wm/outsource-issue-line/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗曡 */
+export function deleteOutsourceIssueLine(id: number) {
+ return requestClient.delete(`/mes/wm/outsource-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/outsourcereceipt/detail/index.ts b/src/api/wls/outsourcereceipt/detail/index.ts
new file mode 100644
index 0000000..45d4da2
--- /dev/null
+++ b/src/api/wls/outsourcereceipt/detail/index.ts
@@ -0,0 +1,61 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceReceiptDetailApi {
+ /** MES 澶栧崗鍏ュ簱鍗曟槑缁� */
+ export interface OutsourceReceiptDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ lineId?: number; // 琛岀紪鍙�
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 涓婃灦鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规缂栫爜
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曟槑缁嗗垪琛� */
+export function getOutsourceReceiptDetailListByLineId(lineId: number) {
+ return requestClient.get<
+ WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail[]
+ >('/mes/wm/outsource-receipt-detail/list-by-line', { params: { lineId } });
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曟槑缁嗚鎯� */
+export function getOutsourceReceiptDetail(id: number) {
+ return requestClient.get<WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail>(
+ `/mes/wm/outsource-receipt-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板澶栧崗鍏ュ簱鍗曟槑缁� */
+export function createOutsourceReceiptDetail(
+ data: WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail,
+) {
+ return requestClient.post('/mes/wm/outsource-receipt-detail/create', data);
+}
+
+/** 淇敼澶栧崗鍏ュ簱鍗曟槑缁� */
+export function updateOutsourceReceiptDetail(
+ data: WlsOutsourceReceiptDetailApi.OutsourceReceiptDetail,
+) {
+ return requestClient.put('/mes/wm/outsource-receipt-detail/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗曟槑缁� */
+export function deleteOutsourceReceiptDetail(id: number) {
+ return requestClient.delete(
+ `/mes/wm/outsource-receipt-detail/delete?id=${id}`,
+ );
+}
diff --git a/src/api/wls/outsourcereceipt/index.ts b/src/api/wls/outsourcereceipt/index.ts
new file mode 100644
index 0000000..e5d495f
--- /dev/null
+++ b/src/api/wls/outsourcereceipt/index.ts
@@ -0,0 +1,80 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceReceiptApi {
+ /** MES 澶栧崗鍏ュ簱鍗� */
+ export interface OutsourceReceipt {
+ id?: number; // 鍏ュ簱鍗曠紪鍙�
+ code?: string; // 鍏ュ簱鍗曠紪鐮�
+ name?: string; // 鍏ュ簱鍗曞悕绉�
+ workOrderId?: number; // 澶栧崗璁㈠崟缂栧彿
+ workOrderCode?: string; // 澶栧崗璁㈠崟缂栫爜
+ vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+ vendorName?: string; // 渚涘簲鍟嗗悕绉�
+ receiptDate?: number; // 鍏ュ簱鏃ユ湡
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曞垎椤� */
+export function getOutsourceReceiptPage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsOutsourceReceiptApi.OutsourceReceipt>
+ >('/mes/wm/outsource-receipt/page', { params });
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曡鎯� */
+export function getOutsourceReceipt(id: number) {
+ return requestClient.get<WlsOutsourceReceiptApi.OutsourceReceipt>(
+ `/mes/wm/outsource-receipt/get?id=${id}`,
+ );
+}
+
+/** 鏂板澶栧崗鍏ュ簱鍗� */
+export function createOutsourceReceipt(
+ data: WlsOutsourceReceiptApi.OutsourceReceipt,
+) {
+ return requestClient.post<number>('/mes/wm/outsource-receipt/create', data);
+}
+
+/** 淇敼澶栧崗鍏ュ簱鍗� */
+export function updateOutsourceReceipt(
+ data: WlsOutsourceReceiptApi.OutsourceReceipt,
+) {
+ return requestClient.put('/mes/wm/outsource-receipt/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗� */
+export function deleteOutsourceReceipt(id: number) {
+ return requestClient.delete(`/mes/wm/outsource-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦澶栧崗鍏ュ簱鍗� */
+export function submitOutsourceReceipt(id: number) {
+ return requestClient.put(`/mes/wm/outsource-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockOutsourceReceipt(id: number) {
+ return requestClient.put(`/mes/wm/outsource-receipt/stock?id=${id}`);
+}
+
+/** 瀹屾垚鍏ュ簱 */
+export function finishOutsourceReceipt(id: number) {
+ return requestClient.put(`/mes/wm/outsource-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷澶栧崗鍏ュ簱鍗� */
+export function cancelOutsourceReceipt(id: number) {
+ return requestClient.put(`/mes/wm/outsource-receipt/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭澶栧崗鍏ュ簱鍗� */
+export function exportOutsourceReceipt(params: any) {
+ return requestClient.download('/mes/wm/outsource-receipt/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/outsourcereceipt/line/index.ts b/src/api/wls/outsourcereceipt/line/index.ts
new file mode 100644
index 0000000..af03c02
--- /dev/null
+++ b/src/api/wls/outsourcereceipt/line/index.ts
@@ -0,0 +1,59 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsOutsourceReceiptLineApi {
+ /** MES 澶栧崗鍏ュ簱鍗曡 */
+ export interface OutsourceReceiptLine {
+ id?: number; // 琛岀紪鍙�
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 鍏ュ簱鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规缂栫爜
+ productionDate?: number; // 鐢熶骇鏃ユ湡
+ expireDate?: number; // 鏈夋晥鏈�
+ lotNumber?: string; // 鐢熶骇鎵瑰彿
+ iqcCheckFlag?: boolean; // 鏄惁闇�瑕佽川妫�
+ qualityStatus?: number; // 璐ㄩ噺鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曡鍒嗛〉 */
+export function getOutsourceReceiptLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsOutsourceReceiptLineApi.OutsourceReceiptLine>
+ >('/mes/wm/outsource-receipt-line/page', { params });
+}
+
+/** 鏌ヨ澶栧崗鍏ュ簱鍗曡璇︽儏 */
+export function getOutsourceReceiptLine(id: number) {
+ return requestClient.get<WlsOutsourceReceiptLineApi.OutsourceReceiptLine>(
+ `/mes/wm/outsource-receipt-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板澶栧崗鍏ュ簱鍗曡 */
+export function createOutsourceReceiptLine(
+ data: WlsOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+ return requestClient.post('/mes/wm/outsource-receipt-line/create', data);
+}
+
+/** 淇敼澶栧崗鍏ュ簱鍗曡 */
+export function updateOutsourceReceiptLine(
+ data: WlsOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+ return requestClient.put('/mes/wm/outsource-receipt-line/update', data);
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗曡 */
+export function deleteOutsourceReceiptLine(id: number) {
+ return requestClient.delete(`/mes/wm/outsource-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/packages/index.ts b/src/api/wls/packages/index.ts
new file mode 100644
index 0000000..e1e2a4f
--- /dev/null
+++ b/src/api/wls/packages/index.ts
@@ -0,0 +1,83 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsPackageApi {
+ /** 瑁呯鍗� */
+ export interface Package {
+ id?: number; // 瑁呯鍗曠紪鍙�
+ code?: string; // 瑁呯鍗曠紪鐮�
+ parentId?: number; // 鐖剁缂栧彿
+ packageDate?: number; // 瑁呯鏃ユ湡
+ salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+ invoiceCode?: string; // 鍙戠エ缂栧彿
+ clientId?: number; // 瀹㈡埛缂栧彿
+ clientCode?: string; // 瀹㈡埛缂栫爜
+ clientName?: string; // 瀹㈡埛鍚嶇О
+ clientNickname?: string; // 瀹㈡埛绠�绉�
+ length?: number; // 绠遍暱搴�
+ width?: number; // 绠卞搴�
+ height?: number; // 绠遍珮搴�
+ sizeUnitId?: number; // 灏哄鍗曚綅缂栧彿
+ sizeUnitName?: string; // 灏哄鍗曚綅鍚嶇О
+ netWeight?: number; // 鍑�閲�
+ grossWeight?: number; // 姣涢噸
+ weightUnitId?: number; // 閲嶉噺鍗曚綅缂栧彿
+ weightUnitName?: string; // 閲嶉噺鍗曚綅鍚嶇О
+ inspectorUserId?: number; // 妫�鏌ュ憳鐢ㄦ埛缂栧彿
+ inspectorName?: string; // 妫�鏌ュ憳鍚嶇О
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: string; // 鍒涘缓鏃堕棿
+ children?: Package[]; // 瀛愮鍒楄〃
+ }
+}
+
+/** 鏌ヨ瑁呯鍗曞垎椤� */
+export function getPackagePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsPackageApi.Package>>(
+ '/mes/wm/package/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ瑁呯鍗曡鎯� */
+export function getPackage(id: number) {
+ return requestClient.get<WlsPackageApi.Package>(
+ `/mes/wm/package/get?id=${id}`,
+ );
+}
+
+/** 鏂板瑁呯鍗� */
+export function createPackage(data: WlsPackageApi.Package) {
+ return requestClient.post<number>('/mes/wm/package/create', data);
+}
+
+/** 淇敼瑁呯鍗� */
+export function updatePackage(data: WlsPackageApi.Package) {
+ return requestClient.put('/mes/wm/package/update', data);
+}
+
+/** 鍒犻櫎瑁呯鍗� */
+export function deletePackage(id: number) {
+ return requestClient.delete(`/mes/wm/package/delete?id=${id}`);
+}
+
+/** 瀹屾垚瑁呯鍗� */
+export function finishPackage(id: number) {
+ return requestClient.put(`/mes/wm/package/finish?id=${id}`);
+}
+
+/** 娣诲姞瀛愮 */
+export function addChildPackage(parentId: number, childId: number) {
+ return requestClient.put('/mes/wm/package/add-child-package', null, {
+ params: { childId, parentId },
+ });
+}
+
+/** 绉婚櫎瀛愮 */
+export function removeChildPackage(childId: number) {
+ return requestClient.put(
+ `/mes/wm/package/remove-child-package?childId=${childId}`,
+ );
+}
diff --git a/src/api/wls/packages/line/index.ts b/src/api/wls/packages/line/index.ts
new file mode 100644
index 0000000..7bce713
--- /dev/null
+++ b/src/api/wls/packages/line/index.ts
@@ -0,0 +1,54 @@
+锘縤mport type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsPackageLineApi {
+ /** 瑁呯鏄庣粏 */
+ export interface PackageLine {
+ id?: number; // 瑁呯鏄庣粏缂栧彿
+ packageId?: number; // 瑁呯鍗曠紪鍙�
+ materialStockId?: number; // 搴撳瓨缂栧彿
+ itemId?: number; // 浜у搧鐗╂枡缂栧彿
+ itemCode?: string; // 浜у搧鐗╂枡缂栫爜
+ itemName?: string; // 浜у搧鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 瑁呯鏁伴噺
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ workOrderCode?: string; // 鐢熶骇璁㈠崟缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ expireDate?: number; // 鏈夋晥鏈�
+ remark?: string; // 澶囨敞
+ createTime?: string; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ瑁呯鏄庣粏鍒嗛〉 */
+export function getPackageLinePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsPackageLineApi.PackageLine>>(
+ '/mes/wm/package-line/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ瑁呯鏄庣粏璇︽儏 */
+export function getPackageLine(id: number) {
+ return requestClient.get<WlsPackageLineApi.PackageLine>(
+ `/mes/wm/package-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板瑁呯鏄庣粏 */
+export function createPackageLine(data: WlsPackageLineApi.PackageLine) {
+ return requestClient.post('/mes/wm/package-line/create', data);
+}
+
+/** 淇敼瑁呯鏄庣粏 */
+export function updatePackageLine(data: WlsPackageLineApi.PackageLine) {
+ return requestClient.put('/mes/wm/package-line/update', data);
+}
+
+/** 鍒犻櫎瑁呯鏄庣粏 */
+export function deletePackageLine(id: number) {
+ return requestClient.delete(`/mes/wm/package-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/productissue/detail/index.ts b/src/api/wls/productissue/detail/index.ts
new file mode 100644
index 0000000..9d6f584
--- /dev/null
+++ b/src/api/wls/productissue/detail/index.ts
@@ -0,0 +1,59 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductIssueDetailApi {
+ /** MES 棰嗘枡鍑哄簱鏄庣粏 */
+ export interface ProductIssueDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ issueId?: number; // 棰嗘枡鍗曠紪鍙�
+ lineId?: number; // 棰嗘枡鍗曡缂栧彿
+ materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ quantity?: number; // 鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getProductIssueDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsProductIssueDetailApi.ProductIssueDetail[]>(
+ '/mes/wm/product-issue-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鏄庣粏璇︽儏 */
+export function getProductIssueDetail(id: number) {
+ return requestClient.get<WlsProductIssueDetailApi.ProductIssueDetail>(
+ `/mes/wm/product-issue-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板棰嗘枡鍑哄簱鏄庣粏 */
+export function createProductIssueDetail(
+ data: WlsProductIssueDetailApi.ProductIssueDetail,
+) {
+ return requestClient.post<number>(
+ '/mes/wm/product-issue-detail/create',
+ data,
+ );
+}
+
+/** 淇敼棰嗘枡鍑哄簱鏄庣粏 */
+export function updateProductIssueDetail(
+ data: WlsProductIssueDetailApi.ProductIssueDetail,
+) {
+ return requestClient.put('/mes/wm/product-issue-detail/update', data);
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鏄庣粏 */
+export function deleteProductIssueDetail(id: number) {
+ return requestClient.delete(`/mes/wm/product-issue-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/productissue/index.ts b/src/api/wls/productissue/index.ts
new file mode 100644
index 0000000..8d4d440
--- /dev/null
+++ b/src/api/wls/productissue/index.ts
@@ -0,0 +1,87 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductIssueApi {
+ /** MES 棰嗘枡鍑哄簱鍗� */
+ export interface ProductIssue {
+ id?: number; // 棰嗘枡鍗曠紪鍙�
+ code?: string; // 棰嗘枡鍗曠紪鍙�
+ name?: string; // 棰嗘枡鍗曞悕绉�
+ workstationId?: number; // 宸ヤ綔绔欑紪鍙�
+ workstationCode?: string; // 宸ヤ綔绔欑紪鐮�
+ workstationName?: string; // 宸ヤ綔绔欏悕绉�
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+ clientCode?: string; // 瀹㈡埛缂栫爜
+ clientName?: string; // 瀹㈡埛鍚嶇О
+ requiredTime?: number; // 闇�姹傛椂闂�
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曞垎椤� */
+export function getProductIssuePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsProductIssueApi.ProductIssue>>(
+ '/mes/wm/product-issue/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曡鎯� */
+export function getProductIssue(id: number) {
+ return requestClient.get<WlsProductIssueApi.ProductIssue>(
+ `/mes/wm/product-issue/get?id=${id}`,
+ );
+}
+
+/** 鏂板棰嗘枡鍑哄簱鍗� */
+export function createProductIssue(data: WlsProductIssueApi.ProductIssue) {
+ return requestClient.post<number>('/mes/wm/product-issue/create', data);
+}
+
+/** 淇敼棰嗘枡鍑哄簱鍗� */
+export function updateProductIssue(data: WlsProductIssueApi.ProductIssue) {
+ return requestClient.put('/mes/wm/product-issue/update', data);
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鍗� */
+export function deleteProductIssue(id: number) {
+ return requestClient.delete(`/mes/wm/product-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦棰嗘枡鍑哄簱鍗� */
+export function submitProductIssue(id: number) {
+ return requestClient.put(`/mes/wm/product-issue/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockProductIssue(id: number) {
+ return requestClient.put(`/mes/wm/product-issue/stock?id=${id}`);
+}
+
+/** 瀹屾垚棰嗘枡鍑哄簱鍗� */
+export function finishProductIssue(id: number) {
+ return requestClient.put(`/mes/wm/product-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷棰嗘枡鍑哄簱鍗� */
+export function cancelProductIssue(id: number) {
+ return requestClient.put(`/mes/wm/product-issue/cancel?id=${id}`);
+}
+
+/** 鏍¢獙棰嗘枡鍑哄簱鍗曟嫞璐ф暟閲忔槸鍚︿笌棰嗘枡鏁伴噺涓�鑷� */
+export function checkProductIssueQuantity(id: number) {
+ return requestClient.get<boolean>(
+ `/mes/wm/product-issue/check-quantity?id=${id}`,
+ );
+}
+
+/** 瀵煎嚭棰嗘枡鍑哄簱鍗� */
+export function exportProductIssue(params: any) {
+ return requestClient.download('/mes/wm/product-issue/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/productissue/line/index.ts b/src/api/wls/productissue/line/index.ts
new file mode 100644
index 0000000..f74552f
--- /dev/null
+++ b/src/api/wls/productissue/line/index.ts
@@ -0,0 +1,52 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductIssueLineApi {
+ /** MES 棰嗘枡鍑哄簱鍗曡 */
+ export interface ProductIssueLine {
+ id?: number; // 琛岀紪鍙�
+ issueId?: number; // 棰嗘枡鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 棰嗘枡鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曡鍒嗛〉 */
+export function getProductIssueLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsProductIssueLineApi.ProductIssueLine>
+ >('/mes/wm/product-issue-line/page', { params });
+}
+
+/** 鏌ヨ棰嗘枡鍑哄簱鍗曡璇︽儏 */
+export function getProductIssueLine(id: number) {
+ return requestClient.get<WlsProductIssueLineApi.ProductIssueLine>(
+ `/mes/wm/product-issue-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板棰嗘枡鍑哄簱鍗曡 */
+export function createProductIssueLine(
+ data: WlsProductIssueLineApi.ProductIssueLine,
+) {
+ return requestClient.post<number>('/mes/wm/product-issue-line/create', data);
+}
+
+/** 淇敼棰嗘枡鍑哄簱鍗曡 */
+export function updateProductIssueLine(
+ data: WlsProductIssueLineApi.ProductIssueLine,
+) {
+ return requestClient.put('/mes/wm/product-issue-line/update', data);
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鍗曡 */
+export function deleteProductIssueLine(id: number) {
+ return requestClient.delete(`/mes/wm/product-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/productproduce/line/index.ts b/src/api/wls/productproduce/line/index.ts
new file mode 100644
index 0000000..4649ad5
--- /dev/null
+++ b/src/api/wls/productproduce/line/index.ts
@@ -0,0 +1,37 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductProduceLineApi {
+ /** MES 浜у搧浜у嚭琛� */
+ export interface ProductProduceLine {
+ id?: number;
+ feedbackId?: number; // 鎶ュ伐缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╄祫缂栫爜
+ itemName?: string; // 鐗╄祫鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureId?: number; // 鍗曚綅缂栧彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 浜у嚭鏁伴噺
+ batchCode?: string; // 鎵规鍙�
+ qualityStatus?: number; // 璐ㄩ噺鐘舵��
+ locationId?: number; // 搴撲綅缂栧彿
+ locationName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+
+ /** MES 浜у搧浜у嚭琛屽垎椤垫煡璇㈠弬鏁� */
+ export interface PageParams extends PageParam {
+ feedbackId?: number;
+ }
+}
+
+/** 鏌ヨ浜у搧浜у嚭琛屽垎椤� */
+export function getProductProduceLinePage(
+ params: WlsProductProduceLineApi.PageParams,
+) {
+ return requestClient.get<
+ PageResult<WlsProductProduceLineApi.ProductProduceLine>
+ >('/mes/wm/product-produce-line/page', { params });
+}
diff --git a/src/api/wls/productreceipt/detail/index.ts b/src/api/wls/productreceipt/detail/index.ts
new file mode 100644
index 0000000..f21c9c4
--- /dev/null
+++ b/src/api/wls/productreceipt/detail/index.ts
@@ -0,0 +1,58 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductReceiptDetailApi {
+ /** MES 浜у搧鍏ュ簱鏄庣粏 */
+ export interface ProductReceiptDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ lineId?: number; // 鍏ュ簱鍗曡缂栧彿
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ quantity?: number; // 鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getProductReceiptDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsProductReceiptDetailApi.ProductReceiptDetail[]>(
+ '/mes/wm/product-receipt-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鏄庣粏璇︽儏 */
+export function getProductReceiptDetail(id: number) {
+ return requestClient.get<WlsProductReceiptDetailApi.ProductReceiptDetail>(
+ `/mes/wm/product-receipt-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板浜у搧鍏ュ簱鏄庣粏 */
+export function createProductReceiptDetail(
+ data: WlsProductReceiptDetailApi.ProductReceiptDetail,
+) {
+ return requestClient.post<number>(
+ '/mes/wm/product-receipt-detail/create',
+ data,
+ );
+}
+
+/** 淇敼浜у搧鍏ュ簱鏄庣粏 */
+export function updateProductReceiptDetail(
+ data: WlsProductReceiptDetailApi.ProductReceiptDetail,
+) {
+ return requestClient.put('/mes/wm/product-receipt-detail/update', data);
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鏄庣粏 */
+export function deleteProductReceiptDetail(id: number) {
+ return requestClient.delete(`/mes/wm/product-receipt-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/productreceipt/index.ts b/src/api/wls/productreceipt/index.ts
new file mode 100644
index 0000000..918c31b
--- /dev/null
+++ b/src/api/wls/productreceipt/index.ts
@@ -0,0 +1,91 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductReceiptApi {
+ /** MES 浜у搧鍏ュ簱鍗� */
+ export interface ProductReceipt {
+ id?: number; // 鍏ュ簱鍗曠紪鍙�
+ code?: string; // 鍏ュ簱鍗曠紪鐮�
+ name?: string; // 鍏ュ簱鍗曞悕绉�
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+ itemId?: number; // 浜у搧鐗╂枡缂栧彿
+ itemCode?: string; // 浜у搧鐗╂枡缂栫爜
+ itemName?: string; // 浜у搧鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ receiptDate?: number; // 鍏ュ簱鏃ユ湡
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曞垎椤� */
+export function getProductReceiptPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsProductReceiptApi.ProductReceipt>>(
+ '/mes/wm/product-receipt/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曡鎯� */
+export function getProductReceipt(id: number) {
+ return requestClient.get<WlsProductReceiptApi.ProductReceipt>(
+ `/mes/wm/product-receipt/get?id=${id}`,
+ );
+}
+
+/** 鏂板浜у搧鍏ュ簱鍗� */
+export function createProductReceipt(
+ data: WlsProductReceiptApi.ProductReceipt,
+) {
+ return requestClient.post<number>('/mes/wm/product-receipt/create', data);
+}
+
+/** 淇敼浜у搧鍏ュ簱鍗� */
+export function updateProductReceipt(
+ data: WlsProductReceiptApi.ProductReceipt,
+) {
+ return requestClient.put('/mes/wm/product-receipt/update', data);
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鍗� */
+export function deleteProductReceipt(id: number) {
+ return requestClient.delete(`/mes/wm/product-receipt/delete?id=${id}`);
+}
+
+/** 鎻愪氦浜у搧鍏ュ簱鍗� */
+export function submitProductReceipt(id: number) {
+ return requestClient.put(`/mes/wm/product-receipt/submit?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockProductReceipt(id: number) {
+ return requestClient.put(`/mes/wm/product-receipt/stock?id=${id}`);
+}
+
+/** 鎵ц鍏ュ簱 */
+export function finishProductReceipt(id: number) {
+ return requestClient.put(`/mes/wm/product-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷浜у搧鍏ュ簱鍗� */
+export function cancelProductReceipt(id: number) {
+ return requestClient.put(`/mes/wm/product-receipt/cancel?id=${id}`);
+}
+
+/** 鏍¢獙浜у搧鍏ュ簱鍗曟槑缁嗘暟閲忔槸鍚︿笌琛屾敹璐ф暟閲忎竴鑷� */
+export function checkProductReceiptQuantity(id: number) {
+ return requestClient.get<boolean>(
+ `/mes/wm/product-receipt/check-quantity?id=${id}`,
+ );
+}
+
+/** 瀵煎嚭浜у搧鍏ュ簱鍗� */
+export function exportProductReceipt(params: any) {
+ return requestClient.download('/mes/wm/product-receipt/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/productreceipt/line/index.ts b/src/api/wls/productreceipt/line/index.ts
new file mode 100644
index 0000000..2a96c0f
--- /dev/null
+++ b/src/api/wls/productreceipt/line/index.ts
@@ -0,0 +1,57 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductReceiptLineApi {
+ /** MES 浜у搧鍏ュ簱鍗曡 */
+ export interface ProductReceiptLine {
+ id?: number; // 琛岀紪鍙�
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 鍏ュ簱鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曡鍒嗛〉 */
+export function getProductReceiptLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsProductReceiptLineApi.ProductReceiptLine>
+ >('/mes/wm/product-receipt-line/page', { params });
+}
+
+/** 鏌ヨ浜у搧鍏ュ簱鍗曡璇︽儏 */
+export function getProductReceiptLine(id: number) {
+ return requestClient.get<WlsProductReceiptLineApi.ProductReceiptLine>(
+ `/mes/wm/product-receipt-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板浜у搧鍏ュ簱鍗曡 */
+export function createProductReceiptLine(
+ data: WlsProductReceiptLineApi.ProductReceiptLine,
+) {
+ return requestClient.post<number>(
+ '/mes/wm/product-receipt-line/create',
+ data,
+ );
+}
+
+/** 淇敼浜у搧鍏ュ簱鍗曡 */
+export function updateProductReceiptLine(
+ data: WlsProductReceiptLineApi.ProductReceiptLine,
+) {
+ return requestClient.put('/mes/wm/product-receipt-line/update', data);
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鍗曡 */
+export function deleteProductReceiptLine(id: number) {
+ return requestClient.delete(`/mes/wm/product-receipt-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/productsales/detail/index.ts b/src/api/wls/productsales/detail/index.ts
new file mode 100644
index 0000000..ab9b0d0
--- /dev/null
+++ b/src/api/wls/productsales/detail/index.ts
@@ -0,0 +1,61 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductSalesDetailApi {
+ /** MES 閿�鍞嚭搴撴槑缁� */
+ export interface ProductSalesDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ lineId?: number; // 鍑哄簱鍗曡缂栧彿
+ salesId?: number; // 鍑哄簱鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ quantity?: number; // 鏁伴噺
+ materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ閿�鍞嚭搴撴槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getProductSalesDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsProductSalesDetailApi.ProductSalesDetail[]>(
+ '/mes/wm/product-sales-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ閿�鍞嚭搴撴槑缁嗚鎯� */
+export function getProductSalesDetail(id: number) {
+ return requestClient.get<WlsProductSalesDetailApi.ProductSalesDetail>(
+ `/mes/wm/product-sales-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板閿�鍞嚭搴撴槑缁� */
+export function createProductSalesDetail(
+ data: WlsProductSalesDetailApi.ProductSalesDetail,
+) {
+ return requestClient.post<number>(
+ '/mes/wm/product-sales-detail/create',
+ data,
+ );
+}
+
+/** 淇敼閿�鍞嚭搴撴槑缁� */
+export function updateProductSalesDetail(
+ data: WlsProductSalesDetailApi.ProductSalesDetail,
+) {
+ return requestClient.put('/mes/wm/product-sales-detail/update', data);
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撴槑缁� */
+export function deleteProductSalesDetail(id: number) {
+ return requestClient.delete(`/mes/wm/product-sales-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/productsales/index.ts b/src/api/wls/productsales/index.ts
new file mode 100644
index 0000000..d97981c
--- /dev/null
+++ b/src/api/wls/productsales/index.ts
@@ -0,0 +1,96 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductSalesApi {
+ /** MES 閿�鍞嚭搴撳崟 */
+ export interface ProductSales {
+ id?: number; // 鍑哄簱鍗曠紪鍙�
+ code?: string; // 鍑哄簱鍗曠紪鍙�
+ name?: string; // 鍑哄簱鍗曞悕绉�
+ noticeId?: number; // 鍙戣揣閫氱煡鍗曠紪鍙�
+ noticeCode?: string; // 鍙戣揣閫氱煡鍗曠紪鐮�
+ clientId?: number; // 瀹㈡埛缂栧彿
+ clientCode?: string; // 瀹㈡埛缂栫爜
+ clientName?: string; // 瀹㈡埛鍚嶇О
+ salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+ salesDate?: number; // 鍑哄簱鏃ユ湡
+ contactName?: string; // 鏀惰揣浜�
+ contactTelephone?: string; // 鑱旂郴鏂瑰紡
+ contactAddress?: string; // 鏀惰揣鍦板潃
+ carrier?: string; // 鎵胯繍鍟�
+ shippingNumber?: string; // 杩愯緭鍗曞彿
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟鍒嗛〉 */
+export function getProductSalesPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsProductSalesApi.ProductSales>>(
+ '/mes/wm/product-sales/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟璇︽儏 */
+export function getProductSales(id: number) {
+ return requestClient.get<WlsProductSalesApi.ProductSales>(
+ `/mes/wm/product-sales/get?id=${id}`,
+ );
+}
+
+/** 鏂板閿�鍞嚭搴撳崟 */
+export function createProductSales(data: WlsProductSalesApi.ProductSales) {
+ return requestClient.post<number>('/mes/wm/product-sales/create', data);
+}
+
+/** 淇敼閿�鍞嚭搴撳崟 */
+export function updateProductSales(data: WlsProductSalesApi.ProductSales) {
+ return requestClient.put('/mes/wm/product-sales/update', data);
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撳崟 */
+export function deleteProductSales(id: number) {
+ return requestClient.delete(`/mes/wm/product-sales/delete?id=${id}`);
+}
+
+/** 鎻愪氦閿�鍞嚭搴撳崟 */
+export function submitProductSales(id: number) {
+ return requestClient.put(`/mes/wm/product-sales/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockProductSales(id: number) {
+ return requestClient.put(`/mes/wm/product-sales/stock?id=${id}`);
+}
+
+/** 濉啓杩愬崟 */
+export function shippingProductSales(data: WlsProductSalesApi.ProductSales) {
+ return requestClient.put('/mes/wm/product-sales/shipping', data);
+}
+
+/** 鎵ц鍑哄簱 */
+export function finishProductSales(id: number) {
+ return requestClient.put(`/mes/wm/product-sales/finish?id=${id}`);
+}
+
+/** 鍙栨秷閿�鍞嚭搴撳崟 */
+export function cancelProductSales(id: number) {
+ return requestClient.put(`/mes/wm/product-sales/cancel?id=${id}`);
+}
+
+/** 鏍¢獙閿�鍞嚭搴撳崟鎷h揣鏁伴噺鏄惁涓庡嚭搴撴暟閲忎竴鑷� */
+export function checkProductSalesQuantity(id: number) {
+ return requestClient.get<boolean>(
+ `/mes/wm/product-sales/check-quantity?id=${id}`,
+ );
+}
+
+/** 瀵煎嚭閿�鍞嚭搴撳崟 */
+export function exportProductSales(params: any) {
+ return requestClient.download('/mes/wm/product-sales/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/productsales/line/index.ts b/src/api/wls/productsales/line/index.ts
new file mode 100644
index 0000000..7da238c
--- /dev/null
+++ b/src/api/wls/productsales/line/index.ts
@@ -0,0 +1,56 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsProductSalesLineApi {
+ /** MES 閿�鍞嚭搴撳崟琛� */
+ export interface ProductSalesLine {
+ id?: number; // 琛岀紪鍙�
+ salesId?: number; // 鍑哄簱鍗曠紪鍙�
+ noticeLineId?: number; // 鍙戣揣閫氱煡鍗曡缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 鍑哄簱鏁伴噺
+ pickedQuantity?: number; // 宸叉嫞璐ф暟閲�
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ oqcCheckFlag?: boolean; // 鏄惁妫�楠�
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟琛屽垎椤� */
+export function getProductSalesLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsProductSalesLineApi.ProductSalesLine>
+ >('/mes/wm/product-sales-line/page', { params });
+}
+
+/** 鏌ヨ閿�鍞嚭搴撳崟琛岃鎯� */
+export function getProductSalesLine(id: number) {
+ return requestClient.get<WlsProductSalesLineApi.ProductSalesLine>(
+ `/mes/wm/product-sales-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板閿�鍞嚭搴撳崟琛� */
+export function createProductSalesLine(
+ data: WlsProductSalesLineApi.ProductSalesLine,
+) {
+ return requestClient.post<number>('/mes/wm/product-sales-line/create', data);
+}
+
+/** 淇敼閿�鍞嚭搴撳崟琛� */
+export function updateProductSalesLine(
+ data: WlsProductSalesLineApi.ProductSalesLine,
+) {
+ return requestClient.put('/mes/wm/product-sales-line/update', data);
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撳崟琛� */
+export function deleteProductSalesLine(id: number) {
+ return requestClient.delete(`/mes/wm/product-sales-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnissue/detail/index.ts b/src/api/wls/returnissue/detail/index.ts
new file mode 100644
index 0000000..f7edcdc
--- /dev/null
+++ b/src/api/wls/returnissue/detail/index.ts
@@ -0,0 +1,56 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnIssueDetailApi {
+ /** MES 鐢熶骇閫�鏂欐槑缁� */
+ export interface ReturnIssueDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ issueId?: number; // 閫�鏂欏崟缂栧彿
+ lineId?: number; // 閫�鏂欏崟琛岀紪鍙�
+ materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ quantity?: number; // 鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欐槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getReturnIssueDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsReturnIssueDetailApi.ReturnIssueDetail[]>(
+ '/mes/wm/return-issue-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欐槑缁嗚鎯� */
+export function getReturnIssueDetail(id: number) {
+ return requestClient.get<WlsReturnIssueDetailApi.ReturnIssueDetail>(
+ `/mes/wm/return-issue-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板鐢熶骇閫�鏂欐槑缁� */
+export function createReturnIssueDetail(
+ data: WlsReturnIssueDetailApi.ReturnIssueDetail,
+) {
+ return requestClient.post<number>('/mes/wm/return-issue-detail/create', data);
+}
+
+/** 淇敼鐢熶骇閫�鏂欐槑缁� */
+export function updateReturnIssueDetail(
+ data: WlsReturnIssueDetailApi.ReturnIssueDetail,
+) {
+ return requestClient.put('/mes/wm/return-issue-detail/update', data);
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欐槑缁� */
+export function deleteReturnIssueDetail(id: number) {
+ return requestClient.delete(`/mes/wm/return-issue-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnissue/index.ts b/src/api/wls/returnissue/index.ts
new file mode 100644
index 0000000..a1078e7
--- /dev/null
+++ b/src/api/wls/returnissue/index.ts
@@ -0,0 +1,78 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnIssueApi {
+ /** MES 鐢熶骇閫�鏂欏崟 */
+ export interface ReturnIssue {
+ id?: number; // 閫�鏂欏崟缂栧彿
+ code?: string; // 閫�鏂欏崟缂栧彿
+ name?: string; // 閫�鏂欏崟鍚嶇О
+ workstationId?: number; // 宸ヤ綔绔欑紪鍙�
+ workstationName?: string; // 宸ヤ綔绔欏悕绉�
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ workOrderCode?: string; // 鐢熶骇璁㈠崟缂栫爜
+ type?: number; // 閫�鏂欑被鍨�
+ returnDate?: number; // 閫�鏂欐棩鏈�
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟鍒嗛〉 */
+export function getReturnIssuePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsReturnIssueApi.ReturnIssue>>(
+ '/mes/wm/return-issue/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟璇︽儏 */
+export function getReturnIssue(id: number) {
+ return requestClient.get<WlsReturnIssueApi.ReturnIssue>(
+ `/mes/wm/return-issue/get?id=${id}`,
+ );
+}
+
+/** 鏂板鐢熶骇閫�鏂欏崟 */
+export function createReturnIssue(data: WlsReturnIssueApi.ReturnIssue) {
+ return requestClient.post<number>('/mes/wm/return-issue/create', data);
+}
+
+/** 淇敼鐢熶骇閫�鏂欏崟 */
+export function updateReturnIssue(data: WlsReturnIssueApi.ReturnIssue) {
+ return requestClient.put('/mes/wm/return-issue/update', data);
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欏崟 */
+export function deleteReturnIssue(id: number) {
+ return requestClient.delete(`/mes/wm/return-issue/delete?id=${id}`);
+}
+
+/** 鎻愪氦鐢熶骇閫�鏂欏崟 */
+export function submitReturnIssue(id: number) {
+ return requestClient.put(`/mes/wm/return-issue/submit?id=${id}`);
+}
+
+/** 鍏ュ簱涓婃灦 */
+export function stockReturnIssue(id: number) {
+ return requestClient.put(`/mes/wm/return-issue/stock?id=${id}`);
+}
+
+/** 瀹屾垚鐢熶骇閫�鏂欏崟 */
+export function finishReturnIssue(id: number) {
+ return requestClient.put(`/mes/wm/return-issue/finish?id=${id}`);
+}
+
+/** 鍙栨秷鐢熶骇閫�鏂欏崟 */
+export function cancelReturnIssue(id: number) {
+ return requestClient.put(`/mes/wm/return-issue/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭鐢熶骇閫�鏂欏崟 */
+export function exportReturnIssue(params: any) {
+ return requestClient.download('/mes/wm/return-issue/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/returnissue/line/index.ts b/src/api/wls/returnissue/line/index.ts
new file mode 100644
index 0000000..d9919dd
--- /dev/null
+++ b/src/api/wls/returnissue/line/index.ts
@@ -0,0 +1,58 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnIssueLineApi {
+ /** MES 鐢熶骇閫�鏂欏崟琛� */
+ export interface ReturnIssueLine {
+ id?: number; // 琛岀紪鍙�
+ issueId?: number; // 閫�鏂欏崟缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+ quantity?: number; // 閫�鏂欐暟閲�
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ rqcCheckFlag?: boolean; // 鏄惁妫�娴�
+ qualityStatus?: number; // 璐ㄩ噺鐘舵��
+ rqcId?: number; // 閫�璐ф楠屽崟缂栧彿
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟琛屽垎椤� */
+export function getReturnIssueLinePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsReturnIssueLineApi.ReturnIssueLine>>(
+ '/mes/wm/return-issue-line/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鐢熶骇閫�鏂欏崟琛岃鎯� */
+export function getReturnIssueLine(id: number) {
+ return requestClient.get<WlsReturnIssueLineApi.ReturnIssueLine>(
+ `/mes/wm/return-issue-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板鐢熶骇閫�鏂欏崟琛� */
+export function createReturnIssueLine(
+ data: WlsReturnIssueLineApi.ReturnIssueLine,
+) {
+ return requestClient.post<number>('/mes/wm/return-issue-line/create', data);
+}
+
+/** 淇敼鐢熶骇閫�鏂欏崟琛� */
+export function updateReturnIssueLine(
+ data: WlsReturnIssueLineApi.ReturnIssueLine,
+) {
+ return requestClient.put('/mes/wm/return-issue-line/update', data);
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欏崟琛� */
+export function deleteReturnIssueLine(id: number) {
+ return requestClient.delete(`/mes/wm/return-issue-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnsales/detail/index.ts b/src/api/wls/returnsales/detail/index.ts
new file mode 100644
index 0000000..6aa18e6
--- /dev/null
+++ b/src/api/wls/returnsales/detail/index.ts
@@ -0,0 +1,55 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnSalesDetailApi {
+ /** MES 閿�鍞��璐ф槑缁� */
+ export interface ReturnSalesDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ returnId?: number; // 閫�璐у崟缂栧彿
+ lineId?: number; // 閫�璐у崟琛岀紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ quantity?: number; // 鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ閿�鍞��璐ф槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getReturnSalesDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsReturnSalesDetailApi.ReturnSalesDetail[]>(
+ '/mes/wm/return-sales-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ閿�鍞��璐ф槑缁嗚鎯� */
+export function getReturnSalesDetail(id: number) {
+ return requestClient.get<WlsReturnSalesDetailApi.ReturnSalesDetail>(
+ `/mes/wm/return-sales-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板閿�鍞��璐ф槑缁� */
+export function createReturnSalesDetail(
+ data: WlsReturnSalesDetailApi.ReturnSalesDetail,
+) {
+ return requestClient.post<number>('/mes/wm/return-sales-detail/create', data);
+}
+
+/** 淇敼閿�鍞��璐ф槑缁� */
+export function updateReturnSalesDetail(
+ data: WlsReturnSalesDetailApi.ReturnSalesDetail,
+) {
+ return requestClient.put('/mes/wm/return-sales-detail/update', data);
+}
+
+/** 鍒犻櫎閿�鍞��璐ф槑缁� */
+export function deleteReturnSalesDetail(id: number) {
+ return requestClient.delete(`/mes/wm/return-sales-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnsales/index.ts b/src/api/wls/returnsales/index.ts
new file mode 100644
index 0000000..6f20fa1
--- /dev/null
+++ b/src/api/wls/returnsales/index.ts
@@ -0,0 +1,78 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnSalesApi {
+ /** MES 閿�鍞��璐у崟 */
+ export interface ReturnSales {
+ id?: number; // 閫�璐у崟缂栧彿
+ code?: string; // 閫�璐у崟缂栧彿
+ name?: string; // 閫�璐у崟鍚嶇О
+ salesOrderCode?: string; // 閿�鍞鍗曞彿
+ clientId?: number; // 瀹㈡埛缂栧彿
+ clientCode?: string; // 瀹㈡埛缂栫爜
+ clientName?: string; // 瀹㈡埛鍚嶇О
+ returnDate?: number; // 閫�璐ф棩鏈�
+ returnReason?: string; // 閫�璐у師鍥�
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ閿�鍞��璐у崟鍒嗛〉 */
+export function getReturnSalesPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsReturnSalesApi.ReturnSales>>(
+ '/mes/wm/return-sales/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ閿�鍞��璐у崟璇︽儏 */
+export function getReturnSales(id: number) {
+ return requestClient.get<WlsReturnSalesApi.ReturnSales>(
+ `/mes/wm/return-sales/get?id=${id}`,
+ );
+}
+
+/** 鏂板閿�鍞��璐у崟 */
+export function createReturnSales(data: WlsReturnSalesApi.ReturnSales) {
+ return requestClient.post<number>('/mes/wm/return-sales/create', data);
+}
+
+/** 淇敼閿�鍞��璐у崟 */
+export function updateReturnSales(data: WlsReturnSalesApi.ReturnSales) {
+ return requestClient.put('/mes/wm/return-sales/update', data);
+}
+
+/** 鍒犻櫎閿�鍞��璐у崟 */
+export function deleteReturnSales(id: number) {
+ return requestClient.delete(`/mes/wm/return-sales/delete?id=${id}`);
+}
+
+/** 鎻愪氦閿�鍞��璐у崟 */
+export function submitReturnSales(id: number) {
+ return requestClient.put(`/mes/wm/return-sales/submit?id=${id}`);
+}
+
+/** 鎵ц閫�璐� */
+export function finishReturnSales(id: number) {
+ return requestClient.put(`/mes/wm/return-sales/finish?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockReturnSales(id: number) {
+ return requestClient.put(`/mes/wm/return-sales/stock?id=${id}`);
+}
+
+/** 鍙栨秷閿�鍞��璐у崟 */
+export function cancelReturnSales(id: number) {
+ return requestClient.put(`/mes/wm/return-sales/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭閿�鍞��璐у崟 */
+export function exportReturnSales(params: any) {
+ return requestClient.download('/mes/wm/return-sales/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/returnsales/line/index.ts b/src/api/wls/returnsales/line/index.ts
new file mode 100644
index 0000000..3952ce5
--- /dev/null
+++ b/src/api/wls/returnsales/line/index.ts
@@ -0,0 +1,57 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnSalesLineApi {
+ /** MES 閿�鍞��璐у崟琛� */
+ export interface ReturnSalesLine {
+ id?: number; // 琛岀紪鍙�
+ returnId?: number; // 閫�璐у崟缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 閫�璐ф暟閲�
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ rqcCheckFlag?: boolean; // 鏄惁闇�瑕佽川妫�
+ rqcId?: number; // 閫�璐ф楠屽崟缂栧彿
+ qualityStatus?: number; // 璐ㄩ噺鐘舵��
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ閿�鍞��璐у崟琛屽垎椤� */
+export function getReturnSalesLinePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsReturnSalesLineApi.ReturnSalesLine>>(
+ '/mes/wm/return-sales-line/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ閿�鍞��璐у崟琛岃鎯� */
+export function getReturnSalesLine(id: number) {
+ return requestClient.get<WlsReturnSalesLineApi.ReturnSalesLine>(
+ `/mes/wm/return-sales-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板閿�鍞��璐у崟琛� */
+export function createReturnSalesLine(
+ data: WlsReturnSalesLineApi.ReturnSalesLine,
+) {
+ return requestClient.post<number>('/mes/wm/return-sales-line/create', data);
+}
+
+/** 淇敼閿�鍞��璐у崟琛� */
+export function updateReturnSalesLine(
+ data: WlsReturnSalesLineApi.ReturnSalesLine,
+) {
+ return requestClient.put('/mes/wm/return-sales-line/update', data);
+}
+
+/** 鍒犻櫎閿�鍞��璐у崟琛� */
+export function deleteReturnSalesLine(id: number) {
+ return requestClient.delete(`/mes/wm/return-sales-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnvendor/detail/index.ts b/src/api/wls/returnvendor/detail/index.ts
new file mode 100644
index 0000000..af62167
--- /dev/null
+++ b/src/api/wls/returnvendor/detail/index.ts
@@ -0,0 +1,59 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnVendorDetailApi {
+ /** MES 渚涘簲鍟嗛��璐ф槑缁� */
+ export interface ReturnVendorDetail {
+ id?: number; // 鏄庣粏缂栧彿
+ returnId?: number; // 閫�璐у崟缂栧彿
+ lineId?: number; // 閫�璐у崟琛岀紪鍙�
+ materialStockId?: number; // 搴撳瓨璁板綍缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ quantity?: number; // 鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐ф槑缁嗗垪琛紙鎸夎缂栧彿锛� */
+export function getReturnVendorDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsReturnVendorDetailApi.ReturnVendorDetail[]>(
+ '/mes/wm/return-vendor-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐ф槑缁嗚鎯� */
+export function getReturnVendorDetail(id: number) {
+ return requestClient.get<WlsReturnVendorDetailApi.ReturnVendorDetail>(
+ `/mes/wm/return-vendor-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板渚涘簲鍟嗛��璐ф槑缁� */
+export function createReturnVendorDetail(
+ data: WlsReturnVendorDetailApi.ReturnVendorDetail,
+) {
+ return requestClient.post<number>(
+ '/mes/wm/return-vendor-detail/create',
+ data,
+ );
+}
+
+/** 淇敼渚涘簲鍟嗛��璐ф槑缁� */
+export function updateReturnVendorDetail(
+ data: WlsReturnVendorDetailApi.ReturnVendorDetail,
+) {
+ return requestClient.put('/mes/wm/return-vendor-detail/update', data);
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐ф槑缁� */
+export function deleteReturnVendorDetail(id: number) {
+ return requestClient.delete(`/mes/wm/return-vendor-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/returnvendor/index.ts b/src/api/wls/returnvendor/index.ts
new file mode 100644
index 0000000..092888f
--- /dev/null
+++ b/src/api/wls/returnvendor/index.ts
@@ -0,0 +1,88 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnVendorApi {
+ /** MES 渚涘簲鍟嗛��璐у崟 */
+ export interface ReturnVendor {
+ id?: number; // 閫�璐у崟缂栧彿
+ code?: string; // 閫�璐у崟缂栧彿
+ name?: string; // 閫�璐у崟鍚嶇О
+ purchaseOrderCode?: string; // 閲囪喘璁㈠崟鍙�
+ vendorId?: number; // 渚涘簲鍟嗙紪鍙�
+ vendorCode?: string; // 渚涘簲鍟嗙紪鐮�
+ vendorName?: string; // 渚涘簲鍟嗗悕绉�
+ vendorNickname?: string; // 渚涘簲鍟嗙畝绉�
+ returnDate?: number; // 閫�璐ф棩鏈�
+ returnReason?: string; // 閫�璐у師鍥�
+ transportCode?: string; // 杩愬崟鍙�
+ transportTelephone?: string; // 鑱旂郴鐢佃瘽
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟鍒嗛〉 */
+export function getReturnVendorPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsReturnVendorApi.ReturnVendor>>(
+ '/mes/wm/return-vendor/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟璇︽儏 */
+export function getReturnVendor(id: number) {
+ return requestClient.get<WlsReturnVendorApi.ReturnVendor>(
+ `/mes/wm/return-vendor/get?id=${id}`,
+ );
+}
+
+/** 鏂板渚涘簲鍟嗛��璐у崟 */
+export function createReturnVendor(data: WlsReturnVendorApi.ReturnVendor) {
+ return requestClient.post<number>('/mes/wm/return-vendor/create', data);
+}
+
+/** 淇敼渚涘簲鍟嗛��璐у崟 */
+export function updateReturnVendor(data: WlsReturnVendorApi.ReturnVendor) {
+ return requestClient.put('/mes/wm/return-vendor/update', data);
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐у崟 */
+export function deleteReturnVendor(id: number) {
+ return requestClient.delete(`/mes/wm/return-vendor/delete?id=${id}`);
+}
+
+/** 鎻愪氦渚涘簲鍟嗛��璐у崟 */
+export function submitReturnVendor(id: number) {
+ return requestClient.put(`/mes/wm/return-vendor/submit?id=${id}`);
+}
+
+/** 鎵ц鎷h揣 */
+export function stockReturnVendor(id: number) {
+ return requestClient.put(`/mes/wm/return-vendor/stock?id=${id}`);
+}
+
+/** 瀹屾垚渚涘簲鍟嗛��璐у崟 */
+export function finishReturnVendor(id: number) {
+ return requestClient.put(`/mes/wm/return-vendor/finish?id=${id}`);
+}
+
+/** 鍙栨秷渚涘簲鍟嗛��璐у崟 */
+export function cancelReturnVendor(id: number) {
+ return requestClient.put(`/mes/wm/return-vendor/cancel?id=${id}`);
+}
+
+/** 鏍¢獙渚涘簲鍟嗛��璐у崟鎷h揣鏁伴噺鏄惁涓庨��璐ф暟閲忎竴鑷� */
+export function checkReturnVendorQuantity(id: number) {
+ return requestClient.get<boolean>(
+ `/mes/wm/return-vendor/check-quantity?id=${id}`,
+ );
+}
+
+/** 瀵煎嚭渚涘簲鍟嗛��璐у崟 */
+export function exportReturnVendor(params: any) {
+ return requestClient.download('/mes/wm/return-vendor/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/returnvendor/line/index.ts b/src/api/wls/returnvendor/line/index.ts
new file mode 100644
index 0000000..c194fb7
--- /dev/null
+++ b/src/api/wls/returnvendor/line/index.ts
@@ -0,0 +1,53 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsReturnVendorLineApi {
+ /** MES 渚涘簲鍟嗛��璐у崟琛� */
+ export interface ReturnVendorLine {
+ id?: number; // 琛岀紪鍙�
+ returnId?: number; // 閫�璐у崟缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ quantity?: number; // 閫�璐ф暟閲�
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟琛屽垎椤� */
+export function getReturnVendorLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsReturnVendorLineApi.ReturnVendorLine>
+ >('/mes/wm/return-vendor-line/page', { params });
+}
+
+/** 鏌ヨ渚涘簲鍟嗛��璐у崟琛岃鎯� */
+export function getReturnVendorLine(id: number) {
+ return requestClient.get<WlsReturnVendorLineApi.ReturnVendorLine>(
+ `/mes/wm/return-vendor-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板渚涘簲鍟嗛��璐у崟琛� */
+export function createReturnVendorLine(
+ data: WlsReturnVendorLineApi.ReturnVendorLine,
+) {
+ return requestClient.post<number>('/mes/wm/return-vendor-line/create', data);
+}
+
+/** 淇敼渚涘簲鍟嗛��璐у崟琛� */
+export function updateReturnVendorLine(
+ data: WlsReturnVendorLineApi.ReturnVendorLine,
+) {
+ return requestClient.put('/mes/wm/return-vendor-line/update', data);
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐у崟琛� */
+export function deleteReturnVendorLine(id: number) {
+ return requestClient.delete(`/mes/wm/return-vendor-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/salesnotice/index.ts b/src/api/wls/salesnotice/index.ts
new file mode 100644
index 0000000..fc1222d
--- /dev/null
+++ b/src/api/wls/salesnotice/index.ts
@@ -0,0 +1,65 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsSalesNoticeApi {
+ /** MES 鍙戣揣閫氱煡鍗� */
+ export interface SalesNotice {
+ id?: number; // 閫氱煡鍗曠紪鍙�
+ code?: string; // 閫氱煡鍗曠紪鍙�
+ name?: string; // 閫氱煡鍗曞悕绉�
+ salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
+ clientId?: number; // 瀹㈡埛缂栧彿
+ clientCode?: string; // 瀹㈡埛缂栫爜
+ clientName?: string; // 瀹㈡埛鍚嶇О
+ salesDate?: number; // 鍙戣揣鏃ユ湡
+ recipientName?: string; // 鏀惰揣浜�
+ recipientTelephone?: string; // 鑱旂郴鏂瑰紡
+ recipientAddress?: string; // 鏀惰揣鍦板潃
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曞垎椤� */
+export function getSalesNoticePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsSalesNoticeApi.SalesNotice>>(
+ '/mes/wm/sales-notice/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曡鎯� */
+export function getSalesNotice(id: number) {
+ return requestClient.get<WlsSalesNoticeApi.SalesNotice>(
+ `/mes/wm/sales-notice/get?id=${id}`,
+ );
+}
+
+/** 鏂板鍙戣揣閫氱煡鍗� */
+export function createSalesNotice(data: WlsSalesNoticeApi.SalesNotice) {
+ return requestClient.post<number>('/mes/wm/sales-notice/create', data);
+}
+
+/** 淇敼鍙戣揣閫氱煡鍗� */
+export function updateSalesNotice(data: WlsSalesNoticeApi.SalesNotice) {
+ return requestClient.put('/mes/wm/sales-notice/update', data);
+}
+
+/** 鍒犻櫎鍙戣揣閫氱煡鍗� */
+export function deleteSalesNotice(id: number) {
+ return requestClient.delete(`/mes/wm/sales-notice/delete?id=${id}`);
+}
+
+/** 鎻愪氦鍙戣揣閫氱煡鍗� */
+export function submitSalesNotice(id: number) {
+ return requestClient.put(`/mes/wm/sales-notice/submit?id=${id}`);
+}
+
+/** 瀵煎嚭鍙戣揣閫氱煡鍗� */
+export function exportSalesNotice(params: any) {
+ return requestClient.download('/mes/wm/sales-notice/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/salesnotice/line/index.ts b/src/api/wls/salesnotice/line/index.ts
new file mode 100644
index 0000000..1a61ea4
--- /dev/null
+++ b/src/api/wls/salesnotice/line/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsSalesNoticeLineApi {
+ /** MES 鍙戣揣閫氱煡鍗曡 */
+ export interface SalesNoticeLine {
+ id?: number; // 琛岀紪鍙�
+ noticeId?: number; // 鍙戣揣閫氱煡鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ quantity?: number; // 鍙戣揣鏁伴噺
+ oqcCheckFlag?: boolean; // 鏄惁妫�楠�
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曡鍒嗛〉 */
+export function getSalesNoticeLinePage(params: PageParam) {
+ return requestClient.get<PageResult<WlsSalesNoticeLineApi.SalesNoticeLine>>(
+ '/mes/wm/sales-notice-line/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鍙戣揣閫氱煡鍗曡璇︽儏 */
+export function getSalesNoticeLine(id: number) {
+ return requestClient.get<WlsSalesNoticeLineApi.SalesNoticeLine>(
+ `/mes/wm/sales-notice-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板鍙戣揣閫氱煡鍗曡 */
+export function createSalesNoticeLine(
+ data: WlsSalesNoticeLineApi.SalesNoticeLine,
+) {
+ return requestClient.post<number>('/mes/wm/sales-notice-line/create', data);
+}
+
+/** 淇敼鍙戣揣閫氱煡鍗曡 */
+export function updateSalesNoticeLine(
+ data: WlsSalesNoticeLineApi.SalesNoticeLine,
+) {
+ return requestClient.put('/mes/wm/sales-notice-line/update', data);
+}
+
+/** 鍒犻櫎鍙戣揣閫氱煡鍗曡 */
+export function deleteSalesNoticeLine(id: number) {
+ return requestClient.delete(`/mes/wm/sales-notice-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/sn/index.ts b/src/api/wls/sn/index.ts
new file mode 100644
index 0000000..63b04df
--- /dev/null
+++ b/src/api/wls/sn/index.ts
@@ -0,0 +1,90 @@
+锘縤mport type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsSnApi {
+ /** MES SN 鐮佸垎缁� */
+ export interface SnGroup {
+ uuid?: string; // 鎵规 UUID
+ count?: number; // SN 鐮佹暟閲�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitName?: string; // 鍗曚綅鍚嶇О
+ batchCode?: string; // 鎵规鍙�
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ createTime?: Date; // 鐢熸垚鏃堕棿
+ }
+
+ /** MES SN 鐮佹槑缁� */
+ export interface Sn {
+ id?: number; // 缂栧彿
+ uuid?: string; // 鎵规 UUID
+ code?: string; // SN 鐮�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitName?: string; // 鍗曚綅鍚嶇О
+ batchCode?: string; // 鎵规鍙�
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ createTime?: Date; // 鐢熸垚鏃堕棿
+ }
+
+ /** MES SN 鐮佺敓鎴愬弬鏁� */
+ export interface SnGenerate {
+ itemId?: number; // 鐗╂枡缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ count?: number; // 鐢熸垚鏁伴噺
+ }
+
+ /** MES SN 鐮佸垎缁勫垎椤垫煡璇㈠弬鏁� */
+ export interface PageParams extends PageParam {
+ uuid?: string; // 鍒嗙粍 UUID
+ code?: string; // SN 鐮�
+ itemId?: number; // 鐗╂枡缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ createTime?: string[]; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鐢熸垚 SN 鐮� */
+export function generateSnCodes(data: WlsSnApi.SnGenerate) {
+ return requestClient.post('/mes/wm/sn/generate', data);
+}
+
+/** 鏌ヨ SN 鐮佸垎缁勫垎椤� */
+export function getSnGroupPage(params: WlsSnApi.PageParams) {
+ return requestClient.get<PageResult<WlsSnApi.SnGroup>>(
+ '/mes/wm/sn/group-page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鎵规 SN 鐮佹槑缁嗗垪琛� */
+export function getSnListByUuid(uuid: string) {
+ return requestClient.get<WlsSnApi.Sn[]>('/mes/wm/sn/list-by-uuid', {
+ params: { uuid },
+ });
+}
+
+/** 鎵归噺鍒犻櫎 SN 鐮侊紙鎸夋壒娆� UUID锛� */
+export function deleteSnBatch(uuid: string) {
+ return requestClient.delete('/mes/wm/sn/delete-batch', {
+ params: { uuid },
+ });
+}
+
+/** 瀵煎嚭 SN 鐮佸垎缁� Excel */
+export function exportSnGroupExcel(params: WlsSnApi.PageParams) {
+ return requestClient.download('/mes/wm/sn/group-export-excel', { params });
+}
+
+/** 瀵煎嚭鎵规 SN 鐮佹槑缁� Excel */
+export function exportSnDetailExcel(uuid: string) {
+ return requestClient.download('/mes/wm/sn/export-excel', {
+ params: { uuid },
+ });
+}
diff --git a/src/api/wls/stocktaking/plan/index.ts b/src/api/wls/stocktaking/plan/index.ts
new file mode 100644
index 0000000..905db8f
--- /dev/null
+++ b/src/api/wls/stocktaking/plan/index.ts
@@ -0,0 +1,68 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingPlanApi {
+ /** 鐩樼偣鏂规 */
+ export interface StockTakingPlan {
+ id?: number; // 鏂规缂栧彿
+ code?: string; // 鏂规缂栫爜
+ name?: string; // 鏂规鍚嶇О
+ type?: number; // 鐩樼偣绫诲瀷
+ startTime?: number; // 寮�濮嬫椂闂�
+ endTime?: number; // 缁撴潫鏃堕棿
+ blindFlag?: boolean; // 鏄惁鐩茬洏
+ frozen?: boolean; // 鏄惁鍐荤粨搴撳瓨
+ status?: number; // 鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: string; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鐩樼偣鏂规鍒嗛〉 */
+export function getStockTakingPlanPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsStockTakingPlanApi.StockTakingPlan>>(
+ '/mes/wm/stocktaking-plan/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鐩樼偣鏂规璇︽儏 */
+export function getStockTakingPlan(id: number) {
+ return requestClient.get<WlsStockTakingPlanApi.StockTakingPlan>(
+ `/mes/wm/stocktaking-plan/get?id=${id}`,
+ );
+}
+
+/** 鏂板鐩樼偣鏂规 */
+export function createStockTakingPlan(
+ data: WlsStockTakingPlanApi.StockTakingPlan,
+) {
+ return requestClient.post<number>('/mes/wm/stocktaking-plan/create', data);
+}
+
+/** 淇敼鐩樼偣鏂规 */
+export function updateStockTakingPlan(
+ data: WlsStockTakingPlanApi.StockTakingPlan,
+) {
+ return requestClient.put('/mes/wm/stocktaking-plan/update', data);
+}
+
+/** 淇敼鐩樼偣鏂规鐘舵�� */
+export function updateStockTakingPlanStatus(id: number, status: number) {
+ return requestClient.put('/mes/wm/stocktaking-plan/update-status', null, {
+ params: { id, status },
+ });
+}
+
+/** 鍒犻櫎鐩樼偣鏂规 */
+export function deleteStockTakingPlan(id: number) {
+ return requestClient.delete(`/mes/wm/stocktaking-plan/delete?id=${id}`);
+}
+
+/** 瀵煎嚭鐩樼偣鏂规 */
+export function exportStockTakingPlan(params: any) {
+ return requestClient.download('/mes/wm/stocktaking-plan/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/stocktaking/plan/param/index.ts b/src/api/wls/stocktaking/plan/param/index.ts
new file mode 100644
index 0000000..7591b01
--- /dev/null
+++ b/src/api/wls/stocktaking/plan/param/index.ts
@@ -0,0 +1,49 @@
+import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingPlanParamApi {
+ /** 鐩樼偣鏂规鏉′欢 */
+ export interface StockTakingPlanParam {
+ id?: number; // 鏉′欢缂栧彿
+ planId?: number; // 鏂规缂栧彿
+ type?: number; // 鏉′欢绫诲瀷
+ valueId?: number; // 鏉′欢鍊肩紪鍙�
+ valueCode?: string; // 鏉′欢鍊肩紪鐮�
+ valueName?: string; // 鏉′欢鍊煎悕绉�
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ鐩樼偣鏂规鏉′欢鍒嗛〉 */
+export function getStockTakingPlanParamPage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsStockTakingPlanParamApi.StockTakingPlanParam>
+ >('/mes/wm/stocktaking-plan-param/page', { params });
+}
+
+/** 鏌ヨ鐩樼偣鏂规鏉′欢璇︽儏 */
+export function getStockTakingPlanParam(id: number) {
+ return requestClient.get<WlsStockTakingPlanParamApi.StockTakingPlanParam>(
+ `/mes/wm/stocktaking-plan-param/get?id=${id}`,
+ );
+}
+
+/** 鏂板鐩樼偣鏂规鏉′欢 */
+export function createStockTakingPlanParam(
+ data: WlsStockTakingPlanParamApi.StockTakingPlanParam,
+) {
+ return requestClient.post('/mes/wm/stocktaking-plan-param/create', data);
+}
+
+/** 淇敼鐩樼偣鏂规鏉′欢 */
+export function updateStockTakingPlanParam(
+ data: WlsStockTakingPlanParamApi.StockTakingPlanParam,
+) {
+ return requestClient.put('/mes/wm/stocktaking-plan-param/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣鏂规鏉′欢 */
+export function deleteStockTakingPlanParam(id: number) {
+ return requestClient.delete(`/mes/wm/stocktaking-plan-param/delete?id=${id}`);
+}
diff --git a/src/api/wls/stocktaking/task/index.ts b/src/api/wls/stocktaking/task/index.ts
new file mode 100644
index 0000000..360fc68
--- /dev/null
+++ b/src/api/wls/stocktaking/task/index.ts
@@ -0,0 +1,86 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingTaskApi {
+ /** 鐩樼偣浠诲姟 */
+ export interface StockTakingTask {
+ id?: number; // 浠诲姟缂栧彿
+ code?: string; // 浠诲姟缂栫爜
+ name?: string; // 浠诲姟鍚嶇О
+ takingDate?: string; // 鐩樼偣鏃ユ湡
+ type?: number; // 鐩樼偣绫诲瀷
+ userId?: number; // 鐩樼偣浜虹敤鎴风紪鍙�
+ userNickname?: string; // 鐩樼偣浜哄悕绉�
+ planId?: number; // 鐩樼偣鏂规缂栧彿
+ planCode?: string; // 鐩樼偣鏂规缂栫爜
+ planName?: string; // 鐩樼偣鏂规鍚嶇О
+ blindFlag?: boolean; // 鏄惁鐩茬洏
+ frozen?: boolean; // 鏄惁鍐荤粨搴撳瓨
+ startTime?: number; // 寮�濮嬫椂闂�
+ endTime?: number; // 缁撴潫鏃堕棿
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: string; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟鍒嗛〉 */
+export function getStockTakingPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsStockTakingTaskApi.StockTakingTask>>(
+ '/mes/wm/stocktaking-task/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟璇︽儏 */
+export function getStockTaking(id: number) {
+ return requestClient.get<WlsStockTakingTaskApi.StockTakingTask>(
+ `/mes/wm/stocktaking-task/get?id=${id}`,
+ );
+}
+
+/** 鏂板鐩樼偣浠诲姟 */
+export function createStockTaking(
+ data: WlsStockTakingTaskApi.StockTakingTask,
+) {
+ return requestClient.post<number>('/mes/wm/stocktaking-task/create', data);
+}
+
+/** 淇敼鐩樼偣浠诲姟 */
+export function updateStockTaking(
+ data: WlsStockTakingTaskApi.StockTakingTask,
+) {
+ return requestClient.put('/mes/wm/stocktaking-task/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣浠诲姟 */
+export function deleteStockTaking(id: number) {
+ return requestClient.delete(`/mes/wm/stocktaking-task/delete?id=${id}`);
+}
+
+/** 鎻愪氦鐩樼偣浠诲姟 */
+export function submitStockTaking(id: number) {
+ return requestClient.put('/mes/wm/stocktaking-task/submit', null, {
+ params: { id },
+ });
+}
+
+/** 鍙栨秷鐩樼偣浠诲姟 */
+export function cancelStockTaking(id: number) {
+ return requestClient.put('/mes/wm/stocktaking-task/cancel', null, {
+ params: { id },
+ });
+}
+
+/** 鎵ц鐩樼偣浠诲姟 */
+export function finishStockTaking(id: number) {
+ return requestClient.put('/mes/wm/stocktaking-task/finish', { id });
+}
+
+/** 瀵煎嚭鐩樼偣浠诲姟 */
+export function exportStockTaking(params: any) {
+ return requestClient.download('/mes/wm/stocktaking-task/export-excel', {
+ params,
+ });
+}
diff --git a/src/api/wls/stocktaking/task/line/index.ts b/src/api/wls/stocktaking/task/line/index.ts
new file mode 100644
index 0000000..2ed3642
--- /dev/null
+++ b/src/api/wls/stocktaking/task/line/index.ts
@@ -0,0 +1,72 @@
+import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingTaskLineApi {
+ /** 鐩樼偣浠诲姟琛� */
+ export interface StockTakingTaskLine {
+ id?: number; // 鐩樼偣琛岀紪鍙�
+ taskId?: number; // 浠诲姟缂栧彿
+ materialStockId?: number; // 搴撳瓨缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ quantity?: number; // 鍦ㄥ簱鏁伴噺
+ takingQuantity?: number; // 鐩樼偣鏁伴噺
+ differenceQuantity?: number; // 宸紓鏁伴噺
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ status?: number; // 鐘舵��
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟琛屽垎椤� */
+export function getStockTakingTaskLinePage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsStockTakingTaskLineApi.StockTakingTaskLine>
+ >('/mes/wm/stocktaking-task-line/page', { params });
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟琛岀簿绠�鍒楄〃 */
+export function getStockTakingTaskLineSimpleList(taskId: number) {
+ return requestClient.get<WlsStockTakingTaskLineApi.StockTakingTaskLine[]>(
+ '/mes/wm/stocktaking-task-line/simple-list',
+ { params: { taskId } },
+ );
+}
+
+/** 鏌ヨ鐩樼偣浠诲姟琛岃鎯� */
+export function getStockTakingTaskLine(id: number) {
+ return requestClient.get<WlsStockTakingTaskLineApi.StockTakingTaskLine>(
+ '/mes/wm/stocktaking-task-line/get',
+ { params: { id } },
+ );
+}
+
+/** 鏂板鐩樼偣浠诲姟琛� */
+export function createStockTakingTaskLine(
+ data: WlsStockTakingTaskLineApi.StockTakingTaskLine,
+) {
+ return requestClient.post('/mes/wm/stocktaking-task-line/create', data);
+}
+
+/** 淇敼鐩樼偣浠诲姟琛� */
+export function updateStockTakingTaskLine(
+ data: WlsStockTakingTaskLineApi.StockTakingTaskLine,
+) {
+ return requestClient.put('/mes/wm/stocktaking-task-line/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣浠诲姟琛� */
+export function deleteStockTakingTaskLine(id: number) {
+ return requestClient.delete(`/mes/wm/stocktaking-task-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/stocktaking/task/result/index.ts b/src/api/wls/stocktaking/task/result/index.ts
new file mode 100644
index 0000000..4e7715f
--- /dev/null
+++ b/src/api/wls/stocktaking/task/result/index.ts
@@ -0,0 +1,66 @@
+import type { PageParam, PageResult } from '../../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsStockTakingResultApi {
+ /** 鐩樼偣缁撴灉 */
+ export interface StockTakingResult {
+ id?: number; // 缁撴灉缂栧彿
+ taskId?: number; // 浠诲姟缂栧彿
+ lineId?: number; // 鐩樼偣琛岀紪鍙�
+ materialStockId?: number; // 搴撳瓨缂栧彿
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ areaId?: number; // 搴撲綅缂栧彿
+ areaName?: string; // 搴撲綅鍚嶇О
+ quantity?: number; // 鍦ㄥ簱鏁伴噺
+ takingQuantity?: number; // 鐩樼偣鏁伴噺
+ remark?: string; // 澶囨敞
+ createTime?: string; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ鐩樼偣缁撴灉鍒嗛〉 */
+export function getStockTakingResultPage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsStockTakingResultApi.StockTakingResult>
+ >('/mes/wm/stocktaking-task-result/page', { params });
+}
+
+/** 鏌ヨ鐩樼偣缁撴灉璇︽儏 */
+export function getStockTakingResult(id: number) {
+ return requestClient.get<WlsStockTakingResultApi.StockTakingResult>(
+ '/mes/wm/stocktaking-task-result/get',
+ { params: { id } },
+ );
+}
+
+/** 鏂板鐩樼偣缁撴灉 */
+export function createStockTakingResult(
+ data: WlsStockTakingResultApi.StockTakingResult,
+) {
+ return requestClient.post('/mes/wm/stocktaking-task-result/create', data);
+}
+
+/** 淇敼鐩樼偣缁撴灉 */
+export function updateStockTakingResult(
+ data: WlsStockTakingResultApi.StockTakingResult,
+) {
+ return requestClient.put('/mes/wm/stocktaking-task-result/update', data);
+}
+
+/** 鍒犻櫎鐩樼偣缁撴灉 */
+export function deleteStockTakingResult(id: number) {
+ return requestClient.delete(
+ `/mes/wm/stocktaking-task-result/delete?id=${id}`,
+ );
+}
diff --git a/src/api/wls/transfer/detail/index.ts b/src/api/wls/transfer/detail/index.ts
new file mode 100644
index 0000000..90de160
--- /dev/null
+++ b/src/api/wls/transfer/detail/index.ts
@@ -0,0 +1,59 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsTransferDetailApi {
+ /** MES 璋冩嫧鏄庣粏 */
+ export interface TransferDetail {
+ id?: number; // 缂栧彿
+ lineId?: number; // 杞Щ鍗曡缂栧彿
+ transferId?: number; // 杞Щ鍗曠紪鍙�
+ itemId?: number; // 鐗╂枡浜у搧缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅鍚嶇О
+ quantity?: number; // 鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ toWarehouseId?: number; // 绉诲叆浠撳簱缂栧彿
+ toWarehouseName?: string; // 绉诲叆浠撳簱鍚嶇О
+ toLocationId?: number; // 绉诲叆搴撳尯缂栧彿
+ toLocationName?: string; // 绉诲叆搴撳尯鍚嶇О
+ toAreaId?: number; // 绉诲叆搴撲綅缂栧彿
+ toAreaName?: string; // 绉诲叆搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ璋冩嫧鏄庣粏鍒楄〃锛堟寜琛岀紪鍙凤級 */
+export function getTransferDetailListByLineId(lineId: number) {
+ return requestClient.get<WlsTransferDetailApi.TransferDetail[]>(
+ '/mes/wm/transfer-detail/list-by-line',
+ { params: { lineId } },
+ );
+}
+
+/** 鏌ヨ璋冩嫧鏄庣粏璇︽儏 */
+export function getTransferDetail(id: number) {
+ return requestClient.get<WlsTransferDetailApi.TransferDetail>(
+ `/mes/wm/transfer-detail/get?id=${id}`,
+ );
+}
+
+/** 鏂板璋冩嫧鏄庣粏 */
+export function createTransferDetail(
+ data: WlsTransferDetailApi.TransferDetail,
+) {
+ return requestClient.post('/mes/wm/transfer-detail/create', data);
+}
+
+/** 淇敼璋冩嫧鏄庣粏 */
+export function updateTransferDetail(
+ data: WlsTransferDetailApi.TransferDetail,
+) {
+ return requestClient.put('/mes/wm/transfer-detail/update', data);
+}
+
+/** 鍒犻櫎璋冩嫧鏄庣粏 */
+export function deleteTransferDetail(id: number) {
+ return requestClient.delete(`/mes/wm/transfer-detail/delete?id=${id}`);
+}
diff --git a/src/api/wls/transfer/index.ts b/src/api/wls/transfer/index.ts
new file mode 100644
index 0000000..f4b5d8d
--- /dev/null
+++ b/src/api/wls/transfer/index.ts
@@ -0,0 +1,92 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsTransferApi {
+ /** MES 杞Щ鍗� */
+ export interface Transfer {
+ id?: number; // 缂栧彿
+ code?: string; // 杞Щ鍗曠紪鍙�
+ name?: string; // 杞Щ鍗曞悕绉�
+ type?: number; // 杞Щ鍗曠被鍨�
+ deliveryFlag?: boolean; // 鏄惁閰嶉��
+ recipientName?: string; // 鏀惰揣浜�
+ recipientTelephone?: string; // 鑱旂郴鐢佃瘽
+ destinationAddress?: string; // 鐩殑鍦�
+ carrier?: string; // 鎵胯繍鍟�
+ shippingNumber?: string; // 杩愯緭鍗曞彿
+ confirmFlag?: boolean; // 鏄惁纭
+ transferDate?: string; // 杞Щ鏃ユ湡
+ status?: number; // 鍗曟嵁鐘舵��
+ remark?: string; // 澶囨敞
+ createTime?: number; // 鍒涘缓鏃堕棿
+ }
+
+ /** MES 杞Щ鍗曞垎椤垫煡璇㈠弬鏁� */
+ export interface PageParams extends PageParam {
+ code?: string;
+ name?: string;
+ type?: number;
+ status?: number;
+ }
+}
+
+/** 鏌ヨ杞Щ鍗曞垎椤� */
+export function getTransferPage(params: WlsTransferApi.PageParams) {
+ return requestClient.get<PageResult<WlsTransferApi.Transfer>>(
+ '/mes/wm/transfer/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ杞Щ鍗曡鎯� */
+export function getTransfer(id: number) {
+ return requestClient.get<WlsTransferApi.Transfer>(
+ `/mes/wm/transfer/get?id=${id}`,
+ );
+}
+
+/** 鏂板杞Щ鍗� */
+export function createTransfer(data: WlsTransferApi.Transfer) {
+ return requestClient.post<number>('/mes/wm/transfer/create', data);
+}
+
+/** 淇敼杞Щ鍗� */
+export function updateTransfer(data: WlsTransferApi.Transfer) {
+ return requestClient.put('/mes/wm/transfer/update', data);
+}
+
+/** 鍒犻櫎杞Щ鍗� */
+export function deleteTransfer(id: number) {
+ return requestClient.delete(`/mes/wm/transfer/delete?id=${id}`);
+}
+
+/** 鎻愪氦杞Щ鍗� */
+export function submitTransfer(id: number) {
+ return requestClient.put(`/mes/wm/transfer/submit?id=${id}`);
+}
+
+/** 鍒拌揣纭 */
+export function confirmTransfer(id: number) {
+ return requestClient.put(`/mes/wm/transfer/confirm?id=${id}`);
+}
+
+/** 鎵ц涓婃灦 */
+export function stockTransfer(id: number) {
+ return requestClient.put(`/mes/wm/transfer/stock?id=${id}`);
+}
+
+/** 瀹屾垚杞Щ */
+export function finishTransfer(id: number) {
+ return requestClient.put(`/mes/wm/transfer/finish?id=${id}`);
+}
+
+/** 鍙栨秷杞Щ鍗� */
+export function cancelTransfer(id: number) {
+ return requestClient.put(`/mes/wm/transfer/cancel?id=${id}`);
+}
+
+/** 瀵煎嚭杞Щ鍗� */
+export function exportTransfer(params: any) {
+ return requestClient.download('/mes/wm/transfer/export-excel', { params });
+}
diff --git a/src/api/wls/transfer/line/index.ts b/src/api/wls/transfer/line/index.ts
new file mode 100644
index 0000000..07881cd
--- /dev/null
+++ b/src/api/wls/transfer/line/index.ts
@@ -0,0 +1,55 @@
+import { requestClient } from '#/api/request';
+
+export namespace WlsTransferLineApi {
+ /** MES 杞Щ鍗曡 */
+ export interface TransferLine {
+ id?: number; // 缂栧彿
+ transferId?: number; // 杞Щ鍗曠紪鍙�
+ materialStockId?: number; // 搴撳瓨鍙拌处缂栧彿
+ itemId?: number; // 鐗╂枡浜у搧缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 鍗曚綅鍚嶇О
+ quantity?: number; // 杞Щ鏁伴噺
+ batchId?: number; // 鎵规缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ fromWarehouseId?: number; // 绉诲嚭浠撳簱缂栧彿
+ fromWarehouseName?: string; // 绉诲嚭浠撳簱鍚嶇О
+ fromLocationId?: number; // 绉诲嚭搴撳尯缂栧彿
+ fromLocationName?: string; // 绉诲嚭搴撳尯鍚嶇О
+ fromAreaId?: number; // 绉诲嚭搴撲綅缂栧彿
+ fromAreaName?: string; // 绉诲嚭搴撲綅鍚嶇О
+ remark?: string; // 澶囨敞
+ }
+}
+
+/** 鏌ヨ杞Щ鍗曡鍒楄〃 */
+export function getTransferLineList(transferId: number) {
+ return requestClient.get<WlsTransferLineApi.TransferLine[]>(
+ '/mes/wm/transfer-line/list',
+ { params: { transferId } },
+ );
+}
+
+/** 鏌ヨ杞Щ鍗曡璇︽儏 */
+export function getTransferLine(id: number) {
+ return requestClient.get<WlsTransferLineApi.TransferLine>(
+ `/mes/wm/transfer-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板杞Щ鍗曡 */
+export function createTransferLine(data: WlsTransferLineApi.TransferLine) {
+ return requestClient.post('/mes/wm/transfer-line/create', data);
+}
+
+/** 淇敼杞Щ鍗曡 */
+export function updateTransferLine(data: WlsTransferLineApi.TransferLine) {
+ return requestClient.put('/mes/wm/transfer-line/update', data);
+}
+
+/** 鍒犻櫎杞Щ鍗曡 */
+export function deleteTransferLine(id: number) {
+ return requestClient.delete(`/mes/wm/transfer-line/delete?id=${id}`);
+}
diff --git a/src/api/wls/warehouse/area/index.ts b/src/api/wls/warehouse/area/index.ts
new file mode 100644
index 0000000..7135c6f
--- /dev/null
+++ b/src/api/wls/warehouse/area/index.ts
@@ -0,0 +1,65 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsWarehouseAreaApi {
+ /** MES 搴撲綅 */
+ export interface WarehouseArea {
+ id?: number; // 搴撲綅缂栧彿
+ code?: string; // 搴撲綅缂栫爜
+ name?: string; // 搴撲綅鍚嶇О
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ locationId?: number; // 搴撳尯缂栧彿
+ locationName?: string; // 搴撳尯鍚嶇О
+ area?: number; // 闈㈢Н
+ maxLoad?: number; // 鏈�澶ц浇鑽�
+ positionX?: number; // X 鍧愭爣
+ positionY?: number; // Y 鍧愭爣
+ positionZ?: number; // Z 鍧愭爣
+ status?: number; // 鐘舵��
+ frozen?: boolean; // 鏄惁鍐荤粨
+ allowItemMixing?: boolean; // 鏄惁鍏佽鐗╂枡娣锋斁
+ allowBatchMixing?: boolean; // 鏄惁鍏佽鎵规娣锋斁
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ搴撲綅鍒嗛〉 */
+export function getWarehouseAreaPage(params: PageParam) {
+ return requestClient.get<PageResult<WlsWarehouseAreaApi.WarehouseArea>>(
+ '/mes/wm/warehouse-area/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ搴撲綅绮剧畝鍒楄〃 */
+export function getWarehouseAreaSimpleList(locationId?: number) {
+ return requestClient.get<WlsWarehouseAreaApi.WarehouseArea[]>(
+ '/mes/wm/warehouse-area/simple-list',
+ { params: { locationId } },
+ );
+}
+
+/** 鏌ヨ搴撲綅璇︽儏 */
+export function getWarehouseArea(id: number) {
+ return requestClient.get<WlsWarehouseAreaApi.WarehouseArea>(
+ `/mes/wm/warehouse-area/get?id=${id}`,
+ );
+}
+
+/** 鏂板搴撲綅 */
+export function createWarehouseArea(data: WlsWarehouseAreaApi.WarehouseArea) {
+ return requestClient.post('/mes/wm/warehouse-area/create', data);
+}
+
+/** 淇敼搴撲綅 */
+export function updateWarehouseArea(data: WlsWarehouseAreaApi.WarehouseArea) {
+ return requestClient.put('/mes/wm/warehouse-area/update', data);
+}
+
+/** 鍒犻櫎搴撲綅 */
+export function deleteWarehouseArea(id: number) {
+ return requestClient.delete(`/mes/wm/warehouse-area/delete?id=${id}`);
+}
diff --git a/src/api/wls/warehouse/index.ts b/src/api/wls/warehouse/index.ts
new file mode 100644
index 0000000..c31419d
--- /dev/null
+++ b/src/api/wls/warehouse/index.ts
@@ -0,0 +1,55 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesWmWarehouseApi {
+ /** MES 浠撳簱 */
+ export interface Warehouse {
+ id?: number; // 浠撳簱缂栧彿
+ code?: string; // 浠撳簱缂栫爜
+ name?: string; // 浠撳簱鍚嶇О
+ address?: string; // 鍦板潃
+ area?: number; // 闈㈢Н
+ chargeUserId?: number; // 璐熻矗浜�
+ frozen?: boolean; // 鏄惁鍐荤粨
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ浠撳簱鍒嗛〉 */
+export function getWarehousePage(params: PageParam) {
+ return requestClient.get<PageResult<MesWmWarehouseApi.Warehouse>>(
+ '/mes/wm/warehouse/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ浠撳簱绮剧畝鍒楄〃 */
+export function getWarehouseSimpleList() {
+ return requestClient.get<MesWmWarehouseApi.Warehouse[]>(
+ '/mes/wm/warehouse/simple-list',
+ );
+}
+
+/** 鏌ヨ浠撳簱璇︽儏 */
+export function getWarehouse(id: number) {
+ return requestClient.get<MesWmWarehouseApi.Warehouse>(
+ `/mes/wm/warehouse/get?id=${id}`,
+ );
+}
+
+/** 鏂板浠撳簱 */
+export function createWarehouse(data: MesWmWarehouseApi.Warehouse) {
+ return requestClient.post('/mes/wm/warehouse/create', data);
+}
+
+/** 淇敼浠撳簱 */
+export function updateWarehouse(data: MesWmWarehouseApi.Warehouse) {
+ return requestClient.put('/mes/wm/warehouse/update', data);
+}
+
+/** 鍒犻櫎浠撳簱 */
+export function deleteWarehouse(id: number) {
+ return requestClient.delete(`/mes/wm/warehouse/delete?id=${id}`);
+}
diff --git a/src/api/wls/warehouse/location/index.ts b/src/api/wls/warehouse/location/index.ts
new file mode 100644
index 0000000..486113c
--- /dev/null
+++ b/src/api/wls/warehouse/location/index.ts
@@ -0,0 +1,72 @@
+import type { PageParam, PageResult } from '../../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WlsWarehouseLocationApi {
+ /** MES 搴撳尯 */
+ export interface WarehouseLocation {
+ id?: number; // 搴撳尯缂栧彿
+ code?: string; // 搴撳尯缂栫爜
+ name?: string; // 搴撳尯鍚嶇О
+ warehouseId?: number; // 浠撳簱缂栧彿
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ area?: number; // 闈㈢Н
+ frozen?: boolean; // 鏄惁鍐荤粨
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ搴撳尯鍒嗛〉 */
+export function getWarehouseLocationPage(params: PageParam) {
+ return requestClient.get<
+ PageResult<WlsWarehouseLocationApi.WarehouseLocation>
+ >('/mes/wm/warehouse-location/page', { params });
+}
+
+/** 鏌ヨ搴撳尯绮剧畝鍒楄〃 */
+export function getWarehouseLocationSimpleList(warehouseId?: number) {
+ return requestClient.get<WlsWarehouseLocationApi.WarehouseLocation[]>(
+ '/mes/wm/warehouse-location/simple-list',
+ { params: { warehouseId } },
+ );
+}
+
+/** 鏌ヨ搴撳尯璇︽儏 */
+export function getWarehouseLocation(id: number) {
+ return requestClient.get<WlsWarehouseLocationApi.WarehouseLocation>(
+ `/mes/wm/warehouse-location/get?id=${id}`,
+ );
+}
+
+/** 鏂板搴撳尯 */
+export function createWarehouseLocation(
+ data: WlsWarehouseLocationApi.WarehouseLocation,
+) {
+ return requestClient.post('/mes/wm/warehouse-location/create', data);
+}
+
+/** 淇敼搴撳尯 */
+export function updateWarehouseLocation(
+ data: WlsWarehouseLocationApi.WarehouseLocation,
+) {
+ return requestClient.put('/mes/wm/warehouse-location/update', data);
+}
+
+/** 鍒犻櫎搴撳尯 */
+export function deleteWarehouseLocation(id: number) {
+ return requestClient.delete(`/mes/wm/warehouse-location/delete?id=${id}`);
+}
+
+/** 鎵归噺璁剧疆搴撳尯涓嬫墍鏈夊簱浣嶇殑娣锋斁瑙勫垯 */
+export function updateAreaByLocationId(
+ locationId: number,
+ allowItemMixing?: boolean,
+ allowBatchMixing?: boolean,
+) {
+ return requestClient.put(
+ '/mes/wm/warehouse-location/update-by-location-id',
+ null,
+ { params: { allowBatchMixing, allowItemMixing, locationId } },
+ );
+}
diff --git a/src/api/wms/approval/index.ts b/src/api/wms/approval/index.ts
new file mode 100644
index 0000000..4676014
--- /dev/null
+++ b/src/api/wms/approval/index.ts
@@ -0,0 +1,69 @@
+import type { PageParam, PageResult } from '../../../packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace WmsApprovalApi {
+ /** 瀹℃壒閰嶇疆 */
+ export interface ApprovalConfig {
+ id?: number; // 閰嶇疆缂栧彿
+ warehouseId: number; // 浠撳簱ID
+ warehouseName?: string; // 浠撳簱鍚嶇О
+ bizType: string; // 涓氬姟绫诲瀷(INBOUND/OUTBOUND/TRANSFER/CHECK)
+ approvalEnabled: boolean; // 鏄惁鍚敤瀹℃壒
+ processDefinitionKey?: string; // 娴佺▼瀹氫箟Key
+ processDefinitionName?: string; // 娴佺▼瀹氫箟鍚嶇О
+ categoryCode?: string; // 娴佺▼鍒嗙被缂栫爜
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+
+ /** 娴佺▼瀹氫箟绠�鍗曚俊鎭� */
+ export interface ProcessSimpleInfo {
+ key: string; // 娴佺▼瀹氫箟Key
+ name: string; // 娴佺▼瀹氫箟鍚嶇О
+ }
+
+ /** 涓氬姟绫诲瀷鏋氫妇 */
+ export type BizType = 'INBOUND' | 'OUTBOUND' | 'TRANSFER' | 'CHECK';
+}
+
+/** 鑾峰彇鍗曚釜瀹℃壒閰嶇疆 */
+export function getApprovalConfig(warehouseId: number, bizType: string) {
+ return requestClient.get<WmsApprovalApi.ApprovalConfig>(
+ '/mes/wms/approval/config/get',
+ {
+ params: { warehouseId, bizType },
+ },
+ );
+}
+
+/** 鑾峰彇浠撳簱鎵�鏈夊鎵归厤缃� */
+export function getApprovalConfigList(warehouseId: number) {
+ return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+ '/mes/wms/approval/config/list',
+ {
+ params: { warehouseId },
+ },
+ );
+}
+
+/** 鑾峰彇鎵�鏈変粨搴撶殑瀹℃壒閰嶇疆鍒楄〃 */
+export function getApprovalConfigAllList() {
+ return requestClient.get<WmsApprovalApi.ApprovalConfig[]>(
+ '/mes/wms/approval/config/all-list',
+ );
+}
+
+/** 淇濆瓨瀹℃壒閰嶇疆 */
+export function saveApprovalConfig(data: WmsApprovalApi.ApprovalConfig) {
+ return requestClient.post('/mes/wms/approval/config/save', data);
+}
+
+/** 鑾峰彇鍙敤瀹℃壒娴佺▼鍒楄〃 */
+export function getAvailableProcessList(warehouseId: number, bizType: string) {
+ return requestClient.get<WmsApprovalApi.ProcessSimpleInfo[]>(
+ '/mes/wms/approval/process-list',
+ {
+ params: { warehouseId, bizType },
+ },
+ );
+}
diff --git a/src/views/mes/wm/arrivalnotice/components/index.ts b/src/views/arrivalnotice/components/index.ts
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/index.ts
rename to src/views/arrivalnotice/components/index.ts
diff --git a/src/views/mes/wm/arrivalnotice/components/line-select-dialog.vue b/src/views/arrivalnotice/components/line-select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/line-select-dialog.vue
rename to src/views/arrivalnotice/components/line-select-dialog.vue
diff --git a/src/views/mes/wm/arrivalnotice/components/line-select.vue b/src/views/arrivalnotice/components/line-select.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/line-select.vue
rename to src/views/arrivalnotice/components/line-select.vue
diff --git a/src/views/mes/wm/arrivalnotice/components/select-dialog.vue b/src/views/arrivalnotice/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/select-dialog.vue
rename to src/views/arrivalnotice/components/select-dialog.vue
diff --git a/src/views/mes/wm/arrivalnotice/components/select.vue b/src/views/arrivalnotice/components/select.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/components/select.vue
rename to src/views/arrivalnotice/components/select.vue
diff --git a/src/views/mes/wm/arrivalnotice/data.ts b/src/views/arrivalnotice/data.ts
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/data.ts
rename to src/views/arrivalnotice/data.ts
diff --git a/src/views/mes/wm/arrivalnotice/index.vue b/src/views/arrivalnotice/index.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/index.vue
rename to src/views/arrivalnotice/index.vue
diff --git a/src/views/mes/wm/arrivalnotice/modules/form.vue b/src/views/arrivalnotice/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/modules/form.vue
rename to src/views/arrivalnotice/modules/form.vue
diff --git a/src/views/mes/wm/arrivalnotice/modules/line-form.vue b/src/views/arrivalnotice/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/modules/line-form.vue
rename to src/views/arrivalnotice/modules/line-form.vue
diff --git a/src/views/mes/wm/arrivalnotice/modules/line-list.vue b/src/views/arrivalnotice/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/arrivalnotice/modules/line-list.vue
rename to src/views/arrivalnotice/modules/line-list.vue
diff --git a/src/views/mes/wm/barcode/components/barcode.vue b/src/views/barcode/components/barcode.vue
similarity index 100%
rename from src/views/mes/wm/barcode/components/barcode.vue
rename to src/views/barcode/components/barcode.vue
diff --git a/src/views/mes/wm/barcode/components/detail.vue b/src/views/barcode/components/detail.vue
similarity index 100%
rename from src/views/mes/wm/barcode/components/detail.vue
rename to src/views/barcode/components/detail.vue
diff --git a/src/views/mes/wm/barcode/components/index.ts b/src/views/barcode/components/index.ts
similarity index 100%
rename from src/views/mes/wm/barcode/components/index.ts
rename to src/views/barcode/components/index.ts
diff --git a/src/views/mes/wm/barcode/components/printer-label.vue b/src/views/barcode/components/printer-label.vue
similarity index 100%
rename from src/views/mes/wm/barcode/components/printer-label.vue
rename to src/views/barcode/components/printer-label.vue
diff --git a/src/views/mes/wm/barcode/config/data.ts b/src/views/barcode/config/data.ts
similarity index 100%
rename from src/views/mes/wm/barcode/config/data.ts
rename to src/views/barcode/config/data.ts
diff --git a/src/views/mes/wm/barcode/config/index.vue b/src/views/barcode/config/index.vue
similarity index 100%
rename from src/views/mes/wm/barcode/config/index.vue
rename to src/views/barcode/config/index.vue
diff --git a/src/views/mes/wm/barcode/config/modules/form.vue b/src/views/barcode/config/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/barcode/config/modules/form.vue
rename to src/views/barcode/config/modules/form.vue
diff --git a/src/views/mes/wm/barcode/data.ts b/src/views/barcode/data.ts
similarity index 100%
rename from src/views/mes/wm/barcode/data.ts
rename to src/views/barcode/data.ts
diff --git a/src/views/mes/wm/barcode/index.vue b/src/views/barcode/index.vue
similarity index 100%
rename from src/views/mes/wm/barcode/index.vue
rename to src/views/barcode/index.vue
diff --git a/src/views/mes/wm/barcode/modules/form.vue b/src/views/barcode/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/barcode/modules/form.vue
rename to src/views/barcode/modules/form.vue
diff --git a/src/views/mes/wm/batch/components/detail.vue b/src/views/batch/components/detail.vue
similarity index 100%
rename from src/views/mes/wm/batch/components/detail.vue
rename to src/views/batch/components/detail.vue
diff --git a/src/views/mes/wm/batch/components/index.ts b/src/views/batch/components/index.ts
similarity index 100%
rename from src/views/mes/wm/batch/components/index.ts
rename to src/views/batch/components/index.ts
diff --git a/src/views/mes/wm/batch/components/select-dialog.vue b/src/views/batch/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/batch/components/select-dialog.vue
rename to src/views/batch/components/select-dialog.vue
diff --git a/src/views/mes/wm/batch/components/select.vue b/src/views/batch/components/select.vue
similarity index 100%
rename from src/views/mes/wm/batch/components/select.vue
rename to src/views/batch/components/select.vue
diff --git a/src/views/mes/wm/batch/data.ts b/src/views/batch/data.ts
similarity index 100%
rename from src/views/mes/wm/batch/data.ts
rename to src/views/batch/data.ts
diff --git a/src/views/erp/purchase/order/components/index.ts b/src/views/erp/purchase/order/components/index.ts
new file mode 100644
index 0000000..7861658
--- /dev/null
+++ b/src/views/erp/purchase/order/components/index.ts
@@ -0,0 +1,4 @@
+import ErpPurchaseOrderSelect from './select.vue';
+import ErpPurchaseOrderSelectDialog from './select-dialog.vue';
+
+export { ErpPurchaseOrderSelect, ErpPurchaseOrderSelectDialog };
\ No newline at end of file
diff --git a/src/views/erp/purchase/order/components/select-dialog.vue b/src/views/erp/purchase/order/components/select-dialog.vue
new file mode 100644
index 0000000..d2dace1
--- /dev/null
+++ b/src/views/erp/purchase/order/components/select-dialog.vue
@@ -0,0 +1,196 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
+
+import { nextTick, ref } from 'vue';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getPurchaseOrderPage } from '#/api/erp/purchase/order';
+
+defineOptions({ name: 'ErpPurchaseOrderSelectDialog' });
+
+const emit = defineEmits<{
+ selected: [rows: ErpPurchaseOrderApi.PurchaseOrder[]];
+}>();
+
+const open = ref(false);
+const multiple = ref(true);
+const selectedRows = ref<ErpPurchaseOrderApi.PurchaseOrder[]>([]);
+const preSelectedIds = ref<number[]>([]);
+
+/** 鑾峰彇褰撳墠琛ㄦ牸鏁版嵁 */
+function getTableRows() {
+ return gridApi.grid.getTableData().fullData as ErpPurchaseOrderApi.PurchaseOrder[];
+}
+
+/** 鑾峰彇澶氶�夎褰� */
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<number, ErpPurchaseOrderApi.PurchaseOrder>();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as ErpPurchaseOrderApi.PurchaseOrder[];
+ records.forEach((row) => {
+ if (row.id) {
+ selectedMap.set(row.id, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: ErpPurchaseOrderApi.PurchaseOrder) {
+ selectedRows.value = [row];
+}
+
+/** 澶勭悊琛屽弻鍑� */
+async function handleCellDblclick({ row }: { row: ErpPurchaseOrderApi.PurchaseOrder }) {
+ if (multiple.value) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夐噰璐鍗� */
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows = getTableRows();
+ for (const row of rows) {
+ if (row.id && preSelectedIds.value.includes(row.id)) {
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+/** 鍒楄〃瀛楁 */
+function useGridColumns(isMultiple: boolean): VxeTableGridOptions<ErpPurchaseOrderApi.PurchaseOrder>['columns'] {
+ return [
+ { type: isMultiple ? 'checkbox' : 'radio', width: 50 },
+ { field: 'no', title: '璁㈠崟鍙�', minWidth: 140 },
+ { field: 'supplierName', title: '渚涘簲鍟�', minWidth: 120 },
+ { field: 'orderTime', title: '璁㈠崟鏃堕棿', width: 120, formatter: 'formatDate' },
+ { field: 'totalCount', title: '鍚堣鏁伴噺', width: 100 },
+ { field: 'totalPrice', title: '鍚堣閲戦', width: 100 },
+ ];
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useGridColumns(true),
+ height: 400,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getPurchaseOrderPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ } as VxeTableGridOptions<ErpPurchaseOrderApi.PurchaseOrder>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({ row }: { row: ErpPurchaseOrderApi.PurchaseOrder }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+}
+
+/** 鎵撳紑閲囪喘璁㈠崟閫夋嫨寮圭獥 */
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? true;
+ preSelectedIds.value = selectedIds || [];
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: useGridColumns(multiple.value),
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+/** 鍏抽棴閲囪喘璁㈠崟閫夋嫨寮圭獥 */
+async function closeModal() {
+ open.value = false;
+ await resetQueryState();
+}
+
+/** 纭閫夋嫨閲囪喘璁㈠崟 */
+function handleConfirm() {
+ const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+ if (rows.length === 0) {
+ message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit('selected', multiple.value ? rows : [rows[0]!]);
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ title="閲囪喘璁㈠崟閫夋嫨"
+ width="70%"
+ :destroy-on-close="true"
+ @ok="handleConfirm"
+ @cancel="closeModal"
+ >
+ <Grid table-title="閲囪喘璁㈠崟鍒楄〃" />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/order/components/select.vue b/src/views/erp/purchase/order/components/select.vue
new file mode 100644
index 0000000..a1db22d
--- /dev/null
+++ b/src/views/erp/purchase/order/components/select.vue
@@ -0,0 +1,138 @@
+<script lang="ts" setup>
+import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getPurchaseOrder } from '#/api/erp/purchase/order';
+
+import ErpPurchaseOrderSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'ErpPurchaseOrderSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ placeholder?: string;
+ }>(),
+ {
+ allowClear: true,
+ disabled: false,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨閲囪喘璁㈠崟',
+ },
+);
+const emit = defineEmits<{
+ change: [item: ErpPurchaseOrderApi.PurchaseOrder | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof ErpPurchaseOrderSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<ErpPurchaseOrderApi.PurchaseOrder>();
+
+const displayLabel = computed(() => selectedItem.value?.no ?? '');
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁閲囪喘璁㈠崟缂栧彿鍥炴樉閫夋嫨鍣� */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getPurchaseOrder(id);
+}
+
+watch(
+ () => props.modelValue,
+ (value) => {
+ resolveItemById(value);
+ },
+ { immediate: true },
+);
+
+/** 娓呯┖宸查�夐噰璐鍗� */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑閲囪喘璁㈠崟閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 鍥炲~閫変腑鐨勯噰璐鍗� */
+function handleSelected(rows: ErpPurchaseOrderApi.PurchaseOrder[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>璁㈠崟鍙凤細{{ selectedItem.no || '-' }}</div>
+ <div>渚涘簲鍟嗭細{{ selectedItem.supplierName || '-' }}</div>
+ <div>璁㈠崟鏃堕棿锛歿{ selectedItem.orderTime || '-' }}</div>
+ <div>鍚堣閲戦锛歿{ selectedItem.totalPrice || '-' }} 鍏�</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <ErpPurchaseOrderSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/erp/stock/warehouseMapping/data.ts b/src/views/erp/stock/warehouseMapping/data.ts
new file mode 100644
index 0000000..219c3bc
--- /dev/null
+++ b/src/views/erp/stock/warehouseMapping/data.ts
@@ -0,0 +1,143 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpWarehouseMappingApi } from '#/api/erp/warehouseMapping';
+
+import { getWarehouseSimpleList as getErpWarehouseSimpleList } from '#/api/erp/stock/warehouse';
+import { getWarehouseSimpleList as getMesWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+
+/** 鐘舵�侀�夐」 */
+export const STATUS_OPTIONS = [
+ { label: '鍚敤', value: 1 },
+ { label: '绂佺敤', value: 0 },
+];
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'erpWarehouseId',
+ label: 'ERP浠撳簱',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getErpWarehouseSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨ERP浠撳簱',
+ },
+ },
+ {
+ fieldName: 'mesWarehouseId',
+ label: 'MES浠撳簱',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getMesWarehouseSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨MES浠撳簱',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: STATUS_OPTIONS,
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<ErpWarehouseMappingApi.WarehouseMapping>['columns'] {
+ return [
+ {
+ field: 'erpWarehouseName',
+ title: 'ERP浠撳簱',
+ minWidth: 160,
+ },
+ {
+ field: 'mesWarehouseName',
+ title: 'MES浠撳簱',
+ minWidth: 160,
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 100,
+ align: 'center',
+ cellRender: {
+ name: 'CellTag',
+ props: {
+ options: STATUS_OPTIONS,
+ colorKeyMap: {
+ 0: 'error',
+ 1: 'success',
+ },
+ },
+ },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'erpWarehouseId',
+ label: 'ERP浠撳簱',
+ component: 'ApiSelect',
+ componentProps: {
+ api: getErpWarehouseSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨ERP浠撳簱',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'mesWarehouseId',
+ label: 'MES浠撳簱',
+ component: 'ApiSelect',
+ componentProps: {
+ api: getMesWarehouseSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨MES浠撳簱',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ defaultValue: 1,
+ componentProps: {
+ options: STATUS_OPTIONS,
+ },
+ },
+ ];
+}
diff --git a/src/views/erp/stock/warehouseMapping/index.vue b/src/views/erp/stock/warehouseMapping/index.vue
new file mode 100644
index 0000000..b1a6f6b
--- /dev/null
+++ b/src/views/erp/stock/warehouseMapping/index.vue
@@ -0,0 +1,132 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpWarehouseMappingApi } from '#/api/erp/warehouseMapping';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteMapping,
+ getMappingPage,
+} from '#/api/erp/warehouseMapping';
+import { $t } from '#/locales';
+
+import MappingForm from './modules/form.vue';
+import { useGridColumns, useGridFormSchema } from './data';
+
+defineOptions({ name: 'ErpWarehouseMapping' });
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: MappingForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鏄犲皠 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 缂栬緫鏄犲皠 */
+function handleEdit(row: ErpWarehouseMappingApi.WarehouseMapping) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎鏄犲皠 */
+async function handleDelete(row: ErpWarehouseMappingApi.WarehouseMapping) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.erpWarehouseName]),
+ duration: 0,
+ });
+ try {
+ await deleteMapping(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.erpWarehouseName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getMappingPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<ErpWarehouseMappingApi.WarehouseMapping>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+
+ <Grid table-title="浠撳簱鏄犲皠鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['浠撳簱鏄犲皠']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['erp:warehouse:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['erp:warehouse:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['erp:warehouse:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.erpWarehouseName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/erp/stock/warehouseMapping/modules/form.vue b/src/views/erp/stock/warehouseMapping/modules/form.vue
new file mode 100644
index 0000000..2b95d07
--- /dev/null
+++ b/src/views/erp/stock/warehouseMapping/modules/form.vue
@@ -0,0 +1,78 @@
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createMapping, getMapping, updateMapping } from '#/api/erp/warehouseMapping';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'ErpWarehouseMappingForm' });
+
+const emit = defineEmits<{ success: [] }>();
+
+const formType = ref<'create' | 'update'>('create');
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ },
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const values = await formApi.getValues();
+ try {
+ if (formType.value === 'create') {
+ await createMapping(values);
+ message.success('鍒涘缓鎴愬姛');
+ } else {
+ await updateMapping(values);
+ message.success('鏇存柊鎴愬姛');
+ }
+ emit('success');
+ modalApi.close();
+ } catch (error) {
+ console.error('淇濆瓨澶辫触:', error);
+ }
+ },
+ onOpenChange(isOpen) {
+ if (isOpen) {
+ const data = modalApi.getData() as {
+ formType: 'create' | 'update';
+ id?: number;
+ };
+ if (data) {
+ formType.value = data.formType;
+ if (data.formType === 'update' && data.id) {
+ // 鍔犺浇璇︽儏
+ getMapping(data.id).then((detail) => {
+ formApi.setValues(detail);
+ });
+ } else {
+ formApi.resetForm();
+ }
+ }
+ }
+ },
+});
+
+const modalTitle = computed(() => {
+ return formType.value === 'create' ? '鏂板浠撳簱鏄犲皠' : '缂栬緫浠撳簱鏄犲皠';
+});
+</script>
+
+<template>
+ <Modal :title="modalTitle" class="w-[500px]">
+ <Form />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/itemreceipt/data.ts b/src/views/itemreceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/itemreceipt/data.ts
rename to src/views/itemreceipt/data.ts
diff --git a/src/views/mes/wm/itemreceipt/index.vue b/src/views/itemreceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/index.vue
rename to src/views/itemreceipt/index.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/detail-form.vue b/src/views/itemreceipt/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/detail-form.vue
rename to src/views/itemreceipt/modules/detail-form.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/detail-list.vue b/src/views/itemreceipt/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/detail-list.vue
rename to src/views/itemreceipt/modules/detail-list.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/form.vue b/src/views/itemreceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/form.vue
rename to src/views/itemreceipt/modules/form.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/line-form.vue b/src/views/itemreceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/line-form.vue
rename to src/views/itemreceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/itemreceipt/modules/line-list.vue b/src/views/itemreceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/itemreceipt/modules/line-list.vue
rename to src/views/itemreceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/materialstock/components/index.ts b/src/views/materialstock/components/index.ts
similarity index 100%
rename from src/views/mes/wm/materialstock/components/index.ts
rename to src/views/materialstock/components/index.ts
diff --git a/src/views/mes/wm/materialstock/components/select-dialog.vue b/src/views/materialstock/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/materialstock/components/select-dialog.vue
rename to src/views/materialstock/components/select-dialog.vue
diff --git a/src/views/mes/wm/materialstock/components/select.vue b/src/views/materialstock/components/select.vue
similarity index 100%
rename from src/views/mes/wm/materialstock/components/select.vue
rename to src/views/materialstock/components/select.vue
diff --git a/src/views/mes/wm/materialstock/data.ts b/src/views/materialstock/data.ts
similarity index 100%
rename from src/views/mes/wm/materialstock/data.ts
rename to src/views/materialstock/data.ts
diff --git a/src/views/mes/wm/materialstock/index.vue b/src/views/materialstock/index.vue
similarity index 100%
rename from src/views/mes/wm/materialstock/index.vue
rename to src/views/materialstock/index.vue
diff --git a/src/views/mes/wm/miscissue/data.ts b/src/views/miscissue/data.ts
similarity index 100%
rename from src/views/mes/wm/miscissue/data.ts
rename to src/views/miscissue/data.ts
diff --git a/src/views/mes/wm/miscissue/index.vue b/src/views/miscissue/index.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/index.vue
rename to src/views/miscissue/index.vue
diff --git a/src/views/mes/wm/miscissue/modules/form.vue b/src/views/miscissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/modules/form.vue
rename to src/views/miscissue/modules/form.vue
diff --git a/src/views/mes/wm/miscissue/modules/line-form.vue b/src/views/miscissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/modules/line-form.vue
rename to src/views/miscissue/modules/line-form.vue
diff --git a/src/views/mes/wm/miscissue/modules/line-list.vue b/src/views/miscissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/miscissue/modules/line-list.vue
rename to src/views/miscissue/modules/line-list.vue
diff --git a/src/views/mes/wm/miscreceipt/data.ts b/src/views/miscreceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/miscreceipt/data.ts
rename to src/views/miscreceipt/data.ts
diff --git a/src/views/mes/wm/miscreceipt/index.vue b/src/views/miscreceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/index.vue
rename to src/views/miscreceipt/index.vue
diff --git a/src/views/mes/wm/miscreceipt/modules/form.vue b/src/views/miscreceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/modules/form.vue
rename to src/views/miscreceipt/modules/form.vue
diff --git a/src/views/mes/wm/miscreceipt/modules/line-form.vue b/src/views/miscreceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/modules/line-form.vue
rename to src/views/miscreceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/miscreceipt/modules/line-list.vue b/src/views/miscreceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/miscreceipt/modules/line-list.vue
rename to src/views/miscreceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/outsourceissue/data.ts b/src/views/outsourceissue/data.ts
similarity index 100%
rename from src/views/mes/wm/outsourceissue/data.ts
rename to src/views/outsourceissue/data.ts
diff --git a/src/views/mes/wm/outsourceissue/index.vue b/src/views/outsourceissue/index.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/index.vue
rename to src/views/outsourceissue/index.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/detail-form.vue b/src/views/outsourceissue/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/detail-form.vue
rename to src/views/outsourceissue/modules/detail-form.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/detail-list.vue b/src/views/outsourceissue/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/detail-list.vue
rename to src/views/outsourceissue/modules/detail-list.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/form.vue b/src/views/outsourceissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/form.vue
rename to src/views/outsourceissue/modules/form.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/line-form.vue b/src/views/outsourceissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/line-form.vue
rename to src/views/outsourceissue/modules/line-form.vue
diff --git a/src/views/mes/wm/outsourceissue/modules/line-list.vue b/src/views/outsourceissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourceissue/modules/line-list.vue
rename to src/views/outsourceissue/modules/line-list.vue
diff --git a/src/views/mes/wm/outsourcereceipt/data.ts b/src/views/outsourcereceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/data.ts
rename to src/views/outsourcereceipt/data.ts
diff --git a/src/views/mes/wm/outsourcereceipt/index.vue b/src/views/outsourcereceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/index.vue
rename to src/views/outsourcereceipt/index.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/detail-form.vue b/src/views/outsourcereceipt/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/detail-form.vue
rename to src/views/outsourcereceipt/modules/detail-form.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/detail-list.vue b/src/views/outsourcereceipt/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/detail-list.vue
rename to src/views/outsourcereceipt/modules/detail-list.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/form.vue b/src/views/outsourcereceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/form.vue
rename to src/views/outsourcereceipt/modules/form.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/line-form.vue b/src/views/outsourcereceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/line-form.vue
rename to src/views/outsourcereceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/outsourcereceipt/modules/line-list.vue b/src/views/outsourcereceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/outsourcereceipt/modules/line-list.vue
rename to src/views/outsourcereceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/packages/components/index.ts b/src/views/packages/components/index.ts
similarity index 100%
rename from src/views/mes/wm/packages/components/index.ts
rename to src/views/packages/components/index.ts
diff --git a/src/views/mes/wm/packages/components/select-dialog.vue b/src/views/packages/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/packages/components/select-dialog.vue
rename to src/views/packages/components/select-dialog.vue
diff --git a/src/views/mes/wm/packages/components/select.vue b/src/views/packages/components/select.vue
similarity index 100%
rename from src/views/mes/wm/packages/components/select.vue
rename to src/views/packages/components/select.vue
diff --git a/src/views/mes/wm/packages/data.ts b/src/views/packages/data.ts
similarity index 100%
rename from src/views/mes/wm/packages/data.ts
rename to src/views/packages/data.ts
diff --git a/src/views/mes/wm/packages/index.vue b/src/views/packages/index.vue
similarity index 100%
rename from src/views/mes/wm/packages/index.vue
rename to src/views/packages/index.vue
diff --git a/src/views/mes/wm/packages/modules/form.vue b/src/views/packages/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/form.vue
rename to src/views/packages/modules/form.vue
diff --git a/src/views/mes/wm/packages/modules/line-form.vue b/src/views/packages/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/line-form.vue
rename to src/views/packages/modules/line-form.vue
diff --git a/src/views/mes/wm/packages/modules/package-line-list.vue b/src/views/packages/modules/package-line-list.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/package-line-list.vue
rename to src/views/packages/modules/package-line-list.vue
diff --git a/src/views/mes/wm/packages/modules/sub-package-list.vue b/src/views/packages/modules/sub-package-list.vue
similarity index 100%
rename from src/views/mes/wm/packages/modules/sub-package-list.vue
rename to src/views/packages/modules/sub-package-list.vue
diff --git a/src/views/mes/wm/productissue/data.ts b/src/views/productissue/data.ts
similarity index 100%
rename from src/views/mes/wm/productissue/data.ts
rename to src/views/productissue/data.ts
diff --git a/src/views/mes/wm/productissue/index.vue b/src/views/productissue/index.vue
similarity index 100%
rename from src/views/mes/wm/productissue/index.vue
rename to src/views/productissue/index.vue
diff --git a/src/views/mes/wm/productissue/modules/detail-form.vue b/src/views/productissue/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/detail-form.vue
rename to src/views/productissue/modules/detail-form.vue
diff --git a/src/views/mes/wm/productissue/modules/detail-list.vue b/src/views/productissue/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/detail-list.vue
rename to src/views/productissue/modules/detail-list.vue
diff --git a/src/views/mes/wm/productissue/modules/form.vue b/src/views/productissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/form.vue
rename to src/views/productissue/modules/form.vue
diff --git a/src/views/mes/wm/productissue/modules/line-form.vue b/src/views/productissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/line-form.vue
rename to src/views/productissue/modules/line-form.vue
diff --git a/src/views/mes/wm/productissue/modules/line-list.vue b/src/views/productissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/productissue/modules/line-list.vue
rename to src/views/productissue/modules/line-list.vue
diff --git a/src/views/mes/wm/productreceipt/data.ts b/src/views/productreceipt/data.ts
similarity index 100%
rename from src/views/mes/wm/productreceipt/data.ts
rename to src/views/productreceipt/data.ts
diff --git a/src/views/mes/wm/productreceipt/index.vue b/src/views/productreceipt/index.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/index.vue
rename to src/views/productreceipt/index.vue
diff --git a/src/views/mes/wm/productreceipt/modules/detail-form.vue b/src/views/productreceipt/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/detail-form.vue
rename to src/views/productreceipt/modules/detail-form.vue
diff --git a/src/views/mes/wm/productreceipt/modules/detail-list.vue b/src/views/productreceipt/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/detail-list.vue
rename to src/views/productreceipt/modules/detail-list.vue
diff --git a/src/views/mes/wm/productreceipt/modules/form.vue b/src/views/productreceipt/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/form.vue
rename to src/views/productreceipt/modules/form.vue
diff --git a/src/views/mes/wm/productreceipt/modules/line-form.vue b/src/views/productreceipt/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/line-form.vue
rename to src/views/productreceipt/modules/line-form.vue
diff --git a/src/views/mes/wm/productreceipt/modules/line-list.vue b/src/views/productreceipt/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/productreceipt/modules/line-list.vue
rename to src/views/productreceipt/modules/line-list.vue
diff --git a/src/views/mes/wm/productsales/data.ts b/src/views/productsales/data.ts
similarity index 100%
rename from src/views/mes/wm/productsales/data.ts
rename to src/views/productsales/data.ts
diff --git a/src/views/mes/wm/productsales/index.vue b/src/views/productsales/index.vue
similarity index 100%
rename from src/views/mes/wm/productsales/index.vue
rename to src/views/productsales/index.vue
diff --git a/src/views/mes/wm/productsales/modules/detail-form.vue b/src/views/productsales/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/detail-form.vue
rename to src/views/productsales/modules/detail-form.vue
diff --git a/src/views/mes/wm/productsales/modules/detail-list.vue b/src/views/productsales/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/detail-list.vue
rename to src/views/productsales/modules/detail-list.vue
diff --git a/src/views/mes/wm/productsales/modules/form.vue b/src/views/productsales/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/form.vue
rename to src/views/productsales/modules/form.vue
diff --git a/src/views/mes/wm/productsales/modules/line-form.vue b/src/views/productsales/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/line-form.vue
rename to src/views/productsales/modules/line-form.vue
diff --git a/src/views/mes/wm/productsales/modules/line-list.vue b/src/views/productsales/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/productsales/modules/line-list.vue
rename to src/views/productsales/modules/line-list.vue
diff --git a/src/views/mes/wm/returnissue/data.ts b/src/views/returnissue/data.ts
similarity index 100%
rename from src/views/mes/wm/returnissue/data.ts
rename to src/views/returnissue/data.ts
diff --git a/src/views/mes/wm/returnissue/index.vue b/src/views/returnissue/index.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/index.vue
rename to src/views/returnissue/index.vue
diff --git a/src/views/mes/wm/returnissue/modules/detail-form.vue b/src/views/returnissue/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/detail-form.vue
rename to src/views/returnissue/modules/detail-form.vue
diff --git a/src/views/mes/wm/returnissue/modules/detail-list.vue b/src/views/returnissue/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/detail-list.vue
rename to src/views/returnissue/modules/detail-list.vue
diff --git a/src/views/mes/wm/returnissue/modules/form.vue b/src/views/returnissue/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/form.vue
rename to src/views/returnissue/modules/form.vue
diff --git a/src/views/mes/wm/returnissue/modules/line-form.vue b/src/views/returnissue/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/line-form.vue
rename to src/views/returnissue/modules/line-form.vue
diff --git a/src/views/mes/wm/returnissue/modules/line-list.vue b/src/views/returnissue/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/returnissue/modules/line-list.vue
rename to src/views/returnissue/modules/line-list.vue
diff --git a/src/views/mes/wm/returnsales/data.ts b/src/views/returnsales/data.ts
similarity index 100%
rename from src/views/mes/wm/returnsales/data.ts
rename to src/views/returnsales/data.ts
diff --git a/src/views/mes/wm/returnsales/index.vue b/src/views/returnsales/index.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/index.vue
rename to src/views/returnsales/index.vue
diff --git a/src/views/mes/wm/returnsales/modules/detail-form.vue b/src/views/returnsales/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/detail-form.vue
rename to src/views/returnsales/modules/detail-form.vue
diff --git a/src/views/mes/wm/returnsales/modules/detail-list.vue b/src/views/returnsales/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/detail-list.vue
rename to src/views/returnsales/modules/detail-list.vue
diff --git a/src/views/mes/wm/returnsales/modules/form.vue b/src/views/returnsales/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/form.vue
rename to src/views/returnsales/modules/form.vue
diff --git a/src/views/mes/wm/returnsales/modules/line-form.vue b/src/views/returnsales/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/line-form.vue
rename to src/views/returnsales/modules/line-form.vue
diff --git a/src/views/mes/wm/returnsales/modules/line-list.vue b/src/views/returnsales/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/returnsales/modules/line-list.vue
rename to src/views/returnsales/modules/line-list.vue
diff --git a/src/views/mes/wm/returnvendor/data.ts b/src/views/returnvendor/data.ts
similarity index 100%
rename from src/views/mes/wm/returnvendor/data.ts
rename to src/views/returnvendor/data.ts
diff --git a/src/views/mes/wm/returnvendor/index.vue b/src/views/returnvendor/index.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/index.vue
rename to src/views/returnvendor/index.vue
diff --git a/src/views/mes/wm/returnvendor/modules/detail-form.vue b/src/views/returnvendor/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/detail-form.vue
rename to src/views/returnvendor/modules/detail-form.vue
diff --git a/src/views/mes/wm/returnvendor/modules/detail-list.vue b/src/views/returnvendor/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/detail-list.vue
rename to src/views/returnvendor/modules/detail-list.vue
diff --git a/src/views/mes/wm/returnvendor/modules/form.vue b/src/views/returnvendor/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/form.vue
rename to src/views/returnvendor/modules/form.vue
diff --git a/src/views/mes/wm/returnvendor/modules/line-form.vue b/src/views/returnvendor/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/line-form.vue
rename to src/views/returnvendor/modules/line-form.vue
diff --git a/src/views/mes/wm/returnvendor/modules/line-list.vue b/src/views/returnvendor/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/returnvendor/modules/line-list.vue
rename to src/views/returnvendor/modules/line-list.vue
diff --git a/src/views/mes/wm/salesnotice/components/index.ts b/src/views/salesnotice/components/index.ts
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/index.ts
rename to src/views/salesnotice/components/index.ts
diff --git a/src/views/mes/wm/salesnotice/components/line-select-dialog.vue b/src/views/salesnotice/components/line-select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/line-select-dialog.vue
rename to src/views/salesnotice/components/line-select-dialog.vue
diff --git a/src/views/mes/wm/salesnotice/components/line-select.vue b/src/views/salesnotice/components/line-select.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/line-select.vue
rename to src/views/salesnotice/components/line-select.vue
diff --git a/src/views/mes/wm/salesnotice/components/select-dialog.vue b/src/views/salesnotice/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/select-dialog.vue
rename to src/views/salesnotice/components/select-dialog.vue
diff --git a/src/views/mes/wm/salesnotice/components/select.vue b/src/views/salesnotice/components/select.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/components/select.vue
rename to src/views/salesnotice/components/select.vue
diff --git a/src/views/mes/wm/salesnotice/data.ts b/src/views/salesnotice/data.ts
similarity index 100%
rename from src/views/mes/wm/salesnotice/data.ts
rename to src/views/salesnotice/data.ts
diff --git a/src/views/mes/wm/salesnotice/index.vue b/src/views/salesnotice/index.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/index.vue
rename to src/views/salesnotice/index.vue
diff --git a/src/views/mes/wm/salesnotice/modules/form.vue b/src/views/salesnotice/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/modules/form.vue
rename to src/views/salesnotice/modules/form.vue
diff --git a/src/views/mes/wm/salesnotice/modules/line-form.vue b/src/views/salesnotice/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/modules/line-form.vue
rename to src/views/salesnotice/modules/line-form.vue
diff --git a/src/views/mes/wm/salesnotice/modules/line-list.vue b/src/views/salesnotice/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/salesnotice/modules/line-list.vue
rename to src/views/salesnotice/modules/line-list.vue
diff --git a/src/views/mes/wm/sn/data.ts b/src/views/sn/data.ts
similarity index 100%
rename from src/views/mes/wm/sn/data.ts
rename to src/views/sn/data.ts
diff --git a/src/views/mes/wm/sn/index.vue b/src/views/sn/index.vue
similarity index 100%
rename from src/views/mes/wm/sn/index.vue
rename to src/views/sn/index.vue
diff --git a/src/views/mes/wm/sn/modules/detail.vue b/src/views/sn/modules/detail.vue
similarity index 100%
rename from src/views/mes/wm/sn/modules/detail.vue
rename to src/views/sn/modules/detail.vue
diff --git a/src/views/mes/wm/sn/modules/form.vue b/src/views/sn/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/sn/modules/form.vue
rename to src/views/sn/modules/form.vue
diff --git a/src/views/mes/wm/stocktaking/plan/components/condition-value-input.vue b/src/views/stocktaking/plan/components/condition-value-input.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/condition-value-input.vue
rename to src/views/stocktaking/plan/components/condition-value-input.vue
diff --git a/src/views/mes/wm/stocktaking/plan/components/index.ts b/src/views/stocktaking/plan/components/index.ts
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/index.ts
rename to src/views/stocktaking/plan/components/index.ts
diff --git a/src/views/mes/wm/stocktaking/plan/components/select-dialog.vue b/src/views/stocktaking/plan/components/select-dialog.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/select-dialog.vue
rename to src/views/stocktaking/plan/components/select-dialog.vue
diff --git a/src/views/mes/wm/stocktaking/plan/components/select.vue b/src/views/stocktaking/plan/components/select.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/components/select.vue
rename to src/views/stocktaking/plan/components/select.vue
diff --git a/src/views/mes/wm/stocktaking/plan/data.ts b/src/views/stocktaking/plan/data.ts
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/data.ts
rename to src/views/stocktaking/plan/data.ts
diff --git a/src/views/mes/wm/stocktaking/plan/index.vue b/src/views/stocktaking/plan/index.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/index.vue
rename to src/views/stocktaking/plan/index.vue
diff --git a/src/views/mes/wm/stocktaking/plan/modules/form.vue b/src/views/stocktaking/plan/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/modules/form.vue
rename to src/views/stocktaking/plan/modules/form.vue
diff --git a/src/views/mes/wm/stocktaking/plan/modules/param-form.vue b/src/views/stocktaking/plan/modules/param-form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/modules/param-form.vue
rename to src/views/stocktaking/plan/modules/param-form.vue
diff --git a/src/views/mes/wm/stocktaking/plan/modules/param-list.vue b/src/views/stocktaking/plan/modules/param-list.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/plan/modules/param-list.vue
rename to src/views/stocktaking/plan/modules/param-list.vue
diff --git a/src/views/mes/wm/stocktaking/task/data.ts b/src/views/stocktaking/task/data.ts
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/data.ts
rename to src/views/stocktaking/task/data.ts
diff --git a/src/views/mes/wm/stocktaking/task/index.vue b/src/views/stocktaking/task/index.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/index.vue
rename to src/views/stocktaking/task/index.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/form.vue b/src/views/stocktaking/task/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/form.vue
rename to src/views/stocktaking/task/modules/form.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/line-list.vue b/src/views/stocktaking/task/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/line-list.vue
rename to src/views/stocktaking/task/modules/line-list.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/result-form.vue b/src/views/stocktaking/task/modules/result-form.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/result-form.vue
rename to src/views/stocktaking/task/modules/result-form.vue
diff --git a/src/views/mes/wm/stocktaking/task/modules/result-list.vue b/src/views/stocktaking/task/modules/result-list.vue
similarity index 100%
rename from src/views/mes/wm/stocktaking/task/modules/result-list.vue
rename to src/views/stocktaking/task/modules/result-list.vue
diff --git a/src/views/mes/wm/transfer/data.ts b/src/views/transfer/data.ts
similarity index 100%
rename from src/views/mes/wm/transfer/data.ts
rename to src/views/transfer/data.ts
diff --git a/src/views/mes/wm/transfer/index.vue b/src/views/transfer/index.vue
similarity index 100%
rename from src/views/mes/wm/transfer/index.vue
rename to src/views/transfer/index.vue
diff --git a/src/views/mes/wm/transfer/modules/detail-form.vue b/src/views/transfer/modules/detail-form.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/detail-form.vue
rename to src/views/transfer/modules/detail-form.vue
diff --git a/src/views/mes/wm/transfer/modules/detail-list.vue b/src/views/transfer/modules/detail-list.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/detail-list.vue
rename to src/views/transfer/modules/detail-list.vue
diff --git a/src/views/mes/wm/transfer/modules/form.vue b/src/views/transfer/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/form.vue
rename to src/views/transfer/modules/form.vue
diff --git a/src/views/mes/wm/transfer/modules/line-form.vue b/src/views/transfer/modules/line-form.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/line-form.vue
rename to src/views/transfer/modules/line-form.vue
diff --git a/src/views/mes/wm/transfer/modules/line-list.vue b/src/views/transfer/modules/line-list.vue
similarity index 100%
rename from src/views/mes/wm/transfer/modules/line-list.vue
rename to src/views/transfer/modules/line-list.vue
diff --git a/src/views/mes/wm/warehouse/area/data.ts b/src/views/warehouse/area/data.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/area/data.ts
rename to src/views/warehouse/area/data.ts
diff --git a/src/views/mes/wm/warehouse/area/index.vue b/src/views/warehouse/area/index.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/area/index.vue
rename to src/views/warehouse/area/index.vue
diff --git a/src/views/mes/wm/warehouse/area/modules/form.vue b/src/views/warehouse/area/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/area/modules/form.vue
rename to src/views/warehouse/area/modules/form.vue
diff --git a/src/views/mes/wm/warehouse/components/area-select.vue b/src/views/warehouse/components/area-select.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/components/area-select.vue
rename to src/views/warehouse/components/area-select.vue
diff --git a/src/views/mes/wm/warehouse/components/index.ts b/src/views/warehouse/components/index.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/components/index.ts
rename to src/views/warehouse/components/index.ts
diff --git a/src/views/mes/wm/warehouse/components/location-select.vue b/src/views/warehouse/components/location-select.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/components/location-select.vue
rename to src/views/warehouse/components/location-select.vue
diff --git a/src/views/mes/wm/warehouse/components/select.vue b/src/views/warehouse/components/select.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/components/select.vue
rename to src/views/warehouse/components/select.vue
diff --git a/src/views/mes/wm/warehouse/data.ts b/src/views/warehouse/data.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/data.ts
rename to src/views/warehouse/data.ts
diff --git a/src/views/mes/wm/warehouse/index.vue b/src/views/warehouse/index.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/index.vue
rename to src/views/warehouse/index.vue
diff --git a/src/views/mes/wm/warehouse/location/data.ts b/src/views/warehouse/location/data.ts
similarity index 100%
rename from src/views/mes/wm/warehouse/location/data.ts
rename to src/views/warehouse/location/data.ts
diff --git a/src/views/mes/wm/warehouse/location/index.vue b/src/views/warehouse/location/index.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/location/index.vue
rename to src/views/warehouse/location/index.vue
diff --git a/src/views/mes/wm/warehouse/location/modules/form.vue b/src/views/warehouse/location/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/location/modules/form.vue
rename to src/views/warehouse/location/modules/form.vue
diff --git a/src/views/mes/wm/warehouse/modules/form.vue b/src/views/warehouse/modules/form.vue
similarity index 100%
rename from src/views/mes/wm/warehouse/modules/form.vue
rename to src/views/warehouse/modules/form.vue
diff --git a/src/views/wls/approval/config/data.ts b/src/views/wls/approval/config/data.ts
new file mode 100644
index 0000000..49ecc5b
--- /dev/null
+++ b/src/views/wls/approval/config/data.ts
@@ -0,0 +1,209 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { WmsApprovalApi } from '#/api/mes/wm/approval';
+
+import { h } from 'vue';
+
+import { Switch, Tag } from 'ant-design-vue';
+
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+
+/** 涓氬姟绫诲瀷閫夐」 */
+export const BIZ_TYPE_OPTIONS = [
+ { label: '鍏ュ簱瀹℃壒', value: 'INBOUND' },
+ { label: '鍑哄簱瀹℃壒', value: 'OUTBOUND' },
+ { label: '璋冩嫧瀹℃壒', value: 'TRANSFER' },
+ { label: '鐩樼偣瀹℃壒', value: 'CHECK' },
+];
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getWarehouseSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(
+ handleChange: (row: any, bizType: string, enabled: boolean) => void,
+): VxeTableGridOptions['columns'] {
+ return [
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'warehouseCode',
+ title: '浠撳簱缂栫爜',
+ minWidth: 120,
+ },
+ {
+ title: '鍏ュ簱瀹℃壒',
+ width: 100,
+ align: 'center',
+ slots: {
+ default: ({ row }) => {
+ const config = row.configs?.find(
+ (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'INBOUND',
+ );
+ return h(Switch, {
+ checked: config?.approvalEnabled ?? false,
+ checkedChildren: '寮�',
+ unCheckedChildren: '鍏�',
+ onChange: (checked: boolean) => {
+ handleChange(row, 'INBOUND', checked);
+ },
+ });
+ },
+ },
+ },
+ {
+ title: '鍑哄簱瀹℃壒',
+ width: 100,
+ align: 'center',
+ slots: {
+ default: ({ row }) => {
+ const config = row.configs?.find(
+ (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'OUTBOUND',
+ );
+ return h(Switch, {
+ checked: config?.approvalEnabled ?? false,
+ checkedChildren: '寮�',
+ unCheckedChildren: '鍏�',
+ onChange: (checked: boolean) => {
+ handleChange(row, 'OUTBOUND', checked);
+ },
+ });
+ },
+ },
+ },
+ {
+ title: '璋冩嫧瀹℃壒',
+ width: 100,
+ align: 'center',
+ slots: {
+ default: ({ row }) => {
+ const config = row.configs?.find(
+ (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'TRANSFER',
+ );
+ return h(Switch, {
+ checked: config?.approvalEnabled ?? false,
+ checkedChildren: '寮�',
+ unCheckedChildren: '鍏�',
+ onChange: (checked: boolean) => {
+ handleChange(row, 'TRANSFER', checked);
+ },
+ });
+ },
+ },
+ },
+ {
+ title: '鐩樼偣瀹℃壒',
+ width: 100,
+ align: 'center',
+ slots: {
+ default: ({ row }) => {
+ const config = row.configs?.find(
+ (c: WmsApprovalApi.ApprovalConfig) => c.bizType === 'CHECK',
+ );
+ return h(Switch, {
+ checked: config?.approvalEnabled ?? false,
+ checkedChildren: '寮�',
+ unCheckedChildren: '鍏�',
+ onChange: (checked: boolean) => {
+ handleChange(row, 'CHECK', checked);
+ },
+ });
+ },
+ },
+ },
+ {
+ title: '娴佺▼閰嶇疆',
+ minWidth: 200,
+ slots: {
+ default: ({ row }) => {
+ const configs = row.configs || [];
+ if (configs.length === 0) {
+ return h(Tag, { color: 'default' }, () => '鏈厤缃�');
+ }
+ const enabledConfigs = configs.filter(
+ (c: WmsApprovalApi.ApprovalConfig) => c.approvalEnabled,
+ );
+ if (enabledConfigs.length === 0) {
+ return h(Tag, { color: 'default' }, () => '鏈厤缃�');
+ }
+ return h(
+ 'div',
+ { class: 'flex flex-wrap gap-1' },
+ enabledConfigs.map((config: WmsApprovalApi.ApprovalConfig) => {
+ const option = BIZ_TYPE_OPTIONS.find(
+ (o) => o.value === config.bizType,
+ );
+ return h(
+ Tag,
+ {
+ color: config.processDefinitionKey ? 'green' : 'orange',
+ },
+ () => option?.label || config.bizType,
+ );
+ }),
+ );
+ },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 100,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 娴佺▼閰嶇疆琛ㄥ崟 */
+export function useProcessFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'bizType',
+ label: '涓氬姟绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: BIZ_TYPE_OPTIONS,
+ placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'processDefinitionKey',
+ label: '瀹℃壒娴佺▼',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ labelField: 'name',
+ valueField: 'key',
+ placeholder: '璇烽�夋嫨瀹℃壒娴佺▼',
+ },
+ rules: 'required',
+ },
+ ];
+}
\ No newline at end of file
diff --git a/src/views/wls/approval/config/index.vue b/src/views/wls/approval/config/index.vue
new file mode 100644
index 0000000..667edfd
--- /dev/null
+++ b/src/views/wls/approval/config/index.vue
@@ -0,0 +1,113 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { WmsApprovalApi } from '#/api/mes/wm/approval';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { saveApprovalConfig } from '#/api/mes/wm/approval';
+
+import ProcessConfigForm from './modules/process-config-form.vue';
+import { useGridColumns, useGridFormSchema } from './data';
+
+defineOptions({ name: 'WlsApprovalConfig' });
+
+const [ProcessConfigModal, processConfigModalApi] = useVbenModal({
+ connectedComponent: ProcessConfigForm,
+ destroyOnClose: true,
+});
+
+/** 浠撳簱閰嶇疆鍒楄〃 */
+const warehouseConfigList = ref<any[]>([]);
+
+/** 瀹℃壒寮�鍏冲彉鏇� */
+async function handleApprovalChange(
+ row: any,
+ bizType: string,
+ enabled: boolean,
+) {
+ try {
+ await saveApprovalConfig({
+ warehouseId: row.warehouseId,
+ bizType: bizType as WmsApprovalApi.BizType,
+ approvalEnabled: enabled,
+ });
+ // 鏇存柊鏈湴鏁版嵁
+ const config = row.configs?.find(
+ (c: WmsApprovalApi.ApprovalConfig) => c.bizType === bizType,
+ );
+ if (config) {
+ config.approvalEnabled = enabled;
+ } else {
+ row.configs = row.configs || [];
+ row.configs.push({
+ warehouseId: row.warehouseId,
+ bizType,
+ approvalEnabled: enabled,
+ });
+ }
+ message.success('閰嶇疆淇濆瓨鎴愬姛');
+ } catch {
+ gridApi.query();
+ }
+}
+
+/** 閰嶇疆娴佺▼ */
+function handleConfigProcess(row: any) {
+ processConfigModalApi.setData({ warehouseId: row.warehouseId, warehouseName: row.warehouseName }).open();
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(handleApprovalChange),
+ height: 'auto',
+ keepSource: true,
+ data: warehouseConfigList,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ // TODO: 璋冪敤鍚庣鎺ュ彛鑾峰彇鎵�鏈変粨搴撳強鍏跺鎵归厤缃�
+ return { list: [], total: 0 };
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'warehouseId',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <ProcessConfigModal @success="gridApi.query()" />
+
+ <Grid table-title="瀹℃壒閰嶇疆">
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '閰嶇疆娴佺▼',
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-approval:save'],
+ onClick: handleConfigProcess.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/approval/config/modules/process-config-form.vue b/src/views/wls/approval/config/modules/process-config-form.vue
new file mode 100644
index 0000000..d041fec
--- /dev/null
+++ b/src/views/wls/approval/config/modules/process-config-form.vue
@@ -0,0 +1,113 @@
+<script lang="ts" setup>
+import type { WmsApprovalApi } from '#/api/mes/wm/approval';
+
+import { computed, ref, watch } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ getAvailableProcessList,
+ getApprovalConfig,
+ saveApprovalConfig,
+} from '#/api/mes/wm/approval';
+
+import { BIZ_TYPE_OPTIONS, useProcessFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [] }>();
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ },
+ layout: 'horizontal',
+ schema: useProcessFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const values = await formApi.getValues();
+ if (!values.bizType) {
+ message.error('璇烽�夋嫨涓氬姟绫诲瀷');
+ return;
+ }
+ if (!values.processDefinitionKey) {
+ message.error('璇烽�夋嫨瀹℃壒娴佺▼');
+ return;
+ }
+
+ await saveApprovalConfig({
+ warehouseId: values.warehouseId,
+ bizType: values.bizType,
+ approvalEnabled: true,
+ processDefinitionKey: values.processDefinitionKey,
+ });
+ message.success('閰嶇疆淇濆瓨鎴愬姛');
+ emit('success');
+ modalApi.close();
+ },
+ onOpenChange(isOpen) {
+ if (isOpen) {
+ const data = modalApi.getData() as {
+ warehouseId: number;
+ warehouseName: string;
+ };
+ if (data) {
+ formApi.setValues({
+ warehouseId: data.warehouseId,
+ warehouseName: data.warehouseName,
+ bizType: undefined,
+ processDefinitionKey: undefined,
+ });
+ }
+ }
+ },
+});
+
+/** 褰撳墠閫変腑鐨勪笟鍔$被鍨� */
+const currentBizType = computed(() => {
+ const values = formApi.getValues();
+ return values.bizType;
+});
+
+/** 鐩戝惉涓氬姟绫诲瀷鍙樺寲锛屽姞杞藉搴旂殑娴佺▼鍒楄〃 */
+watch(currentBizType, async (bizType) => {
+ if (bizType) {
+ const data = modalApi.getData() as { warehouseId: number };
+ if (data?.warehouseId) {
+ try {
+ const config = await getApprovalConfig(data.warehouseId, bizType);
+ if (config) {
+ formApi.setValues({
+ processDefinitionKey: config.processDefinitionKey,
+ });
+ }
+ } catch {
+ // 蹇界暐閿欒
+ }
+
+ formApi.updateSchema([
+ {
+ fieldName: 'processDefinitionKey',
+ componentProps: {
+ api: async () => {
+ return await getAvailableProcessList(data.warehouseId, bizType);
+ },
+ },
+ },
+ ]);
+ }
+ }
+});
+</script>
+
+<template>
+ <Modal title="閰嶇疆瀹℃壒娴佺▼" class="w-[500px]">
+ <Form />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/mes/wm/arrivalnotice/components/index.ts b/src/views/wls/arrivalnotice/components/index.ts
similarity index 100%
copy from src/views/mes/wm/arrivalnotice/components/index.ts
copy to src/views/wls/arrivalnotice/components/index.ts
diff --git a/src/views/wls/arrivalnotice/components/line-select-dialog.vue b/src/views/wls/arrivalnotice/components/line-select-dialog.vue
new file mode 100644
index 0000000..350c4f4
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/line-select-dialog.vue
@@ -0,0 +1,203 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getArrivalNoticeLinePage } from '#/api/mes/wm/arrivalnotice/line';
+
+const emit = defineEmits<{
+ selected: [rows: MesWmArrivalNoticeLineApi.ArrivalNoticeLine[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+const syncingSingleSelection = ref(false); // 鏄惁鍚屾鍗曢�夊嬀閫夌姸鎬�
+const selectedRows = ref<MesWmArrivalNoticeLineApi.ArrivalNoticeLine[]>([]); // 宸查�夎鍒楄〃
+const preSelectedIds = ref<number[]>([]); // 棰勯�夎缂栧彿鍒楄〃
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(): VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>['columns'] {
+ return [
+ {
+ type: 'checkbox',
+ width: 50,
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'arrivalQuantity',
+ title: '鍒拌揣鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'iqcCheckFlag',
+ title: '鏄惁妫�楠�',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'qualifiedQuantity',
+ title: '鍚堟牸鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 120,
+ },
+ ];
+}
+
+/** 鍗曢�夋ā寮忎笅鍚屾 VXE 鍕鹃�夌姸鎬� */
+async function syncSingleSelection(
+ row?: MesWmArrivalNoticeLineApi.ArrivalNoticeLine,
+) {
+ syncingSingleSelection.value = true;
+ await nextTick();
+ await gridApi.grid.clearCheckboxRow();
+ if (row) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ }
+ await nextTick();
+ syncingSingleSelection.value = false;
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖栵紝鍙繚鐣欐渶鍚庝竴鏉� */
+async function handleCheckboxChange({
+ checked,
+ row,
+}: {
+ checked: boolean;
+ row?: MesWmArrivalNoticeLineApi.ArrivalNoticeLine;
+}) {
+ if (syncingSingleSelection.value) {
+ return;
+ }
+ const selected = checked && row ? [row] : [];
+ selectedRows.value = selected;
+ await syncSingleSelection(selected[0]);
+}
+
+/** 鍥炴樉棰勯�夎 */
+function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows =
+ gridApi.grid.getData() as MesWmArrivalNoticeLineApi.ArrivalNoticeLine[];
+ for (const row of rows) {
+ if (row.id && preSelectedIds.value.includes(row.id)) {
+ gridApi.grid.setCheckboxRow(row, true);
+ selectedRows.value = [row];
+ }
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 460,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ reserve: true,
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!noticeId.value) {
+ return { list: [], total: 0 };
+ }
+ return await getArrivalNoticeLinePage({
+ noticeId: noticeId.value,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>,
+ gridEvents: {
+ checkboxChange: handleCheckboxChange,
+ },
+});
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+async function openModal(id: number | undefined, selectedIds?: number[]) {
+ open.value = true;
+ noticeId.value = id;
+ preSelectedIds.value = selectedIds || [];
+ selectedRows.value = [];
+ await nextTick();
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.query();
+ await nextTick();
+ applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+async function closeModal() {
+ open.value = false;
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+}
+
+/** 纭閫夋嫨琛� */
+function handleConfirm() {
+ if (selectedRows.value.length === 0) {
+ message.warning('璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit('selected', [selectedRows.value[0]!]);
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ title="鍒拌揣閫氱煡鍗曡閫夋嫨"
+ width="70%"
+ :destroy-on-close="true"
+ @cancel="closeModal"
+ @ok="handleConfirm"
+ >
+ <Grid table-title="鍒拌揣閫氱煡鍗曡鍒楄〃" />
+ </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/components/line-select.vue b/src/views/wls/arrivalnotice/components/line-select.vue
new file mode 100644
index 0000000..bd7296b
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/line-select.vue
@@ -0,0 +1,153 @@
+<script lang="ts" setup>
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getArrivalNoticeLine } from '#/api/mes/wm/arrivalnotice/line';
+
+import WmArrivalNoticeLineSelectDialog from './line-select-dialog.vue';
+
+defineOptions({ name: 'WmArrivalNoticeLineSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ noticeId?: number; // 鎵�灞炲埌璐ч�氱煡鍗曠紪鍙�
+ placeholder?: string;
+ }>(),
+ {
+ allowClear: true,
+ disabled: false,
+ modelValue: undefined,
+ noticeId: undefined,
+ placeholder: '璇烽�夋嫨鍒拌揣閫氱煡鍗曡',
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmArrivalNoticeLineApi.ArrivalNoticeLine | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmArrivalNoticeLineSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>();
+
+const displayLabel = computed(() => {
+ const item = selectedItem.value;
+ if (!item) {
+ return '';
+ }
+ return `${item.itemCode ?? ''} - ${item.itemName ?? ''}`;
+});
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ琛屼俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getArrivalNoticeLine(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** noticeId 鍙樺寲鏃舵竻绌洪�変腑锛堝叧鑱旂殑琛屽凡澶辨晥锛� */
+watch(
+ () => props.noticeId,
+ () => {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+ },
+);
+
+/** 娓呯┖宸查�夎 */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled || !props.noticeId) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(props.noticeId, selectedIds);
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmArrivalNoticeLineApi.ArrivalNoticeLine[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>鐗╂枡缂栫爜锛歿{ selectedItem.itemCode || '-' }}</div>
+ <div>鐗╂枡鍚嶇О锛歿{ selectedItem.itemName || '-' }}</div>
+ <div>瑙勬牸鍨嬪彿锛歿{ selectedItem.specification || '-' }}</div>
+ <div>鍒拌揣鏁伴噺锛歿{ selectedItem.arrivalQuantity ?? '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <WmArrivalNoticeLineSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/arrivalnotice/components/select-dialog.vue b/src/views/wls/arrivalnotice/components/select-dialog.vue
new file mode 100644
index 0000000..944301b
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/select-dialog.vue
@@ -0,0 +1,288 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getArrivalNoticePage } from '#/api/mes/wm/arrivalnotice';
+
+const emit = defineEmits<{
+ selected: [rows: MesWmArrivalNoticeApi.ArrivalNotice[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(false); // 鏄惁澶氶��
+const fixedStatus = ref<number>(); // 鍥哄畾鐘舵�佺瓫閫�
+const selectedRows = ref<MesWmArrivalNoticeApi.ArrivalNotice[]>([]); // 宸查�夐�氱煡鍗曞垪琛�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夐�氱煡鍗曠紪鍙峰垪琛�
+
+/** 鎼滅储琛ㄥ崟 */
+function useSearchSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '閫氱煡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '閫氱煡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+ },
+ },
+ {
+ fieldName: 'purchaseOrderCode',
+ label: '閲囪喘璁㈠崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�',
+ },
+ },
+ ];
+}
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(
+ multipleSelect = false,
+): VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>['columns'] {
+ return [
+ {
+ type: multipleSelect ? 'checkbox' : 'radio',
+ width: 50,
+ },
+ {
+ field: 'code',
+ title: '閫氱煡鍗曠紪鍙�',
+ minWidth: 160,
+ },
+ {
+ field: 'name',
+ title: '閫氱煡鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'purchaseOrderCode',
+ title: '閲囪喘璁㈠崟缂栧彿',
+ minWidth: 140,
+ },
+ {
+ field: 'vendorName',
+ title: '渚涘簲鍟嗗悕绉�',
+ minWidth: 120,
+ },
+ {
+ field: 'arrivalDate',
+ title: '鍒拌揣鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_ARRIVAL_NOTICE_STATUS },
+ },
+ },
+ ];
+}
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<number, MesWmArrivalNoticeApi.ArrivalNotice>();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as MesWmArrivalNoticeApi.ArrivalNotice[];
+ records.forEach((row) => {
+ const rowId = row.id;
+ if (rowId !== undefined) {
+ selectedMap.set(rowId, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+/** 澶勭悊澶氶�夊嬀閫夊彉鍖� */
+function handleCheckboxSelectChange() {
+ if (!multiple.value) {
+ return;
+ }
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊垏鎹� */
+function handleRadioChange(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+ selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紝澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({
+ row,
+}: {
+ row: MesWmArrivalNoticeApi.ArrivalNotice;
+}) {
+ if (multiple.value) {
+ await toggleMultipleRow(row);
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夐�氱煡鍗� */
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows = gridApi.grid.getData() as MesWmArrivalNoticeApi.ArrivalNotice[];
+ for (const row of rows) {
+ if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
+ continue;
+ }
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useSearchSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(false),
+ height: 520,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ range: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getArrivalNoticePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ status: fixedStatus.value,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({ row }: { row: MesWmArrivalNoticeApi.ArrivalNotice }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+ await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean; status?: number },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? false;
+ fixedStatus.value = options?.status;
+ preSelectedIds.value = selectedIds || [];
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: useGridColumns(multiple.value),
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+/** 鍏抽棴閫氱煡鍗曢�夋嫨寮圭獥 */
+async function closeModal() {
+ open.value = false;
+ await resetQueryState();
+}
+
+/** 纭閫夋嫨閫氱煡鍗� */
+function handleConfirm() {
+ if (selectedRows.value.length === 0) {
+ message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit(
+ 'selected',
+ multiple.value ? selectedRows.value : [selectedRows.value[0]!],
+ );
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ title="鍒拌揣閫氱煡鍗曢�夋嫨"
+ width="70%"
+ :destroy-on-close="true"
+ @cancel="closeModal"
+ @ok="handleConfirm"
+ >
+ <Grid table-title="鍒拌揣閫氱煡鍗曞垪琛�" />
+ </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/components/select.vue b/src/views/wls/arrivalnotice/components/select.vue
new file mode 100644
index 0000000..51b0876
--- /dev/null
+++ b/src/views/wls/arrivalnotice/components/select.vue
@@ -0,0 +1,141 @@
+<script lang="ts" setup>
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getArrivalNotice } from '#/api/mes/wm/arrivalnotice';
+
+import WmArrivalNoticeSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmArrivalNoticeSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ placeholder?: string;
+ status?: number; // 鍥哄畾鐘舵�佺瓫閫�
+ }>(),
+ {
+ allowClear: true,
+ disabled: false,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨鍒拌揣閫氱煡鍗�',
+ status: undefined,
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmArrivalNoticeApi.ArrivalNotice | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmArrivalNoticeSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmArrivalNoticeApi.ArrivalNotice>();
+
+const displayLabel = computed(() => selectedItem.value?.code ?? '');
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== undefined &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ閫氱煡鍗曚俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getArrivalNotice(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夐�氱煡鍗� */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, {
+ multiple: false,
+ status: props.status,
+ });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmArrivalNoticeApi.ArrivalNotice[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+ <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+ <div>渚涘簲鍟嗭細{{ selectedItem.vendorName || '-' }}</div>
+ <div>閲囪喘璁㈠崟锛歿{ selectedItem.purchaseOrderCode || '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <WmArrivalNoticeSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/arrivalnotice/data.ts b/src/views/wls/arrivalnotice/data.ts
new file mode 100644
index 0000000..bbed3bb
--- /dev/null
+++ b/src/views/wls/arrivalnotice/data.ts
@@ -0,0 +1,354 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { ErpPurchaseOrderSelect } from '#/views/erp/purchase/order/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '閫氱煡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+ },
+ rules: 'required',
+ suffix:
+ formType === 'detail'
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_ARRIVAL_NOTICE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '閫氱煡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'purchaseOrderId',
+ label: '閲囪喘璁㈠崟',
+ component: markRaw(ErpPurchaseOrderSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨閲囪喘璁㈠崟',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'arrivalDate',
+ label: '鍒拌揣鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍒拌揣鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'contactName',
+ label: '鑱旂郴浜�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ仈绯讳汉',
+ },
+ },
+ {
+ fieldName: 'contactTelephone',
+ label: '鑱旂郴鏂瑰紡',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ仈绯绘柟寮�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '閫氱煡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '閫氱煡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+ },
+ },
+ {
+ fieldName: 'purchaseOrderNo',
+ label: '閲囪喘璁㈠崟鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ噰璐鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'vendorName',
+ label: '渚涘簲鍟�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヤ緵搴斿晢鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'arrivalDate',
+ label: '鍒拌揣鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '閫氱煡鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '閫氱煡鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'purchaseOrderNo',
+ title: '閲囪喘璁㈠崟鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'vendorName',
+ title: '渚涘簲鍟嗗悕绉�',
+ minWidth: 120,
+ },
+ {
+ field: 'contactName',
+ title: '鑱旂郴浜�',
+ minWidth: 100,
+ },
+ {
+ field: 'contactTelephone',
+ title: '鑱旂郴鏂瑰紡',
+ minWidth: 120,
+ },
+ {
+ field: 'arrivalDate',
+ title: '鍒拌揣鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_ARRIVAL_NOTICE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 180,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 閫氱煡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+): VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>['columns'] {
+ return [
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'arrivalQuantity',
+ title: '鍒拌揣鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'iqcCheckFlag',
+ title: '鏄惁妫�楠�',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'qualifiedQuantity',
+ title: '鍚堟牸鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'iqcCode',
+ title: '妫�楠屽崟鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 120,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 閫氱煡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'arrivalQuantity',
+ label: '鍒拌揣鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0.01,
+ placeholder: '璇疯緭鍏ュ埌璐ф暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'iqcCheckFlag',
+ label: '鏄惁妫�楠�',
+ component: 'Switch',
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/arrivalnotice/index.vue b/src/views/wls/arrivalnotice/index.vue
new file mode 100644
index 0000000..df90182
--- /dev/null
+++ b/src/views/wls/arrivalnotice/index.vue
@@ -0,0 +1,179 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmArrivalNoticeStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteArrivalNotice,
+ exportArrivalNotice,
+ getArrivalNoticePage,
+} from '#/api/mes/wm/arrivalnotice';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鍒拌揣閫氱煡鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鍒拌揣閫氱煡鍗� */
+function handleDetail(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鍒拌揣閫氱煡鍗� */
+function handleEdit(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎鍒拌揣閫氱煡鍗� */
+async function handleDelete(row: MesWmArrivalNoticeApi.ArrivalNotice) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteArrivalNotice(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鎻愮ず鍓嶅線璐ㄦ妯″潡 */
+function handleQc() {
+ message.info('璇峰墠寰�銆愯川閲忕鐞� - 寰呮浠诲姟銆戜腑杩涜鏉ユ枡妫�楠屾搷浣�');
+}
+
+/** 鎻愮ず鍓嶅線閲囪喘鍏ュ簱妯″潡 */
+function handleReceipt() {
+ message.info('璇峰墠寰�銆愪粨搴撶鐞� - 閲囪喘鍏ュ簱銆戜腑杩涜鍏ュ簱鎿嶄綔');
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportArrivalNotice(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鍒拌揣閫氱煡鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getArrivalNoticePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmArrivalNoticeApi.ArrivalNotice>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鍒拌揣閫氱煡鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鍒拌揣閫氱煡鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-arrival-notice:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-arrival-notice:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-arrival-notice:update'],
+ ifShow: row.status === MesWmArrivalNoticeStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-arrival-notice:delete'],
+ ifShow: row.status === MesWmArrivalNoticeStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц璐ㄦ',
+ type: 'link',
+ ifShow: row.status === MesWmArrivalNoticeStatusEnum.PENDING_QC,
+ onClick: handleQc,
+ },
+ {
+ label: '鎵ц鍏ュ簱',
+ type: 'link',
+ ifShow:
+ row.status === MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT,
+ onClick: handleReceipt,
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/arrivalnotice/modules/form.vue b/src/views/wls/arrivalnotice/modules/form.vue
new file mode 100644
index 0000000..a736294
--- /dev/null
+++ b/src/views/wls/arrivalnotice/modules/form.vue
@@ -0,0 +1,171 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmArrivalNoticeStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createArrivalNotice,
+ getArrivalNotice,
+ submitArrivalNotice,
+ updateArrivalNotice,
+} from '#/api/mes/wm/arrivalnotice';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmArrivalNoticeApi.ArrivalNotice>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmArrivalNoticeStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['鍒拌揣閫氱煡鍗�']);
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['鍒拌揣閫氱煡鍗�'])
+ : $t('ui.actionTitle.create', ['鍒拌揣閫氱煡鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫氱煡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmArrivalNoticeApi.ArrivalNotice;
+ await updateArrivalNotice({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitArrivalNotice(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmArrivalNoticeApi.ArrivalNotice;
+ try {
+ if (formData.value?.id) {
+ await updateArrivalNotice({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createArrivalNotice(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmArrivalNoticeStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getArrivalNotice(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :notice-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ュ埌璐ч�氱煡鍗曪紵銆愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/modules/line-form.vue b/src/views/wls/arrivalnotice/modules/line-form.vue
new file mode 100644
index 0000000..518d16a
--- /dev/null
+++ b/src/views/wls/arrivalnotice/modules/line-form.vue
@@ -0,0 +1,94 @@
+<script lang="ts" setup>
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createArrivalNoticeLine,
+ getArrivalNoticeLine,
+ updateArrivalNoticeLine,
+} from '#/api/mes/wm/arrivalnotice/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>();
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鍒拌揣閫氱煡鍗曡'])
+ : $t('ui.actionTitle.create', ['鍒拌揣閫氱煡鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmArrivalNoticeLineApi.ArrivalNoticeLine;
+ data.noticeId = noticeId.value;
+ try {
+ await (formData.value?.id
+ ? updateArrivalNoticeLine({ ...data, id: formData.value.id })
+ : createArrivalNoticeLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; noticeId: number }>();
+ noticeId.value = data.noticeId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getArrivalNoticeLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/arrivalnotice/modules/line-list.vue b/src/views/wls/arrivalnotice/modules/line-list.vue
new file mode 100644
index 0000000..cea373f
--- /dev/null
+++ b/src/views/wls/arrivalnotice/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteArrivalNoticeLine,
+ getArrivalNoticeLinePage,
+} from '#/api/mes/wm/arrivalnotice/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ noticeId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ noticeId: props.noticeId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmArrivalNoticeLineApi.ArrivalNoticeLine) {
+ lineFormModalApi.setData({ id: row.id, noticeId: props.noticeId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmArrivalNoticeLineApi.ArrivalNoticeLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteArrivalNoticeLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value),
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.noticeId) {
+ return { list: [], total: 0 };
+ }
+ return await getArrivalNoticeLinePage({
+ noticeId: props.noticeId,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmArrivalNoticeLineApi.ArrivalNoticeLine>,
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/barcode/components/barcode.vue b/src/views/wls/barcode/components/barcode.vue
new file mode 100644
index 0000000..441415f
--- /dev/null
+++ b/src/views/wls/barcode/components/barcode.vue
@@ -0,0 +1,51 @@
+<script lang="ts" setup>
+import type { BarcodeFormat } from '../../../../packages/effects/common-ui/src';
+
+import { computed, ref } from 'vue';
+
+import { BarcodeFormatEnum, Barcode as CommonBarcode } from '../../../../packages/effects/common-ui/src';
+
+defineOptions({ name: 'WlsBarcode' });
+
+const props = withDefaults(
+ defineProps<{
+ content?: string;
+ displayValue?: boolean;
+ format?: number;
+ height?: number;
+ width?: number;
+ }>(),
+ {
+ content: '',
+ displayValue: true,
+ format: BarcodeFormatEnum.QR_CODE,
+ height: 100,
+ width: 200,
+ },
+);
+
+const emit = defineEmits<{ done: [value: string] }>();
+
+const barcodeRef = ref<InstanceType<typeof CommonBarcode>>();
+const commonFormat = computed<BarcodeFormat>(
+ () => (props.format || BarcodeFormatEnum.QR_CODE) as BarcodeFormat,
+);
+
+function getImageBase64() {
+ return barcodeRef.value?.getImageBase64() || '';
+}
+
+defineExpose({ getImageBase64 });
+</script>
+
+<template>
+ <CommonBarcode
+ ref="barcodeRef"
+ :content="content"
+ :display-value="displayValue"
+ :format="commonFormat"
+ :height="height"
+ :width="width"
+ @done="emit('done', $event)"
+ />
+</template>
diff --git a/src/views/wls/barcode/components/detail.vue b/src/views/wls/barcode/components/detail.vue
new file mode 100644
index 0000000..17a9f37
--- /dev/null
+++ b/src/views/wls/barcode/components/detail.vue
@@ -0,0 +1,169 @@
+<script lang="ts" setup>
+import type Barcode from './barcode.vue';
+
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+
+import { ref } from 'vue';
+
+import { Button, Empty, message, Modal, Tooltip } from 'ant-design-vue';
+
+import { createBarcode, getBarcodeByBusiness } from '#/api/mes/wm/barcode';
+import { useDescription } from '#/components/description';
+
+import { useBarcodeDetailSchema } from '../data';
+import WlsBarcode from './barcode.vue';
+
+defineOptions({ name: 'WlsBarcodeDetail' });
+
+const open = ref(false);
+const barcodeRef = ref<InstanceType<typeof Barcode>>();
+const barcodeData = ref<Partial<MesWmBarcodeApi.Barcode>>({});
+
+const [Descriptions] = useDescription({
+ bordered: true,
+ column: 1,
+ schema: useBarcodeDetailSchema(),
+ useCard: false,
+});
+
+function openModal(row: Partial<MesWmBarcodeApi.Barcode>) {
+ open.value = true;
+ barcodeData.value = { ...row };
+}
+
+async function openByBusiness(
+ bizId: number,
+ bizType: number,
+ bizCode?: string,
+ bizName?: string,
+) {
+ open.value = true;
+ try {
+ const data = await getBarcodeByBusiness(bizType, bizId);
+ barcodeData.value = data || {
+ bizCode,
+ bizId,
+ bizName,
+ bizType,
+ content: '',
+ };
+ if (!data) {
+ message.warning('鏈壘鍒板搴旀潯鐮佹暟鎹�');
+ }
+ } catch {
+ barcodeData.value = { bizCode, bizId, bizName, bizType, content: '' };
+ message.error('鍔犺浇鏉$爜鏁版嵁澶辫触');
+ }
+}
+
+defineExpose({ open: openModal, openByBusiness });
+
+function escapeHtml(value: string) {
+ return value
+ .replaceAll('&', '&')
+ .replaceAll('<', '<')
+ .replaceAll('>', '>');
+}
+
+function handlePrint() {
+ const base64 = barcodeRef.value?.getImageBase64();
+ if (!base64) {
+ message.warning('鏉$爜鐢熸垚澶辫触锛屾棤娉曟墦鍗�');
+ return;
+ }
+ const printWindow = window.open('', '_blank');
+ if (!printWindow) {
+ message.error('鏃犳硶鎵撳紑鎵撳嵃绐楀彛锛岃妫�鏌ユ祻瑙堝櫒璁剧疆');
+ return;
+ }
+ printWindow.document.write(`<!DOCTYPE html>
+<html><head><meta charset="UTF-8"><title>鎵撳嵃鏉$爜</title>
+<style>*{margin:0;padding:0}body{font-family:Arial,sans-serif;padding:20px}.print-container{text-align:center}.barcode-img{max-width:100%;margin:20px 0}.info{margin-top:20px;text-align:left;font-size:12px}.info p{margin:5px 0}@media print{body{padding:0}.print-container{padding:20px}}</style>
+</head><body><div class="print-container">
+<img src="${base64}" class="barcode-img" alt="鏉$爜" />
+<div class="info">
+<p><strong>涓氬姟缂栫爜:</strong> ${escapeHtml(barcodeData.value.bizCode || '')}</p>
+<p><strong>涓氬姟鍚嶇О:</strong> ${escapeHtml(barcodeData.value.bizName || '')}</p>
+<p><strong>鏉$爜鍐呭:</strong> ${escapeHtml(barcodeData.value.content || '')}</p>
+</div></div></body></html>`);
+ printWindow.document.close();
+ printWindow.addEventListener('load', () => {
+ setTimeout(() => printWindow.print(), 500);
+ });
+}
+
+function handleDownload() {
+ const base64 = barcodeRef.value?.getImageBase64();
+ if (!base64) {
+ message.warning('鏉$爜鐢熸垚澶辫触锛屾棤娉曚笅杞�');
+ return;
+ }
+ const link = document.createElement('a');
+ link.href = base64;
+ link.download = `barcode_${barcodeData.value.bizCode || 'unknown'}_${Date.now()}.png`;
+ link.click();
+ message.success('涓嬭浇鎴愬姛');
+}
+
+async function handleGenerate() {
+ const { bizCode, bizId, bizName, bizType } = barcodeData.value;
+ if (!bizType || !bizId) {
+ message.warning('缂哄皯涓氬姟绫诲瀷鎴栦笟鍔$紪鍙凤紝鏃犳硶鐢熸垚鏉$爜');
+ return;
+ }
+ await createBarcode({
+ bizCode: bizCode || '',
+ bizId,
+ bizName: bizName || '',
+ bizType,
+ });
+ message.success('鏉$爜鐢熸垚鎴愬姛');
+ const data = await getBarcodeByBusiness(bizType, bizId);
+ if (data) {
+ barcodeData.value = { ...data };
+ }
+}
+</script>
+
+<template>
+ <Modal v-model:open="open" title="鏌ョ湅鏉$爜" width="500px">
+ <div>
+ <div
+ class="mb-5 flex min-h-50 items-center justify-center rounded bg-gray-100 p-5"
+ >
+ <div
+ v-if="barcodeData.content"
+ class="flex items-center justify-center"
+ >
+ <WlsBarcode
+ ref="barcodeRef"
+ :content="barcodeData.content"
+ :format="barcodeData.format"
+ :height="150"
+ :width="400"
+ />
+ </div>
+ <Empty v-else description="鏆傛棤鏉$爜鏁版嵁" />
+ </div>
+ <Descriptions :data="barcodeData">
+ <template #content>
+ <Tooltip :title="barcodeData.content">
+ <span
+ class="inline-block max-w-75 overflow-hidden text-ellipsis whitespace-nowrap"
+ >
+ {{ barcodeData.content }}
+ </span>
+ </Tooltip>
+ </template>
+ </Descriptions>
+ </div>
+ <template #footer>
+ <Button v-if="!barcodeData.content" @click="handleGenerate">
+ 鐢熸垚
+ </Button>
+ <Button type="primary" @click="handlePrint">鎵撳嵃</Button>
+ <Button @click="handleDownload">涓嬭浇</Button>
+ <Button @click="open = false">鍏抽棴</Button>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/barcode/components/index.ts b/src/views/wls/barcode/components/index.ts
similarity index 100%
copy from src/views/mes/wm/barcode/components/index.ts
copy to src/views/wls/barcode/components/index.ts
diff --git a/src/views/wls/barcode/components/printer-label.vue b/src/views/wls/barcode/components/printer-label.vue
new file mode 100644
index 0000000..7fbeee5
--- /dev/null
+++ b/src/views/wls/barcode/components/printer-label.vue
@@ -0,0 +1,30 @@
+<script lang="ts" setup>
+import { Button, message } from 'ant-design-vue';
+
+defineOptions({ name: 'WlsPrinterLabel' });
+
+withDefaults(
+ defineProps<{
+ bizCode?: string;
+ bizId?: number;
+ bizType?: string;
+ labelText?: string;
+ }>(),
+ {
+ bizCode: undefined,
+ bizId: undefined,
+ bizType: undefined,
+ labelText: '鏍囩鎵撳嵃',
+ },
+);
+
+function handlePrint() {
+ message.warning('鏍囩鎵撳嵃鍔熻兘鏆傛湭瀹炵幇锛屾暚璇锋湡寰�');
+}
+</script>
+
+<template>
+ <Button type="link" @click="handlePrint">
+ {{ labelText }}
+ </Button>
+</template>
diff --git a/src/views/wls/barcode/config/data.ts b/src/views/wls/barcode/config/data.ts
new file mode 100644
index 0000000..08d8dee
--- /dev/null
+++ b/src/views/wls/barcode/config/data.ts
@@ -0,0 +1,114 @@
+import type { VbenFormSchema } from '#/adapter/form';
+
+import { h } from 'vue';
+
+import { CommonStatusEnum, DICT_TYPE } from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+
+/** 琛ㄥ崟 */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'format',
+ label: '鏉$爜鏍煎紡',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
+ placeholder: '璇烽�夋嫨鏉$爜鏍煎紡',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizType',
+ label: '涓氬姟绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+ },
+ dependencies: {
+ triggerFields: ['id'],
+ componentProps: (values) => ({
+ disabled: !!values.id, // 缂栬緫鏃朵笟鍔$被鍨嬩笉鍏佽鍙樻洿
+ }),
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'contentFormat',
+ label: '鍐呭鏍煎紡妯℃澘',
+ component: 'Input',
+ componentProps: {
+ placeholder: '鏀寔{BUSINESSCODE}鍗犱綅绗︼紝濡傦細WH-{BUSINESSCODE}',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'contentExample',
+ label: '鍐呭鏍蜂緥',
+ component: 'Input',
+ componentProps: {
+ placeholder: '濡傦細WH-WH001',
+ },
+ },
+ {
+ fieldName: 'autoGenerateFlag',
+ label: '鑷姩鐢熸垚',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(true),
+ },
+ {
+ fieldName: 'defaultTemplate',
+ label: '榛樿鎵撳嵃妯℃澘',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇烽�夋嫨鎵撳嵃妯℃澘',
+ readonly: true,
+ },
+ // TODO @鑺嬭壙锛氬悗缁鎺� UReport 鎶ヨ〃閫夋嫨鍣紝瀹炵幇鎵撳嵃妯℃澘閫夋嫨鍔熻兘
+ suffix: () =>
+ h(
+ Button,
+ {
+ onClick: () =>
+ message.warning('鎵撳嵃妯℃澘閫夋嫨鍔熻兘鏆傛湭瀹炵幇锛屾暚璇锋湡寰�'),
+ },
+ { default: () => '璁剧疆' },
+ ),
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+ },
+ rules: z.number().default(CommonStatusEnum.ENABLE),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 2,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/barcode/config/index.vue b/src/views/wls/barcode/config/index.vue
new file mode 100644
index 0000000..5e12679
--- /dev/null
+++ b/src/views/wls/barcode/config/index.vue
@@ -0,0 +1,150 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
+
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteBarcodeConfig,
+ getBarcodeConfigPage,
+ updateBarcodeConfig,
+} from '#/api/mes/wm/barcode/config';
+import { $t } from '#/locales';
+
+import { useConfigGridColumns, useConfigGridFormSchema } from '../data';
+import ConfigForm from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: ConfigForm,
+ destroyOnClose: true,
+});
+
+/** 鑷姩鐢熸垚寮�鍏冲彉鏇� */
+async function handleAutoGenerateChange(
+ row: MesWmBarcodeConfigApi.BarcodeConfig,
+): Promise<boolean> {
+ const text = row.autoGenerateFlag ? '鍚敤' : '鍋滅敤';
+ try {
+ await confirm(`纭瑕�${text}鑷姩鐢熸垚鍚楋紵`);
+ } catch {
+ return false;
+ }
+ await updateBarcodeConfig(row);
+ message.success(`${text}鎴愬姛`);
+ return true;
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鏉$爜閰嶇疆 */
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+/** 缂栬緫鏉$爜閰嶇疆 */
+function handleEdit(row: MesWmBarcodeConfigApi.BarcodeConfig) {
+ formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎鏉$爜閰嶇疆 */
+async function handleDelete(row: MesWmBarcodeConfigApi.BarcodeConfig) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [
+ row.contentFormat || `#${row.id}`,
+ ]),
+ duration: 0,
+ });
+ try {
+ await deleteBarcodeConfig(row.id!);
+ message.success(
+ $t('ui.actionMessage.deleteSuccess', [row.contentFormat || `#${row.id}`]),
+ );
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useConfigGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useConfigGridColumns(handleAutoGenerateChange),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getBarcodeConfigPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鏉$爜閰嶇疆鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鏉$爜閰嶇疆']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-barcode-config:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-barcode-config:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-barcode-config:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [
+ row.contentFormat || `#${row.id}`,
+ ]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/barcode/config/modules/form.vue b/src/views/wls/barcode/config/modules/form.vue
new file mode 100644
index 0000000..cf50143
--- /dev/null
+++ b/src/views/wls/barcode/config/modules/form.vue
@@ -0,0 +1,89 @@
+<script lang="ts" setup>
+import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createBarcodeConfig,
+ getBarcodeConfig,
+ updateBarcodeConfig,
+} from '#/api/mes/wm/barcode/config';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmBarcodeConfigApi.BarcodeConfig>();
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鏉$爜閰嶇疆'])
+ : $t('ui.actionTitle.create', ['鏉$爜閰嶇疆']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-2',
+ labelWidth: 130,
+ },
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmBarcodeConfigApi.BarcodeConfig;
+ try {
+ await (formData.value?.id
+ ? updateBarcodeConfig(data)
+ : createBarcodeConfig(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<MesWmBarcodeConfigApi.BarcodeConfig>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getBarcodeConfig(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-2/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/barcode/data.ts b/src/views/wls/barcode/data.ts
new file mode 100644
index 0000000..abd401c
--- /dev/null
+++ b/src/views/wls/barcode/data.ts
@@ -0,0 +1,735 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+import type { MesWmBarcodeConfigApi } from '#/api/mes/wm/barcode/config';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, markRaw } from 'vue';
+
+import {
+ BarcodeBizTypeEnum,
+ CommonStatusEnum,
+ DICT_TYPE,
+ MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+import { formatDateTime } from '../../../packages/utils/src';
+
+import { z } from '#/adapter/form';
+import { generateBarcodeContent } from '#/api/mes/wm/barcode';
+import { DictTag } from '#/components/dict-tag';
+import { DvMachinerySelect } from '#/views/mes/dv/machinery/components';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import {
+ MdWorkshopSelect,
+ MdWorkstationSelect,
+} from '#/views/mes/md/workstation/components';
+import { ProCardSelect } from '#/views/mes/pro/card/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { TmToolSelect } from '#/views/mes/tm/tool/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import { UserSelect } from '#/views/system/user/components';
+
+import { WmMaterialStockSelect } from '../materialstock/components';
+import { WmPackageSelect } from '../packages/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '../warehouse/components';
+
+/** 涓氬姟瀵硅薄閫変腑鍚庡洖濉笟鍔$紪鐮併�佷笟鍔″悕绉般�佹潯鐮佸唴瀹� */
+async function syncBizDetail(formApi: undefined | VbenFormApi, item: any) {
+ if (!formApi) {
+ return;
+ }
+ const values = await formApi.getValues();
+ const bizType = values.bizType as number | undefined;
+ if (!item) {
+ await formApi.setValues({
+ bizCode: undefined,
+ bizName: undefined,
+ content: undefined,
+ });
+ return;
+ }
+ let bizCode: string | undefined;
+ let bizName: string | undefined;
+ switch (bizType) {
+ case BarcodeBizTypeEnum.BATCH: {
+ bizCode = item.code;
+ bizName = item.itemName || item.code;
+ break;
+ }
+ case BarcodeBizTypeEnum.PACKAGE: {
+ bizCode = item.code;
+ bizName = item.clientName || item.code;
+ break;
+ }
+ case BarcodeBizTypeEnum.PROCARD: {
+ bizCode = item.code;
+ bizName = item.workOrderName || item.code;
+ break;
+ }
+ case BarcodeBizTypeEnum.STOCK: {
+ bizCode = item.itemCode;
+ bizName = item.itemName;
+ break;
+ }
+ default: {
+ bizCode = item.code || item.username;
+ bizName = item.name || item.nickname;
+ }
+ }
+ // 鍏堝洖濉笟鍔$紪鐮併�佸悕绉板苟娓呯┖鏃ф潯鐮佸唴瀹�
+ await formApi.setValues({ bizCode, bizName, content: undefined });
+ // 鍐嶆牴鎹笟鍔$被鍨� + 涓氬姟缂栫爜鐢熸垚鏉$爜鍐呭
+ if (bizType && bizCode) {
+ const content = await generateBarcodeContent(bizType, bizCode);
+ await formApi.setFieldValue('content', content);
+ }
+}
+
+/** 鏂板/淇敼鏉$爜鐨勮〃鍗� */
+export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'bizType',
+ label: '涓氬姟绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ // 涓氬姟绫诲瀷鍙樻洿鏃舵竻绌轰笟鍔″瓧娈�
+ onChange: () =>
+ formApi?.setValues({
+ bizId: undefined,
+ bizCode: undefined,
+ bizName: undefined,
+ content: undefined,
+ locationWarehouseId: undefined,
+ areaWarehouseId: undefined,
+ areaLocationId: undefined,
+ }),
+ options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+ },
+ rules: 'required',
+ },
+ // ==================== 涓氬姟瀵硅薄閫夋嫨鍣紙鎸� bizType 鍒囨崲锛� ====================
+ {
+ fieldName: 'bizId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.WAREHOUSE,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'locationWarehouseId',
+ label: '搴撳尯路浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ onChange: () =>
+ formApi?.setValues({
+ bizId: undefined,
+ bizCode: undefined,
+ bizName: undefined,
+ content: undefined,
+ }),
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
+ },
+ },
+ {
+ fieldName: 'bizId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ dependencies: {
+ triggerFields: ['bizType', 'locationWarehouseId'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.LOCATION,
+ componentProps: (values) => ({
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.locationWarehouseId,
+ }),
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'areaWarehouseId',
+ label: '搴撲綅路浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ onChange: () =>
+ formApi?.setValues({
+ areaLocationId: undefined,
+ bizId: undefined,
+ bizCode: undefined,
+ bizName: undefined,
+ content: undefined,
+ }),
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
+ },
+ },
+ {
+ fieldName: 'areaLocationId',
+ label: '搴撲綅路搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ dependencies: {
+ triggerFields: ['bizType', 'areaWarehouseId'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
+ componentProps: (values) => ({
+ onChange: () =>
+ formApi?.setValues({
+ bizId: undefined,
+ bizCode: undefined,
+ bizName: undefined,
+ content: undefined,
+ }),
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.areaWarehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'bizId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ dependencies: {
+ triggerFields: ['bizType', 'areaLocationId'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.AREA,
+ componentProps: (values) => ({
+ locationId: values.areaLocationId,
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.WORKORDER,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '璁惧',
+ component: markRaw(DvMachinerySelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.MACHINERY,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '浜у搧鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.ITEM,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.VENDOR,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '宸ヤ綔绔�',
+ component: markRaw(MdWorkstationSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSTATION,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '杞﹂棿',
+ component: markRaw(MdWorkshopSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.WORKSHOP,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.CLIENT,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '宸ュ叿',
+ component: markRaw(TmToolSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.TOOL,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '搴撳瓨',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.STOCK,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '瑁呯鍗�',
+ component: markRaw(WmPackageSelect),
+ componentProps: {
+ childableOnly: true,
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.PACKAGE,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '鎵规',
+ component: markRaw(WmBatchSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.BATCH,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '娴佽浆鍗�',
+ component: markRaw(ProCardSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.PROCARD,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '浜哄憳',
+ component: markRaw(UserSelect),
+ componentProps: {
+ onChange: (item: any) => syncBizDetail(formApi, item),
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) => values.bizType === BarcodeBizTypeEnum.USER,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizId',
+ label: '涓氬姟缂栧彿',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 1,
+ placeholder: '璇疯緭鍏ヤ笟鍔$紪鍙�',
+ },
+ dependencies: {
+ triggerFields: ['bizType'],
+ show: (values) =>
+ values.bizType !== undefined &&
+ ![
+ BarcodeBizTypeEnum.AREA,
+ BarcodeBizTypeEnum.BATCH,
+ BarcodeBizTypeEnum.CLIENT,
+ BarcodeBizTypeEnum.ITEM,
+ BarcodeBizTypeEnum.LOCATION,
+ BarcodeBizTypeEnum.MACHINERY,
+ BarcodeBizTypeEnum.PACKAGE,
+ BarcodeBizTypeEnum.PROCARD,
+ BarcodeBizTypeEnum.STOCK,
+ BarcodeBizTypeEnum.TOOL,
+ BarcodeBizTypeEnum.USER,
+ BarcodeBizTypeEnum.VENDOR,
+ BarcodeBizTypeEnum.WAREHOUSE,
+ BarcodeBizTypeEnum.WORKORDER,
+ BarcodeBizTypeEnum.WORKSHOP,
+ BarcodeBizTypeEnum.WORKSTATION,
+ ].includes(values.bizType),
+ },
+ },
+ {
+ fieldName: 'bizCode',
+ label: '涓氬姟缂栫爜',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ placeholder: '鑷姩濉厖',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'bizName',
+ label: '涓氬姟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ placeholder: '鑷姩濉厖',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'content',
+ label: '鏉$爜鍐呭',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ潯鐮佸唴瀹规垨鑷姩鐢熸垚',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'RadioGroup',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+ },
+ rules: z.number().default(CommonStatusEnum.ENABLE),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 2,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'bizType',
+ label: '涓氬姟绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+ },
+ },
+ {
+ fieldName: 'bizCode',
+ label: '涓氬姟缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヤ笟鍔$紪鐮�',
+ },
+ },
+ {
+ fieldName: 'bizName',
+ label: '涓氬姟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヤ笟鍔″悕绉�',
+ },
+ },
+ {
+ fieldName: 'content',
+ label: '鏉$爜鍐呭',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ潯鐮佸唴瀹�',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmBarcodeApi.Barcode>['columns'] {
+ return [
+ {
+ type: 'checkbox',
+ width: 50,
+ },
+ {
+ field: 'content',
+ title: '鏉$爜',
+ width: 180,
+ slots: { default: 'barcode' },
+ },
+ {
+ field: 'format',
+ title: '鏉$爜鏍煎紡',
+ width: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
+ },
+ },
+ {
+ field: 'bizType',
+ title: '涓氬姟绫诲瀷',
+ width: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
+ },
+ },
+ {
+ field: 'content',
+ title: '鏉$爜鍐呭',
+ minWidth: 160,
+ },
+ {
+ field: 'bizCode',
+ title: '涓氬姟缂栫爜',
+ width: 130,
+ },
+ {
+ field: 'bizName',
+ title: '涓氬姟鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.COMMON_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 閰嶇疆鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useConfigGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'format',
+ label: '鏉$爜鏍煎紡',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_FORMAT, 'number'),
+ placeholder: '璇烽�夋嫨鏉$爜鏍煎紡',
+ },
+ },
+ {
+ fieldName: 'bizType',
+ label: '涓氬姟绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+ },
+ },
+ ];
+}
+
+/** 閰嶇疆鍒楄〃鐨勫瓧娈� */
+export function useConfigGridColumns(
+ onAutoGenerateChange: (
+ row: MesWmBarcodeConfigApi.BarcodeConfig,
+ ) => Promise<boolean>,
+): VxeTableGridOptions<MesWmBarcodeConfigApi.BarcodeConfig>['columns'] {
+ return [
+ {
+ field: 'id',
+ title: '缂栧彿',
+ width: 100,
+ },
+ {
+ field: 'format',
+ title: '鏉$爜鏍煎紡',
+ width: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_BARCODE_FORMAT },
+ },
+ },
+ {
+ field: 'bizType',
+ title: '涓氬姟绫诲瀷',
+ width: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE },
+ },
+ },
+ {
+ field: 'contentFormat',
+ title: '鍐呭鏍煎紡',
+ minWidth: 160,
+ },
+ {
+ field: 'contentExample',
+ title: '鍐呭鏍蜂緥',
+ minWidth: 160,
+ },
+ {
+ field: 'defaultTemplate',
+ title: '榛樿鎵撳嵃妯℃澘',
+ minWidth: 140,
+ },
+ {
+ field: 'autoGenerateFlag',
+ title: '鑷姩鐢熸垚',
+ width: 100,
+ cellRender: {
+ name: 'CellSwitch',
+ attrs: { beforeChange: onAutoGenerateChange },
+ },
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.COMMON_STATUS },
+ },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鏉$爜璇︽儏鐨勬弿杩板瓧娈� */
+export function useBarcodeDetailSchema(): DescriptionItemSchema[] {
+ return [
+ {
+ field: 'format',
+ label: '鏉$爜鏍煎紡',
+ render: (value) =>
+ value === null
+ ? '-'
+ : h(DictTag, { type: DICT_TYPE.MES_WM_BARCODE_FORMAT, value }),
+ },
+ {
+ field: 'bizType',
+ label: '涓氬姟绫诲瀷',
+ render: (value) =>
+ value === null
+ ? '-'
+ : h(DictTag, { type: DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE, value }),
+ },
+ {
+ field: 'content',
+ label: '鏉$爜鍐呭',
+ slot: 'content',
+ },
+ {
+ field: 'bizCode',
+ label: '涓氬姟缂栫爜',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'bizName',
+ label: '涓氬姟鍚嶇О',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'status',
+ label: '鐘舵��',
+ render: (value) =>
+ value === null
+ ? '-'
+ : h(DictTag, { type: DICT_TYPE.COMMON_STATUS, value }),
+ },
+ {
+ field: 'createTime',
+ label: '鍒涘缓鏃堕棿',
+ render: (value) => formatDateTime(value) || '-',
+ },
+ ];
+}
diff --git a/src/views/wls/barcode/index.vue b/src/views/wls/barcode/index.vue
new file mode 100644
index 0000000..6dfe978
--- /dev/null
+++ b/src/views/wls/barcode/index.vue
@@ -0,0 +1,234 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteBarcode,
+ exportBarcode,
+ getBarcodePage,
+} from '#/api/mes/wm/barcode';
+import { $t } from '#/locales';
+
+import { Barcode, BarcodeDetail } from './components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const checkedIds = ref<number[]>([]);
+function handleRowCheckboxChange({
+ records,
+}: {
+ records: MesWmBarcodeApi.Barcode[];
+}) {
+ checkedIds.value = records.map((item) => item.id!);
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鏉$爜 */
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+/** 缂栬緫鏉$爜 */
+function handleEdit(row: MesWmBarcodeApi.Barcode) {
+ formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎鏉$爜 */
+async function handleDelete(row: MesWmBarcodeApi.Barcode) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [
+ row.bizName || row.bizCode || '',
+ ]),
+ duration: 0,
+ });
+ try {
+ await deleteBarcode(row.id!);
+ message.success(
+ $t('ui.actionMessage.deleteSuccess', [row.bizName || row.bizCode || '']),
+ );
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鎵归噺鍒犻櫎鏉$爜 */
+async function handleDeleteBatch() {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [`${checkedIds.value.length} 鏉]),
+ duration: 0,
+ });
+ try {
+ await Promise.all(checkedIds.value.map((id) => deleteBarcode(id)));
+ message.success($t('ui.actionMessage.deleteSuccess', ['']));
+ checkedIds.value = [];
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅鏉$爜 */
+function handleView(row: MesWmBarcodeApi.Barcode) {
+ barcodeDetailRef.value?.open(row);
+}
+
+/** 璺宠浆鏉$爜閰嶇疆 */
+function handleConfig() {
+ router.push({ name: 'WlsBarcodeConfig' });
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportBarcode(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鏉$爜娓呭崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getBarcodePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ height: 80,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmBarcodeApi.Barcode>,
+ gridEvents: {
+ checkboxAll: handleRowCheckboxChange,
+ checkboxChange: handleRowCheckboxChange,
+ },
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+ <BarcodeDetail ref="barcodeDetailRef" />
+
+ <Grid table-title="鏉$爜鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鏉$爜']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-barcode:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.deleteBatch'),
+ type: 'primary',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-barcode:delete'],
+ disabled: checkedIds.length === 0,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [
+ `${checkedIds.length} 鏉,
+ ]),
+ confirm: handleDeleteBatch,
+ },
+ },
+ {
+ label: '鏉$爜璁剧疆',
+ type: 'primary',
+ auth: ['mes:wm-barcode-config:query'],
+ onClick: handleConfig,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-barcode:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #barcode="{ row }">
+ <div v-if="row.content" class="flex justify-center">
+ <Barcode
+ :content="row.content"
+ :format="row.format"
+ :height="60"
+ :width="140"
+ />
+ </div>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.detail'),
+ type: 'link',
+ auth: ['mes:wm-barcode:query'],
+ onClick: handleView.bind(null, row),
+ },
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-barcode:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-barcode:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [
+ row.bizName || row.bizCode || '',
+ ]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/barcode/modules/form.vue b/src/views/wls/barcode/modules/form.vue
new file mode 100644
index 0000000..6a8f10f
--- /dev/null
+++ b/src/views/wls/barcode/modules/form.vue
@@ -0,0 +1,111 @@
+<script lang="ts" setup>
+import type { MesWmBarcodeApi } from '#/api/mes/wm/barcode';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum, CommonStatusEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createBarcode, getBarcode, updateBarcode } from '#/api/mes/wm/barcode';
+import { getWarehouseArea } from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const formData = ref<MesWmBarcodeApi.Barcode>();
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鏉$爜'])
+ : $t('ui.actionTitle.create', ['鏉$爜']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-2',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+});
+
+/** 鍔犺浇绾ц仈閫夋嫨鍣ㄧ殑鏁版嵁锛堢紪杈戞椂鍥炲~涓婄骇浠撳簱/搴撳尯锛� */
+async function loadCascadeData(data: MesWmBarcodeApi.Barcode) {
+ if (!data.bizType || !data.bizId) {
+ return;
+ }
+ if (data.bizType === BarcodeBizTypeEnum.LOCATION) {
+ const location = await getWarehouseLocation(data.bizId);
+ if (location?.warehouseId) {
+ await formApi.setFieldValue('locationWarehouseId', location.warehouseId);
+ }
+ } else if (data.bizType === BarcodeBizTypeEnum.AREA) {
+ const area = await getWarehouseArea(data.bizId);
+ await formApi.setValues({
+ areaWarehouseId: area?.warehouseId,
+ areaLocationId: area?.locationId,
+ });
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmBarcodeApi.Barcode;
+ try {
+ await (formData.value?.id
+ ? updateBarcode({ ...data, id: formData.value.id })
+ : createBarcode(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useFormSchema(formApi) });
+ await formApi.setValues({ status: CommonStatusEnum.ENABLE });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<MesWmBarcodeApi.Barcode>();
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getBarcode(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ await loadCascadeData(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/batch/components/detail.vue b/src/views/wls/batch/components/detail.vue
new file mode 100644
index 0000000..6308076
--- /dev/null
+++ b/src/views/wls/batch/components/detail.vue
@@ -0,0 +1,52 @@
+<script lang="ts" setup>
+import type { MesWmBatchApi } from '#/api/mes/wm/batch';
+
+import { ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { getBatch } from '#/api/mes/wm/batch';
+import { useDescription } from '#/components/description';
+
+import { useDetailSchema } from '../data';
+
+const detailData = ref<MesWmBatchApi.Batch>(); // 褰撳墠鎵规璇︽儏
+
+const [Descriptions] = useDescription({
+ bordered: true,
+ column: 3,
+ schema: useDetailSchema(),
+ useCard: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ showCancelButton: false,
+ showConfirmButton: false,
+ async onOpenChange(isOpen) {
+ if (!isOpen) {
+ detailData.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ id?: number }>();
+ if (!data?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ detailData.value = await getBatch(data.id);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+
+defineExpose({
+ open: (id: number) => modalApi.setData({ id }).open(),
+});
+</script>
+
+<template>
+ <Modal title="鎵规璇︽儏" class="w-3/5">
+ <Descriptions class="mx-4" :data="detailData" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/batch/components/index.ts b/src/views/wls/batch/components/index.ts
similarity index 100%
copy from src/views/mes/wm/batch/components/index.ts
copy to src/views/wls/batch/components/index.ts
diff --git a/src/views/mes/wm/batch/components/select-dialog.vue b/src/views/wls/batch/components/select-dialog.vue
similarity index 100%
copy from src/views/mes/wm/batch/components/select-dialog.vue
copy to src/views/wls/batch/components/select-dialog.vue
diff --git a/src/views/wls/batch/components/select.vue b/src/views/wls/batch/components/select.vue
new file mode 100644
index 0000000..1243ff9
--- /dev/null
+++ b/src/views/wls/batch/components/select.vue
@@ -0,0 +1,149 @@
+<script lang="ts" setup>
+import type { MesWmBatchApi } from '#/api/mes/wm/batch';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getBatch } from '#/api/mes/wm/batch';
+
+import WmBatchSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmBatchSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ clientId?: number; // 榛樿杩囨护鐨勫鎴� ID
+ disabled?: boolean;
+ itemId?: number; // 榛樿杩囨护鐨勭墿鏂� ID
+ modelValue?: number;
+ placeholder?: string;
+ salesOrderCode?: string; // 榛樿杩囨护鐨勯攢鍞鍗曠紪鍙�
+ vendorId?: number; // 榛樿杩囨护鐨勪緵搴斿晢 ID
+ }>(),
+ {
+ allowClear: true,
+ clientId: undefined,
+ disabled: false,
+ itemId: undefined,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨鎵规',
+ salesOrderCode: undefined,
+ vendorId: undefined,
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmBatchApi.Batch | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmBatchSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmBatchApi.Batch>();
+
+const displayLabel = computed(() => selectedItem.value?.code ?? '');
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ鎵规淇℃伅锛堢敤浜庣紪杈戝洖鏄撅級 */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getBatch(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夋壒娆� */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑鎵规閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, {
+ clientId: props.clientId,
+ itemId: props.itemId,
+ multiple: false,
+ salesOrderCode: props.salesOrderCode,
+ vendorId: props.vendorId,
+ });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmBatchApi.Batch[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>鎵规缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+ <div>鐗╂枡缂栫爜锛歿{ selectedItem.itemCode || '-' }}</div>
+ <div>鐗╂枡鍚嶇О锛歿{ selectedItem.itemName || '-' }}</div>
+ <div>鐢熶骇鎵瑰彿锛歿{ selectedItem.lotNumber || '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <WmBatchSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/batch/data.ts b/src/views/wls/batch/data.ts
new file mode 100644
index 0000000..62111dd
--- /dev/null
+++ b/src/views/wls/batch/data.ts
@@ -0,0 +1,372 @@
+锘縤mport type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmBatchApi } from '#/api/mes/wm/batch';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+import { formatDate } from '../../../packages/utils/src';
+
+import { DictTag } from '#/components/dict-tag';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProTaskSelect } from '#/views/mes/pro/task/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { TmToolSelect } from '#/views/mes/tm/tool/components';
+
+/** 鎵规閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useBatchSelectGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鎵规缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ壒娆$紪鍙�',
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '浜у搧鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐢熶骇璁㈠崟',
+ },
+ },
+ {
+ fieldName: 'workstationId',
+ label: '宸ヤ綔绔�',
+ component: markRaw(MdWorkstationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨宸ヤ綔绔�',
+ },
+ },
+ {
+ fieldName: 'taskId',
+ label: '鐢熶骇浠诲姟',
+ component: markRaw(ProTaskSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐢熶骇浠诲姟',
+ },
+ },
+ {
+ fieldName: 'toolId',
+ label: '宸ュ叿',
+ component: markRaw(TmToolSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨宸ュ叿',
+ },
+ },
+ {
+ fieldName: 'moldId',
+ label: '妯″叿缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユā鍏风紪鍙�',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'purchaseOrderCode',
+ label: '閲囪喘璁㈠崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'lotNumber',
+ label: '鐢熶骇鎵瑰彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ョ敓浜ф壒鍙�',
+ },
+ },
+ {
+ fieldName: 'qualityStatus',
+ label: '璐ㄩ噺鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_QUALITY_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨璐ㄩ噺鐘舵��',
+ },
+ },
+ {
+ fieldName: 'produceDate',
+ label: '鐢熶骇鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'expireDate',
+ label: '鏈夋晥鏈�',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鎵规閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useBatchSelectGridColumns(
+ multiple = false,
+): VxeTableGridOptions<MesWmBatchApi.Batch>['columns'] {
+ return [
+ {
+ type: multiple ? 'checkbox' : 'radio',
+ width: 50,
+ },
+ {
+ field: 'code',
+ title: '鎵规缂栧彿',
+ width: 150,
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ width: 150,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'itemSpecification',
+ title: '瑙勬牸鍨嬪彿',
+ width: 120,
+ },
+ {
+ field: 'unitName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'vendorCode',
+ title: '渚涘簲鍟嗙紪鐮�',
+ width: 120,
+ },
+ {
+ field: 'vendorName',
+ title: '渚涘簲鍟嗗悕绉�',
+ width: 120,
+ },
+ {
+ field: 'clientCode',
+ title: '瀹㈡埛缂栫爜',
+ width: 110,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ width: 110,
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曠紪鍙�',
+ width: 140,
+ },
+ {
+ field: 'purchaseOrderCode',
+ title: '閲囪喘璁㈠崟缂栧彿',
+ width: 140,
+ },
+ {
+ field: 'workOrderCode',
+ title: '璁㈠崟缂栫爜',
+ width: 140,
+ },
+ {
+ field: 'workstationCode',
+ title: '宸ヤ綔绔欑紪鐮�',
+ width: 120,
+ },
+ {
+ field: 'taskCode',
+ title: '鐢熶骇浠诲姟缂栧彿',
+ width: 140,
+ },
+ {
+ field: 'toolCode',
+ title: '宸ュ叿缂栧彿',
+ width: 120,
+ },
+ {
+ field: 'lotNumber',
+ title: '鐢熶骇鎵瑰彿',
+ width: 120,
+ },
+ {
+ field: 'qualityStatus',
+ title: '璐ㄩ噺鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+ },
+ },
+ {
+ field: 'produceDate',
+ title: '鐢熶骇鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'expireDate',
+ title: '鏈夋晥鏈�',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'receiptDate',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ ];
+}
+
+/** 鎵规璇︽儏鐨勬弿杩板瓧娈� */
+export function useDetailSchema(): DescriptionItemSchema[] {
+ return [
+ {
+ field: 'code',
+ label: '鎵规缂栧彿',
+ },
+ {
+ field: 'itemCode',
+ label: '鐗╂枡缂栫爜',
+ },
+ {
+ field: 'itemName',
+ label: '鐗╂枡鍚嶇О',
+ },
+ {
+ field: 'itemSpecification',
+ label: '瑙勬牸鍨嬪彿',
+ },
+ {
+ field: 'unitName',
+ label: '鍗曚綅',
+ },
+ {
+ field: 'lotNumber',
+ label: '鐢熶骇鎵瑰彿',
+ },
+ {
+ field: 'produceDate',
+ label: '鐢熶骇鏃ユ湡',
+ render: (value) => (value ? formatDate(value, 'YYYY-MM-DD') : '-'),
+ },
+ {
+ field: 'expireDate',
+ label: '鏈夋晥鏈�',
+ render: (value) => (value ? formatDate(value, 'YYYY-MM-DD') : '-'),
+ },
+ {
+ field: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ render: (value) => (value ? formatDate(value, 'YYYY-MM-DD') : '-'),
+ },
+ {
+ field: 'vendorName',
+ label: '渚涘簲鍟�',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'clientName',
+ label: '瀹㈡埛',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'workstationCode',
+ label: '宸ヤ綔绔�',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'purchaseOrderCode',
+ label: '閲囪喘璁㈠崟缂栧彿',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'workOrderCode',
+ label: '鐢熶骇璁㈠崟',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'qualityStatus',
+ label: '璐ㄩ噺鐘舵��',
+ render: (value) =>
+ value === null
+ ? '-'
+ : h(DictTag, { type: DICT_TYPE.MES_WM_QUALITY_STATUS, value }),
+ },
+ {
+ field: 'remark',
+ label: '澶囨敞',
+ span: 3,
+ render: (value) => value || '-',
+ },
+ ];
+}
diff --git a/src/views/wls/itemreceipt/data.ts b/src/views/wls/itemreceipt/data.ts
new file mode 100644
index 0000000..da9708b
--- /dev/null
+++ b/src/views/wls/itemreceipt/data.ts
@@ -0,0 +1,527 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmArrivalNoticeApi } from '#/api/mes/wm/arrivalnotice';
+import type { MesWmArrivalNoticeLineApi } from '#/api/mes/wm/arrivalnotice/line';
+import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
+import type { MesWmItemReceiptDetailApi } from '#/api/mes/wm/itemreceipt/detail';
+import type { MesWmItemReceiptLineApi } from '#/api/mes/wm/itemreceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesWmArrivalNoticeStatusEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import {
+ WmArrivalNoticeLineSelect,
+ WmArrivalNoticeSelect,
+} from '#/views/wls/arrivalnotice/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佸叆搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_ITEM_RECEIPT_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'noticeId',
+ label: '鍒拌揣閫氱煡鍗�',
+ component: markRaw(WmArrivalNoticeSelect),
+ componentProps: {
+ // 閫夋嫨鍒拌揣閫氱煡鍗曞悗锛岃嚜鍔ㄥ洖濉緵搴斿晢鍜岄噰璐鍗曞彿
+ onChange: async (notice?: MesWmArrivalNoticeApi.ArrivalNotice) => {
+ await formApi?.setValues({
+ purchaseOrderCode: notice?.purchaseOrderCode,
+ vendorId: notice?.vendorId,
+ });
+ },
+ status: MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT,
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'purchaseOrderCode',
+ label: '閲囪喘璁㈠崟鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ噰璐鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmItemReceiptApi.ItemReceipt>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍏ュ簱鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍏ュ簱鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'purchaseOrderCode',
+ title: '閲囪喘璁㈠崟鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'vendorName',
+ title: '渚涘簲鍟嗗悕绉�',
+ minWidth: 120,
+ },
+ {
+ field: 'receiptDate',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_ITEM_RECEIPT_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmItemReceiptLineApi.ItemReceiptLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'receivedQuantity',
+ title: '鍏ュ簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+ hasNotice: boolean,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'noticeId',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'arrivalNoticeLineId',
+ label: '鍒拌揣閫氱煡鍗曡',
+ component: markRaw(WmArrivalNoticeLineSelect),
+ componentProps: {
+ // 閫夋嫨鍒拌揣閫氱煡鍗曡鍚庯紝鑷姩鍥炲~鐗╂枡鍜屽叆搴撴暟閲�
+ onChange: async (
+ line?: MesWmArrivalNoticeLineApi.ArrivalNoticeLine,
+ ) => {
+ await formApi?.setValues({
+ itemId: line?.itemId,
+ receivedQuantity: line?.arrivalQuantity,
+ });
+ },
+ },
+ dependencies: {
+ triggerFields: ['noticeId'],
+ if: () => hasNotice,
+ componentProps: (values) => ({
+ noticeId: values.noticeId,
+ }),
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['arrivalNoticeLineId'],
+ componentProps: (values) => ({
+ disabled: !!values.arrivalNoticeLineId,
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ }),
+ },
+ },
+ {
+ fieldName: 'receivedQuantity',
+ label: '鍏ュ簱鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'productionDate',
+ label: '鐢熶骇鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鐢熶骇鏃ユ湡',
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'expireDate',
+ label: '鏈夋晥鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'lotNumber',
+ label: '鐢熶骇鎵瑰彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ョ敓浜ф壒鍙�',
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ placeholder: '鐢卞~鍐欎俊鎭嚜鍔ㄧ敓鎴�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmItemReceiptDetailApi.ItemReceiptDetail>['columns'] {
+ return [
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍏ュ簱浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+ onChange: async () => {
+ await formApi?.setValues({
+ areaId: undefined,
+ locationId: undefined,
+ });
+ },
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撳尯',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+ onChange: async () => {
+ await formApi?.setFieldValue('areaId', undefined);
+ },
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撲綅',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ ];
+}
diff --git a/src/views/wls/itemreceipt/index.vue b/src/views/wls/itemreceipt/index.vue
new file mode 100644
index 0000000..9e3f25c
--- /dev/null
+++ b/src/views/wls/itemreceipt/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmItemReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelItemReceipt,
+ deleteItemReceipt,
+ exportItemReceipt,
+ getItemReceiptPage,
+} from '#/api/mes/wm/itemreceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓閲囪喘鍏ュ簱鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅閲囪喘鍏ュ簱鍗� */
+function handleDetail(row: MesWmItemReceiptApi.ItemReceipt) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫閲囪喘鍏ュ簱鍗� */
+function handleEdit(row: MesWmItemReceiptApi.ItemReceipt) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmItemReceiptApi.ItemReceipt) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц鍏ュ簱 */
+function handleFinish(row: MesWmItemReceiptApi.ItemReceipt) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎閲囪喘鍏ュ簱鍗� */
+async function handleDelete(row: MesWmItemReceiptApi.ItemReceipt) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteItemReceipt(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷閲囪喘鍏ュ簱鍗� */
+async function handleCancel(row: MesWmItemReceiptApi.ItemReceipt) {
+ await cancelItemReceipt(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportItemReceipt(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '閲囪喘鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getItemReceiptPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmItemReceiptApi.ItemReceipt>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="閲囪喘鍏ュ簱鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['閲囪喘鍏ュ簱鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-item-receipt:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-item-receipt:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-item-receipt:update'],
+ ifShow: row.status === MesWmItemReceiptStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-item-receipt:delete'],
+ ifShow: row.status === MesWmItemReceiptStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц涓婃灦',
+ type: 'link',
+ auth: ['mes:wm-item-receipt:update'],
+ ifShow: row.status === MesWmItemReceiptStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '鎵ц鍏ュ簱',
+ type: 'link',
+ auth: ['mes:wm-item-receipt:finish'],
+ ifShow: row.status === MesWmItemReceiptStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-item-receipt:update'],
+ ifShow:
+ row.status === MesWmItemReceiptStatusEnum.APPROVING ||
+ row.status === MesWmItemReceiptStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ラ噰璐叆搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/itemreceipt/modules/detail-form.vue b/src/views/wls/itemreceipt/modules/detail-form.vue
new file mode 100644
index 0000000..e1c6dc4
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmItemReceiptDetailApi } from '#/api/mes/wm/itemreceipt/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createItemReceiptDetail,
+ getItemReceiptDetail,
+ updateItemReceiptDetail,
+} from '#/api/mes/wm/itemreceipt/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmItemReceiptDetailApi.ItemReceiptDetail>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲叆搴撳崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+ : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmItemReceiptDetailApi.ItemReceiptDetail;
+ data.receiptId = receiptId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateItemReceiptDetail({ ...data, id: formData.value.id })
+ : createItemReceiptDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ itemId?: number;
+ lineId: number;
+ receiptId: number;
+ }>();
+ receiptId.value = data.receiptId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getItemReceiptDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/itemreceipt/modules/detail-list.vue b/src/views/wls/itemreceipt/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/itemreceipt/modules/detail-list.vue
copy to src/views/wls/itemreceipt/modules/detail-list.vue
diff --git a/src/views/wls/itemreceipt/modules/form.vue b/src/views/wls/itemreceipt/modules/form.vue
new file mode 100644
index 0000000..a0fae09
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/form.vue
@@ -0,0 +1,242 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmItemReceiptApi } from '#/api/mes/wm/itemreceipt';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmItemReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createItemReceipt,
+ finishItemReceipt,
+ getItemReceipt,
+ stockItemReceipt,
+ submitItemReceipt,
+ updateItemReceipt,
+} from '#/api/mes/wm/itemreceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmItemReceiptApi.ItemReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽叆搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmItemReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['閲囪喘鍏ュ簱鍗�']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц涓婃灦';
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц鍏ュ簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['閲囪喘鍏ュ簱鍗�'])
+ : $t('ui.actionTitle.create', ['閲囪喘鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmItemReceiptApi.ItemReceipt;
+ await updateItemReceipt({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitItemReceipt(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await stockItemReceipt(formData.value.id);
+ message.success('涓婃灦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鍏ュ簱 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishItemReceipt(formData.value.id);
+ message.success('鍏ュ簱鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmItemReceiptApi.ItemReceipt;
+ try {
+ if (formData.value?.id) {
+ await updateItemReceipt({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createItemReceipt(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmItemReceiptStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getItemReceipt(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+
+/** 涓婃灦鍓嶇‘璁� */
+async function confirmStock() {
+ try {
+ await confirm('纭鎵ц涓婃灦锛�');
+ } catch {
+ return;
+ }
+ await handleStock();
+}
+
+/** 鍏ュ簱鍓嶇‘璁� */
+async function confirmFinish() {
+ try {
+ await confirm('纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��');
+ } catch {
+ return;
+ }
+ await handleFinish();
+}
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList
+ :form-type="formType"
+ :notice-id="formData.noticeId"
+ :receipt-id="formData.id"
+ />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ噰璐叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="isStock" type="primary" @click="confirmStock">
+ 鎵ц涓婃灦
+ </Button>
+ <Button v-if="isFinish" type="primary" @click="confirmFinish">
+ 鎵ц鍏ュ簱
+ </Button>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/itemreceipt/modules/line-form.vue b/src/views/wls/itemreceipt/modules/line-form.vue
new file mode 100644
index 0000000..a34a436
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/line-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmItemReceiptLineApi } from '#/api/mes/wm/itemreceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createItemReceiptLine,
+ getItemReceiptLine,
+ updateItemReceiptLine,
+} from '#/api/mes/wm/itemreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmItemReceiptLineApi.ItemReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const noticeId = ref<number>(); // 鎵�灞炲埌璐ч�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鐗╂枡鍏ュ簱鍗曡'])
+ : $t('ui.actionTitle.create', ['鐗╂枡鍏ュ簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmItemReceiptLineApi.ItemReceiptLine;
+ data.receiptId = receiptId.value;
+ try {
+ await (formData.value?.id
+ ? updateItemReceiptLine({ ...data, id: formData.value.id })
+ : createItemReceiptLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ id?: number;
+ noticeId?: number;
+ receiptId: number;
+ }>();
+ receiptId.value = data.receiptId;
+ noticeId.value = data.noticeId;
+ formApi.setState({ schema: useLineFormSchema(!!data.noticeId, formApi) });
+ if (data.noticeId) {
+ await formApi.setFieldValue('noticeId', data.noticeId);
+ }
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getItemReceiptLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues({ ...formData.value, noticeId: noticeId.value });
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/itemreceipt/modules/line-list.vue b/src/views/wls/itemreceipt/modules/line-list.vue
new file mode 100644
index 0000000..a30f441
--- /dev/null
+++ b/src/views/wls/itemreceipt/modules/line-list.vue
@@ -0,0 +1,241 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmItemReceiptDetailApi } from '#/api/mes/wm/itemreceipt/detail';
+import type { MesWmItemReceiptLineApi } from '#/api/mes/wm/itemreceipt/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getItemReceiptDetailListByLineId } from '#/api/mes/wm/itemreceipt/detail';
+import {
+ deleteItemReceiptLine,
+ getItemReceiptLinePage,
+} from '#/api/mes/wm/itemreceipt/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ noticeId?: number;
+ receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+ Record<number, MesWmItemReceiptDetailApi.ItemReceiptDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi
+ .setData({ noticeId: props.noticeId, receiptId: props.receiptId })
+ .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+ lineFormModalApi
+ .setData({
+ id: row.id,
+ noticeId: props.noticeId,
+ receiptId: props.receiptId,
+ })
+ .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteItemReceiptLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handleStock(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, itemId, lineId, receiptId: props.receiptId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(row: MesWmItemReceiptLineApi.ItemReceiptLine) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getItemReceiptDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+ row: MesWmItemReceiptLineApi.ItemReceiptLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.receiptId) {
+ return { list: [], total: 0 };
+ }
+ return await getItemReceiptLinePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ receiptId: props.receiptId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmItemReceiptLineApi.ItemReceiptLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmItemReceiptLineApi.ItemReceiptLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <div class="flex items-center justify-center">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '涓婃灦',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handleStock.bind(null, row),
+ },
+ ]"
+ />
+ <PrinterLabel
+ v-if="isStock"
+ :biz-code="row.batchCode"
+ :biz-id="row.batchId"
+ biz-type="ITEM_BATCH"
+ />
+ </div>
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/mes/wm/materialstock/components/index.ts b/src/views/wls/materialstock/components/index.ts
similarity index 100%
copy from src/views/mes/wm/materialstock/components/index.ts
copy to src/views/wls/materialstock/components/index.ts
diff --git a/src/views/mes/wm/materialstock/components/select-dialog.vue b/src/views/wls/materialstock/components/select-dialog.vue
similarity index 100%
copy from src/views/mes/wm/materialstock/components/select-dialog.vue
copy to src/views/wls/materialstock/components/select-dialog.vue
diff --git a/src/views/wls/materialstock/components/select.vue b/src/views/wls/materialstock/components/select.vue
new file mode 100644
index 0000000..e605056
--- /dev/null
+++ b/src/views/wls/materialstock/components/select.vue
@@ -0,0 +1,158 @@
+<script lang="ts" setup>
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getMaterialStock } from '#/api/mes/wm/materialstock';
+
+import WmMaterialStockSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmMaterialStockSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ batchId?: number;
+ disabled?: boolean;
+ itemId?: number;
+ modelValue?: number;
+ placeholder?: string;
+ virtualFilter?: 'all' | 'exclude' | 'only';
+ warehouseId?: number;
+ }>(),
+ {
+ allowClear: true,
+ batchId: undefined,
+ disabled: false,
+ itemId: undefined,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨搴撳瓨',
+ virtualFilter: 'exclude',
+ warehouseId: undefined,
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmMaterialStockApi.MaterialStock | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmMaterialStockSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmMaterialStockApi.MaterialStock>();
+
+const displayLabel = computed(() => {
+ const item = selectedItem.value;
+ if (!item) {
+ return '';
+ }
+ return `${item.warehouseName || '-'} / ${item.batchCode || '-'} / 鏁伴噺:${item.quantity}`;
+});
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁 ID 鍗曟潯鏌ヨ搴撳瓨淇℃伅锛堢敤浜庣紪杈戝洖鏄撅級 */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getMaterialStock(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夊簱瀛� */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑搴撳瓨閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmMaterialStockApi.MaterialStock[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>鐗╂枡锛歿{ selectedItem.itemName || '-' }}</div>
+ <div>鎵规锛歿{ selectedItem.batchCode || '-' }}</div>
+ <div>鏁伴噺锛歿{ selectedItem.quantity ?? '-' }}</div>
+ <div>浠撳簱锛歿{ selectedItem.warehouseName || '-' }}</div>
+ <div>搴撳尯锛歿{ selectedItem.locationName || '-' }}</div>
+ <div>搴撲綅锛歿{ selectedItem.areaName || '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <WmMaterialStockSelectDialog
+ ref="dialogRef"
+ :batch-id="batchId"
+ :item-id="itemId"
+ :virtual-filter="virtualFilter"
+ :warehouse-id="warehouseId"
+ @selected="handleSelected"
+ />
+</template>
diff --git a/src/views/wls/materialstock/data.ts b/src/views/wls/materialstock/data.ts
new file mode 100644
index 0000000..85f864a
--- /dev/null
+++ b/src/views/wls/materialstock/data.ts
@@ -0,0 +1,306 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { markRaw } from 'vue';
+
+import { useAccess } from '../../../packages/effects/access/src';
+import { DICT_TYPE } from '../../../packages/constants/src';
+
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '../warehouse/components';
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ warehouseId: values.warehouseId,
+ placeholder: '璇烽�夋嫨搴撳尯',
+ }),
+ // 浠撳簱鍒囨崲鏃舵竻绌哄簱鍖猴紝閬垮厤鏃у簱鍖烘潯浠舵畫鐣�
+ trigger: (values, formApi) => {
+ if (values.locationId !== undefined) {
+ void formApi.setFieldValue('locationId', undefined);
+ }
+ },
+ },
+ },
+ {
+ fieldName: 'frozen',
+ label: '鏄惁鍐荤粨',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: [
+ { label: '鏄�', value: true },
+ { label: '鍚�', value: false },
+ ],
+ placeholder: '璇烽�夋嫨',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(
+ onFrozenChange: (
+ newFrozen: boolean,
+ row: MesWmMaterialStockApi.MaterialStock,
+ ) => Promise<boolean | undefined>,
+): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
+ const { hasAccessByCodes } = useAccess();
+ return [
+ {
+ field: 'itemCode',
+ title: '浜у搧鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '浜у搧鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'quantity',
+ title: '鍦ㄥ簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 140,
+ slots: { default: 'batchCode' },
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅',
+ minWidth: 100,
+ slots: { default: 'areaName' },
+ },
+ {
+ field: 'receiptTime',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'frozen',
+ title: '鍐荤粨',
+ width: 90,
+ cellRender: {
+ name: 'CellSwitch',
+ attrs: { beforeChange: onFrozenChange },
+ props: {
+ checkedValue: true,
+ // 鍐荤粨寮�鍏冲彈鏇存柊鏉冮檺鎺у埗锛屾棤鏉冮檺鏃剁鐢紝淇濇寔涓庢簮椤圭洰 v-hasPermi 涓�鑷�
+ disabled: !hasAccessByCodes(['mes:wm-material-stock:update']),
+ unCheckedValue: false,
+ },
+ },
+ },
+ ];
+}
+
+/** 閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useSelectGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ warehouseId: values.warehouseId,
+ placeholder: '璇烽�夋嫨搴撳尯',
+ }),
+ // 浠撳簱鍒囨崲鏃舵竻绌哄簱鍖�
+ trigger: (values, formApi) => {
+ if (values.locationId !== undefined) {
+ void formApi.setFieldValue('locationId', undefined);
+ }
+ },
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ dependencies: {
+ triggerFields: ['warehouseId', 'locationId'],
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ // 浠撳簱鎴栧簱鍖哄垏鎹㈡椂娓呯┖搴撲綅
+ trigger: (values, formApi) => {
+ if (values.areaId !== undefined) {
+ void formApi.setFieldValue('areaId', undefined);
+ }
+ },
+ },
+ },
+ ];
+}
+
+/** 閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useSelectGridColumns(
+ multiple = false,
+): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
+ return [
+ {
+ type: multiple ? 'checkbox' : 'radio',
+ width: 50,
+ },
+ {
+ field: 'itemCode',
+ title: '浜у搧鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '浜у搧鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'batchCode',
+ title: '鍏ュ簱鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鍦ㄥ簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'receiptTime',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'frozen',
+ title: '鍐荤粨',
+ width: 80,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/materialstock/index.vue b/src/views/wls/materialstock/index.vue
new file mode 100644
index 0000000..40cb43c
--- /dev/null
+++ b/src/views/wls/materialstock/index.vue
@@ -0,0 +1,175 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { ref } from 'vue';
+
+import { confirm, Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, Card, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ exportMaterialStock,
+ getMaterialStockPage,
+ updateMaterialStockFrozen,
+} from '#/api/mes/wm/materialstock';
+import { $t } from '#/locales';
+import { MdItemTypeTree } from '#/views/mes/md/item/type/components';
+import { WmBatchDetail } from '#/views/wls/batch/components';
+import AreaForm from '#/views/wls/warehouse/area/modules/form.vue';
+
+import { useGridColumns, useGridFormSchema } from './data';
+
+const [AreaModal, areaModalApi] = useVbenModal({
+ connectedComponent: AreaForm,
+ destroyOnClose: true,
+});
+
+const batchDetailRef = ref<InstanceType<typeof WmBatchDetail>>();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportMaterialStock({
+ ...(await gridApi.formApi.getValues()),
+ itemTypeId: searchItemTypeId.value,
+ });
+ downloadFileFromBlobPart({ fileName: '搴撳瓨鍙拌处.xls', source: data });
+}
+
+/** 閫夋嫨鐗╂枡鍒嗙被 */
+const searchItemTypeId = ref<number | undefined>(undefined);
+function handleTypeNodeClick(row: undefined | { id?: number }) {
+ searchItemTypeId.value = row?.id;
+ handleRefresh();
+}
+
+/** 鎵撳紑搴撲綅璇︽儏寮圭獥 */
+function handleOpenAreaDetail(row: MesWmMaterialStockApi.MaterialStock) {
+ if (!row.areaId) {
+ return;
+ }
+ areaModalApi.setData({ formType: 'detail', id: row.areaId }).open();
+}
+
+/** 鎵撳紑鎵规璇︽儏寮圭獥 */
+function handleOpenBatchDetail(row: MesWmMaterialStockApi.MaterialStock) {
+ if (!row.batchId) {
+ return;
+ }
+ batchDetailRef.value?.open(row.batchId);
+}
+
+/** 澶勭悊鍐荤粨鐘舵�佸垏鎹� */
+async function handleFrozenChange(
+ newFrozen: boolean,
+ row: MesWmMaterialStockApi.MaterialStock,
+): Promise<boolean | undefined> {
+ const text = newFrozen ? '鍐荤粨' : '瑙e喕';
+ try {
+ await confirm(`纭瑕�"${text}"璇ュ簱瀛樿褰曞悧锛焋);
+ } catch {
+ return false;
+ }
+ // 鏇存柊鍐荤粨鐘舵��
+ await updateMaterialStockFrozen({
+ id: row.id!,
+ frozen: newFrozen,
+ });
+ // 鎻愮ず骞惰繑鍥炴垚鍔�
+ message.success(`${text}鎴愬姛`);
+ return true;
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(handleFrozenChange),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getMaterialStockPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ itemTypeId: searchItemTypeId.value,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><AreaModal />
+ <WmBatchDetail ref="batchDetailRef" />
+
+ <div class="flex h-full w-full">
+ <!-- 宸︿晶鐗╂枡鍒嗙被鏍� -->
+ <Card class="mr-4 h-full w-1/6">
+ <MdItemTypeTree @node-click="handleTypeNodeClick" />
+ </Card>
+ <!-- 鍙充晶搴撳瓨鍙拌处鍒楄〃 -->
+ <div class="w-5/6">
+ <Grid table-title="搴撳瓨鍙拌处鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-material-stock:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #batchCode="{ row }">
+ <Button
+ v-if="row.batchId"
+ :title="row.batchCode"
+ size="small"
+ type="link"
+ @click="handleOpenBatchDetail(row)"
+ >
+ {{ row.batchCode }}
+ </Button>
+ <span v-else>-</span>
+ </template>
+ <template #areaName="{ row }">
+ <Button
+ v-if="row.areaId"
+ :title="row.areaName"
+ size="small"
+ type="link"
+ @click="handleOpenAreaDetail(row)"
+ >
+ {{ row.areaName }}
+ </Button>
+ <span v-else>-</span>
+ </template>
+ </Grid>
+ </div>
+ </div>
+ </Page>
+</template>
diff --git a/src/views/wls/miscissue/data.ts b/src/views/wls/miscissue/data.ts
new file mode 100644
index 0000000..03c9a89
--- /dev/null
+++ b/src/views/wls/miscissue/data.ts
@@ -0,0 +1,452 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
+import type { MesWmMiscIssueLineApi } from '#/api/mes/wm/miscissue/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堣鎯呫�佹墽琛屽嚭搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍑哄簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_MISC_ISSUE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍑哄簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'type',
+ label: '涓氬姟绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.MES_WM_MISC_ISSUE_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'issueDate',
+ label: '鍑哄簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍑哄簱鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'sourceDocType',
+ label: '鏉ユ簮鍗曟嵁绫诲瀷',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+ },
+ },
+ {
+ fieldName: 'sourceDocCode',
+ label: '鏉ユ簮鍗曟嵁缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鍙�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍑哄簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍑哄簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'type',
+ label: '涓氬姟绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_MISC_ISSUE_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨涓氬姟绫诲瀷',
+ },
+ },
+ {
+ fieldName: 'sourceDocType',
+ label: '鏉ユ簮鍗曟嵁绫诲瀷',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+ },
+ },
+ {
+ fieldName: 'sourceDocCode',
+ label: '鏉ユ簮鍗曟嵁缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鍙�',
+ },
+ },
+ {
+ fieldName: 'issueDate',
+ label: '鍑哄簱鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_MISC_ISSUE_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmMiscIssueApi.MiscIssue>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍑哄簱鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍑哄簱鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'type',
+ title: '涓氬姟绫诲瀷',
+ minWidth: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_MISC_ISSUE_TYPE },
+ },
+ },
+ {
+ field: 'sourceDocType',
+ title: '鏉ユ簮鍗曟嵁绫诲瀷',
+ minWidth: 120,
+ },
+ {
+ field: 'sourceDocCode',
+ title: '鏉ユ簮鍗曟嵁缂栧彿',
+ minWidth: 150,
+ },
+ {
+ field: 'issueDate',
+ title: '鍑哄簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_MISC_ISSUE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鍑哄簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable = true,
+): VxeTableGridOptions<MesWmMiscIssueLineApi.MiscIssueLine>['columns'] {
+ return [
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '鍑哄簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱',
+ minWidth: 120,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯',
+ minWidth: 120,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅',
+ minWidth: 120,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍑哄簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨鐗╄祫',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨鐗╄祫鍚庯紝鑷姩鍥炲~鐗╂枡銆佹壒娆″彿銆佷粨搴撲綅缃拰鏈�澶у彲鍑哄簱鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ areaId: stock?.areaId,
+ batchCode: stock?.batchCode,
+ itemId: stock?.itemId,
+ locationId: stock?.locationId,
+ quantityMax: stock?.quantity,
+ warehouseId: stock?.warehouseId,
+ });
+ },
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['itemId'],
+ componentProps: (values) => ({
+ itemId: values.itemId,
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍑哄簱鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0.01,
+ placeholder: '璇疯緭鍏ュ嚭搴撴暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0.01,
+ placeholder: '璇疯緭鍏ュ嚭搴撴暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ placeholder: '鎵规鍙�',
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ disabled: true,
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ disabled: true,
+ locationId: values.locationId,
+ }),
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/miscissue/index.vue b/src/views/wls/miscissue/index.vue
new file mode 100644
index 0000000..b7f6ec0
--- /dev/null
+++ b/src/views/wls/miscissue/index.vue
@@ -0,0 +1,187 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmMiscIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelMiscIssue,
+ deleteMiscIssue,
+ exportMiscIssue,
+ getMiscIssuePage,
+} from '#/api/mes/wm/miscissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鏉傞」鍑哄簱鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鏉傞」鍑哄簱鍗� */
+function handleDetail(row: MesWmMiscIssueApi.MiscIssue) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鏉傞」鍑哄簱鍗� */
+function handleEdit(row: MesWmMiscIssueApi.MiscIssue) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鍑哄簱 */
+function handleFinish(row: MesWmMiscIssueApi.MiscIssue) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎鏉傞」鍑哄簱鍗� */
+async function handleDelete(row: MesWmMiscIssueApi.MiscIssue) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteMiscIssue(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷鏉傞」鍑哄簱鍗� */
+async function handleCancel(row: MesWmMiscIssueApi.MiscIssue) {
+ await cancelMiscIssue(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportMiscIssue(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鏉傞」鍑哄簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getMiscIssuePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmMiscIssueApi.MiscIssue>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鏉傞」鍑哄簱鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鏉傞」鍑哄簱鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-misc-issue:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-misc-issue:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-misc-issue:update'],
+ ifShow: row.status === MesWmMiscIssueStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-misc-issue:delete'],
+ ifShow: row.status === MesWmMiscIssueStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц鍑哄簱',
+ type: 'link',
+ auth: ['mes:wm-misc-issue:finish'],
+ ifShow: row.status === MesWmMiscIssueStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-misc-issue:update'],
+ ifShow: row.status === MesWmMiscIssueStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ユ潅椤瑰嚭搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/miscissue/modules/form.vue b/src/views/wls/miscissue/modules/form.vue
new file mode 100644
index 0000000..ac9ab5f
--- /dev/null
+++ b/src/views/wls/miscissue/modules/form.vue
@@ -0,0 +1,199 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmMiscIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createMiscIssue,
+ finishMiscIssue,
+ getMiscIssue,
+ submitMiscIssue,
+ updateMiscIssue,
+} from '#/api/mes/wm/miscissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmMiscIssueApi.MiscIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽嚭搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmMiscIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['鏉傞」鍑哄簱鍗�']);
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц鍑哄簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['鏉傞」鍑哄簱鍗�'])
+ : $t('ui.actionTitle.create', ['鏉傞」鍑哄簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍑哄簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data = (await formApi.getValues()) as MesWmMiscIssueApi.MiscIssue;
+ await updateMiscIssue({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitMiscIssue(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鍑哄簱 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishMiscIssue(formData.value.id);
+ message.success('鍑哄簱鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmMiscIssueApi.MiscIssue;
+ try {
+ if (formData.value?.id) {
+ await updateMiscIssue({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createMiscIssue(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmMiscIssueStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(
+ formType.value === 'detail' || formType.value === 'finish',
+ );
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getMiscIssue(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :issue-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ユ潅椤瑰嚭搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭鎵ц鍑哄簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">鎵ц鍑哄簱</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/miscissue/modules/line-form.vue b/src/views/wls/miscissue/modules/line-form.vue
new file mode 100644
index 0000000..b20795a
--- /dev/null
+++ b/src/views/wls/miscissue/modules/line-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { MesWmMiscIssueLineApi } from '#/api/mes/wm/miscissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createMiscIssueLine,
+ getMiscIssueLine,
+ updateMiscIssueLine,
+} from '#/api/mes/wm/miscissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmMiscIssueLineApi.MiscIssueLine>();
+const issueId = ref<number>(); // 鎵�灞炲嚭搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鐗╂枡鍑哄簱鍗曡'])
+ : $t('ui.actionTitle.create', ['鐗╂枡鍑哄簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmMiscIssueLineApi.MiscIssueLine;
+ data.issueId = issueId.value;
+ try {
+ await (formData.value?.id
+ ? updateMiscIssueLine({ ...data, id: formData.value.id })
+ : createMiscIssueLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useLineFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ id?: number;
+ issueId: number;
+ }>();
+ issueId.value = data.issueId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getMiscIssueLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/miscissue/modules/line-list.vue b/src/views/wls/miscissue/modules/line-list.vue
new file mode 100644
index 0000000..4d47403
--- /dev/null
+++ b/src/views/wls/miscissue/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscIssueLineApi } from '#/api/mes/wm/miscissue/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteMiscIssueLine,
+ getMiscIssueLinePage,
+} from '#/api/mes/wm/miscissue/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ issueId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmMiscIssueLineApi.MiscIssueLine) {
+ lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmMiscIssueLineApi.MiscIssueLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteMiscIssueLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value),
+ height: 360,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.issueId) {
+ return { list: [], total: 0 };
+ }
+ return await getMiscIssueLinePage({
+ issueId: props.issueId,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmMiscIssueLineApi.MiscIssueLine>,
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/miscreceipt/data.ts b/src/views/wls/miscreceipt/data.ts
new file mode 100644
index 0000000..fa4491a
--- /dev/null
+++ b/src/views/wls/miscreceipt/data.ts
@@ -0,0 +1,422 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
+import type { MesWmMiscReceiptLineApi } from '#/api/mes/wm/miscreceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堣鎯呫�佹墽琛屽叆搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_MISC_RECEIPT_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'type',
+ label: '鏉傞」绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.MES_WM_MISC_RECEIPT_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鏉傞」绫诲瀷',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'sourceDocType',
+ label: '鏉ユ簮鍗曟嵁绫诲瀷',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+ },
+ },
+ {
+ fieldName: 'sourceDocCode',
+ label: '鏉ユ簮鍗曟嵁缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鐮�',
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'type',
+ label: '鏉傞」绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_MISC_RECEIPT_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鏉傞」绫诲瀷',
+ },
+ },
+ {
+ fieldName: 'sourceDocType',
+ label: '鏉ユ簮鍗曟嵁绫诲瀷',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹被鍨�',
+ },
+ },
+ {
+ fieldName: 'sourceDocCode',
+ label: '鏉ユ簮鍗曟嵁缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ潵婧愬崟鎹紪鍙�',
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_MISC_RECEIPT_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmMiscReceiptApi.MiscReceipt>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍏ュ簱鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍏ュ簱鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'type',
+ title: '鏉傞」绫诲瀷',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_MISC_RECEIPT_TYPE },
+ },
+ },
+ {
+ field: 'sourceDocType',
+ title: '鏉ユ簮鍗曟嵁绫诲瀷',
+ minWidth: 120,
+ },
+ {
+ field: 'sourceDocCode',
+ title: '鏉ユ簮鍗曟嵁缂栧彿',
+ minWidth: 150,
+ },
+ {
+ field: 'receiptDate',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_MISC_RECEIPT_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable = true,
+): VxeTableGridOptions<MesWmMiscReceiptLineApi.MiscReceiptLine>['columns'] {
+ return [
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '鍏ュ簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅',
+ minWidth: 100,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍏ュ簱鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0.01,
+ placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+ onChange: async () => {
+ await formApi?.setValues({
+ areaId: undefined,
+ locationId: undefined,
+ });
+ },
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撳尯',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+ onChange: async () => {
+ await formApi?.setFieldValue('areaId', undefined);
+ },
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撲綅',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/miscreceipt/index.vue b/src/views/wls/miscreceipt/index.vue
new file mode 100644
index 0000000..5039d2e
--- /dev/null
+++ b/src/views/wls/miscreceipt/index.vue
@@ -0,0 +1,187 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmMiscReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelMiscReceipt,
+ deleteMiscReceipt,
+ exportMiscReceipt,
+ getMiscReceiptPage,
+} from '#/api/mes/wm/miscreceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鏉傞」鍏ュ簱鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鏉傞」鍏ュ簱鍗� */
+function handleDetail(row: MesWmMiscReceiptApi.MiscReceipt) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鏉傞」鍏ュ簱鍗� */
+function handleEdit(row: MesWmMiscReceiptApi.MiscReceipt) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鍏ュ簱 */
+function handleFinish(row: MesWmMiscReceiptApi.MiscReceipt) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎鏉傞」鍏ュ簱鍗� */
+async function handleDelete(row: MesWmMiscReceiptApi.MiscReceipt) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteMiscReceipt(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷鏉傞」鍏ュ簱鍗� */
+async function handleCancel(row: MesWmMiscReceiptApi.MiscReceipt) {
+ await cancelMiscReceipt(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportMiscReceipt(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鏉傞」鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getMiscReceiptPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmMiscReceiptApi.MiscReceipt>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鏉傞」鍏ュ簱鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鏉傞」鍏ュ簱鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-misc-receipt:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-misc-receipt:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-misc-receipt:update'],
+ ifShow: row.status === MesWmMiscReceiptStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-misc-receipt:delete'],
+ ifShow: row.status === MesWmMiscReceiptStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц鍏ュ簱',
+ type: 'link',
+ auth: ['mes:wm-misc-receipt:finish'],
+ ifShow: row.status === MesWmMiscReceiptStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-misc-receipt:cancel'],
+ ifShow: row.status === MesWmMiscReceiptStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ユ潅椤瑰叆搴撳崟鍚楋紵',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/miscreceipt/modules/form.vue b/src/views/wls/miscreceipt/modules/form.vue
new file mode 100644
index 0000000..1f38b6c
--- /dev/null
+++ b/src/views/wls/miscreceipt/modules/form.vue
@@ -0,0 +1,200 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmMiscReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createMiscReceipt,
+ finishMiscReceipt,
+ getMiscReceipt,
+ submitMiscReceipt,
+ updateMiscReceipt,
+} from '#/api/mes/wm/miscreceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmMiscReceiptApi.MiscReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽叆搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmMiscReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['鏉傞」鍏ュ簱鍗�']);
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц鍏ュ簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['鏉傞」鍏ュ簱鍗�'])
+ : $t('ui.actionTitle.create', ['鏉傞」鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmMiscReceiptApi.MiscReceipt;
+ await updateMiscReceipt({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitMiscReceipt(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鍏ュ簱 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishMiscReceipt(formData.value.id);
+ message.success('鍏ュ簱鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmMiscReceiptApi.MiscReceipt;
+ try {
+ if (formData.value?.id) {
+ await updateMiscReceipt({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createMiscReceipt(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmMiscReceiptStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(
+ formType.value === 'detail' || formType.value === 'finish',
+ );
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getMiscReceipt(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :receipt-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ユ潅椤瑰叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">鎵ц鍏ュ簱</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/miscreceipt/modules/line-form.vue b/src/views/wls/miscreceipt/modules/line-form.vue
new file mode 100644
index 0000000..b3e42c5
--- /dev/null
+++ b/src/views/wls/miscreceipt/modules/line-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { MesWmMiscReceiptLineApi } from '#/api/mes/wm/miscreceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createMiscReceiptLine,
+ getMiscReceiptLine,
+ updateMiscReceiptLine,
+} from '#/api/mes/wm/miscreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmMiscReceiptLineApi.MiscReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鐗╂枡'])
+ : $t('ui.actionTitle.create', ['鐗╂枡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmMiscReceiptLineApi.MiscReceiptLine;
+ data.receiptId = receiptId.value;
+ try {
+ await (formData.value?.id
+ ? updateMiscReceiptLine({ ...data, id: formData.value.id })
+ : createMiscReceiptLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useLineFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ id?: number;
+ receiptId: number;
+ }>();
+ receiptId.value = data.receiptId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getMiscReceiptLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/miscreceipt/modules/line-list.vue b/src/views/wls/miscreceipt/modules/line-list.vue
new file mode 100644
index 0000000..72f1e24
--- /dev/null
+++ b/src/views/wls/miscreceipt/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMiscReceiptLineApi } from '#/api/mes/wm/miscreceipt/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteMiscReceiptLine,
+ getMiscReceiptLineListByReceiptId,
+} from '#/api/mes/wm/miscreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ receiptId: props.receiptId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmMiscReceiptLineApi.MiscReceiptLine) {
+ lineFormModalApi.setData({ id: row.id, receiptId: props.receiptId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmMiscReceiptLineApi.MiscReceiptLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteMiscReceiptLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value),
+ height: 360,
+ keepSource: true,
+ pagerConfig: {
+ enabled: false,
+ },
+ proxyConfig: {
+ ajax: {
+ query: async () => {
+ if (!props.receiptId) {
+ return { list: [], total: 0 };
+ }
+ const list = await getMiscReceiptLineListByReceiptId(props.receiptId);
+ return { list, total: list.length };
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmMiscReceiptLineApi.MiscReceiptLine>,
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/outsourceissue/data.ts b/src/views/wls/outsourceissue/data.ts
new file mode 100644
index 0000000..7cc6ae2
--- /dev/null
+++ b/src/views/wls/outsourceissue/data.ts
@@ -0,0 +1,487 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
+import type { MesWmOutsourceIssueDetailApi } from '#/api/mes/wm/outsourceissue/detail';
+import type { MesWmOutsourceIssueLineApi } from '#/api/mes/wm/outsourceissue/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+ MesProWorkOrderTypeEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佽鎯呫�侀鍑烘�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍙戞枡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ彂鏂欏崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_OUTSOURCE_ISSUE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍙戞枡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ彂鏂欏崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'issueDate',
+ label: '鍙戞枡鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍙戞枡鏃ユ湡',
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '澶栧崗璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ // 閫夋嫨澶栧崗璁㈠崟鍚庯紝鑷姩鍥炲~渚涘簲鍟�
+ onChange: async (workOrder?: MesProWorkOrderApi.WorkOrder) => {
+ await formApi?.setFieldValue('vendorId', workOrder?.vendorId);
+ },
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ type: MesProWorkOrderTypeEnum.OUTSOURCE,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍙戞枡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ彂鏂欏崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍙戞枡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ彂鏂欏崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ {
+ fieldName: 'issueDate',
+ label: '鍙戞枡鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmOutsourceIssueApi.OutsourceIssue>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍙戞枡鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍙戞枡鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'workOrderCode',
+ title: '鐢熶骇璁㈠崟鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'vendorName',
+ title: '渚涘簲鍟嗗悕绉�',
+ minWidth: 120,
+ },
+ {
+ field: 'issueDate',
+ title: '鍙戞枡鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_OUTSOURCE_ISSUE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鍙戞枡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmOutsourceIssueLineApi.OutsourceIssueLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '棰嗘枡鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 150,
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍙戞枡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍙戞枡鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ュ彂鏂欐暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍙戞枡鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail>['columns'] {
+ return [
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍙戞枡鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨璁板綍',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ areaId: stock?.areaId,
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ locationId: stock?.locationId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ warehouseId: stock?.warehouseId,
+ });
+ },
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['itemId'],
+ componentProps: (values) => ({
+ itemId: values.itemId,
+ }),
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍙戞枡浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ disabled: true,
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ disabled: true,
+ locationId: values.locationId,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/outsourceissue/index.vue b/src/views/wls/outsourceissue/index.vue
new file mode 100644
index 0000000..5784037
--- /dev/null
+++ b/src/views/wls/outsourceissue/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmOutsourceIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelOutsourceIssue,
+ deleteOutsourceIssue,
+ exportOutsourceIssue,
+ getOutsourceIssuePage,
+} from '#/api/mes/wm/outsourceissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓澶栧崗鍙戞枡鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅澶栧崗鍙戞枡鍗� */
+function handleDetail(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫澶栧崗鍙戞枡鍗� */
+function handleEdit(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц棰嗗嚭 */
+function handleFinish(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎澶栧崗鍙戞枡鍗� */
+async function handleDelete(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteOutsourceIssue(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷澶栧崗鍙戞枡鍗� */
+async function handleCancel(row: MesWmOutsourceIssueApi.OutsourceIssue) {
+ await cancelOutsourceIssue(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportOutsourceIssue(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '澶栧崗鍙戞枡鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getOutsourceIssuePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmOutsourceIssueApi.OutsourceIssue>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="澶栧崗鍙戞枡鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['澶栧崗鍙戞枡鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-outsource-issue:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-outsource-issue:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-outsource-issue:update'],
+ ifShow: row.status === MesWmOutsourceIssueStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-outsource-issue:delete'],
+ ifShow: row.status === MesWmOutsourceIssueStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц鎷h揣',
+ type: 'link',
+ auth: ['mes:wm-outsource-issue:update'],
+ ifShow: row.status === MesWmOutsourceIssueStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '鎵ц棰嗗嚭',
+ type: 'link',
+ auth: ['mes:wm-outsource-issue:finish'],
+ ifShow: row.status === MesWmOutsourceIssueStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-outsource-issue:update'],
+ ifShow:
+ row.status === MesWmOutsourceIssueStatusEnum.APPROVING ||
+ row.status === MesWmOutsourceIssueStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ュ鍗忓彂鏂欏崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/outsourceissue/modules/detail-form.vue b/src/views/wls/outsourceissue/modules/detail-form.vue
new file mode 100644
index 0000000..278c904
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceIssueDetailApi } from '#/api/mes/wm/outsourceissue/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createOutsourceIssueDetail,
+ getOutsourceIssueDetail,
+ updateOutsourceIssueDetail,
+} from '#/api/mes/wm/outsourceissue/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail>();
+const issueId = ref<number>(); // 鎵�灞炲彂鏂欏崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲彂鏂欏崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鍙戞枡鏄庣粏'])
+ : $t('ui.actionTitle.create', ['鍙戞枡鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceIssueDetailApi.OutsourceIssueDetail;
+ data.issueId = issueId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateOutsourceIssueDetail({ ...data, id: formData.value.id })
+ : createOutsourceIssueDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ issueId: number;
+ itemId?: number;
+ lineId: number;
+ }>();
+ issueId.value = data.issueId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getOutsourceIssueDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/outsourceissue/modules/detail-list.vue b/src/views/wls/outsourceissue/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/outsourceissue/modules/detail-list.vue
copy to src/views/wls/outsourceissue/modules/detail-list.vue
diff --git a/src/views/wls/outsourceissue/modules/form.vue b/src/views/wls/outsourceissue/modules/form.vue
new file mode 100644
index 0000000..477d21a
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/form.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmOutsourceIssueApi } from '#/api/mes/wm/outsourceissue';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmOutsourceIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ checkOutsourceIssueQuantity,
+ createOutsourceIssue,
+ finishOutsourceIssue,
+ getOutsourceIssue,
+ stockOutsourceIssue,
+ submitOutsourceIssue,
+ updateOutsourceIssue,
+} from '#/api/mes/wm/outsourceissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmOutsourceIssueApi.OutsourceIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岄鍑烘ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmOutsourceIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['澶栧崗鍙戞枡鍗�']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц鎷h揣';
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц棰嗗嚭';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['澶栧崗鍙戞枡鍗�'])
+ : $t('ui.actionTitle.create', ['澶栧崗鍙戞枡鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍙戞枡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceIssueApi.OutsourceIssue;
+ await updateOutsourceIssue({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitOutsourceIssue(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鎷h揣锛氭嫞璐ф暟閲忎笌鍙戞枡鏁伴噺涓嶄竴鑷存椂浜屾纭 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ const quantityMatch = await checkOutsourceIssueQuantity(formData.value.id);
+ if (!quantityMatch) {
+ try {
+ await confirm('鍙戞枡鏁伴噺涓庢嫞璐ф暟閲忎笉涓�鑷达紝纭鎵ц鎷h揣锛�');
+ } catch {
+ return;
+ }
+ }
+ modalApi.lock();
+ try {
+ await stockOutsourceIssue(formData.value.id);
+ message.success('鎷h揣鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц棰嗗嚭 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishOutsourceIssue(formData.value.id);
+ message.success('棰嗗嚭鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceIssueApi.OutsourceIssue;
+ try {
+ if (formData.value?.id) {
+ await updateOutsourceIssue({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createOutsourceIssue(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmOutsourceIssueStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getOutsourceIssue(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :issue-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ュ鍗忓彂鏂欏崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isStock"
+ title="纭鎵ц鎷h揣锛�"
+ @confirm="handleStock"
+ >
+ <Button type="primary">鎵ц鎷h揣</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭鎵ц棰嗗嚭锛熸墽琛屽悗灏嗘墸鍑忓簱瀛橈紝涓旀棤娉曟挙閿�銆�"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">鎵ц棰嗗嚭</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/outsourceissue/modules/line-form.vue b/src/views/wls/outsourceissue/modules/line-form.vue
new file mode 100644
index 0000000..1ae1da1
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/line-form.vue
@@ -0,0 +1,97 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceIssueLineApi } from '#/api/mes/wm/outsourceissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createOutsourceIssueLine,
+ getOutsourceIssueLine,
+ updateOutsourceIssueLine,
+} from '#/api/mes/wm/outsourceissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmOutsourceIssueLineApi.OutsourceIssueLine>();
+const issueId = ref<number>(); // 鎵�灞炲彂鏂欏崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鐗╂枡鍙戞枡鍗曡'])
+ : $t('ui.actionTitle.create', ['鐗╂枡鍙戞枡鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceIssueLineApi.OutsourceIssueLine;
+ data.issueId = issueId.value;
+ try {
+ await (formData.value?.id
+ ? updateOutsourceIssueLine({ ...data, id: formData.value.id })
+ : createOutsourceIssueLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ id?: number;
+ issueId: number;
+ }>();
+ issueId.value = data.issueId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getOutsourceIssueLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/outsourceissue/modules/line-list.vue b/src/views/wls/outsourceissue/modules/line-list.vue
new file mode 100644
index 0000000..4e88748
--- /dev/null
+++ b/src/views/wls/outsourceissue/modules/line-list.vue
@@ -0,0 +1,227 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceIssueDetailApi } from '#/api/mes/wm/outsourceissue/detail';
+import type { MesWmOutsourceIssueLineApi } from '#/api/mes/wm/outsourceissue/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getOutsourceIssueDetailListByLineId } from '#/api/mes/wm/outsourceissue/detail';
+import {
+ deleteOutsourceIssueLine,
+ getOutsourceIssueLinePage,
+} from '#/api/mes/wm/outsourceissue/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ issueId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+ Record<number, MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]>
+>({}); // 宸插睍寮�琛岀殑鍙戞枡鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmOutsourceIssueLineApi.OutsourceIssueLine) {
+ lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(
+ row: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteOutsourceIssueLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmOutsourceIssueLineApi.OutsourceIssueLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鍙戞枡鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, issueId: props.issueId, itemId, lineId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鍙戞枡鏄庣粏 */
+function getExpandedDetails(
+ row: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
+) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鍙戞枡鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getOutsourceIssueDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞藉彂鏂欐槑缁� */
+async function handleExpandChange(
+ row: MesWmOutsourceIssueLineApi.OutsourceIssueLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.issueId) {
+ return { list: [], total: 0 };
+ }
+ return await getOutsourceIssueLinePage({
+ issueId: props.issueId,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmOutsourceIssueLineApi.OutsourceIssueLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmOutsourceIssueLineApi.OutsourceIssueLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎷h揣',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handlePicking.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/outsourcereceipt/data.ts b/src/views/wls/outsourcereceipt/data.ts
new file mode 100644
index 0000000..a96d8ac
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/data.ts
@@ -0,0 +1,540 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder';
+import type { MesWmOutsourceReceiptApi } from '#/api/mes/wm/outsourcereceipt';
+import type { MesWmOutsourceReceiptDetailApi } from '#/api/mes/wm/outsourcereceipt/detail';
+import type { MesWmOutsourceReceiptLineApi } from '#/api/mes/wm/outsourcereceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+ MesProWorkOrderTypeEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佸畬鎴愭�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_OUTSOURCE_RECEIPT_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '澶栧崗璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ // 閫夋嫨澶栧崗璁㈠崟鍚庯紝鑷姩鍥炲~渚涘簲鍟�
+ onChange: async (workOrder?: MesProWorkOrderApi.WorkOrder) => {
+ await formApi?.setFieldValue('vendorId', workOrder?.vendorId);
+ },
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ type: MesProWorkOrderTypeEnum.OUTSOURCE,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'workOrderCode',
+ label: '澶栧崗璁㈠崟鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ鍗忚鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(
+ DICT_TYPE.MES_WM_OUTSOURCE_RECEIPT_STATUS,
+ 'number',
+ ),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmOutsourceReceiptApi.OutsourceReceipt>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍏ュ簱鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍏ュ簱鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'workOrderCode',
+ title: '澶栧崗璁㈠崟鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'vendorName',
+ title: '渚涘簲鍟嗗悕绉�',
+ minWidth: 120,
+ },
+ {
+ field: 'receiptDate',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_OUTSOURCE_RECEIPT_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '鍏ュ簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'iqcCheckFlag',
+ title: '鏄惁妫�楠�',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'qualityStatus',
+ title: '璐ㄩ噺鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+ },
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 180,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍏ュ簱鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ placeholder: '绯荤粺鑷姩鐢熸垚',
+ },
+ },
+ {
+ fieldName: 'productionDate',
+ label: '鐢熶骇鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鐢熶骇鏃ユ湡',
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'expireDate',
+ label: '鏈夋晥鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'lotNumber',
+ label: '鎵瑰彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒鍙�',
+ },
+ },
+ {
+ fieldName: 'iqcCheckFlag',
+ label: '鏄惁璐ㄦ',
+ component: 'Switch',
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍏ュ簱鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ editable: boolean,
+): VxeTableGridOptions<MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail>['columns'] {
+ return [
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍏ュ簱鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍏ュ簱浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+ onChange: async () => {
+ await formApi?.setValues({
+ areaId: undefined,
+ locationId: undefined,
+ });
+ },
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撳尯',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+ onChange: async () => {
+ await formApi?.setFieldValue('areaId', undefined);
+ },
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撲綅',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/outsourcereceipt/index.vue b/src/views/wls/outsourcereceipt/index.vue
new file mode 100644
index 0000000..d5a5066
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceReceiptApi } from '#/api/mes/wm/outsourcereceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmOutsourceReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelOutsourceReceipt,
+ deleteOutsourceReceipt,
+ exportOutsourceReceipt,
+ getOutsourceReceiptPage,
+} from '#/api/mes/wm/outsourcereceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓澶栧崗鍏ュ簱鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅澶栧崗鍏ュ簱鍗� */
+function handleDetail(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫澶栧崗鍏ュ簱鍗� */
+function handleEdit(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 瀹屾垚鍏ュ簱 */
+function handleFinish(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎澶栧崗鍏ュ簱鍗� */
+async function handleDelete(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteOutsourceReceipt(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷澶栧崗鍏ュ簱鍗� */
+async function handleCancel(row: MesWmOutsourceReceiptApi.OutsourceReceipt) {
+ await cancelOutsourceReceipt(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportOutsourceReceipt(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '澶栧崗鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getOutsourceReceiptPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmOutsourceReceiptApi.OutsourceReceipt>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="澶栧崗鍏ュ簱鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['澶栧崗鍏ュ簱鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-outsource-receipt:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-outsource-receipt:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-outsource-receipt:update'],
+ ifShow: row.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-outsource-receipt:delete'],
+ ifShow: row.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц涓婃灦',
+ type: 'link',
+ auth: ['mes:wm-outsource-receipt:update'],
+ ifShow: row.status === MesWmOutsourceReceiptStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '瀹屾垚鍏ュ簱',
+ type: 'link',
+ auth: ['mes:wm-outsource-receipt:finish'],
+ ifShow: row.status === MesWmOutsourceReceiptStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-outsource-receipt:update'],
+ ifShow:
+ row.status === MesWmOutsourceReceiptStatusEnum.APPROVING ||
+ row.status === MesWmOutsourceReceiptStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ュ鍗忓叆搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/outsourcereceipt/modules/detail-form.vue b/src/views/wls/outsourcereceipt/modules/detail-form.vue
new file mode 100644
index 0000000..a4ed921
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/detail-form.vue
@@ -0,0 +1,103 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceReceiptDetailApi } from '#/api/mes/wm/outsourcereceipt/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createOutsourceReceiptDetail,
+ getOutsourceReceiptDetail,
+ updateOutsourceReceiptDetail,
+} from '#/api/mes/wm/outsourcereceipt/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲叆搴撳崟琛岀紪鍙�
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鏀惰揣鏄庣粏'])
+ : $t('ui.actionTitle.create', ['鏀惰揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail;
+ data.receiptId = receiptId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateOutsourceReceiptDetail({ ...data, id: formData.value.id })
+ : createOutsourceReceiptDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ itemId?: number;
+ lineId: number;
+ receiptId: number;
+ }>();
+ receiptId.value = data.receiptId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getOutsourceReceiptDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/outsourcereceipt/modules/detail-list.vue b/src/views/wls/outsourcereceipt/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/outsourcereceipt/modules/detail-list.vue
copy to src/views/wls/outsourcereceipt/modules/detail-list.vue
diff --git a/src/views/wls/outsourcereceipt/modules/form.vue b/src/views/wls/outsourcereceipt/modules/form.vue
new file mode 100644
index 0000000..c538d37
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/form.vue
@@ -0,0 +1,227 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmOutsourceReceiptApi } from '#/api/mes/wm/outsourcereceipt';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmOutsourceReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createOutsourceReceipt,
+ finishOutsourceReceipt,
+ getOutsourceReceipt,
+ stockOutsourceReceipt,
+ submitOutsourceReceipt,
+ updateOutsourceReceipt,
+} from '#/api/mes/wm/outsourcereceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmOutsourceReceiptApi.OutsourceReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓哄畬鎴愬叆搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmOutsourceReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['澶栧崗鍏ュ簱鍗�']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц涓婃灦';
+ }
+ if (formType.value === 'finish') {
+ return '瀹屾垚鍏ュ簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['澶栧崗鍏ュ簱鍗�'])
+ : $t('ui.actionTitle.create', ['澶栧崗鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceReceiptApi.OutsourceReceipt;
+ await updateOutsourceReceipt({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitOutsourceReceipt(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await stockOutsourceReceipt(formData.value.id);
+ message.success('涓婃灦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 瀹屾垚鍏ュ簱 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishOutsourceReceipt(formData.value.id);
+ message.success('鍏ュ簱鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceReceiptApi.OutsourceReceipt;
+ try {
+ if (formData.value?.id) {
+ await updateOutsourceReceipt({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createOutsourceReceipt(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmOutsourceReceiptStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getOutsourceReceipt(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :receipt-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ュ鍗忓叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isStock"
+ title="纭鎵ц涓婃灦锛�"
+ @confirm="handleStock"
+ >
+ <Button type="primary">鎵ц涓婃灦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭瀹屾垚鍏ュ簱锛熷畬鎴愬悗灏嗘洿鏂板簱瀛樺彴璐︺��"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">瀹屾垚鍏ュ簱</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/outsourcereceipt/modules/line-form.vue b/src/views/wls/outsourcereceipt/modules/line-form.vue
new file mode 100644
index 0000000..1b3d829
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/line-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { MesWmOutsourceReceiptLineApi } from '#/api/mes/wm/outsourcereceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createOutsourceReceiptLine,
+ getOutsourceReceiptLine,
+ updateOutsourceReceiptLine,
+} from '#/api/mes/wm/outsourcereceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['澶栧崗鍏ュ簱鍗曡'])
+ : $t('ui.actionTitle.create', ['澶栧崗鍏ュ簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟锛堟壒娆″彿鐢卞悗绔嚜鍔ㄧ敓鎴愶紝涓嶆彁浜わ級
+ const data =
+ (await formApi.getValues()) as MesWmOutsourceReceiptLineApi.OutsourceReceiptLine;
+ data.receiptId = receiptId.value;
+ delete data.batchCode;
+ try {
+ await (formData.value?.id
+ ? updateOutsourceReceiptLine({ ...data, id: formData.value.id })
+ : createOutsourceReceiptLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ id?: number;
+ receiptId: number;
+ }>();
+ receiptId.value = data.receiptId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getOutsourceReceiptLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/outsourcereceipt/modules/line-list.vue b/src/views/wls/outsourcereceipt/modules/line-list.vue
new file mode 100644
index 0000000..8aa3ef4
--- /dev/null
+++ b/src/views/wls/outsourcereceipt/modules/line-list.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmOutsourceReceiptDetailApi } from '#/api/mes/wm/outsourcereceipt/detail';
+import type { MesWmOutsourceReceiptLineApi } from '#/api/mes/wm/outsourcereceipt/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getOutsourceReceiptDetailListByLineId } from '#/api/mes/wm/outsourcereceipt/detail';
+import {
+ deleteOutsourceReceiptLine,
+ getOutsourceReceiptLinePage,
+} from '#/api/mes/wm/outsourcereceipt/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+ Record<number, MesWmOutsourceReceiptDetailApi.OutsourceReceiptDetail[]>
+>({}); // 宸插睍寮�琛岀殑鏀惰揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ receiptId: props.receiptId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine) {
+ lineFormModalApi.setData({ id: row.id, receiptId: props.receiptId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(
+ row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteOutsourceReceiptLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鏀惰揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, itemId, lineId, receiptId: props.receiptId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鏀惰揣鏄庣粏 */
+function getExpandedDetails(
+ row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
+) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鏀惰揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getOutsourceReceiptDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥敹璐ф槑缁� */
+async function handleExpandChange(
+ row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.receiptId) {
+ return { list: [], total: 0 };
+ }
+ return await getOutsourceReceiptLinePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ receiptId: props.receiptId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmOutsourceReceiptLineApi.OutsourceReceiptLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmOutsourceReceiptLineApi.OutsourceReceiptLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <div class="flex items-center justify-center">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '涓婃灦',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handlePicking.bind(null, row),
+ },
+ ]"
+ />
+ <PrinterLabel
+ v-if="isStock"
+ :biz-code="row.batchCode"
+ :biz-id="row.batchId"
+ biz-type="BATCH"
+ />
+ </div>
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/mes/wm/packages/components/index.ts b/src/views/wls/packages/components/index.ts
similarity index 100%
copy from src/views/mes/wm/packages/components/index.ts
copy to src/views/wls/packages/components/index.ts
diff --git a/src/views/wls/packages/components/select-dialog.vue b/src/views/wls/packages/components/select-dialog.vue
new file mode 100644
index 0000000..55f5da9
--- /dev/null
+++ b/src/views/wls/packages/components/select-dialog.vue
@@ -0,0 +1,231 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { nextTick, ref } from 'vue';
+
+import { MesWmPackageStatusEnum } from '../../../../packages/constants/src';
+
+import { Alert, Button, message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getPackagePage } from '#/api/mes/wm/packages';
+
+import { useSelectGridColumns, useSelectGridFormSchema } from '../data';
+
+const props = withDefaults(
+ defineProps<{
+ childableOnly?: boolean; // 鍙睍绀哄彲浣滀负瀛愮鐨勮绠卞崟锛堟棤鐖剁 + 宸插畬鎴愮姸鎬侊級
+ excludeId?: number; // 鎺掗櫎鐨勭紪鍙凤紙閬垮厤閫夋嫨鑷繁浣滀负鐖剁锛�
+ }>(),
+ {
+ childableOnly: false,
+ excludeId: undefined,
+ },
+);
+
+const emit = defineEmits<{
+ selected: [rows: MesWmPackageApi.Package[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(true); // 鏄惁澶氶��
+const selectedRows = ref<MesWmPackageApi.Package[]>([]); // 宸查�夎绠卞崟
+const preSelectedIds = ref<number[]>([]); // 棰勯�夎绠卞崟缂栧彿
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<number, MesWmPackageApi.Package>();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as MesWmPackageApi.Package[];
+ records.forEach((row) => {
+ if (row.id !== undefined && row.id !== null) {
+ selectedMap.set(row.id, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: MesWmPackageApi.Package) {
+ selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmPackageApi.Package) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紱澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({ row }: { row: MesWmPackageApi.Package }) {
+ if (multiple.value) {
+ await toggleMultipleRow(row);
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夎绠卞崟 */
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows = gridApi.grid.getData() as MesWmPackageApi.Package[];
+ for (const row of rows) {
+ if (
+ row.id === undefined ||
+ row.id === null ||
+ !preSelectedIds.value.includes(row.id)
+ ) {
+ continue;
+ }
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useSelectGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useSelectGridColumns(true),
+ height: 480,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ range: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ const data = await getPackagePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ // childableOnly 妯″紡锛氬浐瀹氳繃婊ゆ棤鐖剁 + 宸插畬鎴愮姸鎬�
+ parentId: props.childableOnly ? 0 : undefined,
+ status: props.childableOnly
+ ? MesWmPackageStatusEnum.FINISHED
+ : undefined,
+ });
+ // 鎺掗櫎鎸囧畾缂栧彿锛岄伩鍏嶉�夋嫨鑷繁浣滀负鐖剁
+ const list = props.excludeId
+ ? (data.list || []).filter((item) => item.id !== props.excludeId)
+ : data.list || [];
+ return { list, total: data.total };
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmPackageApi.Package>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({ row }: { row: MesWmPackageApi.Package }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+ await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑瑁呯鍗曢�夋嫨寮圭獥 */
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? true;
+ preSelectedIds.value = selectedIds || [];
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: useSelectGridColumns(multiple.value),
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+function closeModal() {
+ open.value = false;
+}
+
+/** 纭閫夋嫨 */
+function handleConfirm() {
+ const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+ if (rows.length === 0) {
+ message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit('selected', multiple.value ? rows : [rows[0]!]);
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ :destroy-on-close="true"
+ title="瑁呯鍗曢�夋嫨"
+ width="80%"
+ @cancel="closeModal"
+ @ok="handleConfirm"
+ >
+ <Alert
+ v-if="childableOnly"
+ class="mb-3"
+ message="浠呭睍绀哄彲浣滀负瀛愮鐨勮绠卞崟锛堟棤鐖剁 + 宸插畬鎴愶級"
+ show-icon
+ type="info"
+ />
+ <Grid table-title="瑁呯鍗曞垪琛�" />
+ <template #footer>
+ <Button @click="closeModal">鍙栨秷</Button>
+ <Button type="primary" @click="handleConfirm">纭畾</Button>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/packages/components/select.vue b/src/views/wls/packages/components/select.vue
new file mode 100644
index 0000000..d4a8f37
--- /dev/null
+++ b/src/views/wls/packages/components/select.vue
@@ -0,0 +1,143 @@
+<script lang="ts" setup>
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getPackage } from '#/api/mes/wm/packages';
+
+import WmPackageSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmPackageSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ childableOnly?: boolean; // 鍙睍绀哄彲浣滀负瀛愮鐨勮绠卞崟锛堟棤鐖剁 + 宸插畬鎴愮姸鎬侊級
+ disabled?: boolean;
+ excludeId?: number; // 鎺掗櫎鐨勭紪鍙凤紙閬垮厤閫夋嫨鑷繁浣滀负鐖剁锛�
+ modelValue?: number;
+ placeholder?: string;
+ }>(),
+ {
+ allowClear: true,
+ childableOnly: false,
+ disabled: false,
+ excludeId: undefined,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨瑁呯鍗�',
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmPackageApi.Package | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmPackageSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmPackageApi.Package>();
+
+const displayLabel = computed(() => selectedItem.value?.code ?? '');
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ瑁呯鍗曚俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getPackage(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夎绠卞崟 */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑瑁呯鍗曢�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmPackageApi.Package[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+ <div>瀹㈡埛锛歿{ selectedItem.clientName || '-' }}</div>
+ <div>閿�鍞鍗曪細{{ selectedItem.salesOrderCode || '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <WmPackageSelectDialog
+ ref="dialogRef"
+ :childable-only="childableOnly"
+ :exclude-id="excludeId"
+ @selected="handleSelected"
+ />
+</template>
diff --git a/src/views/wls/packages/data.ts b/src/views/wls/packages/data.ts
new file mode 100644
index 0000000..37e3967
--- /dev/null
+++ b/src/views/wls/packages/data.ts
@@ -0,0 +1,641 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+import type { MesWmPackageLineApi } from '#/api/mes/wm/packages/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSimpleUserList } from '#/api/system/user';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdUnitMeasureSelect } from '#/views/mes/md/unitmeasure/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '瑁呯鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ绠卞崟缂栧彿',
+ },
+ rules: 'required',
+ suffix:
+ formType === 'detail'
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_PACKAGE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'packageDate',
+ label: '瑁呯鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨瑁呯鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'inspectorUserId',
+ label: '妫�鏌ュ憳',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨妫�鏌ュ憳',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'invoiceCode',
+ label: '鍙戠エ缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ彂绁ㄧ紪鍙�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'sizeUnitId',
+ label: '灏哄鍗曚綅',
+ component: markRaw(MdUnitMeasureSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨灏哄鍗曚綅',
+ },
+ },
+ {
+ fieldName: 'length',
+ label: '绠遍暱搴�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ョ闀垮害',
+ precision: 2,
+ },
+ },
+ {
+ fieldName: 'width',
+ label: '绠卞搴�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ョ瀹藉害',
+ precision: 2,
+ },
+ },
+ {
+ fieldName: 'height',
+ label: '绠遍珮搴�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ョ楂樺害',
+ precision: 2,
+ },
+ },
+ {
+ fieldName: 'weightUnitId',
+ label: '閲嶉噺鍗曚綅',
+ component: markRaw(MdUnitMeasureSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨閲嶉噺鍗曚綅',
+ },
+ },
+ {
+ fieldName: 'netWeight',
+ label: '鍑�閲�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ュ噣閲�',
+ precision: 2,
+ },
+ },
+ {
+ fieldName: 'grossWeight',
+ label: '姣涢噸',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ瘺閲�',
+ precision: 2,
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '瑁呯鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヨ绠卞崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'inspectorUserId',
+ label: '妫�鏌ュ憳',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨妫�鏌ュ憳',
+ valueField: 'id',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmPackageApi.Package>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '瑁呯鍗曠紪鍙�',
+ minWidth: 250,
+ fixed: 'left',
+ treeNode: true,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'packageDate',
+ title: '瑁呯鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曠紪鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'invoiceCode',
+ title: '鍙戠エ缂栧彿',
+ minWidth: 120,
+ },
+ {
+ field: 'clientCode',
+ title: '瀹㈡埛缂栫爜',
+ minWidth: 100,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'length',
+ title: '绠遍暱搴�',
+ width: 80,
+ },
+ {
+ field: 'width',
+ title: '绠卞搴�',
+ width: 80,
+ },
+ {
+ field: 'height',
+ title: '绠遍珮搴�',
+ width: 80,
+ },
+ {
+ field: 'sizeUnitName',
+ title: '灏哄鍗曚綅',
+ width: 90,
+ },
+ {
+ field: 'netWeight',
+ title: '鍑�閲�',
+ width: 80,
+ },
+ {
+ field: 'grossWeight',
+ title: '姣涢噸',
+ width: 80,
+ },
+ {
+ field: 'weightUnitName',
+ title: '閲嶉噺鍗曚綅',
+ width: 90,
+ },
+ {
+ field: 'inspectorName',
+ title: '妫�鏌ュ憳',
+ minWidth: 100,
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_PACKAGE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 瀛愮鍒楄〃鐨勫瓧娈� */
+export function useSubPackageGridColumns(): VxeTableGridOptions<MesWmPackageApi.Package>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '瑁呯鍗曠紪鍙�',
+ minWidth: 160,
+ fixed: 'left',
+ },
+ {
+ field: 'packageDate',
+ title: '瑁呯鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曠紪鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'invoiceCode',
+ title: '鍙戠エ缂栧彿',
+ minWidth: 120,
+ },
+ {
+ field: 'clientCode',
+ title: '瀹㈡埛缂栫爜',
+ minWidth: 100,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'netWeight',
+ title: '鍑�閲�',
+ width: 80,
+ },
+ {
+ field: 'grossWeight',
+ title: '姣涢噸',
+ width: 80,
+ },
+ {
+ field: 'weightUnitName',
+ title: '閲嶉噺鍗曚綅',
+ width: 90,
+ },
+ {
+ field: 'inspectorName',
+ title: '妫�鏌ュ憳',
+ minWidth: 100,
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_PACKAGE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 100,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 瑁呯鏄庣粏鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐢熶骇璁㈠崟',
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'itemId',
+ label: '浜у搧鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '瑁呯鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0.01,
+ placeholder: '璇疯緭鍏ヨ绠辨暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'expireDate',
+ label: '鏈夋晥鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 瑁呯鏄庣粏鍒楄〃鐨勫瓧娈� */
+export function useLineGridColumns(): VxeTableGridOptions<MesWmPackageLineApi.PackageLine>['columns'] {
+ return [
+ {
+ field: 'itemCode',
+ title: '浜у搧鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '浜у搧鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '瑁呯鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'workOrderCode',
+ title: '鐢熶骇璁㈠崟缂栧彿',
+ minWidth: 140,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'expireDate',
+ title: '鏈夋晥鏈�',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useSelectGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '瑁呯鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヨ绠卞崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'inspectorUserId',
+ label: '妫�鏌ュ憳',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨妫�鏌ュ憳',
+ valueField: 'id',
+ },
+ },
+ ];
+}
+
+/** 閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useSelectGridColumns(
+ multiple = true,
+): VxeTableGridOptions<MesWmPackageApi.Package>['columns'] {
+ return [
+ {
+ type: multiple ? 'checkbox' : 'radio',
+ width: 50,
+ },
+ {
+ field: 'code',
+ title: '瑁呯鍗曠紪鍙�',
+ minWidth: 160,
+ },
+ {
+ field: 'packageDate',
+ title: '瑁呯鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曠紪鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'invoiceCode',
+ title: '鍙戠エ缂栧彿',
+ minWidth: 120,
+ },
+ {
+ field: 'clientCode',
+ title: '瀹㈡埛缂栫爜',
+ minWidth: 100,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'netWeight',
+ title: '鍑�閲�',
+ width: 80,
+ },
+ {
+ field: 'grossWeight',
+ title: '姣涢噸',
+ width: 80,
+ },
+ {
+ field: 'weightUnitName',
+ title: '閲嶉噺鍗曚綅',
+ width: 90,
+ },
+ {
+ field: 'inspectorName',
+ title: '妫�鏌ュ憳',
+ minWidth: 100,
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_PACKAGE_STATUS },
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/packages/index.vue b/src/views/wls/packages/index.vue
new file mode 100644
index 0000000..5f03e30
--- /dev/null
+++ b/src/views/wls/packages/index.vue
@@ -0,0 +1,160 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmPackageStatusEnum } from '../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { deletePackage, getPackagePage } from '#/api/mes/wm/packages';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const isExpanded = ref(true); // 鏍戝舰琛ㄦ牸鏄惁灞曞紑
+
+/** 鍒囨崲鏍戝舰灞曞紑/鏀剁缉鐘舵�� */
+function handleExpand() {
+ isExpanded.value = !isExpanded.value;
+ gridApi.grid.setAllTreeExpand(isExpanded.value);
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓瑁呯鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅瑁呯鍗� */
+function handleDetail(row: MesWmPackageApi.Package) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫瑁呯鍗� */
+function handleEdit(row: MesWmPackageApi.Package) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎瑁呯鍗� */
+async function handleDelete(row: MesWmPackageApi.Package) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deletePackage(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getPackagePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ treeConfig: {
+ parentField: 'parentId',
+ rowField: 'id',
+ transform: true,
+ expandAll: true,
+ reserve: true,
+ },
+ } as VxeTableGridOptions<MesWmPackageApi.Package>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="瑁呯鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['瑁呯鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-package:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: isExpanded ? '鏀剁缉' : '灞曞紑',
+ type: 'primary',
+ onClick: handleExpand,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-package:update'],
+ ifShow: row.status === MesWmPackageStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-package:delete'],
+ ifShow: row.status === MesWmPackageStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/packages/modules/form.vue b/src/views/wls/packages/modules/form.vue
new file mode 100644
index 0000000..fee5a6f
--- /dev/null
+++ b/src/views/wls/packages/modules/form.vue
@@ -0,0 +1,193 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmPackageApi } from '#/api/mes/wm/packages';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmPackageStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, message, Tabs } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createPackage,
+ finishPackage,
+ getPackage,
+ updatePackage,
+} from '#/api/mes/wm/packages';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import PackageLineList from './package-line-list.vue';
+import SubPackageList from './sub-package-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmPackageApi.Package>();
+const subTabsName = ref('subPackage'); // 瀛愯〃褰撳墠 tab
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜� finish 鏃惰烦杩囨湭鍙樻洿鐨勪繚瀛樿姹�
+
+const isEditable = computed(() =>
+ ['create', 'update'].includes(formType.value),
+);
+const canFinish = computed(
+ () =>
+ formType.value === 'update' &&
+ formData.value?.status === MesWmPackageStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['瑁呯鍗�']);
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['瑁呯鍗�'])
+ : $t('ui.actionTitle.create', ['瑁呯鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦琛ㄥ崟锛坈reate/update锛夛紝鏂板鎴愬姛鍚庡垏鎹负缂栬緫鎬佷互缁х画缁存姢瀛愯〃 */
+async function handleSubmit(): Promise<boolean> {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return false;
+ }
+ const data = (await formApi.getValues()) as MesWmPackageApi.Package;
+ if (formData.value?.id) {
+ await updatePackage({ ...data, id: formData.value.id });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createPackage(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmPackageStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', MesWmPackageStatusEnum.PREPARE);
+ formType.value = 'update';
+ }
+ // 鍒锋柊蹇収锛屼笅娆� finish 鐢ㄤ簬鍒ゆ柇鏄惁闇�瑕佸啀淇濆瓨
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ return true;
+}
+
+/** 瀹屾垚瑁呯鍗曪細缂栬緫鎬佹湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤瀹屾垚鎺ュ彛 */
+async function handleFinish() {
+ const id = formData.value?.id;
+ if (!id) {
+ return;
+ }
+ try {
+ await confirm('纭瀹屾垚璇ヨ绠卞崟锛熷畬鎴愬悗灏嗕笉鍙紪杈戙��');
+ } catch {
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data = (await formApi.getValues()) as MesWmPackageApi.Package;
+ await updatePackage({ ...data, id });
+ originalSnapshot.value = current;
+ }
+ await finishPackage(id);
+ message.success('瀹屾垚鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (formType.value === 'detail') {
+ await modalApi.close();
+ return;
+ }
+ modalApi.lock();
+ try {
+ // 鎻愪氦琛ㄥ崟
+ const ok = await handleSubmit();
+ if (!ok) {
+ return;
+ }
+ // 鍏抽棴骞舵彁绀�
+ message.success($t('ui.actionMessage.operationSuccess'));
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ subTabsName.value = 'subPackage';
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(formType.value === 'detail');
+ modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getPackage(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ // 璁板綍鍒濆蹇収锛屽悗缁� finish 鐢ㄤ簬鍒ゆ柇鏄惁闇�瑕佸啀淇濆瓨
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-4/5">
+ <Form class="mx-4" />
+ <Tabs
+ v-if="formData?.id"
+ v-model:active-key="subTabsName"
+ class="mx-4 mt-4"
+ >
+ <Tabs.TabPane key="subPackage" tab="瀛愮">
+ <SubPackageList :editable="isEditable" :package-id="formData.id" />
+ </Tabs.TabPane>
+ <Tabs.TabPane key="packageLine" tab="瑁呯娓呭崟">
+ <PackageLineList :editable="isEditable" :package-id="formData.id" />
+ </Tabs.TabPane>
+ </Tabs>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Button v-if="canFinish" type="primary" @click="handleFinish">
+ 瀹屾垚
+ </Button>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/packages/modules/line-form.vue b/src/views/wls/packages/modules/line-form.vue
new file mode 100644
index 0000000..d81eadf
--- /dev/null
+++ b/src/views/wls/packages/modules/line-form.vue
@@ -0,0 +1,92 @@
+<script lang="ts" setup>
+import type { MesWmPackageLineApi } from '#/api/mes/wm/packages/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createPackageLine,
+ getPackageLine,
+ updatePackageLine,
+} from '#/api/mes/wm/packages/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmPackageLineApi.PackageLine>();
+const packageId = ref<number>(); // 鎵�灞炶绠卞崟缂栧彿
+const getTitle = computed(() =>
+ formData.value?.id
+ ? $t('ui.actionTitle.edit', ['瑁呯鏄庣粏'])
+ : $t('ui.actionTitle.create', ['瑁呯鏄庣粏']),
+);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 100,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmPackageLineApi.PackageLine;
+ data.packageId = packageId.value;
+ try {
+ await (formData.value?.id
+ ? updatePackageLine({ ...data, id: formData.value.id })
+ : createPackageLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; packageId: number }>();
+ packageId.value = data.packageId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getPackageLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-2/5">
+ <Form />
+ </Modal>
+</template>
diff --git a/src/views/wls/packages/modules/package-line-list.vue b/src/views/wls/packages/modules/package-line-list.vue
new file mode 100644
index 0000000..cf4e063
--- /dev/null
+++ b/src/views/wls/packages/modules/package-line-list.vue
@@ -0,0 +1,132 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmPackageLineApi } from '#/api/mes/wm/packages/line';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deletePackageLine,
+ getPackageLinePage,
+} from '#/api/mes/wm/packages/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ editable: boolean; // 鏄惁鍙紪杈戯紙鑽夌鎬侊級
+ packageId: number; // 鎵�灞炶绠卞崟缂栧彿
+}>();
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 娣诲姞瑁呯鏄庣粏 */
+function handleCreate() {
+ lineFormModalApi.setData({ packageId: props.packageId }).open();
+}
+
+/** 缂栬緫瑁呯鏄庣粏 */
+function handleEdit(row: MesWmPackageLineApi.PackageLine) {
+ lineFormModalApi.setData({ id: row.id, packageId: props.packageId }).open();
+}
+
+/** 鍒犻櫎瑁呯鏄庣粏 */
+async function handleDelete(row: MesWmPackageLineApi.PackageLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deletePackageLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(),
+ height: 360,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.packageId) {
+ return { list: [], total: 0 };
+ }
+ return await getPackageLinePage({
+ packageId: props.packageId,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmPackageLineApi.PackageLine>,
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <Grid table-title="瑁呯娓呭崟">
+ <template #toolbar-tools>
+ <TableAction
+ v-if="editable"
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['瑁呯鏄庣粏']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: editable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: editable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/mes/wm/packages/modules/sub-package-list.vue b/src/views/wls/packages/modules/sub-package-list.vue
similarity index 100%
copy from src/views/mes/wm/packages/modules/sub-package-list.vue
copy to src/views/wls/packages/modules/sub-package-list.vue
diff --git a/src/views/wls/productissue/data.ts b/src/views/wls/productissue/data.ts
new file mode 100644
index 0000000..dbf7a60
--- /dev/null
+++ b/src/views/wls/productissue/data.ts
@@ -0,0 +1,475 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
+import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佽鎯呫�佸畬鎴愭�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '棰嗘枡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ鏂欏崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_PRODUCT_ISSUE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '棰嗘枡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ鏂欏崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'requiredTime',
+ label: '闇�姹傛椂闂�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ placeholder: '璇烽�夋嫨闇�姹傛椂闂�',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'workstationId',
+ label: '宸ヤ綔绔�',
+ component: markRaw(MdWorkstationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨宸ヤ綔绔�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '棰嗘枡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ鏂欏崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '棰嗘枡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ鏂欏崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'issueDate',
+ label: '棰嗘枡鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(
+ DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS,
+ 'number',
+ ),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '棰嗘枡鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '棰嗘枡鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'workOrderCode',
+ title: '鐢熶骇璁㈠崟',
+ minWidth: 140,
+ },
+ {
+ field: 'workstationName',
+ title: '宸ヤ綔绔�',
+ minWidth: 120,
+ },
+ {
+ field: 'requiredTime',
+ title: '闇�姹傛椂闂�',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_PRODUCT_ISSUE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 棰嗘枡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '棰嗘枡鏁伴噺',
+ width: 100,
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 棰嗘枡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '浜у搧鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '棰嗘枡鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ラ鏂欐暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鎷h揣鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmProductIssueDetailApi.ProductIssueDetail>['columns'] {
+ return [
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鎷h揣鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨璁板綍',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ areaId: stock?.areaId,
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ locationId: stock?.locationId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ warehouseId: stock?.warehouseId,
+ });
+ },
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['itemId'],
+ componentProps: (values) => ({
+ itemId: values.itemId,
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍑哄簱浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ disabled: true,
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ disabled: true,
+ locationId: values.locationId,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/productissue/index.vue b/src/views/wls/productissue/index.vue
new file mode 100644
index 0000000..d627716
--- /dev/null
+++ b/src/views/wls/productissue/index.vue
@@ -0,0 +1,219 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmProductIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelProductIssue,
+ deleteProductIssue,
+ exportProductIssue,
+ getProductIssuePage,
+ submitProductIssue,
+} from '#/api/mes/wm/productissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓棰嗘枡鍑哄簱鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅棰嗘枡鍑哄簱鍗� */
+function handleDetail(row: MesWmProductIssueApi.ProductIssue) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫棰嗘枡鍑哄簱鍗� */
+function handleEdit(row: MesWmProductIssueApi.ProductIssue) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmProductIssueApi.ProductIssue) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 瀹屾垚棰嗘枡鍑哄簱 */
+function handleFinish(row: MesWmProductIssueApi.ProductIssue) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愪氦棰嗘枡鍑哄簱鍗� */
+async function handleSubmit(row: MesWmProductIssueApi.ProductIssue) {
+ await submitProductIssue(row.id!);
+ message.success('鎻愪氦鎴愬姛');
+ handleRefresh();
+}
+
+/** 鍒犻櫎棰嗘枡鍑哄簱鍗� */
+async function handleDelete(row: MesWmProductIssueApi.ProductIssue) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteProductIssue(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷棰嗘枡鍑哄簱鍗� */
+async function handleCancel(row: MesWmProductIssueApi.ProductIssue) {
+ await cancelProductIssue(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportProductIssue(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '棰嗘枡鍑哄簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getProductIssuePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmProductIssueApi.ProductIssue>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="棰嗘枡鍑哄簱鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['棰嗘枡鍑哄簱鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-product-issue:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-product-issue:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-product-issue:update'],
+ ifShow: row.status === MesWmProductIssueStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.submit'),
+ type: 'link',
+ auth: ['mes:wm-product-issue:update'],
+ ifShow: row.status === MesWmProductIssueStatusEnum.PREPARE,
+ popConfirm: {
+ title: '纭鎻愪氦璇ラ鏂欏嚭搴撳崟锛熸彁浜ゅ悗灏嗕笉鑳戒慨鏀广��',
+ confirm: handleSubmit.bind(null, row),
+ },
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-product-issue:delete'],
+ ifShow: row.status === MesWmProductIssueStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц鎷h揣',
+ type: 'link',
+ auth: ['mes:wm-product-issue:update'],
+ ifShow: row.status === MesWmProductIssueStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '瀹屾垚',
+ type: 'link',
+ auth: ['mes:wm-product-issue:finish'],
+ ifShow: row.status === MesWmProductIssueStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-product-issue:update'],
+ ifShow:
+ row.status === MesWmProductIssueStatusEnum.APPROVING ||
+ row.status === MesWmProductIssueStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ラ鏂欏嚭搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/productissue/modules/detail-form.vue b/src/views/wls/productissue/modules/detail-form.vue
new file mode 100644
index 0000000..191d9bd
--- /dev/null
+++ b/src/views/wls/productissue/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createProductIssueDetail,
+ getProductIssueDetail,
+ updateProductIssueDetail,
+} from '#/api/mes/wm/productissue/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmProductIssueDetailApi.ProductIssueDetail>();
+const issueId = ref<number>(); // 鎵�灞為鏂欏崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為鏂欏崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鎷h揣鏄庣粏'])
+ : $t('ui.actionTitle.create', ['鎷h揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductIssueDetailApi.ProductIssueDetail;
+ data.issueId = issueId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateProductIssueDetail({ ...data, id: formData.value.id })
+ : createProductIssueDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ issueId: number;
+ itemId?: number;
+ lineId: number;
+ }>();
+ issueId.value = data.issueId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getProductIssueDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/productissue/modules/detail-list.vue b/src/views/wls/productissue/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/productissue/modules/detail-list.vue
copy to src/views/wls/productissue/modules/detail-list.vue
diff --git a/src/views/wls/productissue/modules/form.vue b/src/views/wls/productissue/modules/form.vue
new file mode 100644
index 0000000..471ef95
--- /dev/null
+++ b/src/views/wls/productissue/modules/form.vue
@@ -0,0 +1,232 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmProductIssueApi } from '#/api/mes/wm/productissue';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmProductIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ checkProductIssueQuantity,
+ createProductIssue,
+ finishProductIssue,
+ getProductIssue,
+ stockProductIssue,
+ submitProductIssue,
+ updateProductIssue,
+} from '#/api/mes/wm/productissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmProductIssueApi.ProductIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓哄畬鎴愬嚭搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmProductIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['棰嗘枡鍑哄簱鍗�']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц鎷h揣';
+ }
+ if (formType.value === 'finish') {
+ return '瀹屾垚棰嗘枡鍑哄簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['棰嗘枡鍑哄簱鍗�'])
+ : $t('ui.actionTitle.create', ['棰嗘枡鍑哄簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦棰嗘枡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
+ await updateProductIssue({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitProductIssue(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鎷h揣锛氶鏂欐暟閲忎笌鎷h揣鏁伴噺涓嶄竴鑷存椂浜屾纭 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ const quantityMatch = await checkProductIssueQuantity(formData.value.id);
+ if (!quantityMatch) {
+ try {
+ await confirm('棰嗘枡鏁伴噺涓庢嫞璐ф暟閲忎笉涓�鑷达紝纭鎵ц鎷h揣锛�');
+ } catch {
+ return;
+ }
+ }
+ modalApi.lock();
+ try {
+ await stockProductIssue(formData.value.id);
+ message.success('鎷h揣鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 瀹屾垚棰嗘枡鍑哄簱 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishProductIssue(formData.value.id);
+ message.success('瀹屾垚鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductIssueApi.ProductIssue;
+ try {
+ if (formData.value?.id) {
+ await updateProductIssue({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createProductIssue(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmProductIssueStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getProductIssue(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :issue-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ鏂欏嚭搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="isStock" type="primary" @click="handleStock">
+ 鎵ц鎷h揣
+ </Button>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭瀹屾垚璇ラ鏂欏崟骞舵墽琛屽嚭搴撳悧锛�"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">瀹屾垚</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/productissue/modules/line-form.vue b/src/views/wls/productissue/modules/line-form.vue
new file mode 100644
index 0000000..f513551
--- /dev/null
+++ b/src/views/wls/productissue/modules/line-form.vue
@@ -0,0 +1,94 @@
+<script lang="ts" setup>
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createProductIssueLine,
+ getProductIssueLine,
+ updateProductIssueLine,
+} from '#/api/mes/wm/productissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductIssueLineApi.ProductIssueLine>();
+const issueId = ref<number>(); // 鎵�灞為鏂欏崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['棰嗘枡鍑哄簱鍗曡'])
+ : $t('ui.actionTitle.create', ['棰嗘枡鍑哄簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductIssueLineApi.ProductIssueLine;
+ data.issueId = issueId.value;
+ try {
+ await (formData.value?.id
+ ? updateProductIssueLine({ ...data, id: formData.value.id })
+ : createProductIssueLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; issueId: number }>();
+ issueId.value = data.issueId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getProductIssueLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/productissue/modules/line-list.vue b/src/views/wls/productissue/modules/line-list.vue
new file mode 100644
index 0000000..49e5ded
--- /dev/null
+++ b/src/views/wls/productissue/modules/line-list.vue
@@ -0,0 +1,223 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductIssueDetailApi } from '#/api/mes/wm/productissue/detail';
+import type { MesWmProductIssueLineApi } from '#/api/mes/wm/productissue/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getProductIssueDetailListByLineId } from '#/api/mes/wm/productissue/detail';
+import {
+ deleteProductIssueLine,
+ getProductIssueLinePage,
+} from '#/api/mes/wm/productissue/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ issueId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+ Record<number, MesWmProductIssueDetailApi.ProductIssueDetail[]>
+>({}); // 宸插睍寮�琛岀殑鎷h揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmProductIssueLineApi.ProductIssueLine) {
+ lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmProductIssueLineApi.ProductIssueLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteProductIssueLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmProductIssueLineApi.ProductIssueLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鎷h揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, issueId: props.issueId, itemId, lineId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鎷h揣鏄庣粏 */
+function getExpandedDetails(row: MesWmProductIssueLineApi.ProductIssueLine) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鎷h揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getProductIssueDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥嫞璐ф槑缁� */
+async function handleExpandChange(
+ row: MesWmProductIssueLineApi.ProductIssueLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.issueId) {
+ return { list: [], total: 0 };
+ }
+ return await getProductIssueLinePage({
+ issueId: props.issueId,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmProductIssueLineApi.ProductIssueLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmProductIssueLineApi.ProductIssueLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎷h揣',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handlePicking.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/productreceipt/data.ts b/src/views/wls/productreceipt/data.ts
new file mode 100644
index 0000000..0f755ce
--- /dev/null
+++ b/src/views/wls/productreceipt/data.ts
@@ -0,0 +1,452 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
+import type { MesWmProductReceiptDetailApi } from '#/api/mes/wm/productreceipt/detail';
+import type { MesWmProductReceiptLineApi } from '#/api/mes/wm/productreceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佸叆搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.PRODUCTRECPT_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍏ュ簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍏ュ簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ叆搴撳崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmProductReceiptApi.ProductReceipt>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍏ュ簱鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍏ュ簱鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'workOrderCode',
+ title: '鐢熶骇璁㈠崟',
+ minWidth: 140,
+ },
+ {
+ field: 'receiptDate',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_PRODUCT_RECEIPT_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmProductReceiptLineApi.ProductReceiptLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '浜у搧鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '浜у搧鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '鍏ュ簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨璁板綍',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~鐗╂枡/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ itemId: stock?.itemId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ });
+ },
+ virtualFilter: 'only',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍏ュ簱鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ placeholder: '閫夋嫨搴撳瓨鍚庤嚜鍔ㄥ甫鍑�',
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmProductReceiptDetailApi.ProductReceiptDetail>['columns'] {
+ return [
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍏ュ簱浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+ onChange: async () => {
+ await formApi?.setValues({
+ areaId: undefined,
+ locationId: undefined,
+ });
+ },
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撳尯',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+ onChange: async () => {
+ await formApi?.setFieldValue('areaId', undefined);
+ },
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撲綅',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ ];
+}
diff --git a/src/views/wls/productreceipt/index.vue b/src/views/wls/productreceipt/index.vue
new file mode 100644
index 0000000..3fb50a4
--- /dev/null
+++ b/src/views/wls/productreceipt/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmProductReceiptStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelProductReceipt,
+ deleteProductReceipt,
+ exportProductReceipt,
+ getProductReceiptPage,
+} from '#/api/mes/wm/productreceipt';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓浜у搧鍏ュ簱鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅浜у搧鍏ュ簱鍗� */
+function handleDetail(row: MesWmProductReceiptApi.ProductReceipt) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫浜у搧鍏ュ簱鍗� */
+function handleEdit(row: MesWmProductReceiptApi.ProductReceipt) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmProductReceiptApi.ProductReceipt) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц鍏ュ簱 */
+function handleFinish(row: MesWmProductReceiptApi.ProductReceipt) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎浜у搧鍏ュ簱鍗� */
+async function handleDelete(row: MesWmProductReceiptApi.ProductReceipt) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteProductReceipt(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷浜у搧鍏ュ簱鍗� */
+async function handleCancel(row: MesWmProductReceiptApi.ProductReceipt) {
+ await cancelProductReceipt(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportProductReceipt(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '浜у搧鍏ュ簱鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getProductReceiptPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmProductReceiptApi.ProductReceipt>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="浜у搧鍏ュ簱鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['浜у搧鍏ュ簱鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-product-receipt:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-product-receipt:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-product-receipt:update'],
+ ifShow: row.status === MesWmProductReceiptStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-product-receipt:delete'],
+ ifShow: row.status === MesWmProductReceiptStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц涓婃灦',
+ type: 'link',
+ auth: ['mes:wm-product-receipt:update'],
+ ifShow: row.status === MesWmProductReceiptStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '鎵ц鍏ュ簱',
+ type: 'link',
+ auth: ['mes:wm-product-receipt:finish'],
+ ifShow: row.status === MesWmProductReceiptStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-product-receipt:update'],
+ ifShow:
+ row.status === MesWmProductReceiptStatusEnum.APPROVING ||
+ row.status === MesWmProductReceiptStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ヤ骇鍝佸叆搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/productreceipt/modules/detail-form.vue b/src/views/wls/productreceipt/modules/detail-form.vue
new file mode 100644
index 0000000..28e84b9
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmProductReceiptDetailApi } from '#/api/mes/wm/productreceipt/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createProductReceiptDetail,
+ getProductReceiptDetail,
+ updateProductReceiptDetail,
+} from '#/api/mes/wm/productreceipt/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmProductReceiptDetailApi.ProductReceiptDetail>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲叆搴撳崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+ : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductReceiptDetailApi.ProductReceiptDetail;
+ data.receiptId = receiptId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateProductReceiptDetail({ ...data, id: formData.value.id })
+ : createProductReceiptDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ itemId?: number;
+ lineId: number;
+ receiptId: number;
+ }>();
+ receiptId.value = data.receiptId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getProductReceiptDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/productreceipt/modules/detail-list.vue b/src/views/wls/productreceipt/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/productreceipt/modules/detail-list.vue
copy to src/views/wls/productreceipt/modules/detail-list.vue
diff --git a/src/views/wls/productreceipt/modules/form.vue b/src/views/wls/productreceipt/modules/form.vue
new file mode 100644
index 0000000..12e4c66
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/form.vue
@@ -0,0 +1,243 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmProductReceiptApi } from '#/api/mes/wm/productreceipt';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmProductReceiptStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ checkProductReceiptQuantity,
+ createProductReceipt,
+ finishProductReceipt,
+ getProductReceipt,
+ stockProductReceipt,
+ submitProductReceipt,
+ updateProductReceipt,
+} from '#/api/mes/wm/productreceipt';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmProductReceiptApi.ProductReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽叆搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmProductReceiptStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['浜у搧鍏ュ簱鍗�']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц涓婃灦';
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц鍏ュ簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['浜у搧鍏ュ簱鍗�'])
+ : $t('ui.actionTitle.create', ['浜у搧鍏ュ簱鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍏ュ簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmProductReceiptApi.ProductReceipt;
+ await updateProductReceipt({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitProductReceipt(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц涓婃灦锛氬厛纭锛屽啀鍦ㄦ槑缁嗘暟閲忎笌琛屾敹璐ф暟閲忎笉涓�鑷存椂浜屾纭 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ try {
+ await confirm('纭鎵ц涓婃灦锛�');
+ } catch {
+ return;
+ }
+ const quantityMatch = await checkProductReceiptQuantity(formData.value.id);
+ if (!quantityMatch) {
+ try {
+ await confirm('鏄庣粏鏁伴噺涓庤鏀惰揣鏁伴噺涓嶄竴鑷达紝纭鎵ц涓婃灦锛�');
+ } catch {
+ return;
+ }
+ }
+ modalApi.lock();
+ try {
+ await stockProductReceipt(formData.value.id);
+ message.success('涓婃灦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鍏ュ簱 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishProductReceipt(formData.value.id);
+ message.success('鍏ュ簱鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductReceiptApi.ProductReceipt;
+ try {
+ if (formData.value?.id) {
+ await updateProductReceipt({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createProductReceipt(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmProductReceiptStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getProductReceipt(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+
+/** 鍏ュ簱鍓嶇‘璁� */
+async function confirmFinish() {
+ try {
+ await confirm('纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��');
+ } catch {
+ return;
+ }
+ await handleFinish();
+}
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :receipt-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ヤ骇鍝佸叆搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="isStock" type="primary" @click="handleStock">
+ 鎵ц涓婃灦
+ </Button>
+ <Button v-if="isFinish" type="primary" @click="confirmFinish">
+ 鎵ц鍏ュ簱
+ </Button>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/productreceipt/modules/line-form.vue b/src/views/wls/productreceipt/modules/line-form.vue
new file mode 100644
index 0000000..1838ca8
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/line-form.vue
@@ -0,0 +1,95 @@
+<script lang="ts" setup>
+import type { MesWmProductReceiptLineApi } from '#/api/mes/wm/productreceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createProductReceiptLine,
+ getProductReceiptLine,
+ updateProductReceiptLine,
+} from '#/api/mes/wm/productreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductReceiptLineApi.ProductReceiptLine>();
+const receiptId = ref<number>(); // 鎵�灞炲叆搴撳崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鐗╂枡鍏ュ簱鍗曡'])
+ : $t('ui.actionTitle.create', ['鐗╂枡鍏ュ簱鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductReceiptLineApi.ProductReceiptLine;
+ data.receiptId = receiptId.value;
+ try {
+ await (formData.value?.id
+ ? updateProductReceiptLine({ ...data, id: formData.value.id })
+ : createProductReceiptLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useLineFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; receiptId: number }>();
+ receiptId.value = data.receiptId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getProductReceiptLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/productreceipt/modules/line-list.vue b/src/views/wls/productreceipt/modules/line-list.vue
new file mode 100644
index 0000000..56985b6
--- /dev/null
+++ b/src/views/wls/productreceipt/modules/line-list.vue
@@ -0,0 +1,254 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductReceiptDetailApi } from '#/api/mes/wm/productreceipt/detail';
+import type { MesWmProductReceiptLineApi } from '#/api/mes/wm/productreceipt/line';
+
+import { computed, reactive, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getProductReceiptDetailListByLineId } from '#/api/mes/wm/productreceipt/detail';
+import {
+ deleteProductReceiptLine,
+ getProductReceiptLinePage,
+} from '#/api/mes/wm/productreceipt/line';
+import { $t } from '#/locales';
+import { BarcodeDetail, PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ receiptId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+ Record<number, MesWmProductReceiptDetailApi.ProductReceiptDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+const barcodeDetailRef = ref(); // 鏉$爜璇︽儏寮圭獥瀹炰緥
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ receiptId: props.receiptId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmProductReceiptLineApi.ProductReceiptLine) {
+ lineFormModalApi.setData({ id: row.id, receiptId: props.receiptId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(
+ row: MesWmProductReceiptLineApi.ProductReceiptLine,
+) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteProductReceiptLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handleStock(row: MesWmProductReceiptLineApi.ProductReceiptLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鏌ョ湅鐗╂枡鏉$爜 */
+function handleBarcode(row: MesWmProductReceiptLineApi.ProductReceiptLine) {
+ barcodeDetailRef.value?.openByBusiness(
+ row.itemId,
+ BarcodeBizTypeEnum.ITEM,
+ row.itemCode,
+ row.itemName,
+ );
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, itemId, lineId, receiptId: props.receiptId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(
+ row: MesWmProductReceiptLineApi.ProductReceiptLine,
+) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getProductReceiptDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+ row: MesWmProductReceiptLineApi.ProductReceiptLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.receiptId) {
+ return { list: [], total: 0 };
+ }
+ return await getProductReceiptLinePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ receiptId: props.receiptId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmProductReceiptLineApi.ProductReceiptLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmProductReceiptLineApi.ProductReceiptLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <div class="flex items-center justify-center">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '涓婃灦',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '鏉$爜',
+ type: 'link',
+ onClick: handleBarcode.bind(null, row),
+ },
+ ]"
+ />
+ <PrinterLabel
+ v-if="isStock"
+ :biz-code="row.batchCode"
+ :biz-id="row.batchId"
+ biz-type="BATCH"
+ />
+ </div>
+ </template>
+ </Grid>
+ <BarcodeDetail ref="barcodeDetailRef" />
+ </div>
+</template>
diff --git a/src/views/wls/productsales/data.ts b/src/views/wls/productsales/data.ts
new file mode 100644
index 0000000..6e9973f
--- /dev/null
+++ b/src/views/wls/productsales/data.ts
@@ -0,0 +1,701 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
+import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
+import type { MesWmProductSalesLineApi } from '#/api/mes/wm/productsales/line';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesWmSalesNoticeStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmSalesNoticeLineSelect,
+ WmSalesNoticeSelect,
+} from '#/views/wls/salesnotice/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType =
+ | 'create'
+ | 'detail'
+ | 'finish'
+ | 'shipping'
+ | 'stock'
+ | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佸~鍐欒繍鍗曘�佸嚭搴撱�佽鎯呮�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return (
+ formType === 'detail' ||
+ formType === 'finish' ||
+ formType === 'shipping' ||
+ formType === 'stock'
+ );
+}
+
+/** 鏄惁灞曠ず杩愯緭淇℃伅 */
+export function showShippingInfo(formType: FormType): boolean {
+ return (
+ formType === 'shipping' || formType === 'detail' || formType === 'finish'
+ );
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍑哄簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_PRODUCT_SALES_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍑哄簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'noticeId',
+ label: '鍙戣揣閫氱煡鍗�',
+ component: markRaw(WmSalesNoticeSelect),
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ // 閫夋嫨鍙戣揣閫氱煡鍗曞悗锛岃嚜鍔ㄥ洖濉攢鍞鍗曞彿銆佸鎴枫�佹敹璐т汉淇℃伅
+ onChange: async (notice?: MesWmSalesNoticeApi.SalesNotice) => {
+ await formApi?.setValues({
+ clientId: notice?.clientId,
+ contactAddress: notice?.recipientAddress,
+ contactName: notice?.recipientName,
+ contactTelephone: notice?.recipientTelephone,
+ salesOrderCode: notice?.salesOrderCode,
+ });
+ },
+ status: MesWmSalesNoticeStatusEnum.APPROVED,
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'salesDate',
+ label: '鍑哄簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ disabled: isHeaderReadonly(formType),
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍑哄簱鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'contactName',
+ label: '鏀惰揣浜�',
+ component: 'Input',
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇疯緭鍏ユ敹璐т汉',
+ },
+ },
+ {
+ fieldName: 'contactTelephone',
+ label: '鑱旂郴鏂瑰紡',
+ component: 'Input',
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇疯緭鍏ヨ仈绯绘柟寮�',
+ },
+ },
+ {
+ fieldName: 'contactAddress',
+ label: '鏀惰揣鍦板潃',
+ component: 'Input',
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇疯緭鍏ユ敹璐у湴鍧�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ disabled: isHeaderReadonly(formType),
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ {
+ fieldName: 'carrier',
+ label: '鎵胯繍鍟�',
+ component: 'Input',
+ componentProps: {
+ disabled: formType !== 'shipping',
+ placeholder: '璇疯緭鍏ユ壙杩愬晢',
+ },
+ dependencies: {
+ triggerFields: [''],
+ if: () => showShippingInfo(formType),
+ },
+ },
+ {
+ fieldName: 'shippingNumber',
+ label: '杩愯緭鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ disabled: formType !== 'shipping',
+ placeholder: '璇疯緭鍏ヨ繍杈撳崟鍙�',
+ },
+ dependencies: {
+ triggerFields: [''],
+ if: () => showShippingInfo(formType),
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍑哄簱鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍑哄簱鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ嚭搴撳崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'salesDate',
+ label: '鍑哄簱鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(
+ DICT_TYPE.MES_WM_PRODUCT_SALES_STATUS,
+ 'number',
+ ),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmProductSalesApi.ProductSales>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍑哄簱鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍑哄簱鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'noticeCode',
+ title: '鍙戣揣閫氱煡鍗曞彿',
+ minWidth: 160,
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曠紪鍙�',
+ minWidth: 160,
+ },
+ {
+ field: 'clientCode',
+ title: '瀹㈡埛缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'contactName',
+ title: '鏀惰揣浜�',
+ minWidth: 100,
+ },
+ {
+ field: 'carrier',
+ title: '鎵胯繍鍟�',
+ minWidth: 120,
+ },
+ {
+ field: 'shippingNumber',
+ title: '杩愯緭鍗曞彿',
+ minWidth: 160,
+ },
+ {
+ field: 'salesDate',
+ title: '鍑哄簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_PRODUCT_SALES_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鍑哄簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmProductSalesLineApi.ProductSalesLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '浜у搧缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '浜у搧鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '鍑哄簱鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'oqcCheckFlag',
+ title: '鏄惁鏍¢獙',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 150,
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鍑哄簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+ hasNotice: boolean,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'noticeId',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'noticeLineId',
+ label: '鍙戣揣閫氱煡鍗曡',
+ component: markRaw(WmSalesNoticeLineSelect),
+ componentProps: {
+ // 閫夋嫨鍙戣揣閫氱煡鍗曡鍚庯紝鑷姩鍥炲~鐗╂枡銆佹暟閲忋�佹壒娆°�佹槸鍚︽楠�
+ onChange: async (line?: MesWmSalesNoticeLineApi.SalesNoticeLine) => {
+ await formApi?.setValues({
+ batchCode: line?.batchCode,
+ itemId: line?.itemId,
+ oqcCheckFlag: line?.oqcCheckFlag ?? false,
+ quantity: line?.quantity,
+ });
+ },
+ },
+ dependencies: {
+ triggerFields: ['noticeId'],
+ if: () => hasNotice,
+ componentProps: (values) => ({
+ noticeId: values.noticeId,
+ }),
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '浜у搧',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['noticeLineId'],
+ componentProps: (values) => ({
+ disabled: !!values.noticeLineId,
+ placeholder: '璇烽�夋嫨浜у搧',
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍑哄簱鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ュ嚭搴撴暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'oqcCheckFlag',
+ label: '鏄惁鏍¢獙',
+ component: 'RadioGroup',
+ componentProps: {
+ buttonStyle: 'solid',
+ optionType: 'button',
+ options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'),
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鎷h揣鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmProductSalesDetailApi.ProductSalesDetail>['columns'] {
+ return [
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鎷h揣鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'batchId',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'materialStockId',
+ label: '閫夋嫨搴撳瓨',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ areaId: stock?.areaId,
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ locationId: stock?.locationId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ warehouseId: stock?.warehouseId,
+ });
+ },
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['itemId', 'batchId'],
+ componentProps: (values) => ({
+ batchId: values.batchId,
+ itemId: values.itemId,
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍑哄簱浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ disabled: true,
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ disabled: true,
+ locationId: values.locationId,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/productsales/index.vue b/src/views/wls/productsales/index.vue
new file mode 100644
index 0000000..39838a8
--- /dev/null
+++ b/src/views/wls/productsales/index.vue
@@ -0,0 +1,215 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmProductSalesStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelProductSales,
+ deleteProductSales,
+ exportProductSales,
+ getProductSalesPage,
+} from '#/api/mes/wm/productsales';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓閿�鍞嚭搴撳崟 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅閿�鍞嚭搴撳崟 */
+function handleDetail(row: MesWmProductSalesApi.ProductSales) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫閿�鍞嚭搴撳崟 */
+function handleEdit(row: MesWmProductSalesApi.ProductSales) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmProductSalesApi.ProductSales) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 濉啓杩愬崟 */
+function handleShipping(row: MesWmProductSalesApi.ProductSales) {
+ formModalApi.setData({ formType: 'shipping', id: row.id }).open();
+}
+
+/** 鎵ц鍑哄簱 */
+function handleFinish(row: MesWmProductSalesApi.ProductSales) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎閿�鍞嚭搴撳崟 */
+async function handleDelete(row: MesWmProductSalesApi.ProductSales) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteProductSales(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷閿�鍞嚭搴撳崟 */
+async function handleCancel(row: MesWmProductSalesApi.ProductSales) {
+ await cancelProductSales(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportProductSales(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '閿�鍞嚭搴撳崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getProductSalesPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmProductSalesApi.ProductSales>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="閿�鍞嚭搴撳崟鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['閿�鍞嚭搴撳崟']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-product-sales:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-product-sales:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-product-sales:update'],
+ ifShow: row.status === MesWmProductSalesStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-product-sales:delete'],
+ ifShow: row.status === MesWmProductSalesStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎷h揣',
+ type: 'link',
+ auth: ['mes:wm-product-sales:stock'],
+ ifShow: row.status === MesWmProductSalesStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '濉啓杩愬崟',
+ type: 'link',
+ auth: ['mes:wm-product-sales:shipping'],
+ ifShow: row.status === MesWmProductSalesStatusEnum.SHIPPING,
+ onClick: handleShipping.bind(null, row),
+ },
+ {
+ label: '鎵ц鍑哄簱',
+ type: 'link',
+ auth: ['mes:wm-product-sales:finish'],
+ ifShow: row.status === MesWmProductSalesStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-product-sales:cancel'],
+ ifShow:
+ row.status === MesWmProductSalesStatusEnum.CONFIRMED ||
+ row.status === MesWmProductSalesStatusEnum.APPROVING ||
+ row.status === MesWmProductSalesStatusEnum.SHIPPING ||
+ row.status === MesWmProductSalesStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ラ攢鍞嚭搴撳崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/productsales/modules/detail-form.vue b/src/views/wls/productsales/modules/detail-form.vue
new file mode 100644
index 0000000..53385dc
--- /dev/null
+++ b/src/views/wls/productsales/modules/detail-form.vue
@@ -0,0 +1,110 @@
+<script lang="ts" setup>
+import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createProductSalesDetail,
+ getProductSalesDetail,
+ updateProductSalesDetail,
+} from '#/api/mes/wm/productsales/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmProductSalesDetailApi.ProductSalesDetail>();
+const salesId = ref<number>(); // 鎵�灞炲嚭搴撳崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炲嚭搴撳崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鎷h揣鏄庣粏'])
+ : $t('ui.actionTitle.create', ['鎷h揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductSalesDetailApi.ProductSalesDetail;
+ data.salesId = salesId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateProductSalesDetail({ ...data, id: formData.value.id })
+ : createProductSalesDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ batchId?: number;
+ detailId?: number;
+ itemId?: number;
+ lineId: number;
+ salesId: number;
+ }>();
+ salesId.value = data.salesId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getProductSalesDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else {
+ if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ if (data.batchId) {
+ await formApi.setFieldValue('batchId', data.batchId);
+ }
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/productsales/modules/detail-list.vue b/src/views/wls/productsales/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/productsales/modules/detail-list.vue
copy to src/views/wls/productsales/modules/detail-list.vue
diff --git a/src/views/wls/productsales/modules/form.vue b/src/views/wls/productsales/modules/form.vue
new file mode 100644
index 0000000..853d7f4
--- /dev/null
+++ b/src/views/wls/productsales/modules/form.vue
@@ -0,0 +1,270 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmProductSalesApi } from '#/api/mes/wm/productsales';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmProductSalesStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ checkProductSalesQuantity,
+ createProductSales,
+ finishProductSales,
+ getProductSales,
+ shippingProductSales,
+ stockProductSales,
+ submitProductSales,
+ updateProductSales,
+} from '#/api/mes/wm/productsales';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmProductSalesApi.ProductSales>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isShipping = computed(() => formType.value === 'shipping'); // 鏄惁涓哄~鍐欒繍鍗曟ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽嚭搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmProductSalesStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['閿�鍞嚭搴撳崟']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц鎷h揣';
+ }
+ if (formType.value === 'shipping') {
+ return '濉啓杩愬崟';
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц鍑哄簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['閿�鍞嚭搴撳崟'])
+ : $t('ui.actionTitle.create', ['閿�鍞嚭搴撳崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鍑哄簱鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmProductSalesApi.ProductSales;
+ await updateProductSales({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitProductSales(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鎷h揣锛氬嚭搴撴暟閲忎笌鎷h揣鏁伴噺涓嶄竴鑷存椂浜屾纭 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ const quantityMatch = await checkProductSalesQuantity(formData.value.id);
+ if (!quantityMatch) {
+ try {
+ await confirm('鍑哄簱鏁伴噺涓庢嫞璐ф暟閲忎笉涓�鑷达紝纭鎵ц鎷h揣锛�');
+ } catch {
+ return;
+ }
+ }
+ modalApi.lock();
+ try {
+ await stockProductSales(formData.value.id);
+ message.success('鎷h揣鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 濉啓杩愬崟 */
+async function handleShipping() {
+ if (!formData.value?.id) {
+ return;
+ }
+ const values =
+ (await formApi.getValues()) as MesWmProductSalesApi.ProductSales;
+ modalApi.lock();
+ try {
+ await shippingProductSales({
+ carrier: values.carrier,
+ id: formData.value.id,
+ shippingNumber: values.shippingNumber,
+ });
+ message.success('杩愬崟淇℃伅濉啓鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鍑哄簱 */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishProductSales(formData.value.id);
+ message.success('鍑哄簱鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductSalesApi.ProductSales;
+ try {
+ if (formData.value?.id) {
+ await updateProductSales({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createProductSales(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmProductSalesStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(false);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getProductSales(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList
+ :form-type="formType"
+ :notice-id="formData.noticeId"
+ :sales-id="formData.id"
+ />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ攢鍞嚭搴撳崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="isStock" type="primary" @click="handleStock">
+ 鎵ц鎷h揣
+ </Button>
+ <Popconfirm
+ v-if="isShipping"
+ title="纭鎻愪氦杩愬崟淇℃伅锛�"
+ @confirm="handleShipping"
+ >
+ <Button type="primary">纭濉啓</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭鎵ц鍑哄簱锛熸墽琛屽悗灏嗘墸鍑忓簱瀛樸��"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">纭鍑哄簱</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/productsales/modules/line-form.vue b/src/views/wls/productsales/modules/line-form.vue
new file mode 100644
index 0000000..e28b2e6
--- /dev/null
+++ b/src/views/wls/productsales/modules/line-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmProductSalesLineApi } from '#/api/mes/wm/productsales/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createProductSalesLine,
+ getProductSalesLine,
+ updateProductSalesLine,
+} from '#/api/mes/wm/productsales/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmProductSalesLineApi.ProductSalesLine>();
+const salesId = ref<number>(); // 鎵�灞炲嚭搴撳崟缂栧彿
+const noticeId = ref<number>(); // 鎵�灞炲彂璐ч�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['閿�鍞嚭搴撳崟琛�'])
+ : $t('ui.actionTitle.create', ['閿�鍞嚭搴撳崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmProductSalesLineApi.ProductSalesLine;
+ data.salesId = salesId.value;
+ try {
+ await (formData.value?.id
+ ? updateProductSalesLine({ ...data, id: formData.value.id })
+ : createProductSalesLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ id?: number;
+ noticeId?: number;
+ salesId: number;
+ }>();
+ salesId.value = data.salesId;
+ noticeId.value = data.noticeId;
+ formApi.setState({ schema: useLineFormSchema(!!data.noticeId, formApi) });
+ if (data.noticeId) {
+ await formApi.setFieldValue('noticeId', data.noticeId);
+ }
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getProductSalesLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues({ ...formData.value, noticeId: noticeId.value });
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/productsales/modules/line-list.vue b/src/views/wls/productsales/modules/line-list.vue
new file mode 100644
index 0000000..5bc888a
--- /dev/null
+++ b/src/views/wls/productsales/modules/line-list.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmProductSalesDetailApi } from '#/api/mes/wm/productsales/detail';
+import type { MesWmProductSalesLineApi } from '#/api/mes/wm/productsales/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getProductSalesDetailListByLineId } from '#/api/mes/wm/productsales/detail';
+import {
+ deleteProductSalesLine,
+ getProductSalesLinePage,
+} from '#/api/mes/wm/productsales/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ noticeId?: number;
+ salesId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+ Record<number, MesWmProductSalesDetailApi.ProductSalesDetail[]>
+>({}); // 宸插睍寮�琛岀殑鎷h揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi
+ .setData({ noticeId: props.noticeId, salesId: props.salesId })
+ .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmProductSalesLineApi.ProductSalesLine) {
+ lineFormModalApi
+ .setData({ id: row.id, noticeId: props.noticeId, salesId: props.salesId })
+ .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmProductSalesLineApi.ProductSalesLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteProductSalesLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmProductSalesLineApi.ProductSalesLine) {
+ openDetailForm(row.id!, row.itemId, row.batchId);
+}
+
+/** 鎵撳紑鎷h揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(
+ lineId: number,
+ itemId?: number,
+ batchId?: number,
+ detailId?: number,
+) {
+ detailFormModalApi
+ .setData({ batchId, detailId, itemId, lineId, salesId: props.salesId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鎷h揣鏄庣粏 */
+function getExpandedDetails(row: MesWmProductSalesLineApi.ProductSalesLine) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鎷h揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getProductSalesDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥嫞璐ф槑缁� */
+async function handleExpandChange(
+ row: MesWmProductSalesLineApi.ProductSalesLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.salesId) {
+ return { list: [], total: 0 };
+ }
+ return await getProductSalesLinePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ salesId: props.salesId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmProductSalesLineApi.ProductSalesLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmProductSalesLineApi.ProductSalesLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="
+ (detailId) =>
+ openDetailForm(row.id!, row.itemId, row.batchId, detailId)
+ "
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎷h揣',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handlePicking.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/returnissue/data.ts b/src/views/wls/returnissue/data.ts
new file mode 100644
index 0000000..6dfc81f
--- /dev/null
+++ b/src/views/wls/returnissue/data.ts
@@ -0,0 +1,567 @@
+锘縤mport type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
+import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
+import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ MesProWorkOrderStatusEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdWorkstationSelect } from '#/views/mes/md/workstation/components';
+import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佹墽琛岄��鏂欐�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '閫�鏂欏崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��鏂欏崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_RETURN_ISSUE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '閫�鏂欏崟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��鏂欏崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'type',
+ label: '閫�鏂欑被鍨�',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨閫�鏂欑被鍨�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ status: MesProWorkOrderStatusEnum.CONFIRMED,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'workstationId',
+ label: '宸ヤ綔绔�',
+ component: markRaw(MdWorkstationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨宸ヤ綔绔�',
+ },
+ },
+ {
+ fieldName: 'returnDate',
+ label: '閫�鏂欐棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ placeholder: '璇烽�夋嫨閫�鏂欐棩鏈�',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '閫�鏂欏崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ��鏂欏崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '閫�鏂欏崟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ��鏂欏崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'workOrderId',
+ label: '鐢熶骇璁㈠崟',
+ component: markRaw(ProWorkOrderSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐢熶骇璁㈠崟',
+ },
+ },
+ {
+ fieldName: 'type',
+ label: '閫�鏂欑被鍨�',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨閫�鏂欑被鍨�',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '閫�鏂欏崟缂栧彿',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '閫�鏂欏崟鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'type',
+ title: '閫�鏂欑被鍨�',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_TYPE },
+ },
+ },
+ {
+ field: 'workOrderCode',
+ title: '鐢熶骇璁㈠崟',
+ minWidth: 140,
+ },
+ {
+ field: 'workstationName',
+ title: '宸ヤ綔绔�',
+ minWidth: 120,
+ },
+ {
+ field: 'returnDate',
+ title: '閫�鏂欐棩鏈�',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_RETURN_ISSUE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 閫�鏂欏崟琛屽瓙琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmReturnIssueLineApi.ReturnIssueLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '閫�鏂欐暟閲�',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'rqcCheckFlag',
+ title: '鏄惁妫�娴�',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'qualityStatus',
+ title: '璐ㄩ噺鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+ },
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 閫�鏂欏崟琛屾柊澧�/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨璁板綍',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~鐗╂枡/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ itemId: stock?.itemId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ });
+ },
+ virtualFilter: 'only',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '閫�鏂欐暟閲�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ラ��鏂欐暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ラ��鏂欐暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'rqcCheckFlag',
+ label: '闇�瑕佽川妫�',
+ component: 'Switch',
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ placeholder: '閫夋嫨搴撳瓨鍚庤嚜鍔ㄥ甫鍑�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmReturnIssueDetailApi.ReturnIssueDetail>['columns'] {
+ return [
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨璁板綍',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ areaId: stock?.areaId,
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ locationId: stock?.locationId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ warehouseId: stock?.warehouseId,
+ });
+ },
+ virtualFilter: 'only',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['itemId'],
+ componentProps: (values) => ({
+ itemId: values.itemId,
+ virtualFilter: 'only',
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍏ュ簱浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ disabled: true,
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ disabled: true,
+ locationId: values.locationId,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/returnissue/index.vue b/src/views/wls/returnissue/index.vue
new file mode 100644
index 0000000..29463eb
--- /dev/null
+++ b/src/views/wls/returnissue/index.vue
@@ -0,0 +1,213 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmReturnIssueStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelReturnIssue,
+ deleteReturnIssue,
+ exportReturnIssue,
+ getReturnIssuePage,
+} from '#/api/mes/wm/returnissue';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鐢熶骇閫�鏂欏崟 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鐢熶骇閫�鏂欏崟 */
+function handleDetail(row: MesWmReturnIssueApi.ReturnIssue) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鐢熶骇閫�鏂欏崟 */
+function handleEdit(row: MesWmReturnIssueApi.ReturnIssue) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmReturnIssueApi.ReturnIssue) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц閫�鏂� */
+function handleFinish(row: MesWmReturnIssueApi.ReturnIssue) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愮ず鍓嶅線璐ㄦ妯″潡 */
+function handleQc() {
+ message.info('璇峰墠寰�銆愯川閲忕鐞� - 閫�璐ф楠岋紙RQC锛夈�戜腑杩涜閫�鏂欐楠屾搷浣�');
+}
+
+/** 鍒犻櫎鐢熶骇閫�鏂欏崟 */
+async function handleDelete(row: MesWmReturnIssueApi.ReturnIssue) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteReturnIssue(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷鐢熶骇閫�鏂欏崟 */
+async function handleCancel(row: MesWmReturnIssueApi.ReturnIssue) {
+ await cancelReturnIssue(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportReturnIssue(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鐢熶骇閫�鏂欏崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getReturnIssuePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmReturnIssueApi.ReturnIssue>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鐢熶骇閫�鏂欏崟鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鐢熶骇閫�鏂欏崟']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-return-issue:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-return-issue:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-return-issue:update'],
+ ifShow: row.status === MesWmReturnIssueStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-return-issue:delete'],
+ ifShow: row.status === MesWmReturnIssueStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц璐ㄦ',
+ type: 'link',
+ ifShow: row.status === MesWmReturnIssueStatusEnum.CONFIRMED,
+ onClick: handleQc,
+ },
+ {
+ label: '鎵ц涓婃灦',
+ type: 'link',
+ auth: ['mes:wm-return-issue:update'],
+ ifShow: row.status === MesWmReturnIssueStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '鎵ц閫�鏂�',
+ type: 'link',
+ auth: ['mes:wm-return-issue:finish'],
+ ifShow: row.status === MesWmReturnIssueStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-return-issue:update'],
+ ifShow:
+ row.status === MesWmReturnIssueStatusEnum.CONFIRMED ||
+ row.status === MesWmReturnIssueStatusEnum.APPROVING ||
+ row.status === MesWmReturnIssueStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ョ敓浜ч��鏂欏崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/returnissue/modules/detail-form.vue b/src/views/wls/returnissue/modules/detail-form.vue
new file mode 100644
index 0000000..7539d65
--- /dev/null
+++ b/src/views/wls/returnissue/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnIssueDetail,
+ getReturnIssueDetail,
+ updateReturnIssueDetail,
+} from '#/api/mes/wm/returnissue/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmReturnIssueDetailApi.ReturnIssueDetail>();
+const issueId = ref<number>(); // 鎵�灞為��鏂欏崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為��鏂欏崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+ : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmReturnIssueDetailApi.ReturnIssueDetail;
+ data.issueId = issueId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateReturnIssueDetail({ ...data, id: formData.value.id })
+ : createReturnIssueDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ issueId: number;
+ itemId?: number;
+ lineId: number;
+ }>();
+ issueId.value = data.issueId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getReturnIssueDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/returnissue/modules/detail-list.vue b/src/views/wls/returnissue/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/returnissue/modules/detail-list.vue
copy to src/views/wls/returnissue/modules/detail-list.vue
diff --git a/src/views/wls/returnissue/modules/form.vue b/src/views/wls/returnissue/modules/form.vue
new file mode 100644
index 0000000..341d637
--- /dev/null
+++ b/src/views/wls/returnissue/modules/form.vue
@@ -0,0 +1,222 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmReturnIssueApi } from '#/api/mes/wm/returnissue';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmReturnIssueStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnIssue,
+ finishReturnIssue,
+ getReturnIssue,
+ stockReturnIssue,
+ submitReturnIssue,
+ updateReturnIssue,
+} from '#/api/mes/wm/returnissue';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmReturnIssueApi.ReturnIssue>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓哄叆搴撲笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岄��鏂欐ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmReturnIssueStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['鐢熶骇閫�鏂欏崟']);
+ }
+ if (formType.value === 'stock') {
+ return '鍏ュ簱涓婃灦';
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц閫�鏂�';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['鐢熶骇閫�鏂欏崟'])
+ : $t('ui.actionTitle.create', ['鐢熶骇閫�鏂欏崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫�鏂欏崟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
+ await updateReturnIssue({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitReturnIssue(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鍏ュ簱涓婃灦 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await stockReturnIssue(formData.value.id);
+ message.success('鍏ュ簱涓婃灦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц閫�鏂� */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishReturnIssue(formData.value.id);
+ message.success('瀹屾垚鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmReturnIssueApi.ReturnIssue;
+ try {
+ if (formData.value?.id) {
+ await updateReturnIssue({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createReturnIssue(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmReturnIssueStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getReturnIssue(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :issue-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ��鏂欏崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="isStock" type="primary" @click="handleStock">
+ 鍏ュ簱涓婃灦
+ </Button>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭瀹屾垚璇ラ��鏂欏崟骞舵墽琛屽叆搴撳悧锛�"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">鎵ц閫�鏂�</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/returnissue/modules/line-form.vue b/src/views/wls/returnissue/modules/line-form.vue
new file mode 100644
index 0000000..35f2d8e
--- /dev/null
+++ b/src/views/wls/returnissue/modules/line-form.vue
@@ -0,0 +1,95 @@
+<script lang="ts" setup>
+import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnIssueLine,
+ getReturnIssueLine,
+ updateReturnIssueLine,
+} from '#/api/mes/wm/returnissue/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnIssueLineApi.ReturnIssueLine>();
+const issueId = ref<number>(); // 鎵�灞為��鏂欏崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鐢熶骇閫�鏂欏崟琛�'])
+ : $t('ui.actionTitle.create', ['鐢熶骇閫�鏂欏崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmReturnIssueLineApi.ReturnIssueLine;
+ data.issueId = issueId.value;
+ try {
+ await (formData.value?.id
+ ? updateReturnIssueLine({ ...data, id: formData.value.id })
+ : createReturnIssueLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useLineFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; issueId: number }>();
+ issueId.value = data.issueId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getReturnIssueLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/returnissue/modules/line-list.vue b/src/views/wls/returnissue/modules/line-list.vue
new file mode 100644
index 0000000..455e03a
--- /dev/null
+++ b/src/views/wls/returnissue/modules/line-list.vue
@@ -0,0 +1,250 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnIssueDetailApi } from '#/api/mes/wm/returnissue/detail';
+import type { MesWmReturnIssueLineApi } from '#/api/mes/wm/returnissue/line';
+
+import { computed, reactive, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getReturnIssueDetailListByLineId } from '#/api/mes/wm/returnissue/detail';
+import {
+ deleteReturnIssueLine,
+ getReturnIssueLinePage,
+} from '#/api/mes/wm/returnissue/line';
+import { $t } from '#/locales';
+import { BarcodeDetail, PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ issueId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓哄叆搴撲笂鏋舵ā寮�
+const detailMap = reactive<
+ Record<number, MesWmReturnIssueDetailApi.ReturnIssueDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+const barcodeDetailRef = ref(); // 鏉$爜璇︽儏寮圭獥瀹炰緥
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ issueId: props.issueId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+ lineFormModalApi.setData({ id: row.id, issueId: props.issueId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteReturnIssueLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鏌ョ湅鐗╂枡鏉$爜 */
+function handleBarcode(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+ barcodeDetailRef.value?.openByBusiness(
+ row.itemId,
+ BarcodeBizTypeEnum.ITEM,
+ row.itemCode,
+ row.itemName,
+ );
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, issueId: props.issueId, itemId, lineId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(row: MesWmReturnIssueLineApi.ReturnIssueLine) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getReturnIssueDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+ row: MesWmReturnIssueLineApi.ReturnIssueLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.issueId) {
+ return { list: [], total: 0 };
+ }
+ return await getReturnIssueLinePage({
+ issueId: props.issueId,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmReturnIssueLineApi.ReturnIssueLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmReturnIssueLineApi.ReturnIssueLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <div class="flex items-center justify-center">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '涓婃灦',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handlePicking.bind(null, row),
+ },
+ {
+ label: '鏉$爜',
+ type: 'link',
+ onClick: handleBarcode.bind(null, row),
+ },
+ ]"
+ />
+ <PrinterLabel
+ v-if="isStock"
+ :biz-code="row.batchCode"
+ :biz-id="row.batchId"
+ biz-type="BATCH"
+ />
+ </div>
+ </template>
+ </Grid>
+ <BarcodeDetail ref="barcodeDetailRef" />
+ </div>
+</template>
diff --git a/src/views/wls/returnsales/data.ts b/src/views/wls/returnsales/data.ts
new file mode 100644
index 0000000..b7b99db
--- /dev/null
+++ b/src/views/wls/returnsales/data.ts
@@ -0,0 +1,515 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnSalesApi } from '#/api/mes/wm/returnsales';
+import type { MesWmReturnSalesDetailApi } from '#/api/mes/wm/returnsales/detail';
+import type { MesWmReturnSalesLineApi } from '#/api/mes/wm/returnsales/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堜笂鏋躲�佽鎯呫�佹墽琛岄��璐ф�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '閫�璐у崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_RETURN_SALES_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '閫�璐у崟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'returnDate',
+ label: '閫�璐ф棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨閫�璐ф棩鏈�',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'returnReason',
+ label: '閫�璐у師鍥�',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��璐у師鍥�',
+ rows: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '閫�璐у崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '閫�璐у崟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_RETURN_SALES_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmReturnSalesApi.ReturnSales>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '閫�璐у崟缂栧彿',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '閫�璐у崟鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曞彿',
+ minWidth: 140,
+ },
+ {
+ field: 'clientCode',
+ title: '瀹㈡埛缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'returnReason',
+ title: '閫�璐у師鍥�',
+ minWidth: 150,
+ },
+ {
+ field: 'returnDate',
+ title: '閫�璐ф棩鏈�',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_RETURN_SALES_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 閫�璐у崟琛屽瓙琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmReturnSalesLineApi.ReturnSalesLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '閫�璐ф暟閲�',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'rqcCheckFlag',
+ title: '鏄惁闇�瑕佽川妫�',
+ width: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'qualityStatus',
+ title: '璐ㄩ噺鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_QUALITY_STATUS },
+ },
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 閫�璐у崟琛屾柊澧�/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+ clientId?: number,
+ salesOrderCode?: string,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '閫�璐ф暟閲�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ラ��璐ф暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'batchId',
+ label: '鎵规',
+ component: markRaw(WmBatchSelect),
+ componentProps: {
+ clientId,
+ placeholder: '璇烽�夋嫨鎵规',
+ salesOrderCode,
+ },
+ dependencies: {
+ triggerFields: ['itemId'],
+ componentProps: (values) => ({
+ clientId,
+ itemId: values.itemId,
+ placeholder: '璇烽�夋嫨鎵规',
+ salesOrderCode,
+ }),
+ },
+ },
+ {
+ fieldName: 'rqcCheckFlag',
+ label: '闇�瑕佽川妫�',
+ component: 'Switch',
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 涓婃灦鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmReturnSalesDetailApi.ReturnSalesDetail>['columns'] {
+ return [
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 涓婃灦鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鍏ュ簱浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ // 鍒囨崲浠撳簱鍚庢竻绌哄簱鍖哄拰搴撲綅
+ onChange: async () => {
+ await formApi?.setValues({
+ areaId: undefined,
+ locationId: undefined,
+ });
+ },
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撳尯',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ // 鍒囨崲搴撳尯鍚庢竻绌哄簱浣�
+ onChange: async () => {
+ await formApi?.setFieldValue('areaId', undefined);
+ },
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨搴撲綅',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ ];
+}
diff --git a/src/views/wls/returnsales/index.vue b/src/views/wls/returnsales/index.vue
new file mode 100644
index 0000000..128d5b8
--- /dev/null
+++ b/src/views/wls/returnsales/index.vue
@@ -0,0 +1,213 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnSalesApi } from '#/api/mes/wm/returnsales';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmReturnSalesStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelReturnSales,
+ deleteReturnSales,
+ exportReturnSales,
+ getReturnSalesPage,
+} from '#/api/mes/wm/returnsales';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓閿�鍞��璐у崟 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅閿�鍞��璐у崟 */
+function handleDetail(row: MesWmReturnSalesApi.ReturnSales) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫閿�鍞��璐у崟 */
+function handleEdit(row: MesWmReturnSalesApi.ReturnSales) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmReturnSalesApi.ReturnSales) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц閫�璐� */
+function handleFinish(row: MesWmReturnSalesApi.ReturnSales) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愮ず鍓嶅線璐ㄦ妯″潡 */
+function handleQc() {
+ message.info('璇峰墠寰�銆愯川閲忕鐞� - 閫�璐ф楠岋紙RQC锛夈�戜腑杩涜閫�璐ф楠屾搷浣�');
+}
+
+/** 鍒犻櫎閿�鍞��璐у崟 */
+async function handleDelete(row: MesWmReturnSalesApi.ReturnSales) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteReturnSales(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷閿�鍞��璐у崟 */
+async function handleCancel(row: MesWmReturnSalesApi.ReturnSales) {
+ await cancelReturnSales(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportReturnSales(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '閿�鍞��璐у崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getReturnSalesPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmReturnSalesApi.ReturnSales>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="閿�鍞��璐у崟鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['閿�鍞��璐у崟']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-return-sales:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-return-sales:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-return-sales:update'],
+ ifShow: row.status === MesWmReturnSalesStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-return-sales:delete'],
+ ifShow: row.status === MesWmReturnSalesStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц璐ㄦ',
+ type: 'link',
+ ifShow: row.status === MesWmReturnSalesStatusEnum.CONFIRMED,
+ onClick: handleQc,
+ },
+ {
+ label: '鎵ц閫�璐�',
+ type: 'link',
+ auth: ['mes:wm-return-sales:finish'],
+ ifShow: row.status === MesWmReturnSalesStatusEnum.APPROVING,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鎵ц涓婃灦',
+ type: 'link',
+ auth: ['mes:wm-return-sales:stock'],
+ ifShow: row.status === MesWmReturnSalesStatusEnum.APPROVED,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-return-sales:cancel'],
+ ifShow:
+ row.status === MesWmReturnSalesStatusEnum.CONFIRMED ||
+ row.status === MesWmReturnSalesStatusEnum.APPROVING ||
+ row.status === MesWmReturnSalesStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ラ攢鍞��璐у崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/returnsales/modules/detail-form.vue b/src/views/wls/returnsales/modules/detail-form.vue
new file mode 100644
index 0000000..ec451a2
--- /dev/null
+++ b/src/views/wls/returnsales/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmReturnSalesDetailApi } from '#/api/mes/wm/returnsales/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnSalesDetail,
+ getReturnSalesDetail,
+ updateReturnSalesDetail,
+} from '#/api/mes/wm/returnsales/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmReturnSalesDetailApi.ReturnSalesDetail>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為��璐у崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['涓婃灦鏄庣粏'])
+ : $t('ui.actionTitle.create', ['涓婃灦鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmReturnSalesDetailApi.ReturnSalesDetail;
+ data.returnId = returnId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateReturnSalesDetail({ ...data, id: formData.value.id })
+ : createReturnSalesDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ itemId?: number;
+ lineId: number;
+ returnId: number;
+ }>();
+ returnId.value = data.returnId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getReturnSalesDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/returnsales/modules/detail-list.vue b/src/views/wls/returnsales/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/returnsales/modules/detail-list.vue
copy to src/views/wls/returnsales/modules/detail-list.vue
diff --git a/src/views/wls/returnsales/modules/form.vue b/src/views/wls/returnsales/modules/form.vue
new file mode 100644
index 0000000..f43296b
--- /dev/null
+++ b/src/views/wls/returnsales/modules/form.vue
@@ -0,0 +1,231 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmReturnSalesApi } from '#/api/mes/wm/returnsales';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmReturnSalesStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnSales,
+ finishReturnSales,
+ getReturnSales,
+ stockReturnSales,
+ submitReturnSales,
+ updateReturnSales,
+} from '#/api/mes/wm/returnsales';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmReturnSalesApi.ReturnSales>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岄��璐фā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmReturnSalesStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['閿�鍞��璐у崟']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц涓婃灦';
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц閫�璐�';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['閿�鍞��璐у崟'])
+ : $t('ui.actionTitle.create', ['閿�鍞��璐у崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫�璐у崟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmReturnSalesApi.ReturnSales;
+ await updateReturnSales({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitReturnSales(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await stockReturnSales(formData.value.id);
+ message.success('涓婃灦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц閫�璐� */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishReturnSales(formData.value.id);
+ message.success('鎵ц閫�璐ф垚鍔�');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmReturnSalesApi.ReturnSales;
+ try {
+ if (formData.value?.id) {
+ await updateReturnSales({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createReturnSales(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmReturnSalesStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getReturnSales(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList
+ :client-id="formData.clientId"
+ :form-type="formType"
+ :return-id="formData.id"
+ :sales-order-code="formData.salesOrderCode"
+ />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ攢鍞��璐у崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isStock"
+ title="纭鎵ц涓婃灦锛�"
+ @confirm="handleStock"
+ >
+ <Button type="primary">鎵ц涓婃灦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭鎵ц閫�璐э紵鎵ц鍚庡皢杩涘叆寰呬笂鏋剁姸鎬併��"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">鎵ц閫�璐�</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/returnsales/modules/line-form.vue b/src/views/wls/returnsales/modules/line-form.vue
new file mode 100644
index 0000000..bde39cb
--- /dev/null
+++ b/src/views/wls/returnsales/modules/line-form.vue
@@ -0,0 +1,102 @@
+<script lang="ts" setup>
+import type { MesWmReturnSalesLineApi } from '#/api/mes/wm/returnsales/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnSalesLine,
+ getReturnSalesLine,
+ updateReturnSalesLine,
+} from '#/api/mes/wm/returnsales/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnSalesLineApi.ReturnSalesLine>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['閿�鍞��璐у崟琛�'])
+ : $t('ui.actionTitle.create', ['閿�鍞��璐у崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmReturnSalesLineApi.ReturnSalesLine;
+ data.returnId = returnId.value;
+ try {
+ await (formData.value?.id
+ ? updateReturnSalesLine({ ...data, id: formData.value.id })
+ : createReturnSalesLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ clientId?: number;
+ id?: number;
+ returnId: number;
+ salesOrderCode?: string;
+ }>();
+ returnId.value = data.returnId;
+ formApi.setState({
+ schema: useLineFormSchema(data.clientId, data.salesOrderCode),
+ });
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getReturnSalesLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/returnsales/modules/line-list.vue b/src/views/wls/returnsales/modules/line-list.vue
new file mode 100644
index 0000000..e9b9476
--- /dev/null
+++ b/src/views/wls/returnsales/modules/line-list.vue
@@ -0,0 +1,247 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnSalesDetailApi } from '#/api/mes/wm/returnsales/detail';
+import type { MesWmReturnSalesLineApi } from '#/api/mes/wm/returnsales/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getReturnSalesDetailListByLineId } from '#/api/mes/wm/returnsales/detail';
+import {
+ deleteReturnSalesLine,
+ getReturnSalesLinePage,
+} from '#/api/mes/wm/returnsales/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ clientId?: number;
+ formType: FormType;
+ returnId: number;
+ salesOrderCode?: string;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+ Record<number, MesWmReturnSalesDetailApi.ReturnSalesDetail[]>
+>({}); // 宸插睍寮�琛岀殑涓婃灦鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi
+ .setData({
+ clientId: props.clientId,
+ returnId: props.returnId,
+ salesOrderCode: props.salesOrderCode,
+ })
+ .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+ lineFormModalApi
+ .setData({
+ clientId: props.clientId,
+ id: row.id,
+ returnId: props.returnId,
+ salesOrderCode: props.salesOrderCode,
+ })
+ .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteReturnSalesLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handleStock(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑涓婃灦鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, itemId, lineId, returnId: props.returnId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑涓婃灦鏄庣粏 */
+function getExpandedDetails(row: MesWmReturnSalesLineApi.ReturnSalesLine) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑涓婃灦鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getReturnSalesDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞戒笂鏋舵槑缁� */
+async function handleExpandChange(
+ row: MesWmReturnSalesLineApi.ReturnSalesLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.returnId) {
+ return { list: [], total: 0 };
+ }
+ return await getReturnSalesLinePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ returnId: props.returnId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmReturnSalesLineApi.ReturnSalesLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmReturnSalesLineApi.ReturnSalesLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <div class="flex items-center justify-center">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '涓婃灦',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handleStock.bind(null, row),
+ },
+ ]"
+ />
+ <PrinterLabel
+ v-if="isStock"
+ :biz-code="row.batchCode"
+ :biz-id="row.batchId"
+ biz-type="BATCH"
+ />
+ </div>
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/returnvendor/data.ts b/src/views/wls/returnvendor/data.ts
new file mode 100644
index 0000000..6844970
--- /dev/null
+++ b/src/views/wls/returnvendor/data.ts
@@ -0,0 +1,530 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
+import type { MesWmReturnVendorDetailApi } from '#/api/mes/wm/returnvendor/detail';
+import type { MesWmReturnVendorLineApi } from '#/api/mes/wm/returnvendor/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'stock' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟嫞璐с�佽鎯呫�佸畬鎴愭�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish' || formType === 'stock';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '閫�璐у崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_RETURN_VENDOR_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '閫�璐у崟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'purchaseOrderCode',
+ label: '閲囪喘璁㈠崟鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ噰璐鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'returnDate',
+ label: '閫�璐ф棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD HH:mm:ss',
+ placeholder: '璇烽�夋嫨閫�璐ф棩鏈�',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'returnReason',
+ label: '閫�璐у師鍥�',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ��璐у師鍥�',
+ rows: 2,
+ },
+ },
+ {
+ fieldName: 'transportCode',
+ label: '杩愬崟鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ繍鍗曞彿',
+ },
+ },
+ {
+ fieldName: 'transportTelephone',
+ label: '鑱旂郴鐢佃瘽',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ仈绯荤數璇�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '閫�璐у崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ��璐у崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '閫�璐у崟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ��璐у崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'purchaseOrderCode',
+ label: '閲囪喘璁㈠崟缂栧彿',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: markRaw(MdVendorSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨渚涘簲鍟�',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmReturnVendorApi.ReturnVendor>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '閫�璐у崟缂栧彿',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '閫�璐у崟鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'purchaseOrderCode',
+ title: '閲囪喘璁㈠崟鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'vendorCode',
+ title: '渚涘簲鍟嗙紪鐮�',
+ minWidth: 120,
+ },
+ {
+ field: 'vendorName',
+ title: '渚涘簲鍟嗗悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'returnDate',
+ title: '閫�璐ф棩鏈�',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_RETURN_VENDOR_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 閫�璐у崟琛屽瓙琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmReturnVendorLineApi.ReturnVendorLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '閫�璐ф暟閲�',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 閫�璐у崟琛屾柊澧�/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(
+ vendorId?: number,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '浜у搧鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨浜у搧鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '閫�璐ф暟閲�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ラ��璐ф暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'batchId',
+ label: '鎵规鍙�',
+ component: markRaw(WmBatchSelect),
+ componentProps: {
+ // 閫夋嫨鎵规鍚庡悓姝ユ壒娆″彿
+ onChange: async (batch?: { code?: string }) => {
+ await formApi?.setFieldValue('batchCode', batch?.code);
+ },
+ vendorId,
+ },
+ dependencies: {
+ triggerFields: ['itemId'],
+ componentProps: (values) => ({
+ itemId: values.itemId,
+ vendorId,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鎷h揣鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmReturnVendorDetailApi.ReturnVendorDetail>['columns'] {
+ return [
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鎷h揣鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'materialStockId',
+ label: '搴撳瓨璁板綍',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨璁板綍鍚庯紝鑷姩鍥炲~浠撳簱/搴撳尯/搴撲綅/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ areaId: stock?.areaId,
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ locationId: stock?.locationId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ warehouseId: stock?.warehouseId,
+ });
+ },
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['itemId'],
+ componentProps: (values) => ({
+ itemId: values.itemId,
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ disabled: true,
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['locationId'],
+ componentProps: (values) => ({
+ disabled: true,
+ locationId: values.locationId,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/returnvendor/index.vue b/src/views/wls/returnvendor/index.vue
new file mode 100644
index 0000000..01b9dcb
--- /dev/null
+++ b/src/views/wls/returnvendor/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmReturnVendorStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelReturnVendor,
+ deleteReturnVendor,
+ exportReturnVendor,
+ getReturnVendorPage,
+} from '#/api/mes/wm/returnvendor';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓渚涘簲鍟嗛��璐у崟 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅渚涘簲鍟嗛��璐у崟 */
+function handleDetail(row: MesWmReturnVendorApi.ReturnVendor) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫渚涘簲鍟嗛��璐у崟 */
+function handleEdit(row: MesWmReturnVendorApi.ReturnVendor) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鎷h揣 */
+function handleStock(row: MesWmReturnVendorApi.ReturnVendor) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 瀹屾垚閫�璐� */
+function handleFinish(row: MesWmReturnVendorApi.ReturnVendor) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎渚涘簲鍟嗛��璐у崟 */
+async function handleDelete(row: MesWmReturnVendorApi.ReturnVendor) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteReturnVendor(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷渚涘簲鍟嗛��璐у崟 */
+async function handleCancel(row: MesWmReturnVendorApi.ReturnVendor) {
+ await cancelReturnVendor(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportReturnVendor(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '渚涘簲鍟嗛��璐у崟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getReturnVendorPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmReturnVendorApi.ReturnVendor>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="渚涘簲鍟嗛��璐у崟鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['渚涘簲鍟嗛��璐у崟']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-return-vendor:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-return-vendor:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-return-vendor:update'],
+ ifShow: row.status === MesWmReturnVendorStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-return-vendor:delete'],
+ ifShow: row.status === MesWmReturnVendorStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц鎷h揣',
+ type: 'link',
+ auth: ['mes:wm-return-vendor:update'],
+ ifShow: row.status === MesWmReturnVendorStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '瀹屾垚閫�璐�',
+ type: 'link',
+ auth: ['mes:wm-return-vendor:update-status'],
+ ifShow: row.status === MesWmReturnVendorStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-return-vendor:update'],
+ ifShow:
+ row.status === MesWmReturnVendorStatusEnum.APPROVING ||
+ row.status === MesWmReturnVendorStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ヤ緵搴斿晢閫�璐у崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/returnvendor/modules/detail-form.vue b/src/views/wls/returnvendor/modules/detail-form.vue
new file mode 100644
index 0000000..9e07274
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/detail-form.vue
@@ -0,0 +1,104 @@
+<script lang="ts" setup>
+import type { MesWmReturnVendorDetailApi } from '#/api/mes/wm/returnvendor/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnVendorDetail,
+ getReturnVendorDetail,
+ updateReturnVendorDetail,
+} from '#/api/mes/wm/returnvendor/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmReturnVendorDetailApi.ReturnVendorDetail>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞為��璐у崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鎷h揣鏄庣粏'])
+ : $t('ui.actionTitle.create', ['鎷h揣鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmReturnVendorDetailApi.ReturnVendorDetail;
+ data.returnId = returnId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateReturnVendorDetail({ ...data, id: formData.value.id })
+ : createReturnVendorDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useDetailFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ itemId?: number;
+ lineId: number;
+ returnId: number;
+ }>();
+ returnId.value = data.returnId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getReturnVendorDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/returnvendor/modules/detail-list.vue b/src/views/wls/returnvendor/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/returnvendor/modules/detail-list.vue
copy to src/views/wls/returnvendor/modules/detail-list.vue
diff --git a/src/views/wls/returnvendor/modules/form.vue b/src/views/wls/returnvendor/modules/form.vue
new file mode 100644
index 0000000..ace9da0
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/form.vue
@@ -0,0 +1,236 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmReturnVendorStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ checkReturnVendorQuantity,
+ createReturnVendor,
+ finishReturnVendor,
+ getReturnVendor,
+ stockReturnVendor,
+ submitReturnVendor,
+ updateReturnVendor,
+} from '#/api/mes/wm/returnvendor';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmReturnVendorApi.ReturnVendor>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓哄畬鎴愰��璐фā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmReturnVendorStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['渚涘簲鍟嗛��璐у崟']);
+ }
+ if (formType.value === 'stock') {
+ return '鎵ц鎷h揣';
+ }
+ if (formType.value === 'finish') {
+ return '瀹屾垚閫�璐�';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['渚涘簲鍟嗛��璐у崟'])
+ : $t('ui.actionTitle.create', ['渚涘簲鍟嗛��璐у崟']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫�璐у崟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmReturnVendorApi.ReturnVendor;
+ await updateReturnVendor({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitReturnVendor(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鎷h揣锛氭嫞璐ф暟閲忎笌閫�璐ф暟閲忎笉涓�鑷存椂浜屾纭 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ const quantityMatch = await checkReturnVendorQuantity(formData.value.id);
+ if (!quantityMatch) {
+ try {
+ await confirm('閫�璐ф暟閲忎笌鎷h揣鏁伴噺涓嶄竴鑷达紝纭鎵ц鎷h揣锛�');
+ } catch {
+ return;
+ }
+ }
+ modalApi.lock();
+ try {
+ await stockReturnVendor(formData.value.id);
+ message.success('鎷h揣鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 瀹屾垚閫�璐� */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishReturnVendor(formData.value.id);
+ message.success('瀹屾垚鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmReturnVendorApi.ReturnVendor;
+ try {
+ if (formData.value?.id) {
+ await updateReturnVendor({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createReturnVendor(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmReturnVendorStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getReturnVendor(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList
+ :form-type="formType"
+ :return-id="formData.id"
+ :vendor-id="formData.vendorId"
+ />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ラ��璐у崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="isStock" type="primary" @click="handleStock">
+ 鎵ц鎷h揣
+ </Button>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭瀹屾垚璇ラ��璐у崟骞舵墽琛岄��璐э紵"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">瀹屾垚閫�璐�</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/returnvendor/modules/line-form.vue b/src/views/wls/returnvendor/modules/line-form.vue
new file mode 100644
index 0000000..db2307a
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/line-form.vue
@@ -0,0 +1,99 @@
+<script lang="ts" setup>
+import type { MesWmReturnVendorLineApi } from '#/api/mes/wm/returnvendor/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createReturnVendorLine,
+ getReturnVendorLine,
+ updateReturnVendorLine,
+} from '#/api/mes/wm/returnvendor/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmReturnVendorLineApi.ReturnVendorLine>();
+const returnId = ref<number>(); // 鎵�灞為��璐у崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['渚涘簲鍟嗛��璐у崟琛�'])
+ : $t('ui.actionTitle.create', ['渚涘簲鍟嗛��璐у崟琛�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmReturnVendorLineApi.ReturnVendorLine;
+ data.returnId = returnId.value;
+ try {
+ await (formData.value?.id
+ ? updateReturnVendorLine({ ...data, id: formData.value.id })
+ : createReturnVendorLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ id?: number;
+ returnId: number;
+ vendorId?: number;
+ }>();
+ returnId.value = data.returnId;
+ formApi.setState({ schema: useLineFormSchema(data.vendorId, formApi) });
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getReturnVendorLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/returnvendor/modules/line-list.vue b/src/views/wls/returnvendor/modules/line-list.vue
new file mode 100644
index 0000000..9822d7b
--- /dev/null
+++ b/src/views/wls/returnvendor/modules/line-list.vue
@@ -0,0 +1,237 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmReturnVendorDetailApi } from '#/api/mes/wm/returnvendor/detail';
+import type { MesWmReturnVendorLineApi } from '#/api/mes/wm/returnvendor/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getReturnVendorDetailListByLineId } from '#/api/mes/wm/returnvendor/detail';
+import {
+ deleteReturnVendorLine,
+ getReturnVendorLinePage,
+} from '#/api/mes/wm/returnvendor/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ returnId: number;
+ vendorId?: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓烘嫞璐фā寮�
+const detailMap = reactive<
+ Record<number, MesWmReturnVendorDetailApi.ReturnVendorDetail[]>
+>({}); // 宸插睍寮�琛岀殑鎷h揣鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi
+ .setData({ returnId: props.returnId, vendorId: props.vendorId })
+ .open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+ lineFormModalApi
+ .setData({ id: row.id, returnId: props.returnId, vendorId: props.vendorId })
+ .open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteReturnVendorLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鎷h揣锛氱洿鎺ユ墦寮�鏄庣粏鍒涘缓琛ㄥ崟 */
+function handlePicking(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑鎷h揣鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, itemId, lineId, returnId: props.returnId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑鎷h揣鏄庣粏 */
+function getExpandedDetails(row: MesWmReturnVendorLineApi.ReturnVendorLine) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑鎷h揣鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getReturnVendorDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞芥嫞璐ф槑缁� */
+async function handleExpandChange(
+ row: MesWmReturnVendorLineApi.ReturnVendorLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.returnId) {
+ return { list: [], total: 0 };
+ }
+ return await getReturnVendorLinePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ returnId: props.returnId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmReturnVendorLineApi.ReturnVendorLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmReturnVendorLineApi.ReturnVendorLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <div class="flex items-center justify-center">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎷h揣',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handlePicking.bind(null, row),
+ },
+ ]"
+ />
+ <PrinterLabel
+ v-if="isStock"
+ :biz-code="row.batchCode"
+ :biz-id="row.batchId"
+ biz-type="BATCH"
+ />
+ </div>
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/mes/wm/salesnotice/components/index.ts b/src/views/wls/salesnotice/components/index.ts
similarity index 100%
copy from src/views/mes/wm/salesnotice/components/index.ts
copy to src/views/wls/salesnotice/components/index.ts
diff --git a/src/views/wls/salesnotice/components/line-select-dialog.vue b/src/views/wls/salesnotice/components/line-select-dialog.vue
new file mode 100644
index 0000000..39c1058
--- /dev/null
+++ b/src/views/wls/salesnotice/components/line-select-dialog.vue
@@ -0,0 +1,198 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSalesNoticeLinePage } from '#/api/mes/wm/salesnotice/line';
+
+const emit = defineEmits<{
+ selected: [rows: MesWmSalesNoticeLineApi.SalesNoticeLine[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+const syncingSingleSelection = ref(false); // 鏄惁鍚屾鍗曢�夊嬀閫夌姸鎬�
+const selectedRows = ref<MesWmSalesNoticeLineApi.SalesNoticeLine[]>([]); // 宸查�夎鍒楄〃
+const preSelectedIds = ref<number[]>([]); // 棰勯�夎缂栧彿鍒楄〃
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(): VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>['columns'] {
+ return [
+ {
+ type: 'checkbox',
+ width: 50,
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '鍙戣揣鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'oqcCheckFlag',
+ title: '鏄惁妫�楠�',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ ];
+}
+
+/** 鍗曢�夋ā寮忎笅鍚屾 VXE 鍕鹃�夌姸鎬� */
+async function syncSingleSelection(
+ row?: MesWmSalesNoticeLineApi.SalesNoticeLine,
+) {
+ syncingSingleSelection.value = true;
+ await nextTick();
+ await gridApi.grid.clearCheckboxRow();
+ if (row) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ }
+ await nextTick();
+ syncingSingleSelection.value = false;
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖栵紝鍙繚鐣欐渶鍚庝竴鏉� */
+async function handleCheckboxChange({
+ checked,
+ row,
+}: {
+ checked: boolean;
+ row?: MesWmSalesNoticeLineApi.SalesNoticeLine;
+}) {
+ if (syncingSingleSelection.value) {
+ return;
+ }
+ const selected = checked && row ? [row] : [];
+ selectedRows.value = selected;
+ await syncSingleSelection(selected[0]);
+}
+
+/** 鍥炴樉棰勯�夎 */
+function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows =
+ gridApi.grid.getData() as MesWmSalesNoticeLineApi.SalesNoticeLine[];
+ for (const row of rows) {
+ if (row.id && preSelectedIds.value.includes(row.id)) {
+ gridApi.grid.setCheckboxRow(row, true);
+ selectedRows.value = [row];
+ }
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 460,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ reserve: true,
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!noticeId.value) {
+ return { list: [], total: 0 };
+ }
+ return await getSalesNoticeLinePage({
+ noticeId: noticeId.value,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>,
+ gridEvents: {
+ checkboxChange: handleCheckboxChange,
+ },
+});
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+async function openModal(id: number | undefined, selectedIds?: number[]) {
+ open.value = true;
+ noticeId.value = id;
+ preSelectedIds.value = selectedIds || [];
+ selectedRows.value = [];
+ await nextTick();
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.query();
+ await nextTick();
+ applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+async function closeModal() {
+ open.value = false;
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+}
+
+/** 纭閫夋嫨琛� */
+function handleConfirm() {
+ if (selectedRows.value.length === 0) {
+ message.warning('璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit('selected', [selectedRows.value[0]!]);
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ title="鍙戣揣閫氱煡鍗曡閫夋嫨"
+ width="70%"
+ :destroy-on-close="true"
+ @cancel="closeModal"
+ @ok="handleConfirm"
+ >
+ <Grid table-title="鍙戣揣閫氱煡鍗曡鍒楄〃" />
+ </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/components/line-select.vue b/src/views/wls/salesnotice/components/line-select.vue
new file mode 100644
index 0000000..c24d6a0
--- /dev/null
+++ b/src/views/wls/salesnotice/components/line-select.vue
@@ -0,0 +1,153 @@
+<script lang="ts" setup>
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getSalesNoticeLine } from '#/api/mes/wm/salesnotice/line';
+
+import WmSalesNoticeLineSelectDialog from './line-select-dialog.vue';
+
+defineOptions({ name: 'WmSalesNoticeLineSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ noticeId?: number; // 鎵�灞炲彂璐ч�氱煡鍗曠紪鍙�
+ placeholder?: string;
+ }>(),
+ {
+ allowClear: true,
+ disabled: false,
+ modelValue: undefined,
+ noticeId: undefined,
+ placeholder: '璇烽�夋嫨鍙戣揣閫氱煡鍗曡',
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmSalesNoticeLineApi.SalesNoticeLine | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmSalesNoticeLineSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmSalesNoticeLineApi.SalesNoticeLine>();
+
+const displayLabel = computed(() => {
+ const item = selectedItem.value;
+ if (!item) {
+ return '';
+ }
+ return `${item.itemCode ?? ''} - ${item.itemName ?? ''}`;
+});
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ琛屼俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getSalesNoticeLine(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** noticeId 鍙樺寲鏃舵竻绌洪�変腑锛堝叧鑱旂殑琛屽凡澶辨晥锛� */
+watch(
+ () => props.noticeId,
+ () => {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+ },
+);
+
+/** 娓呯┖宸查�夎 */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑琛岄�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled || !props.noticeId) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(props.noticeId, selectedIds);
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmSalesNoticeLineApi.SalesNoticeLine[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>鐗╂枡缂栫爜锛歿{ selectedItem.itemCode || '-' }}</div>
+ <div>鐗╂枡鍚嶇О锛歿{ selectedItem.itemName || '-' }}</div>
+ <div>瑙勬牸鍨嬪彿锛歿{ selectedItem.specification || '-' }}</div>
+ <div>鍙戣揣鏁伴噺锛歿{ selectedItem.quantity ?? '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <WmSalesNoticeLineSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/salesnotice/components/select-dialog.vue b/src/views/wls/salesnotice/components/select-dialog.vue
new file mode 100644
index 0000000..9ed3377
--- /dev/null
+++ b/src/views/wls/salesnotice/components/select-dialog.vue
@@ -0,0 +1,320 @@
+<script lang="ts" setup>
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { markRaw, nextTick, ref } from 'vue';
+
+import { DICT_TYPE } from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSalesNoticePage } from '#/api/mes/wm/salesnotice';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+
+const emit = defineEmits<{
+ selected: [rows: MesWmSalesNoticeApi.SalesNotice[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(false); // 鏄惁澶氶��
+const fixedStatus = ref<number>(); // 鍥哄畾鐘舵�佺瓫閫�
+const selectedRows = ref<MesWmSalesNoticeApi.SalesNotice[]>([]); // 宸查�夐�氱煡鍗曞垪琛�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夐�氱煡鍗曠紪鍙峰垪琛�
+
+/** 鎼滅储琛ㄥ崟锛氭湭鍥哄畾鐘舵�佹椂灞曠ず鐘舵�佷笅鎷夛紝鍥哄畾鐘舵�佹椂闅愯棌 */
+function useSearchSchema(hasFixedStatus: boolean): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '閫氱煡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '閫氱煡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ ...(hasFixedStatus
+ ? []
+ : [
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(
+ DICT_TYPE.MES_WM_SALES_NOTICE_STATUS,
+ 'number',
+ ),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ } as VbenFormSchema,
+ ]),
+ ];
+}
+
+/** 琛ㄦ牸瀛楁 */
+function useGridColumns(
+ multipleSelect = false,
+): VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>['columns'] {
+ return [
+ {
+ type: multipleSelect ? 'checkbox' : 'radio',
+ width: 50,
+ },
+ {
+ field: 'code',
+ title: '閫氱煡鍗曠紪鍙�',
+ minWidth: 160,
+ },
+ {
+ field: 'name',
+ title: '閫氱煡鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曠紪鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'salesDate',
+ title: '鍙戣揣鏃ユ湡',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_SALES_NOTICE_STATUS },
+ },
+ },
+ ];
+}
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<number, MesWmSalesNoticeApi.SalesNotice>();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as MesWmSalesNoticeApi.SalesNotice[];
+ records.forEach((row) => {
+ const rowId = row.id;
+ if (rowId !== undefined) {
+ selectedMap.set(rowId, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+/** 澶勭悊澶氶�夊嬀閫夊彉鍖� */
+function handleCheckboxSelectChange() {
+ if (!multiple.value) {
+ return;
+ }
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊垏鎹� */
+function handleRadioChange(row: MesWmSalesNoticeApi.SalesNotice) {
+ selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmSalesNoticeApi.SalesNotice) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紝澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({
+ row,
+}: {
+ row: MesWmSalesNoticeApi.SalesNotice;
+}) {
+ if (multiple.value) {
+ await toggleMultipleRow(row);
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夐�氱煡鍗� */
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows = gridApi.grid.getData() as MesWmSalesNoticeApi.SalesNotice[];
+ for (const row of rows) {
+ if (row.id === undefined || !preSelectedIds.value.includes(row.id)) {
+ continue;
+ }
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useSearchSchema(false),
+ },
+ gridOptions: {
+ columns: useGridColumns(false),
+ height: 520,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ range: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getSalesNoticePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ // 鍥哄畾鐘舵�佷紭鍏堬紝鏈浐瀹氭椂鐢ㄦ悳绱㈣〃鍗曢噷鐨勭姸鎬�
+ status: fixedStatus.value ?? formValues.status,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({ row }: { row: MesWmSalesNoticeApi.SalesNotice }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+ await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean; status?: number },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? false;
+ fixedStatus.value = options?.status;
+ preSelectedIds.value = selectedIds || [];
+ // 鍥哄畾鐘舵�佹椂闅愯棌鐘舵�佹悳绱㈤」锛屾湭鍥哄畾鏃跺睍绀�
+ gridApi.formApi.setState({
+ schema: useSearchSchema(fixedStatus.value !== undefined),
+ });
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: useGridColumns(multiple.value),
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+/** 鍏抽棴閫氱煡鍗曢�夋嫨寮圭獥 */
+async function closeModal() {
+ open.value = false;
+ await resetQueryState();
+}
+
+/** 纭閫夋嫨閫氱煡鍗� */
+function handleConfirm() {
+ if (selectedRows.value.length === 0) {
+ message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit(
+ 'selected',
+ multiple.value ? selectedRows.value : [selectedRows.value[0]!],
+ );
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ title="鍙戣揣閫氱煡鍗曢�夋嫨"
+ width="70%"
+ :destroy-on-close="true"
+ @cancel="closeModal"
+ @ok="handleConfirm"
+ >
+ <Grid table-title="鍙戣揣閫氱煡鍗曞垪琛�" />
+ </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/components/select.vue b/src/views/wls/salesnotice/components/select.vue
new file mode 100644
index 0000000..45aa8b0
--- /dev/null
+++ b/src/views/wls/salesnotice/components/select.vue
@@ -0,0 +1,141 @@
+<script lang="ts" setup>
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getSalesNotice } from '#/api/mes/wm/salesnotice';
+
+import WmSalesNoticeSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'WmSalesNoticeSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ placeholder?: string;
+ status?: number; // 鍥哄畾鐘舵�佺瓫閫�
+ }>(),
+ {
+ allowClear: true,
+ disabled: false,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨鍙戣揣閫氱煡鍗�',
+ status: undefined,
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmSalesNoticeApi.SalesNotice | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof WmSalesNoticeSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmSalesNoticeApi.SalesNotice>();
+
+const displayLabel = computed(() => selectedItem.value?.name ?? '');
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== undefined &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ閫氱煡鍗曚俊鎭紙鐢ㄤ簬缂栬緫鍥炴樉锛� */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getSalesNotice(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夐�氱煡鍗� */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑閫氱煡鍗曢�夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, {
+ multiple: false,
+ status: props.status,
+ });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmSalesNoticeApi.SalesNotice[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>缂栧彿锛歿{ selectedItem.code || '-' }}</div>
+ <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+ <div>瀹㈡埛锛歿{ selectedItem.clientName || '-' }}</div>
+ <div>閿�鍞鍗曪細{{ selectedItem.salesOrderCode || '-' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <WmSalesNoticeSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/salesnotice/data.ts b/src/views/wls/salesnotice/data.ts
new file mode 100644
index 0000000..a0aa7ba
--- /dev/null
+++ b/src/views/wls/salesnotice/data.ts
@@ -0,0 +1,362 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { h, markRaw } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdClientSelect } from '#/views/mes/md/client/components';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堣鎯呫�佹墽琛屽嚭搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish';
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '閫氱煡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+ },
+ rules: 'required',
+ suffix: isHeaderReadonly(formType)
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_SALES_NOTICE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '閫氱煡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'salesDate',
+ label: '鍙戣揣鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍙戣揣鏃ユ湡',
+ valueFormat: 'x',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'recipientName',
+ label: '鏀惰揣浜�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ敹璐т汉',
+ },
+ },
+ {
+ fieldName: 'recipientTelephone',
+ label: '鑱旂郴鏂瑰紡',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ仈绯绘柟寮�',
+ },
+ },
+ {
+ fieldName: 'recipientAddress',
+ label: '鏀惰揣鍦板潃',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ敹璐у湴鍧�',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '閫氱煡鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '閫氱煡鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ�氱煡鍗曞悕绉�',
+ },
+ },
+ {
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+ },
+ },
+ {
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: markRaw(MdClientSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨瀹㈡埛',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '閫氱煡鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '閫氱煡鍗曞悕绉�',
+ minWidth: 150,
+ },
+ {
+ field: 'salesOrderCode',
+ title: '閿�鍞鍗曠紪鍙�',
+ minWidth: 140,
+ },
+ {
+ field: 'clientName',
+ title: '瀹㈡埛鍚嶇О',
+ minWidth: 120,
+ },
+ {
+ field: 'salesDate',
+ title: '鍙戣揣鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'recipientName',
+ title: '鏀惰揣浜�',
+ minWidth: 100,
+ },
+ {
+ field: 'recipientTelephone',
+ title: '鑱旂郴鏂瑰紡',
+ minWidth: 120,
+ },
+ {
+ field: 'recipientAddress',
+ title: '鏀惰揣鍦板潃',
+ minWidth: 200,
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_SALES_NOTICE_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 閫氱煡鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+): VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>['columns'] {
+ return [
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '鍙戣揣鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'oqcCheckFlag',
+ title: '鏄惁妫�楠�',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 120,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 閫氱煡鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍙戣揣鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0.01,
+ placeholder: '璇疯緭鍏ュ彂璐ф暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'oqcCheckFlag',
+ label: '鏄惁妫�楠�',
+ component: 'Switch',
+ rules: z.boolean().default(true),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/salesnotice/index.vue b/src/views/wls/salesnotice/index.vue
new file mode 100644
index 0000000..e2958c8
--- /dev/null
+++ b/src/views/wls/salesnotice/index.vue
@@ -0,0 +1,168 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmSalesNoticeStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteSalesNotice,
+ exportSalesNotice,
+ getSalesNoticePage,
+} from '#/api/mes/wm/salesnotice';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鍙戣揣閫氱煡鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鍙戣揣閫氱煡鍗� */
+function handleDetail(row: MesWmSalesNoticeApi.SalesNotice) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鍙戣揣閫氱煡鍗� */
+function handleEdit(row: MesWmSalesNoticeApi.SalesNotice) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鍑哄簱 */
+function handleFinish(row: MesWmSalesNoticeApi.SalesNotice) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎鍙戣揣閫氱煡鍗� */
+async function handleDelete(row: MesWmSalesNoticeApi.SalesNotice) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteSalesNotice(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportSalesNotice(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鍙戣揣閫氱煡鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getSalesNoticePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmSalesNoticeApi.SalesNotice>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鍙戣揣閫氱煡鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鍙戣揣閫氱煡鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-sales-notice:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-sales-notice:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-sales-notice:update'],
+ ifShow: row.status === MesWmSalesNoticeStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-sales-notice:delete'],
+ ifShow: row.status === MesWmSalesNoticeStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц鍑哄簱',
+ type: 'link',
+ auth: ['mes:wm-sales-notice:update'],
+ ifShow: row.status === MesWmSalesNoticeStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/salesnotice/modules/form.vue b/src/views/wls/salesnotice/modules/form.vue
new file mode 100644
index 0000000..074d6a9
--- /dev/null
+++ b/src/views/wls/salesnotice/modules/form.vue
@@ -0,0 +1,182 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmSalesNoticeApi } from '#/api/mes/wm/salesnotice';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmSalesNoticeStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createSalesNotice,
+ getSalesNotice,
+ submitSalesNotice,
+ updateSalesNotice,
+} from '#/api/mes/wm/salesnotice';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmSalesNoticeApi.SalesNotice>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽嚭搴撴ā寮�
+const canSubmit = computed(
+ () =>
+ // 鏄惁鍙彁浜�
+ formType.value === 'update' &&
+ formData.value?.status === MesWmSalesNoticeStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['鍙戣揣閫氱煡鍗�']);
+ }
+ if (formType.value === 'finish') {
+ return '鎵ц鍑哄簱';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['鍙戣揣閫氱煡鍗�'])
+ : $t('ui.actionTitle.create', ['鍙戣揣閫氱煡鍗�']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦閫氱煡鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmSalesNoticeApi.SalesNotice;
+ await updateSalesNotice({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitSalesNotice(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鍑哄簱锛堝悗绔殏鏈疄鐜帮紝鎻愮ず鐢ㄦ埛锛� */
+function handleFinish() {
+ message.info('鎵ц鍑哄簱鍔熻兘鏆傛椂涓嶆敮鎸侊紝鏁鏈熷緟锛�');
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmSalesNoticeApi.SalesNotice;
+ try {
+ if (formData.value?.id) {
+ await updateSalesNotice({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createSalesNotice(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmSalesNoticeStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getSalesNotice(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :notice-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ュ彂璐ч�氱煡鍗曪紵銆愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Button v-if="isFinish" type="primary" @click="handleFinish">
+ 鎵ц鍑哄簱
+ </Button>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/modules/line-form.vue b/src/views/wls/salesnotice/modules/line-form.vue
new file mode 100644
index 0000000..740a8ad
--- /dev/null
+++ b/src/views/wls/salesnotice/modules/line-form.vue
@@ -0,0 +1,94 @@
+<script lang="ts" setup>
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createSalesNoticeLine,
+ getSalesNoticeLine,
+ updateSalesNoticeLine,
+} from '#/api/mes/wm/salesnotice/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmSalesNoticeLineApi.SalesNoticeLine>();
+const noticeId = ref<number>(); // 鎵�灞為�氱煡鍗曠紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鍙戣揣閫氱煡鍗曡'])
+ : $t('ui.actionTitle.create', ['鍙戣揣閫氱煡鍗曡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useLineFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmSalesNoticeLineApi.SalesNoticeLine;
+ data.noticeId = noticeId.value;
+ try {
+ await (formData.value?.id
+ ? updateSalesNoticeLine({ ...data, id: formData.value.id })
+ : createSalesNoticeLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; noticeId: number }>();
+ noticeId.value = data.noticeId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getSalesNoticeLine(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/salesnotice/modules/line-list.vue b/src/views/wls/salesnotice/modules/line-list.vue
new file mode 100644
index 0000000..bd3041c
--- /dev/null
+++ b/src/views/wls/salesnotice/modules/line-list.vue
@@ -0,0 +1,140 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSalesNoticeLineApi } from '#/api/mes/wm/salesnotice/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteSalesNoticeLine,
+ getSalesNoticeLinePage,
+} from '#/api/mes/wm/salesnotice/line';
+import { $t } from '#/locales';
+
+import { useLineGridColumns } from '../data';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ noticeId: number;
+}>();
+
+const isEditable = computed(() =>
+ // 鏄惁鍙紪杈戞槑缁嗚
+ ['create', 'update'].includes(props.formType),
+);
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 娣诲姞鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ noticeId: props.noticeId }).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MesWmSalesNoticeLineApi.SalesNoticeLine) {
+ lineFormModalApi.setData({ id: row.id, noticeId: props.noticeId }).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MesWmSalesNoticeLineApi.SalesNoticeLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteSalesNoticeLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value),
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.noticeId) {
+ return { list: [], total: 0 };
+ }
+ return await getSalesNoticeLinePage({
+ noticeId: props.noticeId,
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmSalesNoticeLineApi.SalesNoticeLine>,
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/mes/wm/sn/data.ts b/src/views/wls/sn/data.ts
similarity index 100%
copy from src/views/mes/wm/sn/data.ts
copy to src/views/wls/sn/data.ts
diff --git a/src/views/wls/sn/index.vue b/src/views/wls/sn/index.vue
new file mode 100644
index 0000000..e3bca26
--- /dev/null
+++ b/src/views/wls/sn/index.vue
@@ -0,0 +1,177 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteSnBatch,
+ exportSnDetailExcel,
+ exportSnGroupExcel,
+ getSnGroupPage,
+} from '#/api/mes/wm/sn';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Detail from './modules/detail.vue';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [DetailModal, detailModalApi] = useVbenModal({
+ connectedComponent: Detail,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鐢熸垚 SN 鐮� */
+function handleCreate() {
+ formModalApi.setData(null).open();
+}
+
+/** 瀵煎嚭鍒嗙粍琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportSnGroupExcel(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: 'SN鐮佸垎缁�.xls', source: data });
+}
+
+/** 瀵煎嚭鎵规鏄庣粏 */
+async function handleExportDetail(row: MesWmSnApi.SnGroup) {
+ const data = await exportSnDetailExcel(row.uuid!);
+ downloadFileFromBlobPart({ fileName: 'SN鐮佹槑缁�.xls', source: data });
+}
+
+/** 鏌ョ湅 SN 鐮佹槑缁� */
+function handleDetail(row: MesWmSnApi.SnGroup) {
+ detailModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎 SN 鐮佹壒娆� */
+async function handleDelete(row: MesWmSnApi.SnGroup) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.batchCode || row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteSnBatch(row.uuid!);
+ message.success(
+ $t('ui.actionMessage.deleteSuccess', [row.batchCode || '']),
+ );
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getSnGroupPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'uuid',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmSnApi.SnGroup>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+ <DetailModal />
+
+ <Grid table-title="SN 鐮佸垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '鐢熸垚 SN 鐮�',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-sn:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-sn:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #count="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: `${row.count ?? 0}`,
+ type: 'link',
+ auth: ['mes:wm-sn:query'],
+ onClick: handleDetail.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '鏌ョ湅鏄庣粏',
+ type: 'link',
+ auth: ['mes:wm-sn:query'],
+ onClick: handleDetail.bind(null, row),
+ },
+ {
+ label: '瀵煎嚭鏄庣粏',
+ type: 'link',
+ auth: ['mes:wm-sn:export'],
+ onClick: handleExportDetail.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-sn:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [
+ row.batchCode || row.itemName,
+ ]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/sn/modules/detail.vue b/src/views/wls/sn/modules/detail.vue
new file mode 100644
index 0000000..6162d9e
--- /dev/null
+++ b/src/views/wls/sn/modules/detail.vue
@@ -0,0 +1,100 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { nextTick, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSnListByUuid } from '#/api/mes/wm/sn';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useDetailGridColumns } from '../data';
+
+defineOptions({ name: 'WlsSnDetail' });
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 鏌ョ湅 SN 鐮佹潯鐮� */
+function handleBarcode(row: MesWmSnApi.Sn) {
+ if (!row.id) {
+ message.warning('缂哄皯 SN 鐮佺紪鍙凤紝鏃犳硶鏌ョ湅鏉$爜');
+ return;
+ }
+ barcodeDetailRef.value?.openByBusiness(
+ row.id,
+ BarcodeBizTypeEnum.SN,
+ row.code,
+ row.itemName || row.code,
+ );
+}
+
+/** 鍔犺浇 SN 鐮佹槑缁� */
+async function loadDetail(row?: MesWmSnApi.SnGroup) {
+ if (!row?.uuid) {
+ await gridApi.grid?.loadData([]);
+ return;
+ }
+ gridApi.setLoading(true);
+ try {
+ const list = await getSnListByUuid(row.uuid);
+ await gridApi.grid?.loadData(list);
+ } finally {
+ gridApi.setLoading(false);
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useDetailGridColumns(),
+ height: 520,
+ pagerConfig: {
+ enabled: false,
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ enabled: false,
+ },
+ } as VxeTableGridOptions<MesWmSnApi.Sn>,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ showCancelButton: false,
+ showConfirmButton: false,
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ await gridApi.grid?.loadData([]);
+ return;
+ }
+ await nextTick();
+ await loadDetail(modalApi.getData<MesWmSnApi.SnGroup>());
+ },
+});
+</script>
+
+<template>
+ <Modal class="w-3/5" title="SN 鐮佹槑缁�">
+ <Grid class="mx-4">
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '鏉$爜',
+ type: 'link',
+ auth: ['mes:wm-sn:query'],
+ onClick: handleBarcode.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ <BarcodeDetail ref="barcodeDetailRef" />
+ </Modal>
+</template>
diff --git a/src/views/wls/sn/modules/form.vue b/src/views/wls/sn/modules/form.vue
new file mode 100644
index 0000000..6a22db2
--- /dev/null
+++ b/src/views/wls/sn/modules/form.vue
@@ -0,0 +1,62 @@
+<script lang="ts" setup>
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { generateSnCodes } from '#/api/mes/wm/sn';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-2',
+ labelWidth: 100,
+ },
+ layout: 'horizontal',
+ schema: useFormSchema(),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmSnApi.SnGenerate;
+ try {
+ await generateSnCodes(data);
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success('鐢熸垚鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ return;
+ }
+ await formApi.resetForm();
+ await formApi.setValues({ count: 100 });
+ },
+});
+</script>
+
+<template>
+ <Modal :title="$t('ui.actionTitle.create', ['SN 鐮�'])" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/components/condition-value-input.vue b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
new file mode 100644
index 0000000..1586a6f
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/components/condition-value-input.vue
@@ -0,0 +1,204 @@
+<script lang="ts" setup>
+import type { NumberDictDataType } from '../../../../../packages/effects/hooks/src';
+
+import { ref, watch } from 'vue';
+
+import { DICT_TYPE, WlsStockTakingParamTypeEnum } from '../../../../../packages/constants/src';
+import { getDictOptions } from '../../../../../packages/effects/hooks/src';
+
+import { Select } from 'ant-design-vue';
+
+import { getWarehouseArea } from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmBatchSelect } from '#/views/wls/batch/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+defineOptions({ name: 'StockTakingPlanConditionValueInput' });
+
+const props = withDefaults(
+ defineProps<{
+ modelValue?: number;
+ type?: number;
+ valueCode?: string;
+ }>(),
+ {
+ modelValue: undefined,
+ type: undefined,
+ valueCode: undefined,
+ },
+);
+const emit = defineEmits<{
+ 'update:modelValue': [value?: number];
+ valueChange: [
+ payload: { valueCode?: string; valueId?: number; valueName?: string },
+ ];
+}>();
+
+const qualityStatusOptions = getDictOptions(
+ DICT_TYPE.MES_WM_QUALITY_STATUS,
+ 'number',
+) as NumberDictDataType[];
+
+const locationWarehouseId = ref<number>(); // 搴撳尯閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
+const areaWarehouseId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫変粨搴撳悗浼犵粰搴撳尯閫夋嫨鍣�
+const areaLocationId = ref<number>(); // 搴撲綅閫夋嫨鍣ㄤ复鏃舵暟鎹細閫夊簱鍖哄悗浼犵粰搴撲綅閫夋嫨鍣�
+
+const qualityStatusValue = ref<number>();
+
+/** 閫氱敤閫夋嫨鍣ㄥ彉鍖栵細鍥炲~鏉′欢鍊肩紪鐮併�佸悕绉� */
+function handleSelectorChange(item?: any) {
+ emit('update:modelValue', item?.id);
+ emit('valueChange', {
+ valueId: item?.id,
+ valueCode: item?.code || '',
+ valueName: item?.name || item?.nickname || '',
+ });
+}
+
+/** 鎵规閫夋嫨鍣ㄥ彉鍖� */
+function handleBatchChange(batch?: any) {
+ emit('update:modelValue', batch?.id);
+ emit('valueChange', {
+ valueId: batch?.id,
+ valueCode: batch?.code || '',
+ valueName: batch?.code || '',
+ });
+}
+
+/** 璐ㄩ噺鐘舵�侀�夋嫨鍣ㄥ彉鍖栵細鏃犲疄浣撶紪鍙凤紝浠呰褰曞瓧鍏哥紪鐮佸拰鏂囨 */
+function handleQualityStatusChange(value?: number) {
+ qualityStatusValue.value = value;
+ const selected = qualityStatusOptions.find((item) => item.value === value);
+ emit('update:modelValue', undefined);
+ emit('valueChange', {
+ valueId: undefined,
+ valueCode: value === null ? '' : String(value),
+ valueName: selected?.label || '',
+ });
+}
+
+/** 搴撳尯浠撳簱閫夋嫨鍥炶皟锛氭竻绌哄簱鍖� */
+function handleLocationWarehouseChange() {
+ emit('update:modelValue', undefined);
+ emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
+}
+
+/** 搴撲綅浠撳簱閫夋嫨鍥炶皟锛氭竻绌哄簱鍖哄拰搴撲綅 */
+function handleAreaWarehouseChange() {
+ areaLocationId.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
+}
+
+/** 搴撲綅搴撳尯閫夋嫨鍥炶皟锛氭竻绌哄簱浣� */
+function handleAreaLocationChange() {
+ emit('update:modelValue', undefined);
+ emit('valueChange', { valueId: undefined, valueCode: '', valueName: '' });
+}
+
+/** 缂栬緫鏃跺洖濉骇鑱旈�夋嫨鍣ㄧ殑涓婄骇鏁版嵁锛堝簱鍖烘墍灞炰粨搴撱�佸簱浣嶆墍灞炰粨搴�/搴撳尯锛� */
+async function loadCascadeData() {
+ if (props.type === WlsStockTakingParamTypeEnum.QUALITY_STATUS) {
+ qualityStatusValue.value = props.valueCode
+ ? Number(props.valueCode)
+ : undefined;
+ return;
+ }
+ if (!props.modelValue) {
+ return;
+ }
+ if (props.type === WlsStockTakingParamTypeEnum.LOCATION) {
+ const location = await getWarehouseLocation(props.modelValue);
+ locationWarehouseId.value = location?.warehouseId;
+ } else if (props.type === WlsStockTakingParamTypeEnum.AREA) {
+ const area = await getWarehouseArea(props.modelValue);
+ areaWarehouseId.value = area?.warehouseId;
+ areaLocationId.value = area?.locationId;
+ }
+}
+
+// 鏉′欢绫诲瀷鍙樺寲鏃堕噸缃骇鑱斾复鏃舵暟鎹紱缂栬緫鍥炴樉鏃舵寜绫诲瀷鍔犺浇涓婄骇鏁版嵁
+watch(
+ () => props.type,
+ () => {
+ locationWarehouseId.value = undefined;
+ areaWarehouseId.value = undefined;
+ areaLocationId.value = undefined;
+ qualityStatusValue.value = undefined;
+ void loadCascadeData();
+ },
+ { immediate: true },
+);
+</script>
+
+<template>
+ <WmWarehouseSelect
+ v-if="type === WlsStockTakingParamTypeEnum.WAREHOUSE"
+ :model-value="modelValue"
+ @change="handleSelectorChange"
+ />
+ <div
+ v-else-if="type === WlsStockTakingParamTypeEnum.LOCATION"
+ class="space-y-2"
+ >
+ <WmWarehouseSelect
+ v-model="locationWarehouseId"
+ placeholder="璇烽�夋嫨浠撳簱"
+ @change="handleLocationWarehouseChange"
+ />
+ <WmWarehouseLocationSelect
+ v-if="locationWarehouseId"
+ :model-value="modelValue"
+ :warehouse-id="locationWarehouseId"
+ placeholder="璇烽�夋嫨搴撳尯"
+ @change="handleSelectorChange"
+ />
+ </div>
+ <div
+ v-else-if="type === WlsStockTakingParamTypeEnum.AREA"
+ class="space-y-2"
+ >
+ <WmWarehouseSelect
+ v-model="areaWarehouseId"
+ placeholder="璇烽�夋嫨浠撳簱"
+ @change="handleAreaWarehouseChange"
+ />
+ <WmWarehouseLocationSelect
+ v-if="areaWarehouseId"
+ v-model="areaLocationId"
+ placeholder="璇烽�夋嫨搴撳尯"
+ :warehouse-id="areaWarehouseId"
+ @change="handleAreaLocationChange"
+ />
+ <WmWarehouseAreaSelect
+ v-if="areaLocationId"
+ :model-value="modelValue"
+ :location-id="areaLocationId"
+ placeholder="璇烽�夋嫨搴撲綅"
+ @change="handleSelectorChange"
+ />
+ </div>
+ <MdItemSelect
+ v-else-if="type === WlsStockTakingParamTypeEnum.ITEM"
+ :model-value="modelValue"
+ @change="handleSelectorChange"
+ />
+ <WmBatchSelect
+ v-else-if="type === WlsStockTakingParamTypeEnum.BATCH"
+ :model-value="modelValue"
+ @change="handleBatchChange"
+ />
+ <Select
+ v-else-if="type === WlsStockTakingParamTypeEnum.QUALITY_STATUS"
+ :options="qualityStatusOptions"
+ :value="qualityStatusValue"
+ placeholder="璇烽�夋嫨璐ㄩ噺鐘舵��"
+ class="w-full"
+ @change="(value) => handleQualityStatusChange(value as number)"
+ />
+</template>
diff --git a/src/views/mes/wm/stocktaking/plan/components/index.ts b/src/views/wls/stocktaking/plan/components/index.ts
similarity index 100%
copy from src/views/mes/wm/stocktaking/plan/components/index.ts
copy to src/views/wls/stocktaking/plan/components/index.ts
diff --git a/src/views/wls/stocktaking/plan/components/select-dialog.vue b/src/views/wls/stocktaking/plan/components/select-dialog.vue
new file mode 100644
index 0000000..d16740c
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/components/select-dialog.vue
@@ -0,0 +1,213 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { nextTick, ref } from 'vue';
+
+import { CommonStatusEnum } from '../../../../../packages/constants/src';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getStockTakingPlanPage } from '#/api/mes/wm/stocktaking/plan';
+
+import { useSelectGridColumns, useSelectGridFormSchema } from '../data';
+
+const emit = defineEmits<{
+ selected: [rows: MesWmStockTakingPlanApi.StockTakingPlan[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(true); // 鏄惁澶氶��
+const selectedRows = ref<MesWmStockTakingPlanApi.StockTakingPlan[]>([]); // 宸查�夌洏鐐规柟妗�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夌洏鐐规柟妗堢紪鍙�
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<
+ number,
+ MesWmStockTakingPlanApi.StockTakingPlan
+ >();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as MesWmStockTakingPlanApi.StockTakingPlan[];
+ records.forEach((row) => {
+ if (row.id !== undefined && row.id !== null) {
+ selectedMap.set(row.id, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+ selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑伙細鍗曢�夌洿鎺ョ‘璁わ紱澶氶�夊垏鎹㈠嬀閫� */
+async function handleCellDblclick({
+ row,
+}: {
+ row: MesWmStockTakingPlanApi.StockTakingPlan;
+}) {
+ if (multiple.value) {
+ await toggleMultipleRow(row);
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夌洏鐐规柟妗� */
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows =
+ gridApi.grid.getData() as MesWmStockTakingPlanApi.StockTakingPlan[];
+ for (const row of rows) {
+ if (
+ row.id === undefined ||
+ row.id === null ||
+ !preSelectedIds.value.includes(row.id)
+ ) {
+ continue;
+ }
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useSelectGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useSelectGridColumns(true),
+ height: 480,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ range: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getStockTakingPlanPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ // 閫夋嫨鍣ㄥ彧灞曠ず鍚敤鐘舵�佺殑鏂规
+ status: CommonStatusEnum.ENABLE,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({
+ row,
+ }: {
+ row: MesWmStockTakingPlanApi.StockTakingPlan;
+ }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+ await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑鐩樼偣鏂规閫夋嫨寮圭獥 */
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? true;
+ preSelectedIds.value = selectedIds || [];
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: useSelectGridColumns(multiple.value),
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+/** 鍏抽棴寮圭獥 */
+function closeModal() {
+ open.value = false;
+}
+
+/** 纭閫夋嫨 */
+function handleConfirm() {
+ const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+ if (rows.length === 0) {
+ message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit('selected', multiple.value ? rows : [rows[0]!]);
+ open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal
+ v-model:open="open"
+ :destroy-on-close="true"
+ title="鐩樼偣鏂规閫夋嫨"
+ width="70%"
+ @cancel="closeModal"
+ @ok="handleConfirm"
+ >
+ <Grid table-title="鐩樼偣鏂规鍒楄〃" />
+ </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/components/select.vue b/src/views/wls/stocktaking/plan/components/select.vue
new file mode 100644
index 0000000..cfb9115
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/components/select.vue
@@ -0,0 +1,135 @@
+<script lang="ts" setup>
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '../../../../../packages/icons/src';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getStockTakingPlan } from '#/api/mes/wm/stocktaking/plan';
+
+import StockTakingPlanSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'StockTakingPlanSelect', inheritAttrs: false });
+
+const props = withDefaults(
+ defineProps<{
+ allowClear?: boolean;
+ disabled?: boolean;
+ modelValue?: number;
+ placeholder?: string;
+ }>(),
+ {
+ allowClear: true,
+ disabled: false,
+ modelValue: undefined,
+ placeholder: '璇烽�夋嫨鐩樼偣鏂规',
+ },
+);
+
+const emit = defineEmits<{
+ change: [item: MesWmStockTakingPlanApi.StockTakingPlan | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof StockTakingPlanSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<MesWmStockTakingPlanApi.StockTakingPlan>();
+
+const displayLabel = computed(() => selectedItem.value?.name ?? '');
+
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁缂栧彿鍗曟潯鏌ヨ鐩樼偣鏂规淇℃伅锛堢敤浜庣紪杈戝洖鏄撅級 */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getStockTakingPlan(id);
+}
+
+watch(() => props.modelValue, resolveItemById, { immediate: true });
+
+/** 娓呯┖宸查�夌洏鐐规柟妗� */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑鐩樼偣鏂规閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 寮圭獥閫変腑鍥炶皟 */
+function handleSelected(rows: MesWmStockTakingPlanApi.StockTakingPlan[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+ <template #title>
+ <div v-if="selectedItem" class="leading-6">
+ <div>缂栫爜锛歿{ selectedItem.code || '-' }}</div>
+ <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+ <div>鏄惁鐩茬洏锛歿{ selectedItem.blindFlag ? '鏄�' : '鍚�' }}</div>
+ <div>鏄惁鍐荤粨搴撳瓨锛歿{ selectedItem.frozen ? '鏄�' : '鍚�' }}</div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <StockTakingPlanSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/wls/stocktaking/plan/data.ts b/src/views/wls/stocktaking/plan/data.ts
new file mode 100644
index 0000000..cfbc882
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/data.ts
@@ -0,0 +1,472 @@
+import type { NumberDictDataType } from '../../../../packages/effects/hooks/src';
+
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+import type { MesWmStockTakingPlanParamApi } from '#/api/mes/wm/stocktaking/plan/param';
+
+import { h, markRaw } from 'vue';
+
+import {
+ CommonStatusEnum,
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ WlsStockTakingTypeEnum,
+} from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+
+import { StockTakingPlanConditionValueInput } from './components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鏂规缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ柟妗堢紪鐮�',
+ },
+ rules: 'required',
+ suffix:
+ formType === 'detail'
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_STOCK_TAKING_PLAN_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鏂规鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ柟妗堝悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'type',
+ label: '鐩樼偣绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'startTime',
+ label: '寮�濮嬫椂闂�',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ dependencies: {
+ triggerFields: ['type'],
+ show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+ },
+ },
+ {
+ fieldName: 'endTime',
+ label: '缁撴潫鏃堕棿',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ dependencies: {
+ triggerFields: ['type'],
+ show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+ },
+ },
+ {
+ fieldName: 'blindFlag',
+ label: '鏄惁鐩茬洏',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'frozen',
+ label: '鍐荤粨搴撳瓨',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鏂规缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ柟妗堢紪鐮�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鏂规鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ柟妗堝悕绉�',
+ },
+ },
+ {
+ fieldName: 'type',
+ label: '鐩樼偣绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(
+ onStatusChange?: (
+ newStatus: number,
+ row: MesWmStockTakingPlanApi.StockTakingPlan,
+ ) => Promise<boolean | undefined>,
+): VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鏂规缂栫爜',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鏂规鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'type',
+ title: '鐩樼偣绫诲瀷',
+ minWidth: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TYPE },
+ },
+ },
+ {
+ field: 'startTime',
+ title: '寮�濮嬫椂闂�',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'endTime',
+ title: '缁撴潫鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'blindFlag',
+ title: '鏄惁鐩茬洏',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'frozen',
+ title: '鏄惁鍐荤粨搴撳瓨',
+ width: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 120,
+ cellRender: {
+ attrs: { beforeChange: onStatusChange },
+ name: 'CellSwitch',
+ props: {
+ checkedValue: CommonStatusEnum.ENABLE,
+ unCheckedValue: CommonStatusEnum.DISABLE,
+ },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鐩樼偣鏂规鏉′欢鍒楄〃鐨勫瓧娈� */
+export function useParamGridColumns(
+ editable = true,
+): VxeTableGridOptions<MesWmStockTakingPlanParamApi.StockTakingPlanParam>['columns'] {
+ return [
+ {
+ field: 'type',
+ title: '鏉′欢绫诲瀷',
+ minWidth: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_PLAN_PARAM_TYPE },
+ },
+ },
+ {
+ field: 'valueCode',
+ title: '鏉′欢鍊肩紪鐮�',
+ minWidth: 140,
+ },
+ {
+ field: 'valueName',
+ title: '鏉′欢鍊煎悕绉�',
+ minWidth: 160,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鐩樼偣鏂规鏉′欢琛ㄥ崟 schema */
+export function useParamFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'type',
+ label: '鏉′欢绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(
+ DICT_TYPE.MES_WM_STOCK_TAKING_PLAN_PARAM_TYPE,
+ 'number',
+ ) as NumberDictDataType[],
+ placeholder: '璇烽�夋嫨鏉′欢绫诲瀷',
+ // 鏉′欢绫诲瀷鍙樺寲锛氭竻绌哄凡閫夋潯浠跺�硷紝閬垮厤娈嬬暀鏃х被鍨嬬殑鏉′欢鍊�
+ onChange: async () => {
+ await formApi?.setValues({
+ valueCode: '',
+ valueId: undefined,
+ valueName: '',
+ });
+ },
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'valueId',
+ label: '鏉′欢鍊�',
+ component: markRaw(StockTakingPlanConditionValueInput),
+ // 鏉′欢鍊兼帶浠跺唴閮ㄦ寜鏉′欢绫诲瀷鍒囨崲閫夋嫨鍣紝浠呴�夋嫨绫诲瀷鍚庡睍绀�
+ dependencies: {
+ triggerFields: ['type'],
+ if: (values) => values.type !== null,
+ componentProps: (values) => ({
+ type: values.type,
+ valueCode: values.valueCode,
+ // 鏉′欢鍊兼帶浠跺洖濉� valueId / valueCode / valueName
+ onValueChange: async (payload: {
+ valueCode?: string;
+ valueId?: number;
+ valueName?: string;
+ }) => {
+ await formApi?.setValues({
+ valueCode: payload.valueCode ?? '',
+ valueId: payload.valueId,
+ valueName: payload.valueName ?? '',
+ });
+ },
+ }),
+ },
+ },
+ {
+ // 鏉′欢鍊肩紪鐮侊細鐢辨潯浠跺�兼帶浠跺洖鍐欙紝闅愯棌瀛楁
+ fieldName: 'valueCode',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ // 鏉′欢鍊煎悕绉帮細鐢辨潯浠跺�兼帶浠跺洖鍐欙紝闅愯棌瀛楁
+ fieldName: 'valueName',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useSelectGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鏂规缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ柟妗堢紪鐮�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鏂规鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ柟妗堝悕绉�',
+ },
+ },
+ {
+ fieldName: 'type',
+ label: '鐩樼偣绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+ },
+ },
+ ];
+}
+
+/** 閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useSelectGridColumns(
+ multiple = true,
+): VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>['columns'] {
+ return [
+ {
+ type: multiple ? 'checkbox' : 'radio',
+ width: 50,
+ },
+ {
+ field: 'code',
+ title: '鏂规缂栫爜',
+ width: 200,
+ },
+ {
+ field: 'name',
+ title: '鏂规鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'type',
+ title: '鐩樼偣绫诲瀷',
+ width: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TYPE },
+ },
+ },
+ {
+ field: 'startTime',
+ title: '寮�濮嬫椂闂�',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'endTime',
+ title: '缁撴潫鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'blindFlag',
+ title: '鏄惁鐩茬洏',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'frozen',
+ title: '鏄惁鍐荤粨搴撳瓨',
+ width: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/stocktaking/plan/index.vue b/src/views/wls/stocktaking/plan/index.vue
new file mode 100644
index 0000000..637fd26
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/index.vue
@@ -0,0 +1,176 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { CommonStatusEnum } from '../../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteStockTakingPlan,
+ exportStockTakingPlan,
+ getStockTakingPlanPage,
+ updateStockTakingPlanStatus,
+} from '#/api/mes/wm/stocktaking/plan';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鐩樼偣鏂规 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鐩樼偣鏂规 */
+function handleDetail(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鐩樼偣鏂规 */
+function handleEdit(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鏇存柊鐩樼偣鏂规鐘舵�� */
+async function handleStatusChange(
+ newStatus: number,
+ row: MesWmStockTakingPlanApi.StockTakingPlan,
+): Promise<boolean | undefined> {
+ try {
+ await confirm(
+ `纭瑕�${newStatus === CommonStatusEnum.ENABLE ? '鍚敤' : '鍋滅敤'}"${row.name}"鐩樼偣鏂规鍚楋紵`,
+ );
+ } catch {
+ return false;
+ }
+ await updateStockTakingPlanStatus(row.id!, newStatus);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ return true;
+}
+
+/** 鍒犻櫎鐩樼偣鏂规 */
+async function handleDelete(row: MesWmStockTakingPlanApi.StockTakingPlan) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteStockTakingPlan(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportStockTakingPlan(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鐩樼偣鏂规.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(handleStatusChange),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getStockTakingPlanPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmStockTakingPlanApi.StockTakingPlan>,
+});
+
+const StatusEnum = CommonStatusEnum;
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鐩樼偣鏂规鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鐩樼偣鏂规']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-stock-taking-plan:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-stock-taking-plan:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-stock-taking-plan:update'],
+ disabled: row.status !== StatusEnum.DISABLE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-stock-taking-plan:delete'],
+ disabled: row.status !== StatusEnum.DISABLE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/stocktaking/plan/modules/form.vue b/src/views/wls/stocktaking/plan/modules/form.vue
new file mode 100644
index 0000000..0bcdb29
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/modules/form.vue
@@ -0,0 +1,126 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { Divider, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createStockTakingPlan,
+ getStockTakingPlan,
+ updateStockTakingPlan,
+} from '#/api/mes/wm/stocktaking/plan';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import ParamList from './param-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmStockTakingPlanApi.StockTakingPlan>();
+const isDetail = computed(() => formType.value === 'detail');
+const showParam = computed(
+ () =>
+ (formType.value === 'detail' || formType.value === 'update') &&
+ !!formData.value?.id,
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['鐩樼偣鏂规']);
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['鐩樼偣鏂规'])
+ : $t('ui.actionTitle.create', ['鐩樼偣鏂规']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (isDetail.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmStockTakingPlanApi.StockTakingPlan;
+ try {
+ if (formData.value?.id) {
+ await updateStockTakingPlan({ ...data, id: formData.value.id });
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } else {
+ // 鏂板鎴愬姛鍚庡垏鎹负缂栬緫鎬侊紝缁х画缁存姢鐩樼偣鍙傛暟
+ const id = await createStockTakingPlan(data);
+ formData.value = { ...data, id };
+ await formApi.setFieldValue('id', id);
+ formType.value = 'update';
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ }
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(formType.value === 'detail');
+ modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+ if (!data?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getStockTakingPlan(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 缂栬緫鎴栬鎯呮椂灞曠ず鐩樼偣鍙傛暟 -->
+ <template v-if="showParam">
+ <Divider>鐩樼偣鍙傛暟</Divider>
+ <div class="mx-4">
+ <ParamList :disabled="isDetail" :plan-id="formData!.id!" />
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/modules/param-form.vue b/src/views/wls/stocktaking/plan/modules/param-form.vue
new file mode 100644
index 0000000..2a810a1
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/modules/param-form.vue
@@ -0,0 +1,108 @@
+<script lang="ts" setup>
+import type { MesWmStockTakingPlanParamApi } from '#/api/mes/wm/stocktaking/plan/param';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { WlsStockTakingParamTypeEnum } from '../../../../../packages/constants/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createStockTakingPlanParam,
+ getStockTakingPlanParam,
+ updateStockTakingPlanParam,
+} from '#/api/mes/wm/stocktaking/plan/param';
+import { $t } from '#/locales';
+
+import { useParamFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formId = ref<number>(); // 褰撳墠缂栬緫鐨勬潯浠剁紪鍙�
+const planId = ref<number>(); // 鎵�灞炵洏鐐规柟妗堢紪鍙�
+
+const getTitle = computed(() =>
+ formId.value
+ ? $t('ui.actionTitle.edit', ['鐩樼偣鏉′欢'])
+ : $t('ui.actionTitle.create', ['鐩樼偣鏉′欢']),
+);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-1',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ const values =
+ (await formApi.getValues()) as MesWmStockTakingPlanParamApi.StockTakingPlanParam;
+ // 璐ㄩ噺鐘舵�佹牎楠� valueCode锛屽叾浣欑被鍨嬫牎楠� valueId
+ const valueValid =
+ values.type === WlsStockTakingParamTypeEnum.QUALITY_STATUS
+ ? !!values.valueCode
+ : values.valueId !== null;
+ if (!valueValid) {
+ message.warning('璇烽�夋嫨鏉′欢鍊�');
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = {
+ ...values,
+ id: formId.value,
+ planId: planId.value,
+ } as MesWmStockTakingPlanParamApi.StockTakingPlanParam;
+ try {
+ await (formId.value
+ ? updateStockTakingPlanParam(data)
+ : createStockTakingPlanParam(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formId.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useParamFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; planId: number }>();
+ planId.value = data.planId;
+ formId.value = data.id;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const param = await getStockTakingPlanParam(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(param);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/plan/modules/param-list.vue b/src/views/wls/stocktaking/plan/modules/param-list.vue
new file mode 100644
index 0000000..17900a6
--- /dev/null
+++ b/src/views/wls/stocktaking/plan/modules/param-list.vue
@@ -0,0 +1,131 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanParamApi } from '#/api/mes/wm/stocktaking/plan/param';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteStockTakingPlanParam,
+ getStockTakingPlanParamPage,
+} from '#/api/mes/wm/stocktaking/plan/param';
+import { $t } from '#/locales';
+
+import { useParamGridColumns } from '../data';
+import ParamForm from './param-form.vue';
+
+const props = defineProps<{
+ disabled: boolean; // 鏄惁鍙锛堣鎯呮�侊級
+ planId: number; // 鎵�灞炴柟妗堢紪鍙�
+}>();
+
+const [ParamFormModal, paramFormModalApi] = useVbenModal({
+ connectedComponent: ParamForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 娣诲姞鏉′欢 */
+function handleCreate() {
+ paramFormModalApi.setData({ planId: props.planId }).open();
+}
+
+/** 缂栬緫鏉′欢 */
+function handleEdit(row: MesWmStockTakingPlanParamApi.StockTakingPlanParam) {
+ paramFormModalApi.setData({ id: row.id, planId: props.planId }).open();
+}
+
+/** 鍒犻櫎鏉′欢 */
+async function handleDelete(
+ row: MesWmStockTakingPlanParamApi.StockTakingPlanParam,
+) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.valueName]),
+ duration: 0,
+ });
+ try {
+ await deleteStockTakingPlanParam(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.valueName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useParamGridColumns(!props.disabled),
+ height: 320,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.planId) {
+ return { list: [], total: 0 };
+ }
+ return await getStockTakingPlanParamPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ planId: props.planId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmStockTakingPlanParamApi.StockTakingPlanParam>,
+});
+</script>
+
+<template>
+ <div>
+ <ParamFormModal @success="handleRefresh" />
+ <Grid table-title="鐩樼偣鍙傛暟">
+ <template v-if="!disabled" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鏉′欢',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.valueName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/stocktaking/task/data.ts b/src/views/wls/stocktaking/task/data.ts
new file mode 100644
index 0000000..f16ff9d
--- /dev/null
+++ b/src/views/wls/stocktaking/task/data.ts
@@ -0,0 +1,600 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingPlanApi } from '#/api/mes/wm/stocktaking/plan';
+import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
+import type { MesWmStockTakingTaskLineApi } from '#/api/mes/wm/stocktaking/task/line';
+import type { MesWmStockTakingResultApi } from '#/api/mes/wm/stocktaking/task/result';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ WlsStockTakingTypeEnum,
+} from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSimpleUserList } from '#/api/system/user';
+import { getRangePickerDefaultProps } from '#/utils';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { StockTakingPlanSelect } from '#/views/wls/stocktaking/plan/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'execute' | 'submit' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟彁浜ゃ�佹墽琛岀洏鐐广�佽鎯呮�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return (
+ formType === 'detail' || formType === 'execute' || formType === 'submit'
+ );
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ const headerReadonly = isHeaderReadonly(formType);
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '浠诲姟缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ换鍔$紪鐮�',
+ },
+ rules: 'required',
+ suffix: headerReadonly
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_STOCK_TAKING_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '浠诲姟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ换鍔″悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'planId',
+ label: '鐩樼偣鏂规',
+ component: markRaw(StockTakingPlanSelect),
+ componentProps: {
+ // 閫夋嫨鐩樼偣鏂规鍚庯紝鑷姩甯﹀嚭鍚嶇О銆佺被鍨嬨�佽捣姝㈡椂闂村拰鐩茬洏/鍐荤粨閰嶇疆
+ onChange: async (plan?: MesWmStockTakingPlanApi.StockTakingPlan) => {
+ if (!plan) {
+ return;
+ }
+ await formApi?.setValues({
+ blindFlag: !!plan.blindFlag,
+ endTime: plan.endTime,
+ frozen: !!plan.frozen,
+ name: plan.name,
+ startTime: plan.startTime,
+ type: plan.type,
+ });
+ },
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'type',
+ label: '鐩樼偣绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'startTime',
+ label: '寮�濮嬫椂闂�',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨寮�濮嬫椂闂�',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ dependencies: {
+ triggerFields: ['type'],
+ show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+ },
+ },
+ {
+ fieldName: 'endTime',
+ label: '缁撴潫鏃堕棿',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '璇烽�夋嫨缁撴潫鏃堕棿',
+ showTime: true,
+ valueFormat: 'x',
+ },
+ dependencies: {
+ triggerFields: ['type'],
+ show: (values) => values.type === WlsStockTakingTypeEnum.DYNAMIC,
+ },
+ },
+ {
+ fieldName: 'takingDate',
+ label: '鐩樼偣鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鐩樼偣鏃ユ湡',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'blindFlag',
+ label: '鏄惁鐩茬洏',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'frozen',
+ label: '鏄惁鍐荤粨搴撳瓨',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'userId',
+ label: '鐩樼偣浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨鐩樼偣浜�',
+ valueField: 'id',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'type',
+ label: '鐩樼偣绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_STOCK_TAKING_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨鐩樼偣绫诲瀷',
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '浠诲姟缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヤ换鍔$紪鐮�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '浠诲姟鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヤ换鍔″悕绉�',
+ },
+ },
+ {
+ fieldName: 'takingDate',
+ label: '鐩樼偣鏃ユ湡',
+ component: 'RangePicker',
+ componentProps: {
+ ...getRangePickerDefaultProps(),
+ allowClear: true,
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(
+ DICT_TYPE.MES_WM_STOCK_TAKING_TASK_STATUS,
+ 'number',
+ ),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmStockTakingTaskApi.StockTakingTask>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '浠诲姟缂栫爜',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '浠诲姟鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'type',
+ title: '鐩樼偣绫诲瀷',
+ minWidth: 120,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TYPE },
+ },
+ },
+ {
+ field: 'planName',
+ title: '鐩樼偣鏂规',
+ minWidth: 180,
+ },
+ {
+ field: 'takingDate',
+ title: '鐩樼偣鏃ユ湡',
+ minWidth: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'userNickname',
+ title: '鐩樼偣浜�',
+ minWidth: 120,
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_TASK_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 280,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 鐩樼偣浠诲姟琛屽垪琛ㄧ殑瀛楁 */
+export function useLineGridColumns(
+ editable = true,
+): VxeTableGridOptions<MesWmStockTakingTaskLineApi.StockTakingTaskLine>['columns'] {
+ return [
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 140,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 90,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'quantity',
+ title: '鍦ㄥ簱鏁伴噺',
+ width: 120,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱',
+ minWidth: 120,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯',
+ minWidth: 120,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅',
+ minWidth: 120,
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_STOCK_TAKING_LINE_STATUS },
+ },
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 80,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鐩樼偣缁撴灉鍒楄〃鐨勫瓧娈� */
+export function useResultGridColumns(
+ editable = true,
+): VxeTableGridOptions<MesWmStockTakingResultApi.StockTakingResult>['columns'] {
+ return [
+ {
+ field: 'itemCode',
+ title: '浜у搧鐗╂枡缂栫爜',
+ minWidth: 140,
+ },
+ {
+ field: 'itemName',
+ title: '浜у搧鐗╂枡鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅鍚嶇О',
+ width: 90,
+ },
+ {
+ field: 'warehouseName',
+ title: '浠撳簱',
+ minWidth: 120,
+ },
+ {
+ field: 'locationName',
+ title: '搴撳尯',
+ minWidth: 120,
+ },
+ {
+ field: 'areaName',
+ title: '搴撲綅',
+ minWidth: 120,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ minWidth: 120,
+ },
+ {
+ field: 'takingQuantity',
+ title: '鐩樼偣鏁伴噺',
+ minWidth: 120,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 鐩樼偣缁撴灉鏂板/淇敼鐨勮〃鍗� */
+export function useResultFormSchema(
+ formApi?: VbenFormApi,
+ taskLines: MesWmStockTakingTaskLineApi.StockTakingTaskLine[] = [],
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'lineId',
+ label: '鐩樼偣娓呭崟',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ // 閫夋嫨鐩樼偣娓呭崟鍚庯紝鑷姩甯﹀嚭鐗╂枡銆佹壒娆″拰浠撳偍浣嶇疆淇℃伅
+ onChange: async (lineId?: number) => {
+ const line = taskLines.find((item) => item.id === lineId);
+ await formApi?.setValues({
+ areaId: line?.areaId,
+ batchCode: line?.batchCode,
+ batchId: line?.batchId,
+ itemId: line?.itemId,
+ locationId: line?.locationId,
+ materialStockId: line?.materialStockId,
+ warehouseId: line?.warehouseId,
+ });
+ },
+ options: taskLines.map((line) => ({
+ label: `${line.itemCode} - ${line.itemName} (${line.warehouseName}${
+ line.locationName ? ` / ${line.locationName}` : ''
+ }${line.areaName ? ` / ${line.areaName}` : ''})`,
+ value: line.id,
+ })),
+ placeholder: '璇烽�夋嫨鐩樼偣娓呭崟锛堝彲閫夛級',
+ },
+ formItemClass: 'col-span-3',
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ // 閫変腑鐩樼偣娓呭崟鍚庯紝鐗╂枡鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+ dependencies: {
+ triggerFields: ['lineId'],
+ disabled: (values) => values.lineId !== null,
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ壒娆$紪鐮�',
+ },
+ // 閫変腑鐩樼偣娓呭崟鍚庯紝鎵规鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+ dependencies: {
+ triggerFields: ['lineId'],
+ disabled: (values) => values.lineId !== null,
+ },
+ },
+ {
+ fieldName: 'takingQuantity',
+ label: '鐩樼偣鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ placeholder: '璇疯緭鍏ョ洏鐐规暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ // 浠撳簱鍙樺寲鏃舵竻绌哄簱鍖哄拰搴撲綅
+ onChange: () =>
+ formApi?.setValues({
+ areaId: undefined,
+ locationId: undefined,
+ }),
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'selectRequired',
+ // 閫変腑鐩樼偣娓呭崟鍚庯紝浠撳簱鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+ dependencies: {
+ triggerFields: ['lineId'],
+ disabled: (values) => values.lineId !== null,
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['warehouseId', 'lineId'],
+ show: (values) => !!values.warehouseId,
+ // 閫変腑鐩樼偣娓呭崟鍚庯紝搴撳尯鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+ disabled: (values) => values.lineId !== null,
+ componentProps: (values) => ({
+ onChange: () => formApi?.setFieldValue('areaId', undefined),
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.warehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['locationId', 'lineId'],
+ show: (values) => !!values.locationId,
+ // 閫変腑鐩樼偣娓呭崟鍚庯紝搴撲綅鐢辨竻鍗曞甫鍑轰笖绂佹鏀瑰姩
+ disabled: (values) => values.lineId !== null,
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/stocktaking/task/index.vue b/src/views/wls/stocktaking/task/index.vue
new file mode 100644
index 0000000..472ed33
--- /dev/null
+++ b/src/views/wls/stocktaking/task/index.vue
@@ -0,0 +1,201 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmStockTakingTaskStatusEnum } from '../../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelStockTaking,
+ deleteStockTaking,
+ exportStockTaking,
+ getStockTakingPage,
+} from '#/api/mes/wm/stocktaking/task';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓鐩樼偣浠诲姟 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅鐩樼偣浠诲姟 */
+function handleDetail(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫鐩樼偣浠诲姟 */
+function handleEdit(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎻愪氦鐩樼偣浠诲姟 */
+function handleSubmit(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ formModalApi.setData({ formType: 'submit', id: row.id }).open();
+}
+
+/** 鎵ц鐩樼偣 */
+function handleExecute(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ formModalApi.setData({ formType: 'execute', id: row.id }).open();
+}
+
+/** 鍙栨秷鐩樼偣浠诲姟 */
+async function handleCancel(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ await cancelStockTaking(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 鍒犻櫎鐩樼偣浠诲姟 */
+async function handleDelete(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteStockTaking(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportStockTaking(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '鐩樼偣浠诲姟.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getStockTakingPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmStockTakingTaskApi.StockTakingTask>,
+});
+
+const StatusEnum = MesWmStockTakingTaskStatusEnum;
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="鐩樼偣浠诲姟鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鐩樼偣浠诲姟']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-stock-taking-task:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-stock-taking-task:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-stock-taking-task:update'],
+ ifShow: row.status === StatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: '鎻愪氦',
+ type: 'link',
+ auth: ['mes:wm-stock-taking-task:update'],
+ ifShow: row.status === StatusEnum.PREPARE,
+ onClick: handleSubmit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-stock-taking-task:delete'],
+ ifShow: row.status === StatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鎵ц鐩樼偣',
+ type: 'link',
+ auth: ['mes:wm-stock-taking-task:update'],
+ ifShow: row.status === StatusEnum.APPROVING,
+ onClick: handleExecute.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-stock-taking-task:update'],
+ ifShow: row.status === StatusEnum.APPROVING,
+ popConfirm: {
+ title: '纭鍙栨秷璇ョ洏鐐逛换鍔★紵鍙栨秷鍚庝笉鍙仮澶嶃��',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/form.vue b/src/views/wls/stocktaking/task/modules/form.vue
new file mode 100644
index 0000000..48f190b
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/form.vue
@@ -0,0 +1,226 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { MesWmStockTakingTaskStatusEnum } from '../../../../../packages/constants/src';
+import { useUserStore } from '../../../../../packages/stores/src';
+
+import { Button, message, Popconfirm, Tabs } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createStockTaking,
+ finishStockTaking,
+ getStockTaking,
+ submitStockTaking,
+ updateStockTaking,
+} from '#/api/mes/wm/stocktaking/task';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+import ResultList from './result-list.vue';
+
+const emit = defineEmits(['success']);
+const userStore = useUserStore();
+const formType = ref<FormType>('create');
+const formData = ref<MesWmStockTakingTaskApi.StockTakingTask>();
+const subTabsName = ref('lines'); // 瀛愯〃褰撳墠 tab
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+
+const isEditable = computed(() =>
+ ['create', 'update'].includes(formType.value),
+);
+const isExecute = computed(() => formType.value === 'execute'); // 鏄惁鎵ц鐩樼偣妯″紡
+const isSubmit = computed(() => formType.value === 'submit'); // 鏄惁鎻愪氦妯″紡
+const canSubmit = computed(
+ () =>
+ formType.value === 'update' &&
+ formData.value?.status === MesWmStockTakingTaskStatusEnum.PREPARE,
+);
+const showLineTab = computed(() => !formData.value?.blindFlag); // 鐩茬洏涓嶅睍绀虹洏鐐规竻鍗�
+const showResultTab = computed(
+ () =>
+ isExecute.value ||
+ (!!formData.value?.status &&
+ formData.value.status !== MesWmStockTakingTaskStatusEnum.PREPARE),
+);
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['鐩樼偣浠诲姟']);
+ }
+ if (formType.value === 'execute') {
+ return '鎵ц鐩樼偣';
+ }
+ if (formType.value === 'submit') {
+ return '鎻愪氦鐩樼偣浠诲姟';
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['鐩樼偣浠诲姟'])
+ : $t('ui.actionTitle.create', ['鐩樼偣浠诲姟']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦鐩樼偣浠诲姟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data =
+ (await formApi.getValues()) as MesWmStockTakingTaskApi.StockTakingTask;
+ await updateStockTaking({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitStockTaking(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц鐩樼偣 */
+async function handleExecute() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishStockTaking(formData.value.id);
+ message.success('鎵ц鐩樼偣鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmStockTakingTaskApi.StockTakingTask;
+ try {
+ if (formData.value?.id) {
+ await updateStockTaking({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createStockTaking(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmStockTakingTaskStatusEnum.PREPARE,
+ };
+ await formApi.setFieldValue('id', id);
+ await formApi.setFieldValue('status', formData.value.status);
+ formType.value = 'update';
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ subTabsName.value = data.formType === 'execute' ? 'results' : 'lines';
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(!isEditable.value);
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getStockTaking(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else {
+ // 鏂板缓鏃堕粯璁ょ洏鐐逛汉涓哄綋鍓嶇櫥褰曠敤鎴�
+ await formApi.setFieldValue('userId', userStore.userInfo?.id);
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-4/5">
+ <Form class="mx-4" />
+ <Tabs
+ v-if="formData?.id"
+ v-model:active-key="subTabsName"
+ class="mx-4 mt-4"
+ type="card"
+ >
+ <Tabs.TabPane v-if="showLineTab" key="lines" tab="鐩樼偣娓呭崟">
+ <LineList :form-type="formType" :task-id="formData.id" />
+ </Tabs.TabPane>
+ <Tabs.TabPane v-if="showResultTab" key="results" tab="鐩樼偣缁撴灉">
+ <ResultList
+ :form-type="isExecute ? 'execute' : 'detail'"
+ :task-id="formData.id"
+ />
+ </Tabs.TabPane>
+ </Tabs>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit || isSubmit"
+ title="纭鎻愪氦璇ョ洏鐐逛换鍔★紵銆愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isExecute"
+ title="纭鎵ц鐩樼偣鎿嶄綔锛�"
+ @confirm="handleExecute"
+ >
+ <Button type="primary">鎵ц鐩樼偣</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/line-list.vue b/src/views/wls/stocktaking/task/modules/line-list.vue
new file mode 100644
index 0000000..ee1eee7
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/line-list.vue
@@ -0,0 +1,151 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmStockTakingTaskLineApi } from '#/api/mes/wm/stocktaking/task/line';
+
+import { computed, ref } from 'vue';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ createStockTakingTaskLine,
+ deleteStockTakingTaskLine,
+ getStockTakingTaskLinePage,
+} from '#/api/mes/wm/stocktaking/task/line';
+import { $t } from '#/locales';
+import { WmMaterialStockSelectDialog } from '#/views/wls/materialstock/components';
+
+import { useLineGridColumns } from '../data';
+
+const props = defineProps<{
+ formType: FormType;
+ taskId: number;
+}>();
+
+const isEditable = computed(() => props.formType === 'update'); // 浠呯紪杈戞�佸彲缁存姢鐩樼偣娓呭崟
+const dialogRef = ref<InstanceType<typeof WmMaterialStockSelectDialog>>();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鎵撳紑搴撳瓨閫夋嫨寮圭獥鎵归噺娣诲姞鐗╂枡 */
+function handleAdd() {
+ dialogRef.value?.open([], { multiple: true });
+}
+
+/** 搴撳瓨閫夋嫨纭鍥炶皟锛氬皢閫変腑鐨勫簱瀛樿褰曟壒閲忓垱寤轰负鐩樼偣琛� */
+async function handleStockSelected(
+ rows: MesWmMaterialStockApi.MaterialStock[],
+) {
+ if (rows.length === 0) {
+ return;
+ }
+ for (const stock of rows) {
+ await createStockTakingTaskLine({
+ areaId: stock.areaId,
+ batchId: stock.batchId,
+ itemId: stock.itemId,
+ locationId: stock.locationId,
+ materialStockId: stock.id,
+ quantity: stock.quantity,
+ taskId: props.taskId,
+ warehouseId: stock.warehouseId,
+ });
+ }
+ message.success(`鎴愬姛娣诲姞 ${rows.length} 鏉$洏鐐硅`);
+ handleRefresh();
+}
+
+/** 鍒犻櫎鐩樼偣琛� */
+async function handleDelete(
+ row: MesWmStockTakingTaskLineApi.StockTakingTaskLine,
+) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteStockTakingTaskLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value),
+ height: 360,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.taskId) {
+ return { list: [], total: 0 };
+ }
+ return await getStockTakingTaskLinePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ taskId: props.taskId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmStockTakingTaskLineApi.StockTakingTaskLine>,
+});
+
+defineExpose({ refresh: handleRefresh });
+</script>
+
+<template>
+ <div>
+ <WmMaterialStockSelectDialog
+ ref="dialogRef"
+ @selected="handleStockSelected"
+ />
+ <Grid table-title="鐩樼偣娓呭崟">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-stock-taking-task:update'],
+ onClick: handleAdd,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/result-form.vue b/src/views/wls/stocktaking/task/modules/result-form.vue
new file mode 100644
index 0000000..74d4bf5
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/result-form.vue
@@ -0,0 +1,106 @@
+<script lang="ts" setup>
+import type { MesWmStockTakingResultApi } from '#/api/mes/wm/stocktaking/task/result';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { getStockTakingTaskLineSimpleList } from '#/api/mes/wm/stocktaking/task/line';
+import {
+ createStockTakingResult,
+ getStockTakingResult,
+ updateStockTakingResult,
+} from '#/api/mes/wm/stocktaking/task/result';
+import { $t } from '#/locales';
+
+import { useResultFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmStockTakingResultApi.StockTakingResult>();
+const taskId = ref<number>(); // 鎵�灞炵洏鐐逛换鍔$紪鍙�
+const isExecute = ref(false); // 鏄惁鎵ц鐩樼偣妯″紡锛堝彲閫夋嫨鐩樼偣娓呭崟鍥炲~锛�
+const getTitle = computed(() =>
+ formData.value?.id
+ ? $t('ui.actionTitle.edit', ['鐩樼偣缁撴灉'])
+ : $t('ui.actionTitle.create', ['鐩樼偣缁撴灉']),
+);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 100,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmStockTakingResultApi.StockTakingResult;
+ data.taskId = taskId.value;
+ try {
+ await (formData.value?.id
+ ? updateStockTakingResult({ ...data, id: formData.value.id })
+ : createStockTakingResult(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ execute?: boolean;
+ id?: number;
+ taskId: number;
+ }>();
+ taskId.value = data.taskId;
+ isExecute.value = !!data.execute;
+ // 鎵ц鐩樼偣妯″紡锛氬姞杞界洏鐐规竻鍗曚綔涓哄彲閫夐」锛屼緵閫夋嫨鍚庡洖濉�
+ const taskLines =
+ isExecute.value && !data.id
+ ? await getStockTakingTaskLineSimpleList(data.taskId)
+ : [];
+ formApi.setState({ schema: useResultFormSchema(formApi, taskLines) });
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getStockTakingResult(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form />
+ </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/task/modules/result-list.vue b/src/views/wls/stocktaking/task/modules/result-list.vue
new file mode 100644
index 0000000..a686f42
--- /dev/null
+++ b/src/views/wls/stocktaking/task/modules/result-list.vue
@@ -0,0 +1,136 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockTakingResultApi } from '#/api/mes/wm/stocktaking/task/result';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteStockTakingResult,
+ getStockTakingResultPage,
+} from '#/api/mes/wm/stocktaking/task/result';
+import { $t } from '#/locales';
+
+import { useResultGridColumns } from '../data';
+import ResultForm from './result-form.vue';
+
+const props = defineProps<{
+ formType: 'detail' | 'execute';
+ taskId: number;
+}>();
+
+const isExecute = computed(() => props.formType === 'execute'); // 鎵ц鐩樼偣鎬佸彲缁存姢鐩樼偣缁撴灉
+
+const [ResultFormModal, resultFormModalApi] = useVbenModal({
+ connectedComponent: ResultForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鏂板鐩樼偣缁撴灉 */
+function handleCreate() {
+ resultFormModalApi.setData({ execute: true, taskId: props.taskId }).open();
+}
+
+/** 缂栬緫鐩樼偣缁撴灉 */
+function handleEdit(row: MesWmStockTakingResultApi.StockTakingResult) {
+ resultFormModalApi.setData({ id: row.id, taskId: props.taskId }).open();
+}
+
+/** 鍒犻櫎鐩樼偣缁撴灉 */
+async function handleDelete(row: MesWmStockTakingResultApi.StockTakingResult) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteStockTakingResult(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useResultGridColumns(isExecute.value),
+ height: 360,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }) => {
+ if (!props.taskId) {
+ return { list: [], total: 0 };
+ }
+ return await getStockTakingResultPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ taskId: props.taskId,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmStockTakingResultApi.StockTakingResult>,
+});
+
+defineExpose({ refresh: handleRefresh });
+</script>
+
+<template>
+ <div>
+ <ResultFormModal @success="handleRefresh" />
+ <Grid table-title="鐩樼偣缁撴灉">
+ <template v-if="isExecute" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.create'),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-stock-taking-task:update'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/transfer/data.ts b/src/views/wls/transfer/data.ts
new file mode 100644
index 0000000..994927c
--- /dev/null
+++ b/src/views/wls/transfer/data.ts
@@ -0,0 +1,647 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+import type { MesWmTransferApi } from '#/api/mes/wm/transfer';
+import type { MesWmTransferDetailApi } from '#/api/mes/wm/transfer/detail';
+import type { MesWmTransferLineApi } from '#/api/mes/wm/transfer/line';
+
+import { h, markRaw } from 'vue';
+
+import {
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+ WlsTransferTypeEnum,
+} from '../../../packages/constants/src';
+import { getDictOptions } from '../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType =
+ | 'confirm'
+ | 'create'
+ | 'detail'
+ | 'finish'
+ | 'stock'
+ | 'update';
+
+/** 琛ㄥご鏄惁鍙锛堜笂鏋躲�佺‘璁ゃ�佹墽琛屻�佽鎯呮�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return ['confirm', 'detail', 'finish', 'stock'].includes(formType);
+}
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ const headerReadonly = isHeaderReadonly(formType);
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'status',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '杞Щ鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ヨ浆绉诲崟缂栧彿',
+ },
+ rules: 'required',
+ suffix:
+ formType === 'create' || formType === 'update'
+ ? () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.TRANSFER_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ )
+ : undefined,
+ },
+ {
+ fieldName: 'name',
+ label: '杞Щ鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ヨ浆绉诲崟鍚嶇О',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'type',
+ label: '杞Щ鍗曠被鍨�',
+ component: 'Select',
+ componentProps: {
+ disabled: headerReadonly,
+ options: getDictOptions(DICT_TYPE.MES_WM_TRANSFER_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨杞Щ鍗曠被鍨�',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'transferDate',
+ label: '杞Щ鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ disabled: headerReadonly,
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨杞Щ鏃ユ湡',
+ valueFormat: 'YYYY-MM-DD 00:00:00',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'deliveryFlag',
+ label: '鏄惁閰嶉��',
+ component: 'Switch',
+ componentProps: {
+ disabled: headerReadonly,
+ },
+ rules: z.boolean().default(false),
+ dependencies: {
+ triggerFields: ['type'],
+ show: (values) => values.type === WlsTransferTypeEnum.OUTER,
+ },
+ },
+ {
+ fieldName: 'confirmFlag',
+ label: '鏄惁纭',
+ component: 'Switch',
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['type'],
+ show: (values) => values.type === WlsTransferTypeEnum.OUTER,
+ },
+ },
+ {
+ fieldName: 'recipientName',
+ label: '鏀惰揣浜�',
+ component: 'Input',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ユ敹璐т汉',
+ },
+ dependencies: {
+ triggerFields: ['type', 'deliveryFlag'],
+ show: (values) =>
+ values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+ },
+ },
+ {
+ fieldName: 'recipientTelephone',
+ label: '鑱旂郴鐢佃瘽',
+ component: 'Input',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ヨ仈绯荤數璇�',
+ },
+ dependencies: {
+ triggerFields: ['type', 'deliveryFlag'],
+ show: (values) =>
+ values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+ },
+ },
+ {
+ fieldName: 'carrier',
+ label: '鎵胯繍鍟�',
+ component: 'Input',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ユ壙杩愬晢',
+ },
+ dependencies: {
+ triggerFields: ['type', 'deliveryFlag'],
+ show: (values) =>
+ values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+ },
+ },
+ {
+ fieldName: 'shippingNumber',
+ label: '杩愯緭鍗曞彿',
+ component: 'Input',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ヨ繍杈撳崟鍙�',
+ },
+ dependencies: {
+ triggerFields: ['type', 'deliveryFlag'],
+ show: (values) =>
+ values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+ },
+ },
+ {
+ fieldName: 'destinationAddress',
+ label: '鐩殑鍦�',
+ component: 'Input',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ョ洰鐨勫湴',
+ },
+ dependencies: {
+ triggerFields: ['type', 'deliveryFlag'],
+ show: (values) =>
+ values.type === WlsTransferTypeEnum.OUTER && !!values.deliveryFlag,
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ disabled: headerReadonly,
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '杞Щ鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヨ浆绉诲崟缂栧彿',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '杞Щ鍗曞悕绉�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヨ浆绉诲崟鍚嶇О',
+ },
+ },
+ {
+ fieldName: 'type',
+ label: '杞Щ鍗曠被鍨�',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_TRANSFER_TYPE, 'number'),
+ placeholder: '璇烽�夋嫨杞Щ鍗曠被鍨�',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍗曟嵁鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_WM_TRANSFER_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmTransferApi.Transfer>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '杞Щ鍗曠紪鍙�',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '杞Щ鍗曞悕绉�',
+ minWidth: 160,
+ },
+ {
+ field: 'type',
+ title: '杞Щ鍗曠被鍨�',
+ minWidth: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_TRANSFER_TYPE },
+ },
+ },
+ {
+ field: 'deliveryFlag',
+ title: '鏄惁閰嶉��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'transferDate',
+ title: '杞Щ鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_WM_TRANSFER_STATUS },
+ },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 杞Щ鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable: boolean,
+ stockable: boolean,
+): VxeTableGridOptions<MesWmTransferLineApi.TransferLine>['columns'] {
+ return [
+ {
+ type: 'expand',
+ width: 48,
+ slots: { content: 'detail' },
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitMeasureName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'quantity',
+ title: '杞Щ鏁伴噺',
+ width: 100,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'fromWarehouseName',
+ title: '绉诲嚭浠撳簱',
+ minWidth: 120,
+ },
+ {
+ field: 'fromLocationName',
+ title: '绉诲嚭搴撳尯',
+ minWidth: 120,
+ },
+ {
+ field: 'fromAreaName',
+ title: '绉诲嚭搴撲綅',
+ minWidth: 120,
+ },
+ ...(editable || stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 180,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 杞Щ鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'quantityMax',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'batchId',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'materialStockId',
+ label: '閫夋嫨搴撳瓨',
+ component: markRaw(WmMaterialStockSelect),
+ componentProps: {
+ // 閫夋嫨搴撳瓨鍚庯紝鑷姩鍥炲~鐗╂枡/浠撳簱浣嶇疆/鎵规/鏁伴噺
+ onChange: async (stock?: MesWmMaterialStockApi.MaterialStock) => {
+ await formApi?.setValues({
+ batchCode: stock?.batchCode,
+ batchId: stock?.batchId,
+ fromAreaId: stock?.areaId,
+ fromLocationId: stock?.locationId,
+ fromWarehouseId: stock?.warehouseId,
+ itemId: stock?.itemId,
+ quantity: stock?.quantity,
+ quantityMax: stock?.quantity,
+ });
+ },
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '杞Щ鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヨ浆绉绘暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ dependencies: {
+ triggerFields: ['quantityMax'],
+ componentProps: (values) => ({
+ class: '!w-full',
+ max: values.quantityMax,
+ min: 0,
+ placeholder: '璇疯緭鍏ヨ浆绉绘暟閲�',
+ precision: 2,
+ }),
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'fromWarehouseId',
+ label: '绉诲嚭浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'fromLocationId',
+ label: '绉诲嚭搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['fromWarehouseId'],
+ componentProps: (values) => ({
+ disabled: true,
+ warehouseId: values.fromWarehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'fromAreaId',
+ label: '绉诲嚭搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ disabled: true,
+ },
+ dependencies: {
+ triggerFields: ['fromLocationId'],
+ componentProps: (values) => ({
+ disabled: true,
+ locationId: values.fromLocationId,
+ }),
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 璋冩嫧鏄庣粏瀛愯〃鐨勫瓧娈� */
+export function useDetailGridColumns(
+ stockable: boolean,
+): VxeTableGridOptions<MesWmTransferDetailApi.TransferDetail>['columns'] {
+ return [
+ {
+ field: 'toWarehouseName',
+ title: '绉诲叆浠撳簱',
+ minWidth: 100,
+ },
+ {
+ field: 'toLocationName',
+ title: '绉诲叆搴撳尯',
+ minWidth: 100,
+ },
+ {
+ field: 'toAreaName',
+ title: '绉诲叆搴撲綅',
+ minWidth: 100,
+ },
+ {
+ field: 'quantity',
+ title: '鏁伴噺',
+ width: 100,
+ },
+ ...(stockable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 120,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 璋冩嫧鏄庣粏鏂板/淇敼鐨勮〃鍗� */
+export function useDetailFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ disabled: true,
+ },
+ },
+ {
+ fieldName: 'toWarehouseId',
+ label: '绉诲叆浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨绉诲叆浠撳簱',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'toLocationId',
+ label: '绉诲叆搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨绉诲叆搴撳尯',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['toWarehouseId'],
+ componentProps: (values) => ({
+ placeholder: '璇烽�夋嫨绉诲叆搴撳尯',
+ warehouseId: values.toWarehouseId,
+ }),
+ },
+ },
+ {
+ fieldName: 'toAreaId',
+ label: '绉诲叆搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨绉诲叆搴撲綅',
+ },
+ rules: 'selectRequired',
+ dependencies: {
+ triggerFields: ['toLocationId'],
+ componentProps: (values) => ({
+ locationId: values.toLocationId,
+ placeholder: '璇烽�夋嫨绉诲叆搴撲綅',
+ }),
+ },
+ },
+ {
+ fieldName: 'quantity',
+ label: '鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ユ暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
diff --git a/src/views/wls/transfer/index.vue b/src/views/wls/transfer/index.vue
new file mode 100644
index 0000000..fba3efa
--- /dev/null
+++ b/src/views/wls/transfer/index.vue
@@ -0,0 +1,214 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmTransferApi } from '#/api/mes/wm/transfer';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { MesWmTransferStatusEnum } from '../../../packages/constants/src';
+import { downloadFileFromBlobPart } from '../../../packages/utils/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelTransfer,
+ deleteTransfer,
+ exportTransfer,
+ getTransferPage,
+} from '#/api/mes/wm/transfer';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓杞Щ鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅杞Щ鍗曡鎯� */
+function handleDetail(row: MesWmTransferApi.Transfer) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫杞Щ鍗� */
+function handleEdit(row: MesWmTransferApi.Transfer) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒拌揣纭 */
+function handleConfirm(row: MesWmTransferApi.Transfer) {
+ formModalApi.setData({ formType: 'confirm', id: row.id }).open();
+}
+
+/** 鎵ц涓婃灦 */
+function handleStock(row: MesWmTransferApi.Transfer) {
+ formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎵ц杞Щ */
+function handleFinish(row: MesWmTransferApi.Transfer) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鍒犻櫎杞Щ鍗� */
+async function handleDelete(row: MesWmTransferApi.Transfer) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteTransfer(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷杞Щ鍗� */
+async function handleCancel(row: MesWmTransferApi.Transfer) {
+ await cancelTransfer(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+ const data = await exportTransfer(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '杞Щ鍗�.xls', source: data });
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getTransferPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmTransferApi.Transfer>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+
+ <Grid table-title="杞Щ鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['杞Щ鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-transfer:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:wm-transfer:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-transfer:update'],
+ ifShow: row.status === MesWmTransferStatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-transfer:delete'],
+ ifShow: row.status === MesWmTransferStatusEnum.PREPARE,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.code]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鍒拌揣纭',
+ type: 'link',
+ auth: ['mes:wm-transfer:update'],
+ ifShow: row.status === MesWmTransferStatusEnum.UNCONFIRMED,
+ onClick: handleConfirm.bind(null, row),
+ },
+ {
+ label: '鎵ц涓婃灦',
+ type: 'link',
+ auth: ['mes:wm-transfer:update'],
+ ifShow: row.status === MesWmTransferStatusEnum.APPROVING,
+ onClick: handleStock.bind(null, row),
+ },
+ {
+ label: '鎵ц杞Щ',
+ type: 'link',
+ auth: ['mes:wm-transfer:finish'],
+ ifShow: row.status === MesWmTransferStatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm-transfer:update'],
+ ifShow:
+ row.status === MesWmTransferStatusEnum.UNCONFIRMED ||
+ row.status === MesWmTransferStatusEnum.APPROVING ||
+ row.status === MesWmTransferStatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ヨ浆绉诲崟锛熷彇娑堝悗涓嶅彲鎭㈠銆�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/transfer/modules/detail-form.vue b/src/views/wls/transfer/modules/detail-form.vue
new file mode 100644
index 0000000..8c2dbd6
--- /dev/null
+++ b/src/views/wls/transfer/modules/detail-form.vue
@@ -0,0 +1,103 @@
+<script lang="ts" setup>
+import type { MesWmTransferDetailApi } from '#/api/mes/wm/transfer/detail';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createTransferDetail,
+ getTransferDetail,
+ updateTransferDetail,
+} from '#/api/mes/wm/transfer/detail';
+import { $t } from '#/locales';
+
+import { useDetailFormSchema } from '../data';
+
+const emit = defineEmits<{ success: [lineId: number] }>();
+const formData = ref<MesWmTransferDetailApi.TransferDetail>();
+const transferId = ref<number>(); // 鎵�灞炶浆绉诲崟缂栧彿
+const lineId = ref<number>(); // 鎵�灞炶浆绉诲崟琛岀紪鍙�
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['璋冩嫧鏄庣粏'])
+ : $t('ui.actionTitle.create', ['璋冩嫧鏄庣粏']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-2',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: useDetailFormSchema(),
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmTransferDetailApi.TransferDetail;
+ data.transferId = transferId.value;
+ data.lineId = lineId.value;
+ try {
+ await (formData.value?.id
+ ? updateTransferDetail({ ...data, id: formData.value.id })
+ : createTransferDetail(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success', lineId.value!);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ detailId?: number;
+ itemId?: number;
+ lineId: number;
+ transferId: number;
+ }>();
+ transferId.value = data.transferId;
+ lineId.value = data.lineId;
+ if (data.detailId) {
+ modalApi.lock();
+ try {
+ formData.value = await getTransferDetail(data.detailId);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ } else if (data.itemId) {
+ await formApi.setFieldValue('itemId', data.itemId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-2/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/transfer/modules/detail-list.vue b/src/views/wls/transfer/modules/detail-list.vue
similarity index 100%
copy from src/views/mes/wm/transfer/modules/detail-list.vue
copy to src/views/wls/transfer/modules/detail-list.vue
diff --git a/src/views/wls/transfer/modules/form.vue b/src/views/wls/transfer/modules/form.vue
new file mode 100644
index 0000000..a433608
--- /dev/null
+++ b/src/views/wls/transfer/modules/form.vue
@@ -0,0 +1,258 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmTransferApi } from '#/api/mes/wm/transfer';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { MesWmTransferStatusEnum } from '../../../../packages/constants/src';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ confirmTransfer,
+ createTransfer,
+ finishTransfer,
+ getTransfer,
+ stockTransfer,
+ submitTransfer,
+ updateTransfer,
+} from '#/api/mes/wm/transfer';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+import LineList from './line-list.vue';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmTransferApi.Transfer>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isConfirm = computed(() => formType.value === 'confirm'); // 鏄惁涓哄埌璐х‘璁ゆā寮�
+const isStock = computed(() => formType.value === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛岃浆绉绘ā寮�
+const canSubmit = computed(
+ () =>
+ // 缂栬緫鎬佽崏绋垮彲鎻愪氦
+ formType.value === 'update' &&
+ formData.value?.status === MesWmTransferStatusEnum.PREPARE,
+);
+const getTitle = computed(() => {
+ switch (formType.value) {
+ case 'confirm': {
+ return '鍒拌揣纭';
+ }
+ case 'detail': {
+ return $t('ui.actionTitle.view', ['杞Щ鍗�']);
+ }
+ case 'finish': {
+ return '鎵ц杞Щ';
+ }
+ case 'stock': {
+ return '鎵ц涓婃灦';
+ }
+ case 'update': {
+ return $t('ui.actionTitle.edit', ['杞Щ鍗�']);
+ }
+ default: {
+ return $t('ui.actionTitle.create', ['杞Щ鍗�']);
+ }
+ }
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+/** 鎻愪氦杞Щ鍗曪細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶈皟鐢ㄦ彁浜ゆ帴鍙� */
+async function handleSubmit() {
+ const { valid } = await formApi.validate();
+ if (!valid || !formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ const current = JSON.stringify(await formApi.getValues());
+ if (current !== originalSnapshot.value) {
+ const data = (await formApi.getValues()) as MesWmTransferApi.Transfer;
+ await updateTransfer({ ...formData.value, ...data });
+ originalSnapshot.value = current;
+ }
+ await submitTransfer(formData.value.id);
+ message.success('鎻愪氦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鍒拌揣纭 */
+async function handleConfirm() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await confirmTransfer(formData.value.id);
+ message.success('纭鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц涓婃灦 */
+async function handleStock() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await stockTransfer(formData.value.id);
+ message.success('涓婃灦鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+/** 鎵ц杞Щ */
+async function handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishTransfer(formData.value.id);
+ message.success('鎵ц鎴愬姛');
+ await modalApi.close();
+ emit('success');
+ } finally {
+ modalApi.unlock();
+ }
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!isEditable.value) {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmTransferApi.Transfer;
+ try {
+ if (formData.value?.id) {
+ await updateTransfer({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createTransfer(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmTransferStatusEnum.PREPARE,
+ };
+ formType.value = 'update';
+ // 鍒涘缓鎴愬姛鍚庡垏鎹㈢紪杈戞�侊紝閲嶆寕 schema 骞跺洖濉富閿� / 鐘舵��
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ await formApi.setValues(formData.value);
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ originalSnapshot.value = '';
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ modalApi.setState({ showConfirmButton: isEditable.value });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getTransfer(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ }
+ originalSnapshot.value = JSON.stringify(await formApi.getValues());
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <!-- 闈炴柊寤烘ā寮忓睍绀虹墿鏂欎俊鎭� -->
+ <template v-if="formData?.id">
+ <Divider>鐗╂枡淇℃伅</Divider>
+ <div class="mx-4">
+ <LineList :form-type="formType" :transfer-id="formData.id" />
+ </div>
+ </template>
+ <template #prepend-footer>
+ <div class="flex flex-auto items-center gap-2">
+ <Popconfirm
+ v-if="canSubmit"
+ title="纭鎻愪氦璇ヨ浆绉诲崟锛熴�愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ @confirm="handleSubmit"
+ >
+ <Button type="primary">鎻愪氦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isConfirm"
+ title="纭鍒拌揣鍚庯紝灏嗚繘鍏ュ緟涓婃灦鐘舵�侊紝鏄惁缁х画锛�"
+ @confirm="handleConfirm"
+ >
+ <Button type="primary">鍒拌揣纭</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isStock"
+ title="纭鎵ц涓婃灦锛�"
+ @confirm="handleStock"
+ >
+ <Button type="primary">鎵ц涓婃灦</Button>
+ </Popconfirm>
+ <Popconfirm
+ v-if="isFinish"
+ title="纭鎵ц璋冩嫧锛熸墽琛屽悗灏嗘洿鏂板簱瀛樸��"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">鎵ц杞Щ</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
diff --git a/src/views/wls/transfer/modules/line-form.vue b/src/views/wls/transfer/modules/line-form.vue
new file mode 100644
index 0000000..95b14c6
--- /dev/null
+++ b/src/views/wls/transfer/modules/line-form.vue
@@ -0,0 +1,102 @@
+<script lang="ts" setup>
+import type { MesWmTransferLineApi } from '#/api/mes/wm/transfer/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { getMaterialStock } from '#/api/mes/wm/materialstock';
+import {
+ createTransferLine,
+ getTransferLine,
+ updateTransferLine,
+} from '#/api/mes/wm/transfer/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmTransferLineApi.TransferLine>();
+const transferId = ref<number>(); // 鎵�灞炶浆绉诲崟缂栧彿
+
+const getTitle = computed(() => {
+ return formData.value?.id
+ ? $t('ui.actionTitle.edit', ['璋冩嫧鐗╂枡'])
+ : $t('ui.actionTitle.create', ['璋冩嫧鐗╂枡']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 90,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-3',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmTransferLineApi.TransferLine;
+ data.transferId = transferId.value;
+ try {
+ await (formData.value?.id
+ ? updateTransferLine({ ...data, id: formData.value.id })
+ : createTransferLine(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ formApi.setState({ schema: useLineFormSchema(formApi) });
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ id?: number; transferId: number }>();
+ transferId.value = data.transferId;
+ if (!data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getTransferLine(data.id);
+ // 缂栬緫鏃舵寜褰撳墠搴撳瓨鍦ㄥ簱閲忎綔涓烘暟閲忎笂闄�
+ let quantityMax: number | undefined;
+ if (formData.value.materialStockId) {
+ const stock = await getMaterialStock(formData.value.materialStockId);
+ quantityMax = stock?.quantity;
+ }
+ // 璁剧疆鍒� values
+ await formApi.setValues({ ...formData.value, quantityMax });
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/wls/transfer/modules/line-list.vue b/src/views/wls/transfer/modules/line-list.vue
new file mode 100644
index 0000000..827f2c6
--- /dev/null
+++ b/src/views/wls/transfer/modules/line-list.vue
@@ -0,0 +1,228 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmTransferDetailApi } from '#/api/mes/wm/transfer/detail';
+import type { MesWmTransferLineApi } from '#/api/mes/wm/transfer/line';
+
+import { computed, reactive } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getTransferDetailListByLineId } from '#/api/mes/wm/transfer/detail';
+import {
+ deleteTransferLine,
+ getTransferLineList,
+} from '#/api/mes/wm/transfer/line';
+import { $t } from '#/locales';
+import { PrinterLabel } from '#/views/wls/barcode/components';
+
+import { useLineGridColumns } from '../data';
+import DetailForm from './detail-form.vue';
+import DetailList from './detail-list.vue';
+import LineForm from './line-form.vue';
+
+const props = defineProps<{
+ formType: FormType;
+ transferId: number;
+}>();
+
+const isEditable = computed(() =>
+ ['create', 'update'].includes(props.formType),
+); // 鏄惁鍙紪杈戣皟鎷ㄧ墿鏂欒
+const isStock = computed(() => props.formType === 'stock'); // 鏄惁涓轰笂鏋舵ā寮�
+const detailMap = reactive<
+ Record<number, MesWmTransferDetailApi.TransferDetail[]>
+>({}); // 宸插睍寮�琛岀殑璋冩嫧鏄庣粏缂撳瓨
+
+const [LineFormModal, lineFormModalApi] = useVbenModal({
+ connectedComponent: LineForm,
+ destroyOnClose: true,
+});
+
+const [DetailFormModal, detailFormModalApi] = useVbenModal({
+ connectedComponent: DetailForm,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ for (const id of Object.keys(detailMap)) {
+ delete detailMap[Number(id)];
+ }
+ gridApi.query();
+}
+
+/** 娣诲姞璋冩嫧鐗╂枡 */
+function handleCreate() {
+ lineFormModalApi.setData({ transferId: props.transferId }).open();
+}
+
+/** 缂栬緫璋冩嫧鐗╂枡 */
+function handleEdit(row: MesWmTransferLineApi.TransferLine) {
+ lineFormModalApi.setData({ id: row.id, transferId: props.transferId }).open();
+}
+
+/** 鍒犻櫎璋冩嫧鐗╂枡 */
+async function handleDelete(row: MesWmTransferLineApi.TransferLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.itemName]),
+ duration: 0,
+ });
+ try {
+ await deleteTransferLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.itemName]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 涓婃灦锛氱洿鎺ユ墦寮�鏄庣粏鏂板琛ㄥ崟 */
+function handleStock(row: MesWmTransferLineApi.TransferLine) {
+ openDetailForm(row.id!, row.itemId);
+}
+
+/** 鎵撳紑璋冩嫧鏄庣粏琛ㄥ崟 */
+function openDetailForm(lineId: number, itemId?: number, detailId?: number) {
+ detailFormModalApi
+ .setData({ detailId, itemId, lineId, transferId: props.transferId })
+ .open();
+}
+
+/** 鑾峰彇宸插睍寮�琛岀殑璋冩嫧鏄庣粏 */
+function getExpandedDetails(row: MesWmTransferLineApi.TransferLine) {
+ return detailMap[row.id!] || [];
+}
+
+/** 鍔犺浇鎸囧畾琛岀殑璋冩嫧鏄庣粏 */
+async function loadLineDetails(lineId: number) {
+ detailMap[lineId] = await getTransferDetailListByLineId(lineId);
+}
+
+/** 灞曞紑琛屾椂鎳掑姞杞借皟鎷ㄦ槑缁� */
+async function handleExpandChange(
+ row: MesWmTransferLineApi.TransferLine,
+ expanded: boolean,
+) {
+ if (!expanded) {
+ return;
+ }
+ await loadLineDetails(row.id!);
+}
+
+/** 鏄庣粏琛ㄥ崟鎻愪氦鎴愬姛鍚庯紝鍒锋柊瀵瑰簲琛屽凡灞曞紑鐨勬槑缁� */
+async function handleDetailSuccess(lineId: number) {
+ await loadLineDetails(lineId);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: useLineGridColumns(isEditable.value, isStock.value),
+ expandConfig: {
+ padding: true,
+ },
+ height: 400,
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async () => {
+ if (!props.transferId) {
+ return [];
+ }
+ return await getTransferLineList(props.transferId);
+ },
+ },
+ },
+ pagerConfig: {
+ enabled: false,
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmTransferLineApi.TransferLine>,
+ gridEvents: {
+ toggleRowExpand: ({
+ expanded,
+ row,
+ }: {
+ expanded: boolean;
+ row: MesWmTransferLineApi.TransferLine;
+ }) => {
+ handleExpandChange(row, expanded);
+ },
+ },
+});
+</script>
+
+<template>
+ <div>
+ <LineFormModal @success="handleRefresh" />
+ <DetailFormModal @success="handleDetailSuccess" />
+ <Grid table-title="鐗╂枡淇℃伅">
+ <template v-if="isEditable" #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '娣诲姞璋冩嫧鐗╂枡',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #detail="{ row }">
+ <DetailList
+ :details="getExpandedDetails(row)"
+ :form-type="formType"
+ @edit="(detailId) => openDetailForm(row.id!, row.itemId, detailId)"
+ @refresh="loadLineDetails(row.id!)"
+ />
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ ifShow: isEditable,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ ifShow: isEditable,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.itemName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '涓婃灦',
+ type: 'link',
+ ifShow: isStock,
+ onClick: handleStock.bind(null, row),
+ },
+ ]"
+ />
+ <PrinterLabel
+ v-if="isStock"
+ :biz-code="row.batchCode"
+ :biz-id="row.batchId"
+ biz-type="BATCH"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
diff --git a/src/views/wls/warehouse/area/data.ts b/src/views/wls/warehouse/area/data.ts
new file mode 100644
index 0000000..e73ead8
--- /dev/null
+++ b/src/views/wls/warehouse/area/data.ts
@@ -0,0 +1,338 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseAreaApi } from '#/api/mes/wm/warehouse/area';
+
+import { h } from 'vue';
+
+import {
+ CommonStatusEnum,
+ DICT_TYPE,
+ MesAutoCodeRuleCode,
+} from '../../../../packages/constants/src';
+import { getDictOptions } from '../../../../packages/effects/hooks/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+import { getWarehouseLocationSimpleList } from '#/api/mes/wm/warehouse/location';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鎵�灞炰粨搴�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getWarehouseSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨浠撳簱',
+ // 浠撳簱鍙樻洿鏃舵竻绌烘墍灞炲簱鍖�
+ onChange: () => formApi?.setFieldValue('locationId', undefined),
+ },
+ },
+ {
+ fieldName: 'locationId',
+ label: '鎵�灞炲簱鍖�',
+ component: 'ApiSelect',
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ componentProps: (values) => ({
+ allowClear: true,
+ api: () =>
+ getWarehouseLocationSimpleList(values.warehouseId as number),
+ // 鏀瑰彉 warehouseId 鏃跺己鍒跺埛鏂伴�夐」
+ params: { warehouseId: values.warehouseId },
+ disabled: !values.warehouseId,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨搴撳尯',
+ }),
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'code',
+ label: '搴撲綅缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�',
+ },
+ rules: 'required',
+ suffix:
+ formType === 'detail'
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_AREA_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鑷姩鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '搴撲綅鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ簱浣嶅悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'area',
+ label: '闈㈢Н锛堛帯锛�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ placeholder: '璇疯緭鍏ラ潰绉�',
+ },
+ },
+ {
+ fieldName: 'maxLoad',
+ label: '鏈�澶ц浇閲�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ placeholder: '璇疯緭鍏ユ渶澶ц浇閲�',
+ },
+ },
+ {
+ fieldName: 'positionX',
+ label: '浣嶇疆 X',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヤ綅缃� X',
+ },
+ },
+ {
+ fieldName: 'positionY',
+ label: '浣嶇疆 Y',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヤ綅缃� Y',
+ },
+ },
+ {
+ fieldName: 'positionZ',
+ label: '浣嶇疆 Z',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヤ綅缃� Z',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨',
+ },
+ rules: z.number().default(CommonStatusEnum.ENABLE),
+ },
+ {
+ fieldName: 'frozen',
+ label: '鏄惁鍐荤粨',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'allowItemMixing',
+ label: '鍏佽鐗╂枡娣锋斁',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(true),
+ },
+ {
+ fieldName: 'allowBatchMixing',
+ label: '鍏佽鎵规娣锋斁',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(true),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '搴撲綅缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ簱浣嶇紪鐮�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '搴撲綅鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ簱浣嶅悕绉�',
+ },
+ },
+ {
+ fieldName: 'positionX',
+ label: '浣嶇疆 X',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヤ綅缃� X',
+ },
+ },
+ {
+ fieldName: 'positionY',
+ label: '浣嶇疆 Y',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヤ綅缃� Y',
+ },
+ },
+ {
+ fieldName: 'positionZ',
+ label: '浣嶇疆 Z',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ placeholder: '璇疯緭鍏ヤ綅缃� Z',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmWarehouseAreaApi.WarehouseArea>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '搴撲綅缂栫爜',
+ minWidth: 140,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '搴撲綅鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'area',
+ title: '闈㈢Н锛堛帯锛�',
+ width: 100,
+ },
+ {
+ field: 'maxLoad',
+ title: '鏈�澶ц浇閲�',
+ width: 100,
+ },
+ {
+ field: 'positionX',
+ title: '浣嶇疆 X',
+ width: 80,
+ },
+ {
+ field: 'positionY',
+ title: '浣嶇疆 Y',
+ width: 80,
+ },
+ {
+ field: 'positionZ',
+ title: '浣嶇疆 Z',
+ width: 80,
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.COMMON_STATUS },
+ },
+ },
+ {
+ field: 'frozen',
+ title: '鍐荤粨',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 180,
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/wls/warehouse/area/index.vue b/src/views/wls/warehouse/area/index.vue
new file mode 100644
index 0000000..64b6950
--- /dev/null
+++ b/src/views/wls/warehouse/area/index.vue
@@ -0,0 +1,211 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseAreaApi } from '#/api/mes/wm/warehouse/area';
+
+import { ref } from 'vue';
+import { useRoute } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { Alert, Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteWarehouseArea,
+ getWarehouseAreaPage,
+} from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+
+import { BarcodeDetail } from '../../barcode/components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const route = useRoute();
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 褰撳墠搴撳尯涓婁笅鏂囷紙鍚屾浠� URL 瑙f瀽锛岀‘淇濋娆℃煡璇㈠甫寰椾笂 locationId锛� */
+const currentLocation = ref<{
+ id: number;
+ name: string;
+ warehouseId: number;
+ warehouseName: string;
+}>(
+ (() => {
+ const id = Number(route.query.locationId);
+ return Number.isInteger(id) && id > 0
+ ? { id, name: '', warehouseId: 0, warehouseName: '' }
+ : { id: 0, name: '', warehouseId: 0, warehouseName: '' };
+ })(),
+);
+
+/** 寮傛鍔犺浇搴撳尯/浠撳簱鍚嶇О锛堜笉闃诲鍒楄〃鏌ヨ锛� */
+async function loadLocationName() {
+ if (!currentLocation.value.id) {
+ return;
+ }
+ try {
+ const location = await getWarehouseLocation(currentLocation.value.id);
+ currentLocation.value.name = location.name || '';
+ currentLocation.value.warehouseId = location.warehouseId || 0;
+ currentLocation.value.warehouseName = location.warehouseName || '';
+ } catch {
+ // 蹇界暐涓婄骇鍚嶇О鍔犺浇寮傚父锛屼笉褰卞搷鍒楄〃鏌ヨ
+ }
+}
+loadLocationName();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓搴撲綅 */
+function handleCreate() {
+ formModalApi
+ .setData({
+ formType: 'create',
+ locationId: currentLocation.value.id || undefined,
+ warehouseId: currentLocation.value.warehouseId || undefined,
+ })
+ .open();
+}
+
+/** 鏌ョ湅搴撲綅璇︽儏 */
+function handleDetail(row: MesWmWarehouseAreaApi.WarehouseArea) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫搴撲綅 */
+function handleEdit(row: MesWmWarehouseAreaApi.WarehouseArea) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎搴撲綅 */
+async function handleDelete(row: MesWmWarehouseAreaApi.WarehouseArea) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteWarehouseArea(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅搴撲綅鏉$爜 */
+function handleBarcode(row: MesWmWarehouseAreaApi.WarehouseArea) {
+ barcodeDetailRef.value?.openByBusiness(
+ row.id!,
+ BarcodeBizTypeEnum.AREA,
+ row.code,
+ row.name,
+ );
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getWarehouseAreaPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ locationId: currentLocation.value.id || undefined,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmWarehouseAreaApi.WarehouseArea>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+ <BarcodeDetail ref="barcodeDetailRef" />
+
+ <Alert
+ v-if="currentLocation.id"
+ class="mb-3"
+ :message="`褰撳墠浠撳簱/搴撳尯锛�${currentLocation.warehouseName || `#${currentLocation.warehouseId || '-'}`} / ${currentLocation.name || `#${currentLocation.id}`}`"
+ show-icon
+ type="info"
+ />
+
+ <Grid table-title="搴撲綅鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['搴撲綅']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-warehouse:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-warehouse:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-warehouse:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鏉$爜',
+ type: 'link',
+ auth: ['mes:wm-warehouse:query'],
+ onClick: handleBarcode.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/warehouse/area/modules/form.vue b/src/views/wls/warehouse/area/modules/form.vue
new file mode 100644
index 0000000..5ae39ab
--- /dev/null
+++ b/src/views/wls/warehouse/area/modules/form.vue
@@ -0,0 +1,149 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmWarehouseAreaApi } from '#/api/mes/wm/warehouse/area';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createWarehouseArea,
+ getWarehouseArea,
+ updateWarehouseArea,
+} from '#/api/mes/wm/warehouse/area';
+import { getWarehouseLocation } from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmWarehouseAreaApi.WarehouseArea>();
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['搴撲綅']);
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['搴撲綅'])
+ : $t('ui.actionTitle.create', ['搴撲綅']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-3',
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+});
+
+/** 鏌ョ湅鏉$爜 */
+function handleBarcode() {
+ if (!formData.value?.id) {
+ return;
+ }
+ barcodeDetailRef.value?.openByBusiness(
+ formData.value.id,
+ BarcodeBizTypeEnum.AREA,
+ formData.value.code,
+ formData.value.name,
+ );
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (formType.value === 'detail') {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmWarehouseAreaApi.WarehouseArea;
+ try {
+ await (formData.value?.id
+ ? updateWarehouseArea(data)
+ : createWarehouseArea(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ formType: FormType;
+ id?: number;
+ locationId?: number;
+ warehouseId?: number;
+ }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(formType.value === 'detail');
+ modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getWarehouseArea(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ return;
+ }
+ // 鏂板鎬侊細鏍规嵁鐖剁骇涓婁笅鏂囧甫鍏ヤ粨搴�/搴撳尯
+ let warehouseId = data?.warehouseId;
+ if (data?.locationId) {
+ if (!warehouseId) {
+ try {
+ const location = await getWarehouseLocation(data.locationId);
+ warehouseId = location.warehouseId;
+ } catch {
+ // 蹇界暐
+ }
+ }
+ await formApi.setValues({
+ warehouseId,
+ locationId: data.locationId,
+ });
+ } else if (warehouseId) {
+ await formApi.setFieldValue('warehouseId', warehouseId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <template v-if="formType === 'detail' && formData?.id" #prepend-footer>
+ <Button @click="handleBarcode"> 鏌ョ湅鏉$爜 </Button>
+ </template>
+ <BarcodeDetail ref="barcodeDetailRef" />
+ </Modal>
+</template>
diff --git a/src/views/mes/wm/warehouse/components/area-select.vue b/src/views/wls/warehouse/components/area-select.vue
similarity index 100%
copy from src/views/mes/wm/warehouse/components/area-select.vue
copy to src/views/wls/warehouse/components/area-select.vue
diff --git a/src/views/mes/wm/warehouse/components/index.ts b/src/views/wls/warehouse/components/index.ts
similarity index 100%
copy from src/views/mes/wm/warehouse/components/index.ts
copy to src/views/wls/warehouse/components/index.ts
diff --git a/src/views/mes/wm/warehouse/components/location-select.vue b/src/views/wls/warehouse/components/location-select.vue
similarity index 100%
copy from src/views/mes/wm/warehouse/components/location-select.vue
copy to src/views/wls/warehouse/components/location-select.vue
diff --git a/src/views/mes/wm/warehouse/components/select.vue b/src/views/wls/warehouse/components/select.vue
similarity index 100%
copy from src/views/mes/wm/warehouse/components/select.vue
copy to src/views/wls/warehouse/components/select.vue
diff --git a/src/views/wls/warehouse/data.ts b/src/views/wls/warehouse/data.ts
new file mode 100644
index 0000000..aead660
--- /dev/null
+++ b/src/views/wls/warehouse/data.ts
@@ -0,0 +1,221 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+import type { SystemUserApi } from '#/api/system/user';
+
+import { h } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getSimpleUserList } from '#/api/system/user';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鍏宠仈鏁版嵁 */
+let userList: SystemUserApi.User[] = [];
+getSimpleUserList().then((data) => (userList = data));
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '浠撳簱缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ粨搴撶紪鐮�',
+ },
+ rules: 'required',
+ suffix:
+ formType === 'detail'
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_WAREHOUSE_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鑷姩鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '浠撳簱鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ粨搴撳悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'chargeUserId',
+ label: '璐熻矗浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨璐熻矗浜�',
+ },
+ },
+ {
+ fieldName: 'address',
+ label: '浠撳簱鍦板潃',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヤ粨搴撳湴鍧�',
+ },
+ },
+ {
+ fieldName: 'area',
+ label: '闈㈢Н锛堛帯锛�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ placeholder: '璇疯緭鍏ラ潰绉�',
+ },
+ },
+ {
+ fieldName: 'frozen',
+ label: '鏄惁鍐荤粨',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '浠撳簱缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヤ粨搴撶紪鐮�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '浠撳簱鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ヤ粨搴撳悕绉�',
+ },
+ },
+ {
+ fieldName: 'frozen',
+ label: '鏄惁鍐荤粨',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: [
+ { label: '鏄�', value: true },
+ { label: '鍚�', value: false },
+ ],
+ placeholder: '璇烽�夋嫨',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmWarehouseApi.Warehouse>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '浠撳簱缂栫爜',
+ minWidth: 140,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '浠撳簱鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'address',
+ title: '浠撳簱鍦板潃',
+ minWidth: 180,
+ },
+ {
+ field: 'area',
+ title: '闈㈢Н锛堛帯锛�',
+ width: 110,
+ },
+ {
+ field: 'chargeUserId',
+ title: '璐熻矗浜�',
+ width: 120,
+ formatter: ({ cellValue }) =>
+ userList.find((user) => user.id === cellValue)?.nickname ?? '',
+ },
+ {
+ field: 'frozen',
+ title: '鍐荤粨',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 180,
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 220,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/wls/warehouse/index.vue b/src/views/wls/warehouse/index.vue
new file mode 100644
index 0000000..9edfabd
--- /dev/null
+++ b/src/views/wls/warehouse/index.vue
@@ -0,0 +1,175 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { deleteWarehouse, getWarehousePage } from '#/api/mes/wm/warehouse';
+import { $t } from '#/locales';
+
+import { BarcodeDetail } from '../barcode/components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓浠撳簱 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅浠撳簱璇︽儏 */
+function handleDetail(row: MesWmWarehouseApi.Warehouse) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫浠撳簱 */
+function handleEdit(row: MesWmWarehouseApi.Warehouse) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎浠撳簱 */
+async function handleDelete(row: MesWmWarehouseApi.Warehouse) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteWarehouse(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 璺宠浆搴撳尯鍒楄〃 */
+function handleOpenLocation(row: MesWmWarehouseApi.Warehouse) {
+ router.push({
+ name: 'WlsLocation',
+ query: { warehouseId: String(row.id) },
+ });
+}
+
+/** 鏌ョ湅浠撳簱鏉$爜 */
+function handleBarcode(row: MesWmWarehouseApi.Warehouse) {
+ barcodeDetailRef.value?.openByBusiness(
+ row.id!,
+ BarcodeBizTypeEnum.WAREHOUSE,
+ row.code,
+ row.name,
+ );
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getWarehousePage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmWarehouseApi.Warehouse>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+ <BarcodeDetail ref="barcodeDetailRef" />
+
+ <Grid table-title="浠撳簱鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['浠撳簱']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-warehouse:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '搴撳尯',
+ type: 'link',
+ auth: ['mes:wm-warehouse:query'],
+ onClick: handleOpenLocation.bind(null, row),
+ },
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-warehouse:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-warehouse:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鏉$爜',
+ type: 'link',
+ auth: ['mes:wm-warehouse:query'],
+ onClick: handleBarcode.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/warehouse/location/data.ts b/src/views/wls/warehouse/location/data.ts
new file mode 100644
index 0000000..44652ff
--- /dev/null
+++ b/src/views/wls/warehouse/location/data.ts
@@ -0,0 +1,193 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+import type { MesWmWarehouseLocationApi } from '#/api/mes/wm/warehouse/location';
+
+import { h } from 'vue';
+
+import { DICT_TYPE, MesAutoCodeRuleCode } from '../../../../packages/constants/src';
+
+import { Button } from 'ant-design-vue';
+
+import { z } from '#/adapter/form';
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鍏宠仈鏁版嵁 */
+let warehouseList: MesWmWarehouseApi.Warehouse[] = [];
+getWarehouseSimpleList().then((data) => (warehouseList = data));
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(
+ formType: FormType,
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: { triggerFields: [''], show: () => false },
+ },
+ {
+ fieldName: 'code',
+ label: '搴撳尯缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ簱鍖虹紪鐮�',
+ },
+ rules: 'required',
+ suffix:
+ formType === 'detail'
+ ? undefined
+ : () =>
+ h(
+ Button,
+ {
+ type: 'default',
+ onClick: async () => {
+ const code = await generateAutoCode(
+ MesAutoCodeRuleCode.WM_LOCATION_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鑷姩鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '搴撳尯鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '鎵�灞炰粨搴�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getWarehouseSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'area',
+ label: '闈㈢Н锛堛帯锛�',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0,
+ precision: 2,
+ placeholder: '璇疯緭鍏ラ潰绉�',
+ },
+ },
+ {
+ fieldName: 'frozen',
+ label: '鏄惁鍐荤粨',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '搴撳尯缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ簱鍖虹紪鐮�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '搴撳尯鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ簱鍖哄悕绉�',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmWarehouseLocationApi.WarehouseLocation>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '搴撳尯缂栫爜',
+ minWidth: 140,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '搴撳尯鍚嶇О',
+ minWidth: 160,
+ },
+ {
+ field: 'warehouseId',
+ title: '鎵�灞炰粨搴�',
+ minWidth: 140,
+ formatter: ({ cellValue }) =>
+ warehouseList.find((w) => w.id === cellValue)?.name ?? '',
+ },
+ {
+ field: 'area',
+ title: '闈㈢Н锛堛帯锛�',
+ width: 110,
+ },
+ {
+ field: 'frozen',
+ title: '鍐荤粨',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 180,
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/src/views/wls/warehouse/location/index.vue b/src/views/wls/warehouse/location/index.vue
new file mode 100644
index 0000000..9de6261
--- /dev/null
+++ b/src/views/wls/warehouse/location/index.vue
@@ -0,0 +1,218 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmWarehouseLocationApi } from '#/api/mes/wm/warehouse/location';
+
+import { ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { Alert, Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getWarehouse } from '#/api/mes/wm/warehouse';
+import {
+ deleteWarehouseLocation,
+ getWarehouseLocationPage,
+} from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+
+import { BarcodeDetail } from '../../barcode/components';
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const route = useRoute();
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+/** 褰撳墠浠撳簱涓婁笅鏂囷紙鍚屾浠� URL 瑙f瀽锛岀‘淇濋娆℃煡璇㈠甫寰椾笂 warehouseId锛� */
+const currentWarehouse = ref<{ id: number; name: string }>(
+ (() => {
+ const id = Number(route.query.warehouseId);
+ return Number.isInteger(id) && id > 0
+ ? { id, name: '' }
+ : { id: 0, name: '' };
+ })(),
+);
+
+/** 寮傛鍔犺浇浠撳簱鍚嶇О锛堜笉闃诲鍒楄〃鏌ヨ锛� */
+async function loadWarehouseName() {
+ if (!currentWarehouse.value.id) {
+ return;
+ }
+ try {
+ const warehouse = await getWarehouse(currentWarehouse.value.id);
+ currentWarehouse.value.name = warehouse.name || '';
+ } catch {
+ // 蹇界暐涓婄骇鍚嶇О鍔犺浇寮傚父锛屼笉褰卞搷鍒楄〃鏌ヨ
+ }
+}
+loadWarehouseName();
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓搴撳尯 */
+function handleCreate() {
+ formModalApi
+ .setData({
+ formType: 'create',
+ warehouseId: currentWarehouse.value.id || undefined,
+ })
+ .open();
+}
+
+/** 鏌ョ湅搴撳尯璇︽儏 */
+function handleDetail(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫搴撳尯 */
+function handleEdit(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鍒犻櫎搴撳尯 */
+async function handleDelete(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteWarehouseLocation(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 璺宠浆搴撲綅鍒楄〃 */
+function handleOpenArea(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+ router.push({
+ name: 'WlsArea',
+ query: { locationId: String(row.id) },
+ });
+}
+
+/** 鏌ョ湅搴撳尯鏉$爜 */
+function handleBarcode(row: MesWmWarehouseLocationApi.WarehouseLocation) {
+ barcodeDetailRef.value?.openByBusiness(
+ row.id!,
+ BarcodeBizTypeEnum.LOCATION,
+ row.code,
+ row.name,
+ );
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getWarehouseLocationPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ warehouseId: currentWarehouse.value.id || undefined,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmWarehouseLocationApi.WarehouseLocation>,
+});
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+ <BarcodeDetail ref="barcodeDetailRef" />
+
+ <Alert
+ v-if="currentWarehouse.id"
+ class="mb-3"
+ :message="`褰撳墠浠撳簱锛�${currentWarehouse.name || `#${currentWarehouse.id}`}`"
+ show-icon
+ type="info"
+ />
+
+ <Grid table-title="搴撳尯鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['搴撳尯']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-warehouse:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '搴撲綅',
+ type: 'link',
+ auth: ['mes:wm-warehouse:query'],
+ onClick: handleOpenArea.bind(null, row),
+ },
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm-warehouse:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-warehouse:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: '鏉$爜',
+ type: 'link',
+ auth: ['mes:wm-warehouse:query'],
+ onClick: handleBarcode.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/wls/warehouse/location/modules/form.vue b/src/views/wls/warehouse/location/modules/form.vue
new file mode 100644
index 0000000..430edcc
--- /dev/null
+++ b/src/views/wls/warehouse/location/modules/form.vue
@@ -0,0 +1,184 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmWarehouseLocationApi } from '#/api/mes/wm/warehouse/location';
+
+import { computed, ref } from 'vue';
+
+import { confirm, useVbenModal } from '../../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createWarehouseLocation,
+ getWarehouseLocation,
+ updateAreaByLocationId,
+ updateWarehouseLocation,
+} from '#/api/mes/wm/warehouse/location';
+import { $t } from '#/locales';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmWarehouseLocationApi.WarehouseLocation>();
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['搴撳尯']);
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['搴撳尯'])
+ : $t('ui.actionTitle.create', ['搴撳尯']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ wrapperClass: 'grid-cols-3',
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+});
+
+/** 鏌ョ湅鏉$爜 */
+function handleBarcode() {
+ if (!formData.value?.id) {
+ return;
+ }
+ barcodeDetailRef.value?.openByBusiness(
+ formData.value.id,
+ BarcodeBizTypeEnum.LOCATION,
+ formData.value.code,
+ formData.value.name,
+ );
+}
+
+/** 鎵归噺璁剧疆搴撲綅娣锋斁瑙勫垯 */
+async function setMixingRule(type: 'batch' | 'item', allow: boolean) {
+ if (!formData.value?.id) {
+ return;
+ }
+ const text = type === 'item' ? '浜у搧娣锋斁' : '鎵规娣锋斁';
+ try {
+ await confirm(`纭瑕侀噸缃簱鍖轰笅鎵�鏈夊簱浣嶇殑${text}瑙勫垯鍚楋紵`);
+ } catch {
+ return;
+ }
+ await updateAreaByLocationId(
+ formData.value.id,
+ type === 'item' ? allow : undefined,
+ type === 'batch' ? allow : undefined,
+ );
+ message.success('璁剧疆鎴愬姛');
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (formType.value === 'detail') {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data =
+ (await formApi.getValues()) as MesWmWarehouseLocationApi.WarehouseLocation;
+ try {
+ await (formData.value?.id
+ ? updateWarehouseLocation(data)
+ : createWarehouseLocation(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{
+ formType: FormType;
+ id?: number;
+ warehouseId?: number;
+ }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(formType.value === 'detail');
+ modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+ if (data?.id) {
+ modalApi.lock();
+ try {
+ formData.value = await getWarehouseLocation(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ return;
+ }
+ if (data?.warehouseId) {
+ await formApi.setFieldValue('warehouseId', data.warehouseId);
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <div
+ v-if="formType === 'update' && formData?.id"
+ class="mx-4 mt-2 border-t pt-4"
+ >
+ <div class="mb-2 font-medium">搴撲綅娣锋斁瑙勫垯锛堟壒閲忚缃級</div>
+ <div class="flex flex-wrap items-center gap-3">
+ <span class="w-20">浜у搧娣锋斁</span>
+ <Button
+ size="small"
+ type="primary"
+ @click="setMixingRule('item', true)"
+ >
+ 鍏佽
+ </Button>
+ <Button size="small" danger @click="setMixingRule('item', false)">
+ 涓嶅厑璁�
+ </Button>
+ </div>
+ <div class="mt-2 flex flex-wrap items-center gap-3">
+ <span class="w-20">鎵规娣锋斁</span>
+ <Button
+ size="small"
+ type="primary"
+ @click="setMixingRule('batch', true)"
+ >
+ 鍏佽
+ </Button>
+ <Button size="small" danger @click="setMixingRule('batch', false)">
+ 涓嶅厑璁�
+ </Button>
+ </div>
+ </div>
+ <template v-if="formType === 'detail' && formData?.id" #prepend-footer>
+ <Button @click="handleBarcode"> 鏌ョ湅鏉$爜 </Button>
+ </template>
+ <BarcodeDetail ref="barcodeDetailRef" />
+ </Modal>
+</template>
diff --git a/src/views/wls/warehouse/modules/form.vue b/src/views/wls/warehouse/modules/form.vue
new file mode 100644
index 0000000..b20c558
--- /dev/null
+++ b/src/views/wls/warehouse/modules/form.vue
@@ -0,0 +1,124 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmWarehouseApi } from '#/api/mes/wm/warehouse';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { BarcodeBizTypeEnum } from '../../../../packages/constants/src';
+
+import { Button, message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createWarehouse,
+ getWarehouse,
+ updateWarehouse,
+} from '#/api/mes/wm/warehouse';
+import { $t } from '#/locales';
+import { BarcodeDetail } from '#/views/wls/barcode/components';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesWmWarehouseApi.Warehouse>();
+const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return $t('ui.actionTitle.view', ['浠撳簱']);
+ }
+ return formType.value === 'update'
+ ? $t('ui.actionTitle.edit', ['浠撳簱'])
+ : $t('ui.actionTitle.create', ['浠撳簱']);
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ wrapperClass: 'grid-cols-3',
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+});
+
+/** 鏌ョ湅鏉$爜 */
+function handleBarcode() {
+ if (!formData.value?.id) {
+ return;
+ }
+ barcodeDetailRef.value?.openByBusiness(
+ formData.value.id,
+ BarcodeBizTypeEnum.WAREHOUSE,
+ formData.value.code,
+ formData.value.name,
+ );
+}
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (formType.value === 'detail') {
+ await modalApi.close();
+ return;
+ }
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ // 鎻愪氦琛ㄥ崟
+ const data = (await formApi.getValues()) as MesWmWarehouseApi.Warehouse;
+ try {
+ await (formData.value?.id
+ ? updateWarehouse(data)
+ : createWarehouse(data));
+ // 鍏抽棴骞舵彁绀�
+ await modalApi.close();
+ emit('success');
+ message.success($t('ui.actionMessage.operationSuccess'));
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ formData.value = undefined;
+ return;
+ }
+ // 鍔犺浇鏁版嵁
+ const data = modalApi.getData<{ formType: FormType; id?: number }>();
+ formType.value = data.formType;
+ formApi.setState({ schema: useFormSchema(formType.value, formApi) });
+ formApi.setDisabled(formType.value === 'detail');
+ modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+ if (!data?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getWarehouse(data.id);
+ // 璁剧疆鍒� values
+ await formApi.setValues(formData.value);
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-3/5">
+ <Form class="mx-4" />
+ <template v-if="formType === 'detail' && formData?.id" #prepend-footer>
+ <Button @click="handleBarcode"> 鏌ョ湅鏉$爜 </Button>
+ </template>
+ <BarcodeDetail ref="barcodeDetailRef" />
+ </Modal>
+</template>
diff --git a/vite.config.ts b/vite.config.ts
index 97be055..22580b3 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -16,7 +16,7 @@
"/admin-api": {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/admin-api/, ""),
- target: "http://192.168.0.244:48080/admin-api",
+ target: "http://192.168.0.226:48080/admin-api",
ws: true,
},
},
--
Gitblit v1.9.3