From 1c1af9b0fc10778ae5ac13cc68fd4030affbcfe1 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 09 九月 2026 10:59:53 +0800
Subject: [PATCH] feat(wm): 新增SN码管理和设备入库功能
---
src/api/mes/wm/returnvendor/index.ts | 19
src/views/hrm/training/modules/form.vue | 96 +
src/views/wls/stocktaking/plan/data.ts | 49
src/views/wls/sn/data.ts | 289 ++++
src/views/hrm/training/data.ts | 243 +++
src/views/wls/equipmentreceipt/modules/line-list.vue | 142 +
src/views/wls/transaction/data.ts | 5
src/views/hrm/training/modules/participants.vue | 278 +++
src/api/hrm/work-hour-summary/index.ts | 36
src/views/wls/returnvendor/modules/form.vue | 38
src/views/wls/stocktaking/task/modules/form.vue | 58
src/api/hrm/training/index.ts | 141 +
src/api/mes/dv/machinery/index.ts | 1
src/api/mes/wm/equipmentreceipt/index.ts | 81 +
src/views/wls/stocktaking/task/index.vue | 75
src/views/wls/returnvendor/index.vue | 64
src/views/wls/sn/index.vue | 298 +++
src/views/wls/sn/modules/in-stock-modal.vue | 62
src/views/wls/sn/modules/out-stock-modal.vue | 62
src/api/mes/wm/miscissue/index.ts | 19
src/api/mes/wm/equipmentreceipt/line/index.ts | 55
src/views/hrm/training/index.vue | 263 +++
src/api/mes/wm/stocktaking/plan/index.ts | 3
src/views/wls/barcode/components/index.ts | 1
src/views/wls/miscreceipt/modules/form.vue | 38
src/api/mes/wm/stocktaking/task/index.ts | 21
src/views/wls/barcode/components/scan-modal.vue | 178 ++
src/views/hrm/workHourSummary/data.ts | 107 +
src/views/wls/equipmentreceipt/modules/line-form.vue | 98 +
src/packages/constants/src/biz-mes-enum.ts | 18
src/views/wls/miscissue/modules/form.vue | 37
src/views/wls/sn/location-maps.ts | 51
src/views/wls/sn/modules/sn-detail-modal.vue | 118 +
src/api/mes/wm/sn/index.ts | 78 +
src/components/table-action/icons.ts | 1
src/views/wls/miscissue/index.vue | 64
src/views/wls/barcode/index.vue | 16
src/views/wls/miscreceipt/index.vue | 64
src/views/wls/equipmentreceipt/data.ts | 342 ++++
src/views/wls/stocktaking/task/data.ts | 8
src/views/hrm/workHourSummary/index.vue | 128 +
src/api/mes/wm/miscreceipt/index.ts | 19
src/views/wls/equipmentreceipt/modules/form.vue | 163 ++
src/views/wls/components/process-select-modal.vue | 83 +
src/api/mes/wm/barcode/index.ts | 29
src/views/wls/equipmentreceipt/index.vue | 243 +++
46 files changed, 4,020 insertions(+), 262 deletions(-)
diff --git a/src/api/hrm/training/index.ts b/src/api/hrm/training/index.ts
new file mode 100644
index 0000000..51c9a8e
--- /dev/null
+++ b/src/api/hrm/training/index.ts
@@ -0,0 +1,141 @@
+import type { PageParam, PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace HrmTrainingApi {
+ /** 鍩硅鍙傝浜哄憳 */
+ export interface Participant {
+ id?: number;
+ trainingId?: number;
+ employeeId?: number;
+ employeeNo?: string;
+ employeeName?: string;
+ attended?: boolean;
+ score?: number;
+ isPass?: boolean;
+ certificateId?: number;
+ remark?: string;
+ createTime?: string;
+ }
+
+ /** 鍩硅娲诲姩 */
+ export interface Training {
+ id?: number;
+ trainingNo?: string;
+ name?: string;
+ trainingType?: number;
+ trainingTypeName?: string;
+ trainer?: string;
+ location?: string;
+ hours?: number;
+ startDate?: string;
+ endDate?: string;
+ issueCertificate?: boolean;
+ certValidityMonths?: number;
+ status?: number;
+ statusName?: string;
+ participantCount?: number;
+ participants?: Participant[];
+ remark?: string;
+ createTime?: string;
+ }
+
+ /** 鍩硅娲诲姩鏌ヨ鍙傛暟 */
+ export interface TrainingPageParam extends PageParam {
+ trainingNo?: string;
+ name?: string;
+ trainingType?: number;
+ status?: number;
+ }
+}
+
+/** 鏌ヨ鍩硅娲诲姩鍒嗛〉 */
+export function getTrainingPage(params: HrmTrainingApi.TrainingPageParam) {
+ return requestClient.get<PageResult<HrmTrainingApi.Training>>(
+ '/hrm/training/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ鍩硅娲诲姩璇︽儏锛堝惈鍙傝浜哄憳锛� */
+export function getTraining(id: number) {
+ return requestClient.get<HrmTrainingApi.Training>(`/hrm/training/get?id=${id}`);
+}
+
+/** 鍒涘缓鍩硅娲诲姩 */
+export function createTraining(data: ReqTraining) {
+ return requestClient.post('/hrm/training/create', data);
+}
+
+/** 鏇存柊鍩硅娲诲姩 */
+export function updateTraining(data: ReqTraining) {
+ return requestClient.put('/hrm/training/update', data);
+}
+
+/** 鍒犻櫎鍩硅娲诲姩 */
+export function deleteTraining(ids: number[]) {
+ return requestClient.delete('/hrm/training/delete', { params: { ids: ids.join(',') } });
+}
+
+/** 寮�濮嬪煿璁紙鑽夌 鈫� 寰呭紑灞曪級 */
+export function startTraining(id: number) {
+ return requestClient.post('/hrm/training/start', null, { params: { id } });
+}
+
+/** 瀹屾垚鍩硅锛堣崏绋�/寰呭紑灞� 鈫� 宸插畬鎴愶級 */
+export function completeTraining(id: number) {
+ return requestClient.post('/hrm/training/complete', null, { params: { id } });
+}
+
+/** 鍙栨秷鍩硅锛堣崏绋�/寰呭紑灞� 鈫� 宸插彇娑堬級 */
+export function cancelTraining(id: number) {
+ return requestClient.post('/hrm/training/cancel', null, { params: { id } });
+}
+
+/** 鎵归噺娣诲姞鍙傝鍛樺伐 */
+export function addTrainingParticipants(trainingId: number, employeeIds: number[]) {
+ return requestClient.post('/hrm/training-participant/add', { trainingId, employeeIds });
+}
+
+/** 鏇存柊鍙傝璁板綍 */
+export function updateTrainingParticipant(data: ReqParticipant) {
+ return requestClient.put('/hrm/training-participant/update', data);
+}
+
+/** 鍒犻櫎鍙傝璁板綍 */
+export function deleteTrainingParticipants(ids: number[]) {
+ return requestClient.delete('/hrm/training-participant/delete', {
+ params: { ids: ids.join(',') },
+ });
+}
+
+/** 鑾峰彇鏌愭鍩硅鐨勫弬璁汉鍛樺垪琛� */
+export function getTrainingParticipants(trainingId: number) {
+ return requestClient.get<HrmTrainingApi.Participant[]>(
+ `/hrm/training-participant/list?trainingId=${trainingId}`,
+ );
+}
+
+/** 鍩硅娲诲姩鏂板/鏇存柊璇锋眰 */
+export interface ReqTraining {
+ id?: number;
+ name?: string;
+ trainingType?: number;
+ trainer?: string;
+ location?: string;
+ hours?: number;
+ startDate?: string;
+ endDate?: string;
+ issueCertificate?: boolean;
+ certValidityMonths?: number;
+ remark?: string;
+}
+
+/** 鍙傝璁板綍鏇存柊璇锋眰 */
+export interface ReqParticipant {
+ id?: number;
+ attended?: boolean;
+ score?: number;
+ isPass?: boolean;
+ remark?: string;
+}
\ No newline at end of file
diff --git a/src/api/hrm/work-hour-summary/index.ts b/src/api/hrm/work-hour-summary/index.ts
new file mode 100644
index 0000000..fac0244
--- /dev/null
+++ b/src/api/hrm/work-hour-summary/index.ts
@@ -0,0 +1,36 @@
+import type { PageParam, PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace HrmWorkHourSummaryApi {
+ /** 鏈堝害宸ユ椂姹囨�� */
+ export interface WorkHourSummary {
+ id?: number;
+ employeeId?: number; // 鍛樺伐ID
+ employeeNo?: string; // 鍛樺伐缂栧彿
+ employeeName?: string; // 鍛樺伐濮撳悕
+ deptId?: number; // 閮ㄩ棬ID
+ deptName?: string; // 閮ㄩ棬鍚嶇О
+ period?: string; // 鏍哥畻鍛ㄦ湡锛坹yyy-MM锛�
+ workDays?: number; // 鍑哄嫟澶╂暟
+ workHours?: number; // 姝e父宸ユ椂锛堝皬鏃讹級
+ overtimeHours?: number; // 鍔犵彮宸ユ椂锛堝皬鏃讹級
+ remark?: string; // 澶囨敞
+ createTime?: string;
+ }
+}
+
+/** 鐢熸垚鏈堝害宸ユ椂姹囨�� */
+export function generateWorkHourSummary(period: string) {
+ return requestClient.post('/hrm/work-hour-summary/generate-monthly', null, {
+ params: { period },
+ });
+}
+
+/** 鑾峰彇鏈堝害宸ユ椂姹囨�诲垎椤� */
+export function getWorkHourSummaryPage(params: PageParam) {
+ return requestClient.get<PageResult<HrmWorkHourSummaryApi.WorkHourSummary>>(
+ '/hrm/work-hour-summary/page',
+ { params },
+ );
+}
\ No newline at end of file
diff --git a/src/api/mes/dv/machinery/index.ts b/src/api/mes/dv/machinery/index.ts
index 601c672..4a414b6 100644
--- a/src/api/mes/dv/machinery/index.ts
+++ b/src/api/mes/dv/machinery/index.ts
@@ -13,6 +13,7 @@
specification?: string; // 瑙勬牸鍨嬪彿
machineryTypeId?: number; // 璁惧绫诲瀷缂栧彿
machineryTypeName?: string; // 璁惧绫诲瀷鍚嶇О
+ itemId?: number; // 璁惧鐗╂枡缂栧彿锛圡DM 鐗╂枡锛�
workshopId?: number; // 鎵�灞炶溅闂寸紪鍙�
workshopName?: string; // 鎵�灞炶溅闂村悕绉�
status?: number; // 璁惧鐘舵��
diff --git a/src/api/mes/wm/barcode/index.ts b/src/api/mes/wm/barcode/index.ts
index 39673a3..507f02f 100644
--- a/src/api/mes/wm/barcode/index.ts
+++ b/src/api/mes/wm/barcode/index.ts
@@ -62,9 +62,32 @@
return requestClient.download('/mes/wm/barcode/export-excel', { params });
}
-/** 鐢熸垚鏉$爜鍐呭 */
-export function generateBarcodeContent(bizType: number, bizCode: string) {
+/** 鐢熸垚鏉$爜鍐呭锛堟敮鎸佸鍗犱綅绗︾粍鍚堬紝鍙紶鍏ヤ笟鍔″悕绉帮級 */
+export function generateBarcodeContent(
+ bizType: number,
+ bizCode: string,
+ bizName?: string,
+) {
return requestClient.get<string>('/mes/wm/barcode/generate-content', {
- params: { bizCode, bizType },
+ params: { bizCode, bizName, bizType },
+ });
+}
+
+/** 鐢熸垚鏉$爜/浜岀淮鐮佸浘鐗囷紙鏈嶅姟绔� zxing 鎴愬浘锛岃繑鍥� PNG Base64锛屼笉钀藉簱锛� */
+export function renderBarcodeImage(
+ content: string,
+ format?: number,
+ width?: number,
+ height?: number,
+) {
+ return requestClient.post<string>('/mes/wm/barcode/render', null, {
+ params: { content, format, height, width },
+ });
+}
+
+/** 瑙f瀽鏉$爜鍐呭锛堜紭鍏堢簿纭懡涓紝鍏舵鎸夊惎鐢ㄩ厤缃ā鏉垮弽瑙o紝杩斿洖涓氬姟瀛楁锛� */
+export function parseBarcodeContent(content: string) {
+ return requestClient.post<Record<string, any>>('/mes/wm/barcode/parse', null, {
+ params: { content },
});
}
diff --git a/src/api/mes/wm/equipmentreceipt/index.ts b/src/api/mes/wm/equipmentreceipt/index.ts
new file mode 100644
index 0000000..782a8b1
--- /dev/null
+++ b/src/api/mes/wm/equipmentreceipt/index.ts
@@ -0,0 +1,81 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+import type { MesWmEquipmentReceiptLineApi } from './line';
+
+export namespace MesWmEquipmentReceiptApi {
+ /** MES 璁惧鍏ュ簱鍗� */
+ export interface EquipmentReceipt {
+ id?: number; // 缂栧彿
+ code?: string; // 鍗曟嵁缂栫爜
+ name?: string; // 鍗曟嵁鍚嶇О
+ receiptDate?: number; // 鍏ュ簱鏃ユ湡
+ source?: string; // 鍏ュ簱鏉ユ簮
+ status?: number; // 鍗曟嵁鐘舵��
+ processInstanceId?: string; // 瀹℃壒娴佺▼瀹炰緥缂栧彿
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ lines?: MesWmEquipmentReceiptLineApi.EquipmentReceiptLine[]; // 鏄庣粏琛�
+ }
+}
+
+/** 鏌ヨ璁惧鍏ュ簱鍗曞垎椤� */
+export function getEquipmentReceiptPage(params: PageParam) {
+ return requestClient.get<
+ PageResult<MesWmEquipmentReceiptApi.EquipmentReceipt>
+ >('/mes/wm/equipment-receipt/page', { params });
+}
+
+/** 鏌ヨ璁惧鍏ュ簱鍗曡鎯� */
+export function getEquipmentReceipt(id: number) {
+ return requestClient.get<MesWmEquipmentReceiptApi.EquipmentReceipt>(
+ `/mes/wm/equipment-receipt/get?id=${id}`,
+ );
+}
+
+/** 鏂板璁惧鍏ュ簱鍗� */
+export function createEquipmentReceipt(
+ data: MesWmEquipmentReceiptApi.EquipmentReceipt,
+) {
+ return requestClient.post<number>('/mes/wm/equipment-receipt/create', data);
+}
+
+/** 淇敼璁惧鍏ュ簱鍗� */
+export function updateEquipmentReceipt(
+ data: MesWmEquipmentReceiptApi.EquipmentReceipt,
+) {
+ return requestClient.put('/mes/wm/equipment-receipt/update', data);
+}
+
+/** 鍒犻櫎璁惧鍏ュ簱鍗� */
+export function deleteEquipmentReceipt(id: number) {
+ return requestClient.delete(`/mes/wm/equipment-receipt/delete?id=${id}`);
+}
+
+/** 鏌ヨ鍙敤鐨勮澶囧叆搴撳崟瀹℃壒娴佺▼瀹氫箟 */
+export function getEquipmentReceiptApproveProcessList() {
+ return requestClient.get<{ id: string; name: string; key: string }[]>(
+ '/mes/wm/equipment-receipt/approval-process-list',
+ );
+}
+
+/** 鎻愪氦璁惧鍏ュ簱鍗曞鎵� */
+export function submitEquipmentReceiptApproval(
+ id: number,
+ processDefinitionKey: string,
+) {
+ return requestClient.put(
+ `/mes/wm/equipment-receipt/submit-approval?id=${id}&processDefinitionKey=${processDefinitionKey}`,
+ );
+}
+
+/** 鎵ц璁惧鍏ュ簱 */
+export function finishEquipmentReceipt(id: number) {
+ return requestClient.put(`/mes/wm/equipment-receipt/finish?id=${id}`);
+}
+
+/** 鍙栨秷璁惧鍏ュ簱鍗� */
+export function cancelEquipmentReceipt(id: number) {
+ return requestClient.put(`/mes/wm/equipment-receipt/cancel?id=${id}`);
+}
\ No newline at end of file
diff --git a/src/api/mes/wm/equipmentreceipt/line/index.ts b/src/api/mes/wm/equipmentreceipt/line/index.ts
new file mode 100644
index 0000000..a51138d
--- /dev/null
+++ b/src/api/mes/wm/equipmentreceipt/line/index.ts
@@ -0,0 +1,55 @@
+import { requestClient } from '#/api/request';
+
+export namespace MesWmEquipmentReceiptLineApi {
+ /** MES 璁惧鍏ュ簱鍗曡 */
+ export interface EquipmentReceiptLine {
+ id?: number; // 缂栧彿
+ receiptId?: number; // 鍏ュ簱鍗曠紪鍙�
+ machineryId?: number; // 璁惧鍙拌处缂栧彿
+ machineryCode?: string; // 璁惧缂栫爜
+ machineryName?: string; // 璁惧鍚嶇О
+ itemId?: number; // 璁惧鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ unitMeasureName?: string; // 璁¢噺鍗曚綅鍚嶇О
+ quantity?: number; // 鍏ュ簱鏁伴噺
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ璁惧鍏ュ簱鍗曡鍒楄〃 */
+export function getEquipmentReceiptLineListByReceiptId(receiptId: number) {
+ return requestClient.get<
+ MesWmEquipmentReceiptLineApi.EquipmentReceiptLine[]
+ >(`/mes/wm/equipment-receipt-line/list-by-receipt-id?receiptId=${receiptId}`);
+}
+
+/** 鏌ヨ璁惧鍏ュ簱鍗曡璇︽儏 */
+export function getEquipmentReceiptLine(id: number) {
+ return requestClient.get<MesWmEquipmentReceiptLineApi.EquipmentReceiptLine>(
+ `/mes/wm/equipment-receipt-line/get?id=${id}`,
+ );
+}
+
+/** 鏂板璁惧鍏ュ簱鍗曡 */
+export function createEquipmentReceiptLine(
+ data: MesWmEquipmentReceiptLineApi.EquipmentReceiptLine,
+) {
+ return requestClient.post('/mes/wm/equipment-receipt-line/create', data);
+}
+
+/** 淇敼璁惧鍏ュ簱鍗曡 */
+export function updateEquipmentReceiptLine(
+ data: MesWmEquipmentReceiptLineApi.EquipmentReceiptLine,
+) {
+ return requestClient.put('/mes/wm/equipment-receipt-line/update', data);
+}
+
+/** 鍒犻櫎璁惧鍏ュ簱鍗曡 */
+export function deleteEquipmentReceiptLine(id: number) {
+ return requestClient.delete(
+ `/mes/wm/equipment-receipt-line/delete?id=${id}`,
+ );
+}
\ No newline at end of file
diff --git a/src/api/mes/wm/miscissue/index.ts b/src/api/mes/wm/miscissue/index.ts
index 1a54df4..7a7a8d2 100644
--- a/src/api/mes/wm/miscissue/index.ts
+++ b/src/api/mes/wm/miscissue/index.ts
@@ -14,6 +14,7 @@
sourceDocCode?: string; // 鏉ユ簮鍗曟嵁缂栫爜
issueDate?: number; // 鍑哄簱鏃ユ湡
status?: number; // 鍗曟嵁鐘舵��
+ processInstanceId?: string; // 瀹℃壒娴佺▼瀹炰緥缂栧彿
remark?: string; // 澶囨敞
createTime?: Date; // 鍒涘缓鏃堕棿
}
@@ -49,9 +50,21 @@
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 getMiscIssueApproveProcessList() {
+ return requestClient.get<{ id: string; name: string; key: string }[]>(
+ '/mes/wm/misc-issue/approval-process-list',
+ );
+}
+
+/** 鎻愪氦鏉傞」鍑哄簱鍗曞鎵� */
+export function submitMiscIssueApproval(
+ id: number,
+ processDefinitionKey: string,
+) {
+ return requestClient.put(
+ `/mes/wm/misc-issue/submit?id=${id}&processDefinitionKey=${processDefinitionKey}`,
+ );
}
/** 鎵ц鍑哄簱 */
diff --git a/src/api/mes/wm/miscreceipt/index.ts b/src/api/mes/wm/miscreceipt/index.ts
index 6c6b4de..ec3e2ee 100644
--- a/src/api/mes/wm/miscreceipt/index.ts
+++ b/src/api/mes/wm/miscreceipt/index.ts
@@ -14,6 +14,7 @@
sourceDocCode?: string; // 鏉ユ簮鍗曟嵁缂栫爜
receiptDate?: number; // 鍏ュ簱鏃ユ湡
status?: number; // 鍗曟嵁鐘舵��
+ processInstanceId?: string; // 瀹℃壒娴佺▼瀹炰緥缂栧彿
remark?: string; // 澶囨敞
createTime?: Date; // 鍒涘缓鏃堕棿
}
@@ -49,9 +50,21 @@
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 getMiscReceiptApproveProcessList() {
+ return requestClient.get<{ id: string; name: string; key: string }[]>(
+ '/mes/wm/misc-receipt/approval-process-list',
+ );
+}
+
+/** 鎻愪氦鏉傞」鍏ュ簱鍗曞鎵� */
+export function submitMiscReceiptApproval(
+ id: number,
+ processDefinitionKey: string,
+) {
+ return requestClient.put(
+ `/mes/wm/misc-receipt/submit?id=${id}&processDefinitionKey=${processDefinitionKey}`,
+ );
}
/** 鎵ц鍏ュ簱 */
diff --git a/src/api/mes/wm/returnvendor/index.ts b/src/api/mes/wm/returnvendor/index.ts
index af94123..53d0b64 100644
--- a/src/api/mes/wm/returnvendor/index.ts
+++ b/src/api/mes/wm/returnvendor/index.ts
@@ -19,6 +19,7 @@
transportCode?: string; // 杩愬崟鍙�
transportTelephone?: string; // 鑱旂郴鐢佃瘽
status?: number; // 鍗曟嵁鐘舵��
+ processInstanceId?: string; // 瀹℃壒娴佺▼瀹炰緥缂栧彿
remark?: string; // 澶囨敞
createTime?: Date; // 鍒涘缓鏃堕棿
}
@@ -54,9 +55,21 @@
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}`);
+/** 鏌ヨ鍙敤鐨勪緵搴斿晢閫�璐у崟瀹℃壒娴佺▼瀹氫箟 */
+export function getReturnVendorApproveProcessList() {
+ return requestClient.get<{ id: string; name: string; key: string }[]>(
+ '/mes/wm/return-vendor/approval-process-list',
+ );
+}
+
+/** 鎻愪氦渚涘簲鍟嗛��璐у崟瀹℃壒 */
+export function submitReturnVendorApproval(
+ id: number,
+ processDefinitionKey: string,
+) {
+ return requestClient.put(
+ `/mes/wm/return-vendor/submit?id=${id}&processDefinitionKey=${processDefinitionKey}`,
+ );
}
/** 鎵ц鎷h揣 */
diff --git a/src/api/mes/wm/sn/index.ts b/src/api/mes/wm/sn/index.ts
index 553ac0a..bac987e 100644
--- a/src/api/mes/wm/sn/index.ts
+++ b/src/api/mes/wm/sn/index.ts
@@ -29,7 +29,60 @@
unitName?: string; // 鍗曚綅鍚嶇О
batchCode?: string; // 鎵规鍙�
workOrderId?: number; // 鐢熶骇璁㈠崟缂栧彿
+ status?: number; // 鐘舵�侊紙1-鍦ㄥ簱 2-宸插嚭搴擄級
+ warehouseId?: number; // 鍦ㄥ簱浠撳簱 ID
+ locationId?: number; // 鍦ㄥ簱搴撳尯 ID
+ areaId?: number; // 鍦ㄥ簱搴撲綅 ID
createTime?: Date; // 鐢熸垚鏃堕棿
+ }
+
+ /** MES SN 鐮佸崟浠惰鎯� */
+ export interface SnDetail {
+ id?: number; // 缂栧彿
+ code?: string; // SN 鐮�
+ itemId?: number; // 鐗╂枡缂栧彿
+ itemCode?: string; // 鐗╂枡缂栫爜
+ itemName?: string; // 鐗╂枡鍚嶇О
+ batchCode?: string; // 鎵规鍙�
+ status?: number; // 鐘舵�侊紙1-鍦ㄥ簱 2-宸插嚭搴擄級
+ warehouseId?: number; // 鍦ㄥ簱浠撳簱 ID
+ locationId?: number; // 鍦ㄥ簱搴撳尯 ID
+ areaId?: number; // 鍦ㄥ簱搴撲綅 ID
+ lastTransactionType?: number; // 鏈�杩戜簨鍔$被鍨嬶紙1-鍏ュ簱 2-鍑哄簱锛�
+ lastTransactionTypeName?: string; // 鏈�杩戜簨鍔$被鍨嬪悕绉�
+ lastTransactionQuantity?: number; // 鏈�杩戜簨鍔″彉鍔ㄦ暟閲�
+ lastTransactionBizType?: number; // 鏈�杩戜簨鍔′笟鍔$被鍨�
+ lastTransactionBizCode?: string; // 鏈�杩戜簨鍔′笟鍔″崟鍙�
+ lastTransactionTime?: string; // 鏈�杩戜簨鍔″彂鐢熸椂闂�
+ }
+
+ /** SN 鐮佸叆搴撶櫥璁板弬鏁� */
+ export interface SnInStock {
+ code?: string; // SN 鐮�
+ itemId?: number; // 鐗╂枡缂栧彿
+ warehouseId?: number; // 浠撳簱 ID
+ locationId?: number; // 搴撳尯 ID
+ areaId?: number; // 搴撲綅 ID
+ batchCode?: string; // 鎵规鍙�
+ remark?: string; // 澶囨敞
+ }
+
+ /** SN 鐮佸嚭搴撴牳閿�鍙傛暟 */
+ export interface SnOutStock {
+ code?: string; // SN 鐮�
+ itemId?: number; // 鐗╂枡缂栧彿
+ remark?: string; // 澶囨敞
+ }
+
+ /** SN 鐮佸湪搴�/鐘舵�佸垎椤垫煡璇㈠弬鏁� */
+ export interface SnInStockPageParams extends PageParam {
+ code?: string; // SN 鐮�
+ itemId?: number; // 鐗╂枡缂栧彿
+ batchCode?: string; // 鎵规鍙�
+ warehouseId?: number; // 浠撳簱 ID
+ locationId?: number; // 搴撳尯 ID
+ areaId?: number; // 搴撲綅 ID
+ status?: number; // 鐘舵�侊紙1-鍦ㄥ簱 2-宸插嚭搴擄級
}
/** MES SN 鐮佺敓鎴愬弬鏁� */
@@ -77,6 +130,31 @@
});
}
+/** SN 鐮佸叆搴撶櫥璁� */
+export function registerSnInStock(data: MesWmSnApi.SnInStock) {
+ return requestClient.post<boolean>('/mes/wm/sn/in-stock', data);
+}
+
+/** SN 鐮佸嚭搴撴牳閿� */
+export function outboundSn(data: MesWmSnApi.SnOutStock) {
+ return requestClient.post<boolean>('/mes/wm/sn/out-stock', data);
+}
+
+/** 鏌ヨ SN 鐮佸崟浠惰鎯� */
+export function getSnDetail(code: string) {
+ return requestClient.get<MesWmSnApi.SnDetail>('/mes/wm/sn/detail', {
+ params: { code },
+ });
+}
+
+/** 鏌ヨ SN 鐮佸湪搴�/鐘舵�佸垎椤� */
+export function getSnInStockPage(params: MesWmSnApi.SnInStockPageParams) {
+ return requestClient.get<PageResult<MesWmSnApi.Sn>>(
+ '/mes/wm/sn/in-stock-page',
+ { params },
+ );
+}
+
/** 瀵煎嚭 SN 鐮佸垎缁� Excel */
export function exportSnGroupExcel(params: MesWmSnApi.PageParams) {
return requestClient.download('/mes/wm/sn/group-export-excel', { params });
diff --git a/src/api/mes/wm/stocktaking/plan/index.ts b/src/api/mes/wm/stocktaking/plan/index.ts
index c6f29f0..c4a4217 100644
--- a/src/api/mes/wm/stocktaking/plan/index.ts
+++ b/src/api/mes/wm/stocktaking/plan/index.ts
@@ -13,6 +13,9 @@
endTime?: number; // 缁撴潫鏃堕棿
blindFlag?: boolean; // 鏄惁鐩茬洏
frozen?: boolean; // 鏄惁鍐荤粨搴撳瓨
+ autoGenerate?: boolean; // 鏄惁鑷姩鐢熸垚鐩樼偣浠诲姟
+ generateCron?: string; // 鑷姩鐢熸垚鍛ㄦ湡(Cron)
+ lastGenerateTime?: string; // 涓婃鐢熸垚浠诲姟鏃堕棿
status?: number; // 鐘舵��
remark?: string; // 澶囨敞
createTime?: string; // 鍒涘缓鏃堕棿
diff --git a/src/api/mes/wm/stocktaking/task/index.ts b/src/api/mes/wm/stocktaking/task/index.ts
index 77622f3..985818a 100644
--- a/src/api/mes/wm/stocktaking/task/index.ts
+++ b/src/api/mes/wm/stocktaking/task/index.ts
@@ -20,6 +20,7 @@
startTime?: number; // 寮�濮嬫椂闂�
endTime?: number; // 缁撴潫鏃堕棿
status?: number; // 鍗曟嵁鐘舵��
+ processInstanceId?: string; // 瀹℃壒娴佺▼瀹炰緥缂栧彿
remark?: string; // 澶囨敞
createTime?: string; // 鍒涘缓鏃堕棿
}
@@ -59,7 +60,7 @@
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 },
@@ -73,9 +74,21 @@
});
}
-/** 鎵ц鐩樼偣浠诲姟 */
-export function finishStockTaking(id: number) {
- return requestClient.put('/mes/wm/stocktaking-task/finish', { id });
+/** 鏌ヨ鍙敤鐨勭洏鐐圭粨鏋滃鎵规祦绋嬪畾涔� */
+export function getStockTakingApproveProcessList() {
+ return requestClient.get<{ id: string; name: string; key: string }[]>(
+ '/mes/wm/stocktaking-task/approval-process-list',
+ );
+}
+
+/** 鎻愪氦鐩樼偣缁撴灉瀹℃壒 */
+export function submitStockTakingApproval(
+ id: number,
+ processDefinitionKey: string,
+) {
+ return requestClient.put(
+ `/mes/wm/stocktaking-task/submit-approval?id=${id}&processDefinitionKey=${processDefinitionKey}`,
+ );
}
/** 瀵煎嚭鐩樼偣浠诲姟 */
diff --git a/src/components/table-action/icons.ts b/src/components/table-action/icons.ts
index c56cb1b..4f72d6d 100644
--- a/src/components/table-action/icons.ts
+++ b/src/components/table-action/icons.ts
@@ -14,4 +14,5 @@
BOOK: 'lucide:book',
AUDIT: 'lucide:file-check',
CALC: 'lucide:calculator',
+ SCAN: 'lucide:scan-line',
};
diff --git a/src/packages/constants/src/biz-mes-enum.ts b/src/packages/constants/src/biz-mes-enum.ts
index 14f9650..f052e36 100644
--- a/src/packages/constants/src/biz-mes-enum.ts
+++ b/src/packages/constants/src/biz-mes-enum.ts
@@ -187,6 +187,7 @@
PRODUCTRECPT_CODE: 'PRODUCTRECPT_CODE',
WM_MISC_ISSUE_CODE: 'WM_MISC_ISSUE_CODE',
WM_MISC_RECEIPT_CODE: 'WM_MISC_RECEIPT_CODE',
+ WM_EQUIP_RECEIPT_CODE: 'WM_EQUIP_RECEIPT_CODE',
WM_OUTSOURCE_ISSUE_CODE: 'WM_OUTSOURCE_ISSUE_CODE',
WM_OUTSOURCE_RECEIPT_CODE: 'WM_OUTSOURCE_RECEIPT_CODE',
WM_PACKAGE_CODE: 'WM_PACKAGE_CODE',
@@ -316,6 +317,7 @@
/** MES 鏉傞」鍑哄簱鍗曠姸鎬佹灇涓� */
export const MesWmMiscIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
+ APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
@@ -324,6 +326,7 @@
/** MES 鏉傞」鍏ュ簱鍗曠姸鎬佹灇涓� */
export const MesWmMiscReceiptStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
+ APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED,
@@ -631,3 +634,18 @@
USER = 604,
CLIENT = 605,
}
+
+/** MES SN 鐮佺姸鎬佹灇涓� */
+export enum MesWmSnStatusEnum {
+ IN_STOCK = 1, // 鍦ㄥ簱
+ OUT_STOCK = 2, // 宸插嚭搴�
+}
+
+/** MES 璁惧鍏ュ簱鍗曠姸鎬佹灇涓� */
+export const MesWmEquipmentReceiptStatusEnum = {
+ PREPARE: MesOrderStatusConstants.DRAFT,
+ APPROVING: MesOrderStatusConstants.APPROVING,
+ APPROVED: MesOrderStatusConstants.APPROVED,
+ FINISHED: MesOrderStatusConstants.FINISHED,
+ CANCELED: MesOrderStatusConstants.CANCELLED,
+} as const;
diff --git a/src/views/hrm/training/data.ts b/src/views/hrm/training/data.ts
new file mode 100644
index 0000000..e00aac7
--- /dev/null
+++ b/src/views/hrm/training/data.ts
@@ -0,0 +1,243 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmTrainingApi } from '#/api/hrm/training';
+
+/** 鍩硅绫诲瀷閫夐」 */
+export const trainingTypeOptions = [
+ { label: '瀹夊叏鍩硅', value: 1 },
+ { label: '鎶�鑳藉煿璁�', value: 2 },
+ { label: '绠$悊鍩硅', value: 3 },
+ { label: '鍏朵粬', value: 4 },
+];
+
+/** 鍩硅鐘舵�侀�夐」 */
+export const trainingStatusOptions = [
+ { label: '鑽夌', value: 0 },
+ { label: '寰呭紑灞�', value: 10 },
+ { label: '宸插畬鎴�', value: 20 },
+ { label: '宸插彇娑�', value: 30 },
+];
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'edit' | 'detail';
+
+/** 琛ㄥ崟鐨勯厤缃」 */
+export function useFormSchema(formType: FormType): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍩硅鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ煿璁悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'trainingType',
+ label: '鍩硅绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ options: trainingTypeOptions,
+ placeholder: '璇烽�夋嫨鍩硅绫诲瀷',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'trainer',
+ label: '璁插笀',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ甯�',
+ },
+ },
+ {
+ fieldName: 'location',
+ label: '鍩硅鍦扮偣',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ煿璁湴鐐�',
+ },
+ },
+ {
+ fieldName: 'hours',
+ label: '鍩硅瀛︽椂',
+ component: 'InputNumber',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ煿璁鏃�',
+ min: 0,
+ precision: 1,
+ style: { width: '100%' },
+ },
+ },
+ {
+ fieldName: 'startDate',
+ label: '寮�濮嬫棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '閫夋嫨寮�濮嬫棩鏈�',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'endDate',
+ label: '缁撴潫鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ placeholder: '閫夋嫨缁撴潫鏃ユ湡',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'issueCertificate',
+ label: '鑷姩鍙戣瘉',
+ component: 'Select',
+ componentProps: {
+ options: [
+ { label: '鏄�', value: 1 },
+ { label: '鍚�', value: 0 },
+ ],
+ placeholder: '鏄惁鑷姩鍙戣瘉',
+ },
+ },
+ {
+ fieldName: 'certValidityMonths',
+ label: '璇佷功鏈夋晥鏈�(鏈�)',
+ component: 'InputNumber',
+ componentProps: {
+ placeholder: '鑷姩鍙戣瘉鏃跺~鍐�',
+ min: 0,
+ precision: 0,
+ style: { width: '100%' },
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ autoSize: { minRows: 1, maxRows: 2 },
+ },
+ formItemClass: 'col-span-2',
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'trainingNo',
+ label: '鍩硅缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ煿璁紪鍙�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍩硅鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ煿璁悕绉�',
+ },
+ },
+ {
+ fieldName: 'trainingType',
+ label: '鍩硅绫诲瀷',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: trainingTypeOptions,
+ placeholder: '璇烽�夋嫨鍩硅绫诲瀷',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: trainingStatusOptions,
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<HrmTrainingApi.Training>['columns'] {
+ return [
+ {
+ field: 'trainingNo',
+ title: '鍩硅缂栧彿',
+ width: 140,
+ },
+ {
+ field: 'name',
+ title: '鍩硅鍚嶇О',
+ minWidth: 180,
+ },
+ {
+ field: 'trainingTypeName',
+ title: '鍩硅绫诲瀷',
+ width: 100,
+ },
+ {
+ field: 'trainer',
+ title: '璁插笀',
+ width: 100,
+ },
+ {
+ field: 'location',
+ title: '鍩硅鍦扮偣',
+ minWidth: 120,
+ },
+ {
+ field: 'hours',
+ title: '瀛︽椂',
+ width: 80,
+ },
+ {
+ field: 'startDate',
+ title: '寮�濮嬫棩鏈�',
+ width: 105,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'endDate',
+ title: '缁撴潫鏃ユ湡',
+ width: 105,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'participantCount',
+ title: '鍙傝浜烘暟',
+ width: 90,
+ },
+ {
+ field: 'statusName',
+ title: '鐘舵��',
+ width: 90,
+ slots: { default: 'status' },
+ },
+ {
+ field: 'action',
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
\ No newline at end of file
diff --git a/src/views/hrm/training/index.vue b/src/views/hrm/training/index.vue
new file mode 100644
index 0000000..30d130a
--- /dev/null
+++ b/src/views/hrm/training/index.vue
@@ -0,0 +1,263 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmTrainingApi } from '#/api/hrm/training';
+
+import { ref } from 'vue';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { isEmpty } from '@vben/utils';
+
+import { message, Modal, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelTraining,
+ completeTraining,
+ deleteTraining,
+ getTrainingPage,
+ startTraining,
+} from '#/api/hrm/training';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+import Participants from './modules/participants.vue';
+
+/** HRM 鍩硅绠$悊鍒楄〃 */
+defineOptions({ name: 'HrmTraining' });
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+const [ParticipantsModal, participantsModalApi] = useVbenModal({
+ connectedComponent: Participants,
+ destroyOnClose: true,
+});
+
+function handleRefresh() {
+ gridApi.query();
+}
+
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+function handleEdit(row: HrmTrainingApi.Training) {
+ formModalApi.setData({ formType: 'edit', id: row.id }).open();
+}
+
+function handleDetail(row: HrmTrainingApi.Training) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+function handleParticipants(row: HrmTrainingApi.Training) {
+ participantsModalApi.setData({ trainingId: row.id }).open();
+}
+
+async function handleDelete(ids: number[]) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting'),
+ duration: 0,
+ });
+ try {
+ await deleteTraining(ids);
+ message.success($t('ui.actionMessage.deleteSuccess'));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 寮�濮嬪煿璁細鑽夌 鈫� 寰呭紑灞� */
+function handleStart(row: HrmTrainingApi.Training) {
+ Modal.confirm({
+ title: '寮�濮嬪煿璁�',
+ content: `鏄惁寮�濮嬪煿璁��${row.name}銆嶏紵`,
+ onOk: async () => {
+ await startTraining(row.id!);
+ message.success('寮�濮嬪煿璁垚鍔�');
+ handleRefresh();
+ },
+ });
+}
+
+/** 瀹屾垚鍩硅锛氳崏绋�/寰呭紑灞� 鈫� 宸插畬鎴� */
+function handleComplete(row: HrmTrainingApi.Training) {
+ Modal.confirm({
+ title: '瀹屾垚鍩硅',
+ content: `鏄惁瀹屾垚鍩硅銆�${row.name}銆嶏紵`,
+ onOk: async () => {
+ await completeTraining(row.id!);
+ message.success('瀹屾垚鍩硅鎴愬姛');
+ handleRefresh();
+ },
+ });
+}
+
+/** 鍙栨秷鍩硅锛氳崏绋�/寰呭紑灞� 鈫� 宸插彇娑� */
+function handleCancel(row: HrmTrainingApi.Training) {
+ Modal.confirm({
+ title: '鍙栨秷鍩硅',
+ content: `鏄惁鍙栨秷鍩硅銆�${row.name}銆嶏紵`,
+ onOk: async () => {
+ await cancelTraining(row.id!);
+ message.success('鍙栨秷鍩硅鎴愬姛');
+ handleRefresh();
+ },
+ });
+}
+
+const checkedIds = ref<number[]>([]);
+function handleRowCheckboxChange({
+ records,
+}: {
+ records: HrmTrainingApi.Training[];
+}) {
+ checkedIds.value = records.map((item) => item.id!);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getTrainingPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<HrmTrainingApi.Training>,
+ gridEvents: {
+ checkboxAll: handleRowCheckboxChange,
+ checkboxChange: handleRowCheckboxChange,
+ },
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
+ <ParticipantsModal @success="handleRefresh" />
+ <Grid table-title="鍩硅绠$悊鍒楄〃">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['鍩硅']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['hrm:training:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: '鎵归噺鍒犻櫎',
+ type: 'primary',
+ danger: true,
+ disabled: isEmpty(checkedIds),
+ icon: ACTION_ICON.DELETE,
+ auth: ['hrm:training:delete'],
+ popConfirm: {
+ title: '鏄惁鍒犻櫎鎵�閫変腑鏁版嵁锛�',
+ confirm: handleDelete.bind(null, checkedIds),
+ },
+ },
+ ]"
+ />
+ </template>
+ <template #status="{ row }">
+ <Tag v-if="row.status === 0" color="default">鑽夌</Tag>
+ <Tag v-else-if="row.status === 10" color="processing">寰呭紑灞�</Tag>
+ <Tag v-else-if="row.status === 20" color="success">宸插畬鎴�</Tag>
+ <Tag v-else-if="row.status === 30" color="error">宸插彇娑�</Tag>
+ <Tag v-else color="default">-</Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.detail'),
+ type: 'link',
+ icon: ACTION_ICON.VIEW,
+ auth: ['hrm:training:query'],
+ onClick: handleDetail.bind(null, row),
+ },
+ {
+ label: '缂栬緫',
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['hrm:training:update'],
+ ifShow: () => row.status === 0,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: '鍙傝浜哄憳',
+ type: 'link',
+ icon: 'lucide:users',
+ auth: ['hrm:training:update'],
+ ifShow: () => row.status === 0 || row.status === 10 || row.status === 20,
+ onClick: handleParticipants.bind(null, row),
+ },
+ {
+ label: '寮�濮�',
+ type: 'link',
+ icon: 'lucide:play',
+ auth: ['hrm:training:update'],
+ ifShow: () => row.status === 0,
+ onClick: handleStart.bind(null, row),
+ },
+ {
+ label: '瀹屾垚',
+ type: 'link',
+ icon: 'lucide:check',
+ auth: ['hrm:training:update'],
+ ifShow: () => row.status === 0 || row.status === 10,
+ onClick: handleComplete.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.CLOSE,
+ auth: ['hrm:training:update'],
+ ifShow: () => row.status === 0 || row.status === 10,
+ popConfirm: {
+ title: '鏄惁鍙栨秷鍩硅锛�',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['hrm:training:delete'],
+ ifShow: () => row.status === 0,
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, [row.id!]),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/hrm/training/modules/form.vue b/src/views/hrm/training/modules/form.vue
new file mode 100644
index 0000000..755e528
--- /dev/null
+++ b/src/views/hrm/training/modules/form.vue
@@ -0,0 +1,96 @@
+<script lang="ts" setup>
+import type { HrmTrainingApi, ReqTraining } from '#/api/hrm/training';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createTraining, getTraining, updateTraining } from '#/api/hrm/training';
+import { $t } from '#/locales';
+
+import type { FormType } from '../data';
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<HrmTrainingApi.Training>();
+const formType = ref<FormType>('create');
+
+const getTitle = computed(() => {
+ if (formType.value === 'create') {
+ return $t('ui.actionTitle.create', ['鍩硅']);
+ } else if (formType.value === 'edit') {
+ return $t('ui.actionTitle.edit', ['鍩硅']);
+ }
+ return '鍩硅璇︽儏';
+});
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-3',
+ layout: 'vertical',
+ schema: useFormSchema(formType.value),
+ showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ const { valid } = await formApi.validate();
+ if (!valid) {
+ return;
+ }
+ modalApi.lock();
+ const values = await formApi.getValues<ReqTraining>();
+ // 鑷姩鍙戣瘉瀛楁锛氳〃鍗曠敤 0/1 涓嬫媺锛屾彁浜ゆ椂杞竷灏�
+ const data = {
+ ...values,
+ issueCertificate: Boolean(values.issueCertificate),
+ } as ReqTraining;
+ try {
+ await (formType.value === 'create' ? createTraining(data) : updateTraining(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.setDisabled(formType.value === 'detail');
+ formApi.updateSchema(useFormSchema(formType.value));
+ if (!data || !data.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getTraining(data.id);
+ await formApi.setValues({
+ ...formData.value,
+ // 甯冨皵 鈫� 0/1 涓嬫媺鍊�
+ issueCertificate: formData.value.issueCertificate ? 1 : 0,
+ });
+ } finally {
+ modalApi.unlock();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal :title="getTitle" class="w-2/3" :show-confirm-button="formType !== 'detail'">
+ <Form class="mx-3" />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/hrm/training/modules/participants.vue b/src/views/hrm/training/modules/participants.vue
new file mode 100644
index 0000000..2a996e8
--- /dev/null
+++ b/src/views/hrm/training/modules/participants.vue
@@ -0,0 +1,278 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmEmployeeApi } from '#/api/hrm/employee';
+import type { HrmTrainingApi } from '#/api/hrm/training';
+
+import { ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message, Modal as AntModal, Tag } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ addTrainingParticipants,
+ deleteTrainingParticipants,
+ getTrainingParticipants,
+ updateTrainingParticipant,
+} from '#/api/hrm/training';
+import { $t } from '#/locales';
+
+import { HrmEmployeeSelectDialog } from '#/views/hrm/employee/components';
+
+defineOptions({ name: 'HrmTrainingParticipants' });
+
+const emit = defineEmits(['success']);
+
+const trainingId = ref<number>();
+
+/** 鍙傝浜哄憳缂栬緫琛ㄥ崟 */
+const participantFormData = ref<HrmTrainingApi.Participant>();
+const editVisible = ref(false);
+const editSaving = ref(false);
+const [EditForm, editFormApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ labelWidth: 120,
+ },
+ wrapperClass: 'grid-cols-2',
+ layout: 'vertical',
+ schema: [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'attended',
+ label: '鏄惁鍙傝',
+ component: 'Select',
+ componentProps: {
+ options: [
+ { label: '鏄�', value: 1 },
+ { label: '鍚�', value: 0 },
+ ],
+ placeholder: '鏄惁鍙傝',
+ },
+ },
+ {
+ fieldName: 'score',
+ label: '鎴愮哗',
+ component: 'InputNumber',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ垚缁�',
+ min: 0,
+ precision: 1,
+ style: { width: '100%' },
+ },
+ },
+ {
+ fieldName: 'isPass',
+ label: '鏄惁鍚堟牸',
+ component: 'Select',
+ componentProps: {
+ options: [
+ { label: '鏄�', value: 1 },
+ { label: '鍚�', value: 0 },
+ ],
+ placeholder: '鏄惁鍚堟牸',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ autoSize: { minRows: 1, maxRows: 2 },
+ },
+ formItemClass: 'col-span-2',
+ },
+ ],
+ showDefaultActions: false,
+});
+
+/** 鎵撳紑缂栬緫寮圭獥 */
+function openEditForm(row: HrmTrainingApi.Participant) {
+ participantFormData.value = row;
+ editFormApi.setValues({
+ id: row.id,
+ attended: row.attended ? 1 : 0,
+ score: row.score,
+ isPass: row.isPass ? 1 : 0,
+ remark: row.remark,
+ });
+ editVisible.value = true;
+}
+
+/** 淇濆瓨鍙傝璁板綍 */
+async function handleEditSave() {
+ const { valid } = await editFormApi.validate();
+ if (!valid) {
+ return;
+ }
+ editSaving.value = true;
+ const values = await editFormApi.getValues<HrmTrainingApi.Participant>();
+ try {
+ await updateTrainingParticipant({
+ id: values.id,
+ attended: Boolean(values.attended),
+ score: values.score,
+ isPass: Boolean(values.isPass),
+ remark: values.remark,
+ } as HrmTrainingApi.Participant);
+ message.success($t('ui.actionMessage.operationSuccess'));
+ editVisible.value = false;
+ await loadParticipants();
+ } finally {
+ editSaving.value = false;
+ }
+}
+
+/** 鍒犻櫎鍙傝璁板綍 */
+async function handleDelete(row: HrmTrainingApi.Participant) {
+ await deleteTrainingParticipants([row.id!]);
+ message.success($t('ui.actionMessage.deleteSuccess'));
+ await loadParticipants();
+}
+
+/** 鍛樺伐閫夋嫨寮圭獥锛堝閫夛級 */
+const employeeDialogRef = ref<InstanceType<typeof HrmEmployeeSelectDialog>>();
+function openEmployeeSelect() {
+ employeeDialogRef.value?.open([], { multiple: true });
+}
+
+async function handleEmployeesSelected(rows: HrmEmployeeApi.Employee[]) {
+ const employeeIds = rows
+ .map((item) => item.id)
+ .filter((id): id is number => id !== undefined && id !== null);
+ if (employeeIds.length === 0) {
+ return;
+ }
+ try {
+ await addTrainingParticipants(trainingId.value!, employeeIds);
+ message.success('娣诲姞鍙傝浜哄憳鎴愬姛');
+ await loadParticipants();
+ emit('success');
+ } catch (e) {
+ // 鍚庣鍏滃簳鎻愮ず宸插脊鍑�
+ }
+}
+
+async function loadParticipants() {
+ if (!trainingId.value) {
+ return;
+ }
+ const list = await getTrainingParticipants(trainingId.value);
+ gridApi.grid.loadData(list);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ gridOptions: {
+ columns: [
+ { type: 'seq', title: '搴忓彿', width: 60 },
+ { field: 'employeeNo', title: '鍛樺伐缂栧彿', width: 130 },
+ { field: 'employeeName', title: '鍛樺伐濮撳悕', minWidth: 120 },
+ {
+ field: 'attended',
+ title: '鏄惁鍙傝',
+ width: 100,
+ slots: { default: 'attended' },
+ },
+ {
+ field: 'score',
+ title: '鎴愮哗',
+ width: 90,
+ },
+ {
+ field: 'isPass',
+ title: '鏄惁鍚堟牸',
+ width: 100,
+ slots: { default: 'isPass' },
+ },
+ { field: 'remark', title: '澶囨敞', minWidth: 120 },
+ {
+ field: 'action',
+ title: '鎿嶄綔',
+ width: 140,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ],
+ height: 'auto',
+ keepSource: true,
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: false,
+ search: false,
+ },
+ } as VxeTableGridOptions<HrmTrainingApi.Participant>,
+});
+
+const [Modal, modalApi] = useVbenModal({
+ onOpenChange(isOpen) {
+ if (!isOpen) {
+ trainingId.value = undefined;
+ return;
+ }
+ const data = modalApi.getData<{ trainingId?: number }>();
+ trainingId.value = data?.trainingId;
+ if (trainingId.value) {
+ loadParticipants();
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal title="鍙傝浜哄憳绠$悊" class="w-[900px]" :show-confirm-button="false">
+ <HrmEmployeeSelectDialog
+ ref="employeeDialogRef"
+ @selected="handleEmployeesSelected"
+ />
+ <Grid table-title="鍙傝浜哄憳鍒楄〃">
+ <template #toolbar-tools>
+ <a-button type="primary" @click="openEmployeeSelect">
+ <i-ant-design-plus-outlined class="mr-1" />娣诲姞鍙傝浜哄憳
+ </a-button>
+ </template>
+ <template #attended="{ row }">
+ <Tag v-if="row.attended" color="success">鏄�</Tag>
+ <Tag v-else color="default">鍚�</Tag>
+ </template>
+ <template #isPass="{ row }">
+ <Tag v-if="row.isPass" color="success">鍚堟牸</Tag>
+ <Tag v-else-if="row.attended" color="error">涓嶅悎鏍�</Tag>
+ <Tag v-else color="default">-</Tag>
+ </template>
+ <template #actions="{ row }">
+ <a-button type="link" size="small" @click="openEditForm(row)">缂栬緫</a-button>
+ <a-popconfirm
+ :title="$t('ui.actionMessage.deleteConfirm', [row.employeeName ?? row.id])"
+ @confirm="handleDelete(row)"
+ >
+ <a-button type="link" danger size="small">鍒犻櫎</a-button>
+ </a-popconfirm>
+ </template>
+ </Grid>
+
+ <AntModal
+ v-model:open="editVisible"
+ title="缂栬緫鍙傝璁板綍"
+ width="480px"
+ :confirm-loading="editSaving"
+ @ok="handleEditSave"
+ >
+ <EditForm class="pt-4" />
+ </AntModal>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/hrm/workHourSummary/data.ts b/src/views/hrm/workHourSummary/data.ts
new file mode 100644
index 0000000..5156893
--- /dev/null
+++ b/src/views/hrm/workHourSummary/data.ts
@@ -0,0 +1,107 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmWorkHourSummaryApi } from '#/api/hrm/work-hour-summary';
+
+import { getDeptList } from '#/api/system/dept';
+import { getEmployeeSimpleList } from '#/api/hrm/employee';
+import { handleTree } from '#/packages/utils/src';
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'period',
+ label: '鏍哥畻鍛ㄦ湡',
+ component: 'DatePicker',
+ componentProps: {
+ picker: 'month',
+ allowClear: true,
+ format: 'YYYY-MM',
+ valueFormat: 'YYYY-MM',
+ style: { width: '100%' },
+ placeholder: '璇烽�夋嫨鏍哥畻鍛ㄦ湡',
+ },
+ },
+ {
+ fieldName: 'employeeId',
+ label: '鍛樺伐',
+ component: 'ApiSelect',
+ componentProps: {
+ api: getEmployeeSimpleList,
+ labelField: 'name',
+ valueField: 'id',
+ allowClear: true,
+ placeholder: '璇烽�夋嫨鍛樺伐',
+ },
+ },
+ {
+ fieldName: 'deptId',
+ label: '閮ㄩ棬',
+ component: 'ApiTreeSelect',
+ componentProps: {
+ api: async () => {
+ const data = await getDeptList();
+ return handleTree(data);
+ },
+ labelField: 'name',
+ valueField: 'id',
+ childrenField: 'children',
+ allowClear: true,
+ placeholder: '璇烽�夋嫨閮ㄩ棬',
+ treeDefaultExpandAll: true,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<HrmWorkHourSummaryApi.WorkHourSummary>['columns'] {
+ return [
+ {
+ field: 'period',
+ title: '鏍哥畻鍛ㄦ湡',
+ width: 100,
+ },
+ {
+ field: 'employeeNo',
+ title: '鍛樺伐缂栧彿',
+ width: 130,
+ },
+ {
+ field: 'employeeName',
+ title: '鍛樺伐濮撳悕',
+ minWidth: 110,
+ },
+ {
+ field: 'deptName',
+ title: '閮ㄩ棬',
+ minWidth: 120,
+ },
+ {
+ field: 'workDays',
+ title: '鍑哄嫟澶╂暟',
+ width: 90,
+ },
+ {
+ field: 'workHours',
+ title: '姝e父宸ユ椂(h)',
+ width: 110,
+ },
+ {
+ field: 'overtimeHours',
+ title: '鍔犵彮宸ユ椂(h)',
+ width: 110,
+ },
+ {
+ field: 'remark',
+ title: '澶囨敞',
+ minWidth: 120,
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 100,
+ formatter: 'formatDate',
+ },
+ ];
+}
\ No newline at end of file
diff --git a/src/views/hrm/workHourSummary/index.vue b/src/views/hrm/workHourSummary/index.vue
new file mode 100644
index 0000000..fa571f5
--- /dev/null
+++ b/src/views/hrm/workHourSummary/index.vue
@@ -0,0 +1,128 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { HrmWorkHourSummaryApi } from '#/api/hrm/work-hour-summary';
+
+import { ref } from 'vue';
+
+import { Page } from '#/packages/effects/common-ui/src';
+
+import { message, DatePicker, Form, FormItem, Modal } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ generateWorkHourSummary,
+ getWorkHourSummaryPage,
+} from '#/api/hrm/work-hour-summary';
+
+import { useGridColumns, useGridFormSchema } from './data';
+
+/** HRM 鏈堝害宸ユ椂姹囨�� */
+defineOptions({ name: 'HrmWorkHourSummary' });
+
+// 鐢熸垚鏈堝害姹囨�诲脊绐�
+const generateVisible = ref(false);
+const generatePeriod = ref<string>();
+const generateLoading = ref(false);
+
+/** 鎵撳紑鐢熸垚寮圭獥锛屽甫鍑烘悳绱㈡潯浠朵腑鐨勬牳绠楀懆鏈� */
+function openGenerateModal() {
+ void gridApi.formApi.getValues().then((values) => {
+ generatePeriod.value = values.period;
+ });
+ generateVisible.value = true;
+}
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鎵ц鐢熸垚 */
+async function handleGenerate() {
+ if (!generatePeriod.value) {
+ message.warning('璇烽�夋嫨鏍哥畻鍛ㄦ湡');
+ return;
+ }
+ generateLoading.value = true;
+ try {
+ await generateWorkHourSummary(generatePeriod.value);
+ message.success('鏈堝害宸ユ椂姹囨�荤敓鎴愭垚鍔�');
+ generateVisible.value = false;
+ handleRefresh();
+ } finally {
+ generateLoading.value = false;
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getWorkHourSummaryPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<HrmWorkHourSummaryApi.WorkHourSummary>,
+});
+</script>
+
+<template>
+ <Page auto-content-height>
+ <!-- 鐢熸垚鏈堝害宸ユ椂姹囨�诲脊绐� -->
+ <Modal
+ v-model:open="generateVisible"
+ title="鐢熸垚鏈堝害宸ユ椂姹囨��"
+ width="420px"
+ :confirm-loading="generateLoading"
+ @ok="handleGenerate"
+ >
+ <Form layout="vertical" class="pt-4">
+ <FormItem label="鏍哥畻鍛ㄦ湡" required>
+ <DatePicker
+ v-model:value="generatePeriod"
+ picker="month"
+ format="YYYY-MM"
+ valueFormat="YYYY-MM"
+ placeholder="璇烽�夋嫨鏍哥畻鍛ㄦ湡"
+ style="width: 100%"
+ />
+ </FormItem>
+ </Form>
+ </Modal>
+
+ <Grid table-title="鏈堝害宸ユ椂姹囨�诲垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '鐢熸垚鏈堝害姹囨��',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['hrm:work-hour:generate'],
+ onClick: openGenerateModal,
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/barcode/components/index.ts b/src/views/wls/barcode/components/index.ts
index 9bd45df..e67cfce 100644
--- a/src/views/wls/barcode/components/index.ts
+++ b/src/views/wls/barcode/components/index.ts
@@ -1,3 +1,4 @@
export { default as Barcode } from './barcode.vue';
export { default as BarcodeDetail } from './detail.vue';
export { default as PrinterLabel } from './printer-label.vue';
+export { default as ScanModal } from './scan-modal.vue';
diff --git a/src/views/wls/barcode/components/scan-modal.vue b/src/views/wls/barcode/components/scan-modal.vue
new file mode 100644
index 0000000..d0838d7
--- /dev/null
+++ b/src/views/wls/barcode/components/scan-modal.vue
@@ -0,0 +1,178 @@
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+
+import {
+ Button,
+ Descriptions,
+ Divider,
+ Empty,
+ Image,
+ Input,
+ message,
+ Modal,
+ Tag,
+} from 'ant-design-vue';
+
+import {
+ parseBarcodeContent,
+ renderBarcodeImage,
+} from '#/api/mes/wm/barcode';
+import { DictTag } from '#/components/dict-tag';
+
+defineOptions({ name: 'WlsBarcodeScanModal' });
+
+const open = ref(false);
+const content = ref('');
+const parseResult = ref<Record<string, any>>();
+const imageBase64 = ref('');
+const parsing = ref(false);
+const rendering = ref(false);
+
+function openModal() {
+ open.value = true;
+ content.value = '';
+ parseResult.value = undefined;
+ imageBase64.value = '';
+}
+
+/** 瑙f瀽鏉$爜鍐呭 */
+async function handleParse() {
+ if (!content.value?.trim()) {
+ message.warning('璇峰厛鎵爜鏋壂鍏ユ垨鎵嬪姩杈撳叆鏉$爜鍐呭');
+ return;
+ }
+ parsing.value = true;
+ try {
+ parseResult.value = await parseBarcodeContent(content.value.trim());
+ } finally {
+ parsing.value = false;
+ }
+}
+
+/** 鏈嶅姟绔垚鍥鹃瑙堬紙PNG Base64锛屼笉钀藉簱锛� */
+async function handleRender() {
+ if (!content.value?.trim()) {
+ message.warning('璇峰厛鎵爜鏋壂鍏ユ垨鎵嬪姩杈撳叆鏉$爜鍐呭');
+ return;
+ }
+ rendering.value = true;
+ try {
+ const base64 = await renderBarcodeImage(
+ content.value.trim(),
+ parseResult.value?.format,
+ );
+ imageBase64.value = base64 ? `data:image/png;base64,${base64}` : '';
+ if (!base64) {
+ message.warning('鎴愬浘澶辫触锛岃妫�鏌ユ潯鐮佸唴瀹规槸鍚︾鍚堟墍閫夋牸寮�');
+ }
+ } finally {
+ rendering.value = false;
+ }
+}
+
+/** 鍖归厤鏂瑰紡鏂囨 */
+const matchedLabel = computed(() => {
+ if (parseResult.value?.matched) {
+ return '绮剧‘鍛戒腑';
+ }
+ if (parseResult.value?.fields) {
+ return '妯℃澘鍙嶈В';
+ }
+ return '鏈尮閰�';
+});
+const matchedColor = computed(() => {
+ if (parseResult.value?.matched) {
+ return 'green';
+ }
+ if (parseResult.value?.fields) {
+ return 'blue';
+ }
+ return 'orange';
+});
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+ <Modal v-model:open="open" title="鎵爜瑙f瀽 / 鏉$爜棰勮" width="720px">
+ <div class="space-y-4">
+ <div class="flex gap-2">
+ <Input
+ v-model:value="content"
+ placeholder="鎵爜鏋壂鍏ユ垨鎵嬪姩杈撳叆鏉$爜鍐呭锛屽洖杞﹁В鏋�"
+ @press-enter="handleParse"
+ />
+ <Button type="primary" :loading="parsing" @click="handleParse">
+ 瑙f瀽
+ </Button>
+ <Button :loading="rendering" @click="handleRender">鎴愬浘棰勮</Button>
+ </div>
+
+ <div v-if="parseResult">
+ <Divider orientation="left">瑙f瀽缁撴灉</Divider>
+ <Descriptions
+ v-if="parseResult.bizType != null || parseResult.bizCode != null"
+ bordered
+ :column="2"
+ size="small"
+ >
+ <Descriptions.Item label="鍖归厤鏂瑰紡">
+ <Tag :color="matchedColor">{{ matchedLabel }}</Tag>
+ </Descriptions.Item>
+ <Descriptions.Item v-if="parseResult.format != null" label="鏉$爜鏍煎紡">
+ <DictTag
+ :type="DICT_TYPE.MES_WM_BARCODE_FORMAT"
+ :value="parseResult.format"
+ />
+ </Descriptions.Item>
+ <Descriptions.Item v-if="parseResult.bizType != null" label="涓氬姟绫诲瀷">
+ <DictTag
+ :type="DICT_TYPE.MES_WM_BARCODE_BIZ_TYPE"
+ :value="parseResult.bizType"
+ />
+ </Descriptions.Item>
+ <Descriptions.Item v-if="parseResult.barcodeId != null" label="鏉$爜缂栧彿">
+ {{ parseResult.barcodeId }}
+ </Descriptions.Item>
+ <Descriptions.Item label="涓氬姟缂栫爜">
+ {{ parseResult.bizCode || '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item label="涓氬姟鍚嶇О">
+ {{ parseResult.bizName || '-' }}
+ </Descriptions.Item>
+ <Descriptions.Item v-if="parseResult.bizId != null" label="涓氬姟缂栧彿">
+ {{ parseResult.bizId }}
+ </Descriptions.Item>
+ </Descriptions>
+
+ <div v-if="parseResult.fields" class="mt-2">
+ <div class="mb-1 text-sm font-medium">妯℃澘瀛楁</div>
+ <div
+ v-for="(value, key) in parseResult.fields"
+ :key="key"
+ class="mb-1 flex items-center gap-2"
+ >
+ <Tag>{{ key }}</Tag>
+ <span>{{ value }}</span>
+ </div>
+ </div>
+ <Empty
+ v-else-if="parseResult.bizType == null && parseResult.bizCode == null"
+ description="鏈尮閰嶅埌鏉$爜璁板綍鎴栨潯鐮侀厤缃ā鏉�"
+ />
+ </div>
+
+ <div v-if="imageBase64">
+ <Divider orientation="left">鎴愬浘棰勮</Divider>
+ <div class="flex justify-center rounded bg-gray-100 p-4">
+ <Image :src="imageBase64" :width="220" />
+ </div>
+ </div>
+ </div>
+ <template #footer>
+ <Button @click="open = false">鍏抽棴</Button>
+ </template>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/barcode/index.vue b/src/views/wls/barcode/index.vue
index f549980..82b53c2 100644
--- a/src/views/wls/barcode/index.vue
+++ b/src/views/wls/barcode/index.vue
@@ -18,7 +18,7 @@
} from '#/api/mes/wm/barcode';
import { $t } from '#/locales';
-import { Barcode, BarcodeDetail } from './components';
+import { Barcode, BarcodeDetail, ScanModal } from './components';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
@@ -30,6 +30,12 @@
});
const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>();
+const scanModalRef = ref<InstanceType<typeof ScanModal>>();
+
+/** 鎵爜瑙f瀽 / 鎴愬浘棰勮 */
+function handleScan() {
+ scanModalRef.value?.open();
+}
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
@@ -145,12 +151,20 @@
<template>
<Page auto-content-height><FormModal @success="handleRefresh" />
<BarcodeDetail ref="barcodeDetailRef" />
+ <ScanModal ref="scanModalRef" />
<Grid table-title="鏉$爜鍒楄〃">
<template #toolbar-tools>
<TableAction
:actions="[
{
+ label: '鎵爜瑙f瀽',
+ type: 'primary',
+ icon: ACTION_ICON.SCAN,
+ auth: ['mes:wm-barcode:query'],
+ onClick: handleScan,
+ },
+ {
label: $t('ui.actionTitle.create', ['鏉$爜']),
type: 'primary',
icon: ACTION_ICON.ADD,
diff --git a/src/views/wls/components/process-select-modal.vue b/src/views/wls/components/process-select-modal.vue
new file mode 100644
index 0000000..d32d546
--- /dev/null
+++ b/src/views/wls/components/process-select-modal.vue
@@ -0,0 +1,83 @@
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message, Select } from 'ant-design-vue';
+
+interface ProcessDefinition {
+ id: string;
+ name: string;
+ key: string;
+}
+
+type ProcessSubmitFn = (id: number, processDefinitionKey: string) => Promise<unknown>;
+
+const emit = defineEmits(['success']);
+
+const bizId = ref<number>();
+const processList = ref<ProcessDefinition[]>([]);
+const selectedProcessKey = ref<string>('');
+
+const processOptions = computed(() => {
+ return processList.value.map((p) => ({
+ label: p.name,
+ value: p.key,
+ }));
+});
+
+const [Modal, modalApi] = useVbenModal({
+ async onConfirm() {
+ if (!selectedProcessKey.value) {
+ message.warning('璇烽�夋嫨瀹℃壒娴佺▼');
+ return;
+ }
+ modalApi.lock();
+ try {
+ await modalApi.getData<{ submitFn: ProcessSubmitFn }>().submitFn(
+ bizId.value!,
+ selectedProcessKey.value,
+ );
+ await modalApi.close();
+ emit('success');
+ message.success('鎻愪氦瀹℃壒鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ bizId.value = undefined;
+ selectedProcessKey.value = '';
+ processList.value = [];
+ return;
+ }
+ const data = modalApi.getData<{
+ id: number;
+ processList: ProcessDefinition[];
+ submitFn: ProcessSubmitFn;
+ }>();
+ bizId.value = data.id;
+ processList.value = data.processList || [];
+ // 榛樿閫夋嫨绗竴涓祦绋�
+ const first = processList.value[0];
+ if (first) {
+ selectedProcessKey.value = first.key;
+ }
+ },
+});
+</script>
+
+<template>
+ <Modal title="閫夋嫨瀹℃壒娴佺▼" class="w-1/3">
+ <div class="mb-4">
+ <span class="mr-2">瀹℃壒娴佺▼锛�</span>
+ <Select
+ v-model:value="selectedProcessKey"
+ :options="processOptions"
+ style="width: 200px"
+ placeholder="璇烽�夋嫨瀹℃壒娴佺▼"
+ />
+ </div>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/equipmentreceipt/data.ts b/src/views/wls/equipmentreceipt/data.ts
new file mode 100644
index 0000000..d2db989
--- /dev/null
+++ b/src/views/wls/equipmentreceipt/data.ts
@@ -0,0 +1,342 @@
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmEquipmentReceiptApi } from '#/api/mes/wm/equipmentreceipt';
+import type { MesWmEquipmentReceiptLineApi } from '#/api/mes/wm/equipmentreceipt/line';
+
+import { h, markRaw } from 'vue';
+
+import { MesAutoCodeRuleCode, MesWmEquipmentReceiptStatusEnum } from '@vben/constants';
+
+import { Button } from 'ant-design-vue';
+
+import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { getRangePickerDefaultProps } from '#/utils';
+import { DvMachinerySelect } from '#/views/mes/dv/machinery/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'finish' | 'update';
+
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堣鎯呫�佹墽琛屽叆搴撴�侊級 */
+function isHeaderReadonly(formType: FormType): boolean {
+ return formType === 'detail' || formType === 'finish';
+}
+
+/** 璁惧鍏ュ簱鍗曠姸鎬佸睍绀烘槧灏� */
+export const EQUIPMENT_RECEIPT_STATUS_MAP: Record<
+ number,
+ { text: string; color: string }
+> = {
+ [MesWmEquipmentReceiptStatusEnum.PREPARE]: {
+ text: '鑽夌',
+ color: 'default',
+ },
+ [MesWmEquipmentReceiptStatusEnum.APPROVING]: {
+ text: '瀹℃壒涓�',
+ color: 'processing',
+ },
+ [MesWmEquipmentReceiptStatusEnum.APPROVED]: {
+ text: '宸插鎵�',
+ color: 'success',
+ },
+ [MesWmEquipmentReceiptStatusEnum.FINISHED]: {
+ text: '宸插畬鎴�',
+ color: 'cyan',
+ },
+ [MesWmEquipmentReceiptStatusEnum.CANCELED]: {
+ text: '宸插彇娑�',
+ color: 'warning',
+ },
+};
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+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_EQUIP_RECEIPT_CODE,
+ );
+ await formApi?.setFieldValue('code', code);
+ },
+ },
+ { default: () => '鐢熸垚' },
+ ),
+ },
+ {
+ fieldName: 'name',
+ label: '鍗曟嵁鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ崟鎹悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'source',
+ 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: 'source',
+ 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: Object.entries(EQUIPMENT_RECEIPT_STATUS_MAP).map(
+ ([value, item]) => ({
+ label: item.text,
+ value: Number(value),
+ }),
+ ),
+ placeholder: '璇烽�夋嫨鍗曟嵁鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmEquipmentReceiptApi.EquipmentReceipt>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍗曟嵁缂栫爜',
+ minWidth: 160,
+ slots: { default: 'code' },
+ },
+ {
+ field: 'name',
+ title: '鍗曟嵁鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'source',
+ title: '鍏ュ簱鏉ユ簮',
+ minWidth: 120,
+ },
+ {
+ field: 'receiptDate',
+ title: '鍏ュ簱鏃ユ湡',
+ width: 180,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍗曟嵁鐘舵��',
+ minWidth: 100,
+ slots: { default: 'status' },
+ },
+ {
+ title: '鎿嶄綔',
+ width: 240,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** 璁惧鍏ュ簱鍗曡瀛愯〃鐨勫瓧娈� */
+export function useLineGridColumns(
+ editable = true,
+): VxeTableGridOptions<MesWmEquipmentReceiptLineApi.EquipmentReceiptLine>['columns'] {
+ return [
+ {
+ field: 'machineryCode',
+ title: '璁惧缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'machineryName',
+ title: '璁惧鍚嶇О',
+ minWidth: 140,
+ },
+ {
+ 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: 'remark',
+ title: '澶囨敞',
+ minWidth: 120,
+ },
+ ...(editable
+ ? [
+ {
+ title: '鎿嶄綔',
+ width: 160,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ } as const,
+ ]
+ : []),
+ ];
+}
+
+/** 璁惧鍏ュ簱鍗曡鏂板/淇敼鐨勮〃鍗� */
+export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'itemId',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'machineryId',
+ label: '璁惧',
+ component: markRaw(DvMachinerySelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨璁惧',
+ onChange: async (machinery?: { itemId?: number }) => {
+ await formApi?.setFieldValue('itemId', machinery?.itemId);
+ },
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'quantity',
+ label: '鍏ュ簱鏁伴噺',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 0.01,
+ defaultValue: 1,
+ placeholder: '璇疯緭鍏ュ叆搴撴暟閲�',
+ precision: 2,
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
\ No newline at end of file
diff --git a/src/views/wls/equipmentreceipt/index.vue b/src/views/wls/equipmentreceipt/index.vue
new file mode 100644
index 0000000..fe1d8a0
--- /dev/null
+++ b/src/views/wls/equipmentreceipt/index.vue
@@ -0,0 +1,243 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmEquipmentReceiptApi } from '#/api/mes/wm/equipmentreceipt';
+
+import { useRouter } from 'vue-router';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { MesWmEquipmentReceiptStatusEnum } from '@vben/constants';
+
+import { Button, message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ cancelEquipmentReceipt,
+ deleteEquipmentReceipt,
+ getEquipmentReceiptApproveProcessList,
+ getEquipmentReceiptPage,
+ submitEquipmentReceiptApproval,
+} from '#/api/mes/wm/equipmentreceipt';
+import { $t } from '#/locales';
+
+import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
+import { EQUIPMENT_RECEIPT_STATUS_MAP, useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const router = useRouter();
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+ connectedComponent: ProcessSelectModal,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓璁惧鍏ュ簱鍗� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅璁惧鍏ュ簱鍗� */
+function handleDetail(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
+ formModalApi.setData({ formType: 'detail', id: row.id }).open();
+}
+
+/** 缂栬緫璁惧鍏ュ簱鍗� */
+function handleEdit(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
+ formModalApi.setData({ formType: 'update', id: row.id }).open();
+}
+
+/** 鎵ц鍏ュ簱 */
+function handleFinish(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
+ formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愪氦瀹℃壒 - 鍏堣幏鍙栨祦绋嬪垪琛� */
+async function handleSubmitApproval(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
+ const hideLoading = message.loading({
+ content: '鑾峰彇瀹℃壒娴佺▼...',
+ duration: 0,
+ });
+ try {
+ const processList = await getEquipmentReceiptApproveProcessList();
+ hideLoading();
+ if (!processList || processList.length === 0) {
+ message.warning('鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堥厤缃� BPM 娴佺▼');
+ return;
+ }
+ processModalApi
+ .setData({
+ id: row.id,
+ processList,
+ submitFn: submitEquipmentReceiptApproval,
+ })
+ .open();
+ } catch {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅瀹℃壒璇︽儏 */
+function handleProcessDetail(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
+ if (!row.processInstanceId) {
+ message.warning('璇ュ崟鎹皻鏈彁浜ゅ鎵�');
+ return;
+ }
+ router.push({
+ name: 'BpmProcessInstanceDetail',
+ query: { id: row.processInstanceId },
+ });
+}
+
+/** 鍒犻櫎璁惧鍏ュ簱鍗� */
+async function handleDelete(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.code]),
+ duration: 0,
+ });
+ try {
+ await deleteEquipmentReceipt(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 鍙栨秷璁惧鍏ュ簱鍗� */
+async function handleCancel(row: MesWmEquipmentReceiptApi.EquipmentReceipt) {
+ await cancelEquipmentReceipt(row.id!);
+ message.success('鍙栨秷鎴愬姛');
+ handleRefresh();
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getEquipmentReceiptPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmEquipmentReceiptApi.EquipmentReceipt>,
+});
+
+const StatusEnum = MesWmEquipmentReceiptStatusEnum;
+</script>
+
+<template>
+ <Page auto-content-height><FormModal @success="handleRefresh" />
+ <ProcessModal @success="handleRefresh" />
+
+ <Grid table-title="璁惧鍏ュ簱鍗曞垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: $t('ui.actionTitle.create', ['璁惧鍏ュ簱鍗�']),
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm:equipment-receipt:create'],
+ onClick: handleCreate,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #status="{ row }">
+ <Tag :color="EQUIPMENT_RECEIPT_STATUS_MAP[row.status ?? 0]?.color">
+ {{ EQUIPMENT_RECEIPT_STATUS_MAP[row.status ?? 0]?.text ?? row.status }}
+ </Tag>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:wm:equipment-receipt:update'],
+ ifShow: row.status === StatusEnum.PREPARE,
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm:equipment-receipt: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:equipment-receipt:submit'],
+ ifShow: row.status === StatusEnum.PREPARE,
+ onClick: handleSubmitApproval.bind(null, row),
+ },
+ {
+ label: '鏌ョ湅瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm:equipment-receipt:query'],
+ ifShow: row.status === StatusEnum.APPROVING,
+ onClick: handleProcessDetail.bind(null, row),
+ },
+ {
+ label: '鎵ц鍏ュ簱',
+ type: 'link',
+ auth: ['mes:wm:equipment-receipt:finish'],
+ ifShow: row.status === StatusEnum.APPROVED,
+ onClick: handleFinish.bind(null, row),
+ },
+ {
+ label: '鍙栨秷',
+ type: 'link',
+ danger: true,
+ auth: ['mes:wm:equipment-receipt:cancel'],
+ ifShow: row.status === StatusEnum.APPROVED,
+ popConfirm: {
+ title: '纭鍙栨秷璇ヨ澶囧叆搴撳崟鍚楋紵',
+ confirm: handleCancel.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/equipmentreceipt/modules/form.vue b/src/views/wls/equipmentreceipt/modules/form.vue
new file mode 100644
index 0000000..1a8f0f8
--- /dev/null
+++ b/src/views/wls/equipmentreceipt/modules/form.vue
@@ -0,0 +1,163 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesWmEquipmentReceiptApi } from '#/api/mes/wm/equipmentreceipt';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { MesWmEquipmentReceiptStatusEnum } from '@vben/constants';
+
+import { Button, Divider, message, Popconfirm } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createEquipmentReceipt,
+ finishEquipmentReceipt,
+ getEquipmentReceipt,
+ updateEquipmentReceipt,
+} from '#/api/mes/wm/equipmentreceipt';
+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<MesWmEquipmentReceiptApi.EquipmentReceipt>();
+const originalSnapshot = ref(''); // 琛ㄥ崟鍘熷鏁版嵁蹇収锛岀敤浜庢彁浜ゆ椂璺宠繃鏈彉鏇寸殑淇濆瓨璇锋眰
+const isEditable = computed(() =>
+ // 鏄惁涓虹紪杈戞ā寮忥紙鍙繚瀛橈級
+ ['create', 'update'].includes(formType.value),
+);
+const isFinish = computed(() => formType.value === 'finish'); // 鏄惁涓烘墽琛屽叆搴撴ā寮�
+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 handleFinish() {
+ if (!formData.value?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ await finishEquipmentReceipt(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 MesWmEquipmentReceiptApi.EquipmentReceipt;
+ try {
+ if (formData.value?.id) {
+ await updateEquipmentReceipt({ ...formData.value, ...data });
+ formData.value = { ...formData.value, ...data };
+ } else {
+ const id = await createEquipmentReceipt(data);
+ formData.value = {
+ ...data,
+ id,
+ status: MesWmEquipmentReceiptStatusEnum.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 getEquipmentReceipt(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="isFinish"
+ title="纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗚澶囪鍏ヨ澶囦粨搴撳瓨鍙拌处銆�"
+ @confirm="handleFinish"
+ >
+ <Button type="primary">鎵ц鍏ュ簱</Button>
+ </Popconfirm>
+ </div>
+ </template>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/equipmentreceipt/modules/line-form.vue b/src/views/wls/equipmentreceipt/modules/line-form.vue
new file mode 100644
index 0000000..838324c
--- /dev/null
+++ b/src/views/wls/equipmentreceipt/modules/line-form.vue
@@ -0,0 +1,98 @@
+<script lang="ts" setup>
+import type { MesWmEquipmentReceiptLineApi } from '#/api/mes/wm/equipmentreceipt/line';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createEquipmentReceiptLine,
+ getEquipmentReceiptLine,
+ updateEquipmentReceiptLine,
+} from '#/api/mes/wm/equipmentreceipt/line';
+import { $t } from '#/locales';
+
+import { useLineFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<MesWmEquipmentReceiptLineApi.EquipmentReceiptLine>();
+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 MesWmEquipmentReceiptLineApi.EquipmentReceiptLine;
+ data.receiptId = receiptId.value;
+ try {
+ await (formData.value?.id
+ ? updateEquipmentReceiptLine({ ...data, id: formData.value.id })
+ : createEquipmentReceiptLine(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 getEquipmentReceiptLine(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>
\ No newline at end of file
diff --git a/src/views/wls/equipmentreceipt/modules/line-list.vue b/src/views/wls/equipmentreceipt/modules/line-list.vue
new file mode 100644
index 0000000..e156dab
--- /dev/null
+++ b/src/views/wls/equipmentreceipt/modules/line-list.vue
@@ -0,0 +1,142 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmEquipmentReceiptLineApi } from '#/api/mes/wm/equipmentreceipt/line';
+
+import { computed } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteEquipmentReceiptLine,
+ getEquipmentReceiptLineListByReceiptId,
+} from '#/api/mes/wm/equipmentreceipt/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: MesWmEquipmentReceiptLineApi.EquipmentReceiptLine) {
+ lineFormModalApi.setData({ id: row.id, receiptId: props.receiptId }).open();
+}
+
+/** 鍒犻櫎璁惧 */
+async function handleDelete(row: MesWmEquipmentReceiptLineApi.EquipmentReceiptLine) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.machineryName]),
+ duration: 0,
+ });
+ try {
+ await deleteEquipmentReceiptLine(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.machineryName]));
+ 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 getEquipmentReceiptLineListByReceiptId(
+ props.receiptId,
+ );
+ return { list, total: list.length };
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ },
+ } as VxeTableGridOptions<MesWmEquipmentReceiptLineApi.EquipmentReceiptLine>,
+});
+</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.machineryName]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </div>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/miscissue/index.vue b/src/views/wls/miscissue/index.vue
index 90406ca..382faf1 100644
--- a/src/views/wls/miscissue/index.vue
+++ b/src/views/wls/miscissue/index.vue
@@ -2,6 +2,8 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmMiscIssueApi } from '#/api/mes/wm/miscissue';
+import { useRouter } from 'vue-router';
+
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmMiscIssueStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
@@ -13,15 +15,25 @@
cancelMiscIssue,
deleteMiscIssue,
exportMiscIssue,
+ getMiscIssueApproveProcessList,
getMiscIssuePage,
+ submitMiscIssueApproval,
} from '#/api/mes/wm/miscissue';
import { $t } from '#/locales';
+import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
+const router = useRouter();
+
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+ connectedComponent: ProcessSelectModal,
destroyOnClose: true,
});
@@ -48,6 +60,43 @@
/** 鎵ц鍑哄簱 */
function handleFinish(row: MesWmMiscIssueApi.MiscIssue) {
formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愪氦瀹℃壒 - 鍏堣幏鍙栨祦绋嬪垪琛� */
+async function handleSubmitApproval(row: MesWmMiscIssueApi.MiscIssue) {
+ const hideLoading = message.loading({
+ content: '鑾峰彇瀹℃壒娴佺▼...',
+ duration: 0,
+ });
+ try {
+ const processList = await getMiscIssueApproveProcessList();
+ hideLoading();
+ if (!processList || processList.length === 0) {
+ message.warning('鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堥厤缃� BPM 娴佺▼');
+ return;
+ }
+ processModalApi
+ .setData({
+ id: row.id,
+ processList,
+ submitFn: submitMiscIssueApproval,
+ })
+ .open();
+ } catch {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅瀹℃壒璇︽儏 */
+function handleProcessDetail(row: MesWmMiscIssueApi.MiscIssue) {
+ if (!row.processInstanceId) {
+ message.warning('璇ュ崟鎹皻鏈彁浜ゅ鎵�');
+ return;
+ }
+ router.push({
+ name: 'BpmProcessInstanceDetail',
+ query: { id: row.processInstanceId },
+ });
}
/** 鍒犻櫎鏉傞」鍑哄簱鍗� */
@@ -111,6 +160,7 @@
<template>
<Page auto-content-height><FormModal @success="handleRefresh" />
+ <ProcessModal @success="handleRefresh" />
<Grid table-title="鏉傞」鍑哄簱鍗曞垪琛�">
<template #toolbar-tools>
@@ -162,6 +212,20 @@
},
},
{
+ label: '鎻愪氦瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm-misc-issue:update'],
+ ifShow: row.status === MesWmMiscIssueStatusEnum.PREPARE,
+ onClick: handleSubmitApproval.bind(null, row),
+ },
+ {
+ label: '鏌ョ湅瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm-misc-issue:query'],
+ ifShow: row.status === MesWmMiscIssueStatusEnum.APPROVING,
+ onClick: handleProcessDetail.bind(null, row),
+ },
+ {
label: '鎵ц鍑哄簱',
type: 'link',
auth: ['mes:wm-misc-issue:finish'],
diff --git a/src/views/wls/miscissue/modules/form.vue b/src/views/wls/miscissue/modules/form.vue
index 3883b97..61f126a 100644
--- a/src/views/wls/miscissue/modules/form.vue
+++ b/src/views/wls/miscissue/modules/form.vue
@@ -15,7 +15,6 @@
createMiscIssue,
finishMiscIssue,
getMiscIssue,
- submitMiscIssue,
updateMiscIssue,
} from '#/api/mes/wm/miscissue';
import { $t } from '#/locales';
@@ -32,12 +31,6 @@
['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', ['鏉傞」鍑哄簱鍗�']);
@@ -63,29 +56,6 @@
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() {
@@ -179,13 +149,6 @@
</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="纭鎵ц鍑哄簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��"
diff --git a/src/views/wls/miscreceipt/index.vue b/src/views/wls/miscreceipt/index.vue
index b9d2d2f..03a6f12 100644
--- a/src/views/wls/miscreceipt/index.vue
+++ b/src/views/wls/miscreceipt/index.vue
@@ -2,6 +2,8 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmMiscReceiptApi } from '#/api/mes/wm/miscreceipt';
+import { useRouter } from 'vue-router';
+
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmMiscReceiptStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
@@ -13,15 +15,25 @@
cancelMiscReceipt,
deleteMiscReceipt,
exportMiscReceipt,
+ getMiscReceiptApproveProcessList,
getMiscReceiptPage,
+ submitMiscReceiptApproval,
} from '#/api/mes/wm/miscreceipt';
import { $t } from '#/locales';
+import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
+const router = useRouter();
+
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+ connectedComponent: ProcessSelectModal,
destroyOnClose: true,
});
@@ -48,6 +60,43 @@
/** 鎵ц鍏ュ簱 */
function handleFinish(row: MesWmMiscReceiptApi.MiscReceipt) {
formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鎻愪氦瀹℃壒 - 鍏堣幏鍙栨祦绋嬪垪琛� */
+async function handleSubmitApproval(row: MesWmMiscReceiptApi.MiscReceipt) {
+ const hideLoading = message.loading({
+ content: '鑾峰彇瀹℃壒娴佺▼...',
+ duration: 0,
+ });
+ try {
+ const processList = await getMiscReceiptApproveProcessList();
+ hideLoading();
+ if (!processList || processList.length === 0) {
+ message.warning('鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堥厤缃� BPM 娴佺▼');
+ return;
+ }
+ processModalApi
+ .setData({
+ id: row.id,
+ processList,
+ submitFn: submitMiscReceiptApproval,
+ })
+ .open();
+ } catch {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅瀹℃壒璇︽儏 */
+function handleProcessDetail(row: MesWmMiscReceiptApi.MiscReceipt) {
+ if (!row.processInstanceId) {
+ message.warning('璇ュ崟鎹皻鏈彁浜ゅ鎵�');
+ return;
+ }
+ router.push({
+ name: 'BpmProcessInstanceDetail',
+ query: { id: row.processInstanceId },
+ });
}
/** 鍒犻櫎鏉傞」鍏ュ簱鍗� */
@@ -111,6 +160,7 @@
<template>
<Page auto-content-height><FormModal @success="handleRefresh" />
+ <ProcessModal @success="handleRefresh" />
<Grid table-title="鏉傞」鍏ュ簱鍗曞垪琛�">
<template #toolbar-tools>
@@ -162,6 +212,20 @@
},
},
{
+ label: '鎻愪氦瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm:misc-receipt:submit'],
+ ifShow: row.status === MesWmMiscReceiptStatusEnum.PREPARE,
+ onClick: handleSubmitApproval.bind(null, row),
+ },
+ {
+ label: '鏌ョ湅瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm:misc-receipt:query'],
+ ifShow: row.status === MesWmMiscReceiptStatusEnum.APPROVING,
+ onClick: handleProcessDetail.bind(null, row),
+ },
+ {
label: '鎵ц鍏ュ簱',
type: 'link',
auth: ['mes:wm-misc-receipt:finish'],
diff --git a/src/views/wls/miscreceipt/modules/form.vue b/src/views/wls/miscreceipt/modules/form.vue
index cfab99d..435f58c 100644
--- a/src/views/wls/miscreceipt/modules/form.vue
+++ b/src/views/wls/miscreceipt/modules/form.vue
@@ -15,7 +15,6 @@
createMiscReceipt,
finishMiscReceipt,
getMiscReceipt,
- submitMiscReceipt,
updateMiscReceipt,
} from '#/api/mes/wm/miscreceipt';
import { $t } from '#/locales';
@@ -32,12 +31,6 @@
['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', ['鏉傞」鍏ュ簱鍗�']);
@@ -63,30 +56,6 @@
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() {
@@ -180,13 +149,6 @@
</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="纭鎵ц鍏ュ簱锛熸墽琛屽悗灏嗘洿鏂板簱瀛樺彴璐︺��"
diff --git a/src/views/wls/returnvendor/index.vue b/src/views/wls/returnvendor/index.vue
index 895e494..62d5716 100644
--- a/src/views/wls/returnvendor/index.vue
+++ b/src/views/wls/returnvendor/index.vue
@@ -2,6 +2,8 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmReturnVendorApi } from '#/api/mes/wm/returnvendor';
+import { useRouter } from 'vue-router';
+
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmReturnVendorStatusEnum } from '@vben/constants';
import { downloadFileFromBlobPart } from '@vben/utils';
@@ -13,15 +15,25 @@
cancelReturnVendor,
deleteReturnVendor,
exportReturnVendor,
+ getReturnVendorApproveProcessList,
getReturnVendorPage,
+ submitReturnVendorApproval,
} from '#/api/mes/wm/returnvendor';
import { $t } from '#/locales';
+import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
+const router = useRouter();
+
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+ connectedComponent: ProcessSelectModal,
destroyOnClose: true,
});
@@ -48,6 +60,43 @@
/** 鎵ц鎷h揣 */
function handleStock(row: MesWmReturnVendorApi.ReturnVendor) {
formModalApi.setData({ formType: 'stock', id: row.id }).open();
+}
+
+/** 鎻愪氦瀹℃壒 - 鍏堣幏鍙栨祦绋嬪垪琛� */
+async function handleSubmitApproval(row: MesWmReturnVendorApi.ReturnVendor) {
+ const hideLoading = message.loading({
+ content: '鑾峰彇瀹℃壒娴佺▼...',
+ duration: 0,
+ });
+ try {
+ const processList = await getReturnVendorApproveProcessList();
+ hideLoading();
+ if (!processList || processList.length === 0) {
+ message.warning('鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堥厤缃� BPM 娴佺▼');
+ return;
+ }
+ processModalApi
+ .setData({
+ id: row.id,
+ processList,
+ submitFn: submitReturnVendorApproval,
+ })
+ .open();
+ } catch {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅瀹℃壒璇︽儏 */
+function handleProcessDetail(row: MesWmReturnVendorApi.ReturnVendor) {
+ if (!row.processInstanceId) {
+ message.warning('璇ュ崟鎹皻鏈彁浜ゅ鎵�');
+ return;
+ }
+ router.push({
+ name: 'BpmProcessInstanceDetail',
+ query: { id: row.processInstanceId },
+ });
}
/** 瀹屾垚閫�璐� */
@@ -116,6 +165,7 @@
<template>
<Page auto-content-height><FormModal @success="handleRefresh" />
+ <ProcessModal @success="handleRefresh" />
<Grid table-title="渚涘簲鍟嗛��璐у崟鍒楄〃">
<template #toolbar-tools>
@@ -147,6 +197,13 @@
<TableAction
:actions="[
{
+ label: '鎻愪氦瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm-return-vendor:update'],
+ ifShow: row.status === MesWmReturnVendorStatusEnum.PREPARE,
+ onClick: handleSubmitApproval.bind(null, row),
+ },
+ {
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
@@ -167,6 +224,13 @@
},
},
{
+ label: '鏌ョ湅瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm-return-vendor:query'],
+ ifShow: row.status === MesWmReturnVendorStatusEnum.APPROVING,
+ onClick: handleProcessDetail.bind(null, row),
+ },
+ {
label: '鎵ц鎷h揣',
type: 'link',
auth: ['mes:wm-return-vendor:update'],
diff --git a/src/views/wls/returnvendor/modules/form.vue b/src/views/wls/returnvendor/modules/form.vue
index 252e785..ebca15a 100644
--- a/src/views/wls/returnvendor/modules/form.vue
+++ b/src/views/wls/returnvendor/modules/form.vue
@@ -18,7 +18,6 @@
finishReturnVendor,
getReturnVendor,
stockReturnVendor,
- submitReturnVendor,
updateReturnVendor,
} from '#/api/mes/wm/returnvendor';
import { $t } from '#/locales';
@@ -36,12 +35,6 @@
);
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', ['渚涘簲鍟嗛��璐у崟']);
@@ -80,30 +73,6 @@
return formatDate(value, 'YYYY-MM-DD HH:mm:ss');
}
return undefined;
-}
-
-/** 鎻愪氦閫�璐у崟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
-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揣锛氭嫞璐ф暟閲忎笌閫�璐ф暟閲忎笉涓�鑷存椂浜屾纭 */
@@ -228,13 +197,6 @@
</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>
diff --git a/src/views/wls/sn/data.ts b/src/views/wls/sn/data.ts
index 2023366..880c7a0 100644
--- a/src/views/wls/sn/data.ts
+++ b/src/views/wls/sn/data.ts
@@ -1,11 +1,26 @@
-import type { VbenFormSchema } from '#/adapter/form';
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmSnApi } from '#/api/mes/wm/sn';
+import type { DescriptionItemSchema } from '#/components/description';
import { markRaw } from 'vue';
+import { MesWmSnStatusEnum } from '@vben/constants';
+import { formatDateTime } from '@vben/utils';
+
import { getRangePickerDefaultProps } from '#/utils';
import { MdItemSelect } from '#/views/mes/md/item/components';
+import {
+ WmWarehouseAreaSelect,
+ WmWarehouseLocationSelect,
+ WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+/** SN 鐘舵�侀�夐」锛堟棤鐙珛瀛楀吀锛屼娇鐢ㄥ父閲忔槧灏勶級 */
+export const SN_STATUS_OPTIONS = [
+ { label: '鍦ㄥ簱', value: MesWmSnStatusEnum.IN_STOCK },
+ { label: '宸插嚭搴�', value: MesWmSnStatusEnum.OUT_STOCK },
+];
/** 鐢熸垚 SN 鐮佺殑琛ㄥ崟 */
export function useFormSchema(): VbenFormSchema[] {
@@ -181,3 +196,275 @@
},
];
}
+
+/** 鍦ㄥ簱鍗曚欢鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useInStockGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: 'SN 鐮�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏� SN 鐮�',
+ },
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: SN_STATUS_OPTIONS,
+ placeholder: '璇烽�夋嫨鐘舵��',
+ },
+ },
+ ];
+}
+
+/** 鍦ㄥ簱鍗曚欢鍒楄〃鐨勫瓧娈� */
+export function useInStockGridColumns(): VxeTableGridOptions<MesWmSnApi.Sn>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: 'SN 鐮�',
+ minWidth: 180,
+ },
+ {
+ field: 'itemCode',
+ title: '鐗╂枡缂栫爜',
+ minWidth: 120,
+ },
+ {
+ field: 'itemName',
+ title: '鐗╂枡鍚嶇О',
+ minWidth: 150,
+ },
+ {
+ field: 'specification',
+ title: '瑙勬牸鍨嬪彿',
+ minWidth: 120,
+ },
+ {
+ field: 'unitName',
+ title: '鍗曚綅',
+ width: 80,
+ },
+ {
+ field: 'batchCode',
+ title: '鎵规鍙�',
+ minWidth: 120,
+ },
+ {
+ field: 'status',
+ title: '鐘舵��',
+ width: 90,
+ slots: { default: 'status' },
+ },
+ {
+ field: 'warehouseId',
+ title: '浠撳簱',
+ minWidth: 140,
+ slots: { default: 'locations' },
+ },
+ {
+ field: 'createTime',
+ title: '鐢熸垚鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 140,
+ fixed: 'right',
+ slots: { default: 'actions' },
+ },
+ ];
+}
+
+/** SN 鐮佸叆搴撶櫥璁扮殑琛ㄥ崟 */
+export function useInStockFormSchema(
+ formApi?: VbenFormApi,
+): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: 'SN 鐮�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '鎵爜鏋壂鍏ユ垨杈撳叆 SN 鐮�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡锛堥』鍚敤搴忓垪鍙风鐞嗭級',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'warehouseId',
+ label: '浠撳簱',
+ component: markRaw(WmWarehouseSelect),
+ componentProps: {
+ onChange: () =>
+ formApi?.setValues({ areaId: undefined, locationId: undefined }),
+ placeholder: '璇烽�夋嫨浠撳簱',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'locationId',
+ label: '搴撳尯',
+ component: markRaw(WmWarehouseLocationSelect),
+ dependencies: {
+ triggerFields: ['warehouseId'],
+ show: (values) => !!values.warehouseId,
+ componentProps: (values) => ({
+ onChange: () => formApi?.setFieldValue('areaId', undefined),
+ placeholder: '璇烽�夋嫨搴撳尯',
+ warehouseId: values.warehouseId,
+ }),
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'areaId',
+ label: '搴撲綅',
+ component: markRaw(WmWarehouseAreaSelect),
+ dependencies: {
+ triggerFields: ['locationId'],
+ show: (values) => !!values.locationId,
+ componentProps: (values) => ({
+ locationId: values.locationId,
+ placeholder: '璇烽�夋嫨搴撲綅',
+ }),
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'batchCode',
+ label: '鎵规鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ壒娆″彿',
+ },
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 2,
+ },
+ },
+ ];
+}
+
+/** SN 鐮佸嚭搴撴牳閿�鐨勮〃鍗� */
+export function useOutStockFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: 'SN 鐮�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '鎵爜鏋壂鍏ユ垨杈撳叆 SN 鐮�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'itemId',
+ label: '鐗╂枡',
+ component: markRaw(MdItemSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨鐗╂枡',
+ },
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-2',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 2,
+ },
+ },
+ ];
+}
+
+/** SN 鐮佸崟浠惰鎯呯殑鎻忚堪瀛楁 */
+export function useSnDetailSchema(): DescriptionItemSchema[] {
+ return [
+ {
+ field: 'code',
+ label: 'SN 鐮�',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'itemCode',
+ label: '鐗╂枡缂栫爜',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'itemName',
+ label: '鐗╂枡鍚嶇О',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'batchCode',
+ label: '鎵规鍙�',
+ render: (value) => value || '-',
+ },
+ {
+ field: 'status',
+ label: '鐘舵��',
+ render: (value) =>
+ value === MesWmSnStatusEnum.OUT_STOCK
+ ? '宸插嚭搴�'
+ : value === MesWmSnStatusEnum.IN_STOCK
+ ? '鍦ㄥ簱'
+ : '-',
+ },
+ {
+ field: 'warehouseId',
+ label: '鍦ㄥ簱浣嶇疆',
+ slot: 'location',
+ },
+ {
+ field: 'lastTransactionTypeName',
+ label: '鏈�杩戞祦姘�',
+ slot: 'lastTransaction',
+ },
+ {
+ field: 'lastTransactionTime',
+ label: '鏈�杩戞祦姘存椂闂�',
+ render: (value) => formatDateTime(value) || '-',
+ },
+ ];
+}
diff --git a/src/views/wls/sn/index.vue b/src/views/wls/sn/index.vue
index c881303..f1c5572 100644
--- a/src/views/wls/sn/index.vue
+++ b/src/views/wls/sn/index.vue
@@ -2,10 +2,12 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MesWmSnApi } from '#/api/mes/wm/sn';
+import { ref } from 'vue';
+
import { Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart } from '@vben/utils';
-import { message } from 'ant-design-vue';
+import { message, Tabs, Tag } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -13,12 +15,28 @@
exportSnDetailExcel,
exportSnGroupExcel,
getSnGroupPage,
+ getSnInStockPage,
} from '#/api/mes/wm/sn';
import { $t } from '#/locales';
-import { useGridColumns, useGridFormSchema } from './data';
+import {
+ useGridColumns,
+ useGridFormSchema,
+ useInStockGridColumns,
+ useInStockGridFormSchema,
+} from './data';
+import {
+ formatWmLocation,
+ getWmLocationMaps,
+ type WmLocationMaps,
+} from './location-maps';
import Detail from './modules/detail.vue';
import Form from './modules/form.vue';
+import InStockModal from './modules/in-stock-modal.vue';
+import OutStockModal from './modules/out-stock-modal.vue';
+import SnDetailModal from './modules/sn-detail-modal.vue';
+
+const activeKey = ref('group');
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
@@ -30,9 +48,25 @@
destroyOnClose: true,
});
-/** 鍒锋柊琛ㄦ牸 */
+const [InStockModalRef, inStockModalApi] = useVbenModal({
+ connectedComponent: InStockModal,
+ destroyOnClose: true,
+});
+
+const [OutStockModalRef, outStockModalApi] = useVbenModal({
+ connectedComponent: OutStockModal,
+ destroyOnClose: true,
+});
+
+const snDetailModalRef = ref<InstanceType<typeof SnDetailModal>>();
+
+/** 鍒锋柊褰撳墠椤电 */
function handleRefresh() {
- gridApi.query();
+ if (activeKey.value === 'group') {
+ gridApi.query();
+ } else {
+ inStockGridApi.query();
+ }
}
/** 鐢熸垚 SN 鐮� */
@@ -74,6 +108,34 @@
}
}
+/** 鍏ュ簱鐧昏 */
+function handleInStock() {
+ inStockModalApi.open();
+}
+
+/** 鍑哄簱鏍搁攢 */
+function handleOutStock() {
+ outStockModalApi.open();
+}
+
+/** 鏌ョ湅鍗曚欢璇︽儏 */
+function handleSnDetail(row: MesWmSnApi.Sn) {
+ snDetailModalRef.value?.open(row.code);
+}
+
+/** 鍦ㄥ簱鍗曚欢鍒楄〃浣嶇疆鍚嶇О鏄犲皠锛堟噿鍔犺浇锛岄娆¤闂椂鍒濆鍖栵級 */
+const locationMapsRef = ref<Awaited<ReturnType<typeof getWmLocationMaps>>>();
+const EMPTY_MAPS: WmLocationMaps = {
+ warehouseNameMap: {},
+ locationNameMap: {},
+ areaNameMap: {},
+};
+async function ensureLocationMaps() {
+ if (!locationMapsRef.value) {
+ locationMapsRef.value = await getWmLocationMaps();
+ }
+}
+
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
@@ -103,75 +165,171 @@
},
} as VxeTableGridOptions<MesWmSnApi.SnGroup>,
});
+
+const [InStockGrid, inStockGridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useInStockGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useInStockGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getSnInStockPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesWmSnApi.Sn>,
+});
+
+// 棣栨鎸傝浇鏃堕鐑綅缃悕绉版槧灏勶紝渚涘垪琛ㄥ睍绀�
+void ensureLocationMaps();
</script>
<template>
- <Page auto-content-height><FormModal @success="handleRefresh" />
+ <Page auto-content-height>
+ <FormModal @success="handleRefresh" />
<DetailModal />
+ <InStockModalRef @success="handleRefresh" />
+ <OutStockModalRef @success="handleRefresh" />
+ <SnDetailModal ref="snDetailModalRef" />
- <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>
+ <Tabs v-model:active-key="activeKey" class="px-4 pt-1" type="card">
+ <Tabs.TabPane key="group" tab="SN 鐮佸垎缁�">
+ <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>
+ </Tabs.TabPane>
+ <Tabs.TabPane key="in-stock" tab="鍦ㄥ簱鍗曚欢">
+ <InStockGrid table-title="SN 鍗曚欢鐘舵�佸垪琛�">
+ <template #toolbar-tools>
+ <TableAction
+ :actions="[
+ {
+ label: '鍏ュ簱鐧昏',
+ type: 'primary',
+ icon: ACTION_ICON.ADD,
+ auth: ['mes:wm-sn:update'],
+ onClick: handleInStock,
+ },
+ {
+ label: '鍑哄簱鏍搁攢',
+ type: 'primary',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:wm-sn:update'],
+ onClick: handleOutStock,
+ },
+ ]"
+ />
+ </template>
+ <template #status="{ row }">
+ <Tag :color="row.status === 1 ? 'green' : 'orange'">
+ {{ row.status === 1 ? '鍦ㄥ簱' : '宸插嚭搴�' }}
+ </Tag>
+ </template>
+ <template #locations="{ row }">
+ <span v-if="row.status === 1">
+ {{
+ formatWmLocation(
+ locationMapsRef ?? EMPTY_MAPS,
+ row.warehouseId,
+ row.locationId,
+ row.areaId,
+ )
+ }}
+ </span>
+ <span v-else>-</span>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: '璇︽儏',
+ type: 'link',
+ auth: ['mes:wm-sn:query'],
+ onClick: handleSnDetail.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </InStockGrid>
+ </Tabs.TabPane>
+ </Tabs>
</Page>
-</template>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/sn/location-maps.ts b/src/views/wls/sn/location-maps.ts
new file mode 100644
index 0000000..762f3c4
--- /dev/null
+++ b/src/views/wls/sn/location-maps.ts
@@ -0,0 +1,51 @@
+import { getWarehouseAreaSimpleList } from '#/api/mes/wm/warehouse/area';
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
+import { getWarehouseLocationSimpleList } from '#/api/mes/wm/warehouse/location';
+
+export interface WmLocationMaps {
+ warehouseNameMap: Record<number, string>;
+ locationNameMap: Record<number, string>;
+ areaNameMap: Record<number, string>;
+}
+
+let cached: WmLocationMaps | null = null;
+
+/** 鍔犺浇浠撳簱/搴撳尯/搴撲綅 ID -> 鍚嶇О鏄犲皠锛堟ā鍧楃骇缂撳瓨锛屼粎棣栨鍔犺浇锛� */
+export async function getWmLocationMaps(): Promise<WmLocationMaps> {
+ if (cached) {
+ return cached;
+ }
+ const [warehouses, locations, areas] = await Promise.all([
+ getWarehouseSimpleList(),
+ getWarehouseLocationSimpleList(),
+ getWarehouseAreaSimpleList(),
+ ]);
+ cached = {
+ warehouseNameMap: Object.fromEntries(
+ warehouses.map((w) => [w.id!, w.name || w.code || '']),
+ ),
+ locationNameMap: Object.fromEntries(
+ locations.map((l) => [l.id!, l.name || l.code || '']),
+ ),
+ areaNameMap: Object.fromEntries(
+ areas.map((a) => [a.id!, a.name || a.code || '']),
+ ),
+ };
+ return cached;
+}
+
+/** 鏍规嵁 ID 娓叉煋 浠撳簱/搴撳尯/搴撲綅 鏂囨湰锛圛D 缂哄け鏃惰繑鍥炵┖涓诧級 */
+export function formatWmLocation(
+ maps: WmLocationMaps,
+ warehouseId?: number,
+ locationId?: number,
+ areaId?: number,
+): string {
+ return [
+ warehouseId ? maps.warehouseNameMap[warehouseId] : '',
+ locationId ? maps.locationNameMap[locationId] : '',
+ areaId ? maps.areaNameMap[areaId] : '',
+ ]
+ .filter(Boolean)
+ .join(' / ');
+}
\ No newline at end of file
diff --git a/src/views/wls/sn/modules/in-stock-modal.vue b/src/views/wls/sn/modules/in-stock-modal.vue
new file mode 100644
index 0000000..540ad6c
--- /dev/null
+++ b/src/views/wls/sn/modules/in-stock-modal.vue
@@ -0,0 +1,62 @@
+<script lang="ts" setup>
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { registerSnInStock } from '#/api/mes/wm/sn';
+
+import { useInStockFormSchema } from '../data';
+
+defineOptions({ name: 'WlsSnInStockModal' });
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 100,
+ },
+ layout: 'horizontal',
+ schema: [],
+ 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 MesWmSnApi.SnInStock;
+ try {
+ await registerSnInStock(data);
+ await modalApi.close();
+ emit('success');
+ message.success('鍏ュ簱鐧昏鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ return;
+ }
+ formApi.setState({ schema: useInStockFormSchema(formApi) });
+ await formApi.resetForm();
+ },
+});
+</script>
+
+<template>
+ <Modal title="SN 鐮佸叆搴撶櫥璁�" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/sn/modules/out-stock-modal.vue b/src/views/wls/sn/modules/out-stock-modal.vue
new file mode 100644
index 0000000..0550842
--- /dev/null
+++ b/src/views/wls/sn/modules/out-stock-modal.vue
@@ -0,0 +1,62 @@
+<script lang="ts" setup>
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { outboundSn } from '#/api/mes/wm/sn';
+
+import { useOutStockFormSchema } from '../data';
+
+defineOptions({ name: 'WlsSnOutStockModal' });
+
+const emit = defineEmits(['success']);
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: {
+ class: 'w-full',
+ },
+ formItemClass: 'col-span-1',
+ labelWidth: 100,
+ },
+ layout: 'horizontal',
+ schema: [],
+ 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 MesWmSnApi.SnOutStock;
+ try {
+ await outboundSn(data);
+ await modalApi.close();
+ emit('success');
+ message.success('鍑哄簱鏍搁攢鎴愬姛');
+ } finally {
+ modalApi.unlock();
+ }
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) {
+ return;
+ }
+ formApi.setState({ schema: useOutStockFormSchema() });
+ await formApi.resetForm();
+ },
+});
+</script>
+
+<template>
+ <Modal title="SN 鐮佸嚭搴撴牳閿�" class="w-1/2">
+ <Form class="mx-4" />
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/sn/modules/sn-detail-modal.vue b/src/views/wls/sn/modules/sn-detail-modal.vue
new file mode 100644
index 0000000..757cf09
--- /dev/null
+++ b/src/views/wls/sn/modules/sn-detail-modal.vue
@@ -0,0 +1,118 @@
+<script lang="ts" setup>
+import type { MesWmSnApi } from '#/api/mes/wm/sn';
+
+import { computed, ref } from 'vue';
+
+import { Button, Empty, Input, message, Modal } from 'ant-design-vue';
+
+import { getSnDetail } from '#/api/mes/wm/sn';
+import { useDescription } from '#/components/description';
+
+import { useSnDetailSchema } from '../data';
+import { formatWmLocation, getWmLocationMaps } from '../location-maps';
+
+defineOptions({ name: 'WlsSnDetailModal' });
+
+const open = ref(false);
+const code = ref('');
+const detail = ref<MesWmSnApi.SnDetail>();
+const loading = ref(false);
+const locationMaps = ref<Awaited<ReturnType<typeof getWmLocationMaps>>>();
+
+const locationText = computed(() =>
+ detail.value
+ ? formatWmLocation(
+ locationMaps.value ?? {
+ warehouseNameMap: {},
+ locationNameMap: {},
+ areaNameMap: {},
+ },
+ detail.value.warehouseId,
+ detail.value.locationId,
+ detail.value.areaId,
+ )
+ : '-',
+);
+
+const lastTransactionText = computed(() => {
+ const d = detail.value;
+ if (!d) {
+ return '-';
+ }
+ const direction = d.lastTransactionTypeName || '';
+ const quantity = d.lastTransactionQuantity ?? '';
+ const bizCode = d.lastTransactionBizCode || '-';
+ return [direction, quantity, bizCode].filter(Boolean).join(' ');
+});
+
+async function loadDetail(snCode: string) {
+ loading.value = true;
+ try {
+ detail.value = await getSnDetail(snCode);
+ if (!locationMaps.value) {
+ locationMaps.value = await getWmLocationMaps();
+ }
+ } finally {
+ loading.value = false;
+ }
+}
+
+async function handleQuery() {
+ if (!code.value?.trim()) {
+ message.warning('璇疯緭鍏ユ垨鎵爜鑾峰彇 SN 鐮�');
+ return;
+ }
+ await loadDetail(code.value.trim());
+}
+
+function openModal(snCode?: string) {
+ open.value = true;
+ code.value = snCode ?? '';
+ detail.value = undefined;
+ if (snCode) {
+ void loadDetail(snCode);
+ }
+}
+
+defineExpose({ open: openModal });
+
+const [Descriptions] = useDescription({
+ bordered: true,
+ column: 1,
+ schema: useSnDetailSchema(),
+ useCard: false,
+ labelStyle: { width: '80px', whiteSpace: 'nowrap' },
+});
+</script>
+
+<template>
+ <Modal v-model:open="open" title="SN 鐮佸崟浠惰鎯�" width="720px">
+ <div class="flex gap-2">
+ <Input
+ v-model:value="code"
+ placeholder="杈撳叆鎴栨壂鐮� SN 鐮佸悗鏌ヨ"
+ @press-enter="handleQuery"
+ />
+ <Button type="primary" :loading="loading" @click="handleQuery">
+ 鏌ヨ
+ </Button>
+ </div>
+ <div class="mt-4">
+ <Descriptions v-if="detail" :data="detail">
+ <template #location>
+ {{ locationText }}
+ </template>
+ <template #lastTransaction>
+ {{ lastTransactionText }}
+ </template>
+ </Descriptions>
+ <Empty
+ v-else
+ :description="loading ? '鏌ヨ涓�...' : '鏆傛棤鏁版嵁锛岃杈撳叆 SN 鐮佹煡璇�'"
+ />
+ </div>
+ <template #footer>
+ <Button @click="open = false">鍏抽棴</Button>
+ </template>
+ </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/wls/stocktaking/plan/data.ts b/src/views/wls/stocktaking/plan/data.ts
index 263e3fa..c301458 100644
--- a/src/views/wls/stocktaking/plan/data.ts
+++ b/src/views/wls/stocktaking/plan/data.ts
@@ -133,6 +133,40 @@
rules: z.boolean().default(false),
},
{
+ fieldName: 'autoGenerate',
+ label: '鑷姩鐢熸垚',
+ component: 'Switch',
+ componentProps: {
+ checkedChildren: '鏄�',
+ unCheckedChildren: '鍚�',
+ },
+ rules: z.boolean().default(false),
+ },
+ {
+ fieldName: 'generateCron',
+ label: '鐢熸垚鍛ㄦ湡(Cron)',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏� Cron 琛ㄨ揪寮忥紝濡� 0 0 2 * * ?',
+ helpMessage:
+ '鍚敤鑷姩鐢熸垚鍚庯紝闇�鍦ㄣ�岀郴缁熺鐞�->瀹氭椂浠诲姟銆嶉厤缃� handler=MesWmStockTakingPlanAutoJob 鐨� cron 浠诲姟锛屽埌鏈熻嚜鍔ㄦ寜鏈柟妗堢敓鎴愮洏鐐逛换鍔�',
+ },
+ dependencies: {
+ triggerFields: ['autoGenerate'],
+ show: (values) => !!values.autoGenerate,
+ },
+ },
+ {
+ fieldName: 'lastGenerateTime',
+ label: '涓婃鐢熸垚鏃堕棿',
+ component: 'Input',
+ disabled: true,
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
fieldName: 'remark',
label: '澶囨敞',
component: 'Textarea',
@@ -238,6 +272,21 @@
},
},
{
+ field: 'autoGenerate',
+ title: '鑷姩鐢熸垚',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING },
+ },
+ },
+ {
+ field: 'lastGenerateTime',
+ title: '涓婃鐢熸垚鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
field: 'status',
title: '鐘舵��',
width: 120,
diff --git a/src/views/wls/stocktaking/task/data.ts b/src/views/wls/stocktaking/task/data.ts
index ed62e0c..f16203e 100644
--- a/src/views/wls/stocktaking/task/data.ts
+++ b/src/views/wls/stocktaking/task/data.ts
@@ -29,13 +29,11 @@
} from '#/views/wls/warehouse/components';
/** 琛ㄥ崟绫诲瀷 */
-export type FormType = 'create' | 'detail' | 'execute' | 'submit' | 'update';
+export type FormType = 'create' | 'detail' | 'submit' | 'update';
-/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟彁浜ゃ�佹墽琛岀洏鐐广�佽鎯呮�侊級 */
+/** 琛ㄥ崟澶撮儴鏄惁鍙锛堟彁浜ゃ�佽鎯呮�侊級 */
function isHeaderReadonly(formType: FormType): boolean {
- return (
- formType === 'detail' || formType === 'execute' || formType === 'submit'
- );
+ return formType === 'detail' || formType === 'submit';
}
/** 鏂板/淇敼鐨勮〃鍗� */
diff --git a/src/views/wls/stocktaking/task/index.vue b/src/views/wls/stocktaking/task/index.vue
index c38699d..30c46cc 100644
--- a/src/views/wls/stocktaking/task/index.vue
+++ b/src/views/wls/stocktaking/task/index.vue
@@ -3,6 +3,7 @@
import type { MesWmStockTakingTaskApi } from '#/api/mes/wm/stocktaking/task';
import { ref } from 'vue';
+import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { MesWmStockTakingTaskStatusEnum } from '@vben/constants';
@@ -15,7 +16,9 @@
cancelStockTaking,
deleteStockTaking,
exportStockTaking,
+ getStockTakingApproveProcessList,
getStockTakingPage,
+ submitStockTakingApproval,
} from '#/api/mes/wm/stocktaking/task';
import {
importStockTakingQuantity,
@@ -23,11 +26,19 @@
} from '#/api/wls/stocktaking/task/line';
import { $t } from '#/locales';
+import ProcessSelectModal from '#/views/wls/components/process-select-modal.vue';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
+const router = useRouter();
+
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+ connectedComponent: ProcessSelectModal,
destroyOnClose: true,
});
@@ -51,14 +62,46 @@
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 handleSubmitApproval(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ const hideLoading = message.loading({
+ content: '鑾峰彇瀹℃壒娴佺▼...',
+ duration: 0,
+ });
+ try {
+ const processList = await getStockTakingApproveProcessList();
+ hideLoading();
+ if (!processList || processList.length === 0) {
+ message.warning('鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堥厤缃� BPM 娴佺▼');
+ return;
+ }
+ processModalApi
+ .setData({
+ id: row.id,
+ processList,
+ submitFn: submitStockTakingApproval,
+ })
+ .open();
+ } catch {
+ hideLoading();
+ }
+}
+
+/** 鏌ョ湅瀹℃壒璇︽儏 */
+function handleProcessDetail(row: MesWmStockTakingTaskApi.StockTakingTask) {
+ if (!row.processInstanceId) {
+ message.warning('璇ヤ换鍔″皻鏈彁浜ょ粨鏋滃鎵�');
+ return;
+ }
+ router.push({
+ name: 'BpmProcessInstanceDetail',
+ query: { id: row.processInstanceId },
+ });
}
/** 鍙栨秷鐩樼偣浠诲姟 */
@@ -159,6 +202,7 @@
<template>
<Page auto-content-height><FormModal @success="handleRefresh" />
+ <ProcessModal @success="handleRefresh" />
<Grid table-title="鐩樼偣浠诲姟鍒楄〃">
<template #toolbar-tools>
@@ -205,7 +249,7 @@
onClick: handleEdit.bind(null, row),
},
{
- label: '鎻愪氦',
+ label: '寮�濮嬬洏鐐�',
type: 'link',
auth: ['mes:wm-stock-taking-task:update'],
ifShow: row.status === StatusEnum.PREPARE,
@@ -224,13 +268,6 @@
},
},
{
- label: '鎵ц鐩樼偣',
- type: 'link',
- auth: ['mes:wm-stock-taking-task:update'],
- ifShow: row.status === StatusEnum.APPROVING,
- onClick: handleExecute.bind(null, row),
- },
- {
label: '瀵煎叆瀹炵洏鏁�',
type: 'link',
icon: ACTION_ICON.UPLOAD,
@@ -239,6 +276,20 @@
onClick: handleImport.bind(null, row),
},
{
+ label: '鎻愪氦缁撴灉瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm-stock-taking-task:update'],
+ ifShow: row.status === StatusEnum.APPROVING,
+ onClick: handleSubmitApproval.bind(null, row),
+ },
+ {
+ label: '鏌ョ湅瀹℃壒',
+ type: 'link',
+ auth: ['mes:wm-stock-taking-task:query'],
+ ifShow: row.status === StatusEnum.APPROVING,
+ onClick: handleProcessDetail.bind(null, row),
+ },
+ {
label: '鍙栨秷',
type: 'link',
danger: true,
diff --git a/src/views/wls/stocktaking/task/modules/form.vue b/src/views/wls/stocktaking/task/modules/form.vue
index ec1c5ba..05cfca2 100644
--- a/src/views/wls/stocktaking/task/modules/form.vue
+++ b/src/views/wls/stocktaking/task/modules/form.vue
@@ -14,7 +14,6 @@
import { useVbenForm } from '#/adapter/form';
import {
createStockTaking,
- finishStockTaking,
getStockTaking,
submitStockTaking,
updateStockTaking,
@@ -35,29 +34,23 @@
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 isSubmit = computed(() => formType.value === 'submit'); // 鏄惁寮�濮嬬洏鐐规ā寮�
+const isResultEditable = computed(
+ // 鐩樼偣涓紙瀹℃壒涓級鍙淮鎶ょ洏鐐圭粨鏋�
+ () => formData.value?.status === MesWmStockTakingTaskStatusEnum.APPROVING,
);
const showLineTab = computed(() => !formData.value?.blindFlag); // 鐩茬洏涓嶅睍绀虹洏鐐规竻鍗�
const showResultTab = computed(
() =>
- isExecute.value ||
- (!!formData.value?.status &&
- formData.value.status !== MesWmStockTakingTaskStatusEnum.PREPARE),
+ !!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 '寮�濮嬬洏鐐�';
}
return formType.value === 'update'
? $t('ui.actionTitle.edit', ['鐩樼偣浠诲姟'])
@@ -78,7 +71,7 @@
wrapperClass: 'grid-cols-3',
});
-/** 鎻愪氦鐩樼偣浠诲姟锛氳〃鍗曟湁淇敼鏃跺厛淇濆瓨锛屽啀璋冪敤鎻愪氦鎺ュ彛 */
+/** 寮�濮嬬洏鐐癸細琛ㄥ崟鏈変慨鏀规椂鍏堜繚瀛橈紝鍐嶆彁浜� */
async function handleSubmit() {
const { valid } = await formApi.validate();
if (!valid || !formData.value?.id) {
@@ -94,23 +87,7 @@
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('鎵ц鐩樼偣鎴愬姛');
+ message.success('寮�濮嬬洏鐐规垚鍔�');
await modalApi.close();
emit('success');
} finally {
@@ -163,7 +140,7 @@
// 鍔犺浇鏁版嵁
const data = modalApi.getData<{ formType: FormType; id?: number }>();
formType.value = data.formType;
- subTabsName.value = data.formType === 'execute' ? 'results' : 'lines';
+ subTabsName.value = 'lines';
formApi.setState({ schema: useFormSchema(formType.value, formApi) });
formApi.setDisabled(!isEditable.value);
modalApi.setState({ showConfirmButton: isEditable.value });
@@ -199,7 +176,7 @@
</Tabs.TabPane>
<Tabs.TabPane v-if="showResultTab" key="results" tab="鐩樼偣缁撴灉">
<ResultList
- :form-type="isExecute ? 'execute' : 'detail'"
+ :form-type="isResultEditable ? 'execute' : 'detail'"
:task-id="formData.id"
/>
</Tabs.TabPane>
@@ -207,18 +184,11 @@
<template #prepend-footer>
<div class="flex flex-auto items-center gap-2">
<Popconfirm
- v-if="canSubmit || isSubmit"
- title="纭鎻愪氦璇ョ洏鐐逛换鍔★紵銆愭彁浜ゅ悗灏嗕笉鑳戒慨鏀广��"
+ v-if="isSubmit"
+ title="纭寮�濮嬬洏鐐癸紵寮�濮嬪悗灏嗗喕缁撳簱瀛樺苟鍙綍鍏�/瀵煎叆瀹炵洏鏁般��"
@confirm="handleSubmit"
>
- <Button type="primary">鎻愪氦</Button>
- </Popconfirm>
- <Popconfirm
- v-if="isExecute"
- title="纭鎵ц鐩樼偣鎿嶄綔锛�"
- @confirm="handleExecute"
- >
- <Button type="primary">鎵ц鐩樼偣</Button>
+ <Button type="primary">寮�濮嬬洏鐐�</Button>
</Popconfirm>
</div>
</template>
diff --git a/src/views/wls/transaction/data.ts b/src/views/wls/transaction/data.ts
index 117aa3f..a294a3e 100644
--- a/src/views/wls/transaction/data.ts
+++ b/src/views/wls/transaction/data.ts
@@ -30,6 +30,11 @@
{ label: '閿�鍞��璐�', value: 120 },
{ label: '澶栧崗鍙戞枡', value: 121 },
{ label: '澶栧崗鍏ュ簱', value: 122 },
+ { label: '鐩樼偣璋冩暣', value: 107 },
+ { label: 'SN 鍑哄叆搴�', value: 109 },
+ { label: '閲囪喘鍏ュ簱绾㈠啿', value: 111 },
+ { label: '璁惧鍙拌处鍏ュ簱', value: 400 },
+ { label: '璁惧鍏ュ簱', value: 401 },
];
/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
--
Gitblit v1.9.3