From e1e0d48eb7ab2b305e6062f23610f8be9db3bed6 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 29 八月 2026 13:46:26 +0800
Subject: [PATCH] feat(wms): 新增库存汇总统计与预警功能

---
 src/views/wls/stocktaking/task/index.vue               |   29 +
 src/views/wls/stocktaking/task/modules/result-list.vue |   65 +++
 src/api/mes/wm/stock-warning/index.ts                  |   61 +++
 src/views/wls/stock-warning/data.ts                    |  211 +++++++++++++
 src/views/wls/stock-warning/modules/current-modal.vue  |   63 +++
 src/api/mes/wm/stocktaking/task/index.ts               |   19 +
 src/views/wls/stock-summary/data.ts                    |  130 ++++++++
 src/views/wls/stock-warning/modules/handle-modal.vue   |   74 ++++
 src/views/wls/stock-warning/index.vue                  |  133 ++++++++
 src/packages/constants/src/biz-mes-enum.ts             |   20 +
 src/api/mes/wm/materialstock/index.ts                  |   48 +++
 src/views/wls/stock-summary/index.vue                  |   87 +++++
 12 files changed, 938 insertions(+), 2 deletions(-)

diff --git a/src/api/mes/wm/materialstock/index.ts b/src/api/mes/wm/materialstock/index.ts
index 3527368..7316dc2 100644
--- a/src/api/mes/wm/materialstock/index.ts
+++ b/src/api/mes/wm/materialstock/index.ts
@@ -36,6 +36,20 @@
     frozen?: boolean; // 鏄惁鍐荤粨
     createTime?: Date; // 鍒涘缓鏃堕棿
   }
+
+  /** 搴撳瓨姹囨�荤粨鏋� */
+  export interface MaterialStockSummary {
+    itemId?: number; // 鍝佺ID锛堟寜鍝佺姹囨�绘椂杩斿洖锛�
+    itemCode?: string; // 鍝佺缂栫爜
+    itemName?: string; // 鍝佺鍚嶇О
+    batchId?: number; // 鎵规ID锛堟寜鎵规姹囨�绘椂杩斿洖锛�
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱ID锛堟寜浠撳簱姹囨�绘椂杩斿洖锛�
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    stockState?: number; // 搴撳瓨鐘舵��
+    stockStateName?: string; // 搴撳瓨鐘舵�佸悕绉�
+    totalQuantity?: number; // 姹囨�绘暟閲�
+  }
 }
 
 /** 鏌ヨ搴撳瓨鍙拌处鍒嗛〉 */
@@ -142,3 +156,37 @@
 export function adjustMaterialStock(data: MaterialStockAdjustParams) {
   return requestClient.post<number>('/mes/wm/material-stock/adjust', data);
 }
+
+/** 搴撳瓨姹囨�绘煡璇㈠弬鏁� */
+export interface MaterialStockSummaryParams {
+  stockState?: number; // 搴撳瓨鐘舵�侊細10鏆傚瓨搴擄紝20鍚堟牸搴擄紝30宸插嚭搴�
+  itemId?: number; // 鍝佺ID
+  batchCode?: string; // 鎵规鍙�
+  warehouseId?: number; // 浠撳簱ID
+  locationId?: number; // 搴撳尯ID
+  areaId?: number; // 搴撲綅ID
+}
+
+/** 鎸夊搧绉嶆眹鎬诲簱瀛樻暟閲� */
+export function getStockSummaryByItem(params: MaterialStockSummaryParams) {
+  return requestClient.get<MesWmMaterialStockApi.MaterialStockSummary[]>(
+    '/mes/wm/material-stock/summary-by-item',
+    { params },
+  );
+}
+
+/** 鎸夋壒娆℃眹鎬诲簱瀛樻暟閲� */
+export function getStockSummaryByBatch(params: MaterialStockSummaryParams) {
+  return requestClient.get<MesWmMaterialStockApi.MaterialStockSummary[]>(
+    '/mes/wm/material-stock/summary-by-batch',
+    { params },
+  );
+}
+
+/** 鎸変粨搴撴眹鎬诲簱瀛樻暟閲� */
+export function getStockSummaryByWarehouse(params: MaterialStockSummaryParams) {
+  return requestClient.get<MesWmMaterialStockApi.MaterialStockSummary[]>(
+    '/mes/wm/material-stock/summary-by-warehouse',
+    { params },
+  );
+}
diff --git a/src/api/mes/wm/stock-warning/index.ts b/src/api/mes/wm/stock-warning/index.ts
new file mode 100644
index 0000000..05dfbba
--- /dev/null
+++ b/src/api/mes/wm/stock-warning/index.ts
@@ -0,0 +1,61 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesWmStockWarningApi {
+  /** MES 搴撳瓨棰勮璁板綍 */
+  export interface StockWarning {
+    id?: number; // 缂栧彿
+    itemId?: number; // 鍝佺ID
+    itemCode?: string; // 鍝佺缂栫爜
+    itemName?: string; // 鍝佺鍚嶇О
+    batchId?: number; // 鎵规ID
+    batchCode?: string; // 鎵规鍙�
+    warehouseId?: number; // 浠撳簱ID
+    warehouseName?: string; // 浠撳簱鍚嶇О
+    warningType?: number; // 棰勮绫诲瀷锛�10-浣庡簱瀛橈紝20-涓存湡锛�30-搴撻緞
+    warningTypeName?: string; // 棰勮绫诲瀷鍚嶇О
+    thresholdValue?: number; // 闃堝�硷紙浣庡簱瀛�=鏈�浣庡簱瀛橈紝涓存湡=鍓╀綑澶╂暟锛屽簱榫�=瓒呴緞澶╂暟锛�
+    currentValue?: number; // 褰撳墠鍊硷紙浣庡簱瀛�=鍦ㄥ簱鏁伴噺锛屼复鏈�=鍓╀綑澶╂暟锛屽簱榫�=搴撻緞澶╂暟锛�
+    expireDate?: string; // 杩囨湡鏃ユ湡锛堜复鏈熼璀︽墠鏈夛級
+    receiptTime?: string; // 鍏ュ簱鏃堕棿锛堝簱榫勯璀︽墠鏈夛級
+    status?: number; // 澶勭悊鐘舵�侊細10-鏈鐞嗭紝20-宸插鐞�
+    statusName?: string; // 澶勭悊鐘舵�佸悕绉�
+    handleUserId?: number; // 澶勭悊浜篒D
+    handleTime?: string; // 澶勭悊鏃堕棿
+    handleRemark?: string; // 澶勭悊澶囨敞
+  }
+
+  /** 搴撳瓨棰勮鍒嗛〉鏌ヨ鍙傛暟 */
+  export interface StockWarningPageParams extends PageParam {
+    itemCode?: string; // 鍝佺缂栫爜妯$硦鏌ヨ
+    batchCode?: string; // 鎵规鍙锋ā绯婃煡璇�
+    warningType?: number; // 棰勮绫诲瀷
+    status?: number; // 澶勭悊鐘舵��
+  }
+}
+
+/** 鏌ヨ搴撳瓨棰勮鍒嗛〉锛堣惤搴撹褰曪級 */
+export function getStockWarningPage(params: MesWmStockWarningApi.StockWarningPageParams) {
+  return requestClient.get<PageResult<MesWmStockWarningApi.StockWarning>>(
+    '/mes/wm/stock-warning/page',
+    { params },
+  );
+}
+
+/** 瀹炴椂璁$畻褰撳墠搴撳瓨棰勮锛堜笉钀藉簱锛� */
+export function getCurrentStockWarnings() {
+  return requestClient.get<MesWmStockWarningApi.StockWarning[]>(
+    '/mes/wm/stock-warning/current',
+  );
+}
+
+/** 閲嶆柊璁$畻骞惰惤搴撳綋鍓嶅簱瀛橀璀� */
+export function refreshStockWarnings() {
+  return requestClient.post<number>('/mes/wm/stock-warning/refresh');
+}
+
+/** 澶勭悊搴撳瓨棰勮锛堟爣璁板凡澶勭悊锛� */
+export function handleStockWarning(data: { id: number; handleRemark?: string }) {
+  return requestClient.put<boolean>('/mes/wm/stock-warning/handle', data);
+}
diff --git a/src/api/mes/wm/stocktaking/task/index.ts b/src/api/mes/wm/stocktaking/task/index.ts
index 77622f3..d438400 100644
--- a/src/api/mes/wm/stocktaking/task/index.ts
+++ b/src/api/mes/wm/stocktaking/task/index.ts
@@ -84,3 +84,22 @@
     params,
   });
 }
+
+/** PDA 鎵爜鐩樼偣 */
+export function scanStockTaking(data: {
+  taskId: number; // 鐩樼偣浠诲姟缂栧彿
+  scanCode: string; // 杩芥函鐮�/琚嬬爜/鎵樼洏鐮�/鎵规鍙�
+  takingQuantity: number; // 鐩樼偣鏁伴噺
+  remark?: string; // 澶囨敞
+}) {
+  return requestClient.post<number>('/mes/wm/stocktaking-task/scan', data);
+}
+
+/** 澶勭悊鐩樼偣宸紓锛堢洏鐩堝叆搴�/鐩樹簭鍑哄簱锛岃皟鏁村簱瀛橈級 */
+export function processStockTakingDifference(id: number) {
+  return requestClient.post<boolean>(
+    '/mes/wm/stocktaking-task/process-difference',
+    null,
+    { params: { id } },
+  );
+}
diff --git a/src/packages/constants/src/biz-mes-enum.ts b/src/packages/constants/src/biz-mes-enum.ts
index 4c1981e..e26d510 100644
--- a/src/packages/constants/src/biz-mes-enum.ts
+++ b/src/packages/constants/src/biz-mes-enum.ts
@@ -226,6 +226,26 @@
   LOSS: 3,
 } as const;
 
+/** MES 搴撳瓨鐘舵�佹灇涓撅紙瀵归綈鍚庣 MesWmStockStateEnum锛� */
+export const MesWmStockStateEnum = {
+  TEMP: 10, // 鏆傚瓨搴擄紙寰呰川妫�锛�
+  QUALIFIED: 20, // 鍚堟牸搴擄紙姝e紡搴撳瓨锛�
+  OUT: 30, // 宸插嚭搴�
+} as const;
+
+/** MES 搴撳瓨棰勮绫诲瀷鏋氫妇 */
+export const MesWmStockWarningTypeEnum = {
+  LOW_STOCK: 10, // 浣庡簱瀛�
+  EXPIRING: 20, // 涓存湡
+  AGEING: 30, // 搴撻緞
+} as const;
+
+/** MES 搴撳瓨棰勮澶勭悊鐘舵�佹灇涓� */
+export const MesWmStockWarningStatusEnum = {
+  UNHANDLED: 10, // 鏈鐞�
+  HANDLED: 20, // 宸插鐞�
+} as const;
+
 /** MES 鐩樼偣鏂规鍙傛暟绫诲瀷鏋氫妇 */
 export const MesWmStockTakingParamTypeEnum = {
   WAREHOUSE: 102,
diff --git a/src/views/wls/stock-summary/data.ts b/src/views/wls/stock-summary/data.ts
new file mode 100644
index 0000000..63af57f
--- /dev/null
+++ b/src/views/wls/stock-summary/data.ts
@@ -0,0 +1,130 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { markRaw } from 'vue';
+
+import { MdItemSelect } from '#/views/mes/md/item/components';
+import { WmWarehouseSelect } from '../warehouse/components';
+
+export type SummaryDimension = 'item' | 'batch' | 'warehouse';
+
+export const SUMMARY_DIMENSION_OPTIONS: {
+  label: string;
+  value: SummaryDimension;
+}[] = [
+  { label: '鎸夊搧绉嶆眹鎬�', value: 'item' },
+  { label: '鎸夋壒娆℃眹鎬�', value: 'batch' },
+  { label: '鎸変粨搴撴眹鎬�', value: 'warehouse' },
+];
+
+/** 姹囨�绘煡璇㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemId',
+      label: '鍝佺',
+      component: markRaw(MdItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鍝佺',
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+      },
+    },
+    {
+      fieldName: 'stockState',
+      label: '搴撳瓨鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: [
+          { label: '鏆傚瓨搴擄紙寰呰川妫�锛�', value: 10 },
+          { label: '鍚堟牸搴擄紙姝e紡搴撳瓨锛�', value: 20 },
+          { label: '宸插嚭搴擄紙閿�鍞墸鍑忥級', value: 30 },
+        ],
+        placeholder: '璇烽�夋嫨搴撳瓨鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 姹囨�诲垪琛ㄥ瓧娈� */
+export function useGridColumns(
+  dimension: SummaryDimension,
+): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStockSummary>['columns'] {
+  const base: VxeTableGridOptions<MesWmMaterialStockApi.MaterialStockSummary>['columns'] = [
+    {
+      field: 'stockState',
+      title: '搴撳瓨鐘舵��',
+      width: 110,
+      cellRender: {
+        name: 'CellTag',
+        props: {
+          options: [
+            { label: '鏆傚瓨搴�', value: 10, color: 'orange' },
+            { label: '鍚堟牸搴�', value: 20, color: 'green' },
+            { label: '宸插嚭搴�', value: 30, color: 'default' },
+          ],
+        },
+      },
+    },
+    {
+      field: 'totalQuantity',
+      title: '姹囨�绘暟閲�',
+      width: 120,
+    },
+  ];
+  if (dimension === 'item') {
+    return [
+      {
+        field: 'itemCode',
+        title: '鍝佺缂栫爜',
+        minWidth: 120,
+      },
+      {
+        field: 'itemName',
+        title: '鍝佺鍚嶇О',
+        minWidth: 140,
+      },
+      ...base,
+    ];
+  }
+  if (dimension === 'batch') {
+    return [
+      {
+        field: 'itemName',
+        title: '鍝佺鍚嶇О',
+        minWidth: 140,
+      },
+      {
+        field: 'batchCode',
+        title: '鎵规鍙�',
+        minWidth: 160,
+      },
+      ...base,
+    ];
+  }
+  return [
+    {
+      field: 'warehouseName',
+      title: '浠撳簱鍚嶇О',
+      minWidth: 140,
+    },
+    ...base,
+  ];
+}
diff --git a/src/views/wls/stock-summary/index.vue b/src/views/wls/stock-summary/index.vue
new file mode 100644
index 0000000..a8abedf
--- /dev/null
+++ b/src/views/wls/stock-summary/index.vue
@@ -0,0 +1,87 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
+
+import { ref } from 'vue';
+
+import { Page } from '@vben/common-ui';
+
+import { Segmented } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  getStockSummaryByBatch,
+  getStockSummaryByItem,
+  getStockSummaryByWarehouse,
+} from '#/api/mes/wm/materialstock';
+
+import {
+  SUMMARY_DIMENSION_OPTIONS,
+  useGridColumns,
+  useGridFormSchema,
+  type SummaryDimension,
+} from './data';
+
+/** 褰撳墠姹囨�荤淮搴� */
+const dimension = ref<SummaryDimension>('item');
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(dimension.value),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async (_, formValues) => {
+          const params = { ...formValues };
+          if (dimension.value === 'item') {
+            const list = await getStockSummaryByItem(params);
+            return { list, total: list.length };
+          }
+          if (dimension.value === 'batch') {
+            const list = await getStockSummaryByBatch(params);
+            return { list, total: list.length };
+          }
+          const list = await getStockSummaryByWarehouse(params);
+          return { list, total: list.length };
+        },
+      },
+    },
+    pagerConfig: {
+      enabled: false,
+    },
+    rowConfig: {
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmMaterialStockApi.MaterialStockSummary>,
+});
+
+/** 鍒囨崲姹囨�荤淮搴� */
+function handleDimensionChange(value: string | number) {
+  dimension.value = value as SummaryDimension;
+  gridApi.setGridOptions({
+    columns: useGridColumns(dimension.value),
+  });
+  gridApi.reload();
+}
+</script>
+
+<template>
+  <Page auto-content-height>
+    <div class="mb-4">
+      <Segmented
+        :options="SUMMARY_DIMENSION_OPTIONS"
+        :value="dimension"
+        @change="handleDimensionChange"
+      />
+    </div>
+    <Grid table-title="搴撳瓨澶氱淮姹囨��" />
+  </Page>
+</template>
diff --git a/src/views/wls/stock-warning/data.ts b/src/views/wls/stock-warning/data.ts
new file mode 100644
index 0000000..c83b5e5
--- /dev/null
+++ b/src/views/wls/stock-warning/data.ts
@@ -0,0 +1,211 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockWarningApi } from '#/api/mes/wm/stock-warning';
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'itemCode',
+      label: '鍝佺缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ュ搧绉嶇紪鐮�',
+      },
+    },
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+    },
+    {
+      fieldName: 'warningType',
+      label: '棰勮绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: [
+          { label: '浣庡簱瀛�', value: 10 },
+          { label: '涓存湡', value: 20 },
+          { label: '搴撻緞', value: 30 },
+        ],
+        placeholder: '璇烽�夋嫨棰勮绫诲瀷',
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '澶勭悊鐘舵��',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        options: [
+          { label: '鏈鐞�', value: 10 },
+          { label: '宸插鐞�', value: 20 },
+        ],
+        placeholder: '璇烽�夋嫨澶勭悊鐘舵��',
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesWmStockWarningApi.StockWarning>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '鍝佺缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鍝佺鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 100,
+    },
+    {
+      field: 'warningType',
+      title: '棰勮绫诲瀷',
+      width: 100,
+      cellRender: {
+        name: 'CellTag',
+        props: {
+          options: [
+            { label: '浣庡簱瀛�', value: 10, color: 'orange' },
+            { label: '涓存湡', value: 20, color: 'gold' },
+            { label: '搴撻緞', value: 30, color: 'purple' },
+          ],
+        },
+      },
+    },
+    {
+      field: 'thresholdValue',
+      title: '闃堝��',
+      width: 100,
+    },
+    {
+      field: 'currentValue',
+      title: '褰撳墠鍊�',
+      width: 100,
+    },
+    {
+      field: 'expireDate',
+      title: '杩囨湡鏃ユ湡',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'receiptTime',
+      title: '鍏ュ簱鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'status',
+      title: '澶勭悊鐘舵��',
+      width: 100,
+      cellRender: {
+        name: 'CellTag',
+        props: {
+          options: [
+            { label: '鏈鐞�', value: 10, color: 'red' },
+            { label: '宸插鐞�', value: 20, color: 'green' },
+          ],
+        },
+      },
+    },
+    {
+      field: 'handleTime',
+      title: '澶勭悊鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'handleRemark',
+      title: '澶勭悊澶囨敞',
+      minWidth: 140,
+    },
+    {
+      title: '鎿嶄綔',
+      width: 80,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
+
+/** 瀹炴椂璁$畻闈㈡澘鐨勫瓧娈� */
+export function useCurrentGridColumns(): VxeTableGridOptions<MesWmStockWarningApi.StockWarning>['columns'] {
+  return [
+    {
+      field: 'itemCode',
+      title: '鍝佺缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'itemName',
+      title: '鍝佺鍚嶇О',
+      minWidth: 140,
+    },
+    {
+      field: 'batchCode',
+      title: '鎵规鍙�',
+      minWidth: 140,
+    },
+    {
+      field: 'warehouseName',
+      title: '浠撳簱',
+      minWidth: 100,
+    },
+    {
+      field: 'warningType',
+      title: '棰勮绫诲瀷',
+      width: 100,
+      cellRender: {
+        name: 'CellTag',
+        props: {
+          options: [
+            { label: '浣庡簱瀛�', value: 10, color: 'orange' },
+            { label: '涓存湡', value: 20, color: 'gold' },
+            { label: '搴撻緞', value: 30, color: 'purple' },
+          ],
+        },
+      },
+    },
+    {
+      field: 'thresholdValue',
+      title: '闃堝��',
+      width: 100,
+    },
+    {
+      field: 'currentValue',
+      title: '褰撳墠鍊�',
+      width: 100,
+    },
+    {
+      field: 'expireDate',
+      title: '杩囨湡鏃ユ湡',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      field: 'receiptTime',
+      title: '鍏ュ簱鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+  ];
+}
diff --git a/src/views/wls/stock-warning/index.vue b/src/views/wls/stock-warning/index.vue
new file mode 100644
index 0000000..87edba1
--- /dev/null
+++ b/src/views/wls/stock-warning/index.vue
@@ -0,0 +1,133 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockWarningApi } from '#/api/mes/wm/stock-warning';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { MesWmStockWarningStatusEnum } from '@vben/constants';
+
+import { message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  getStockWarningPage,
+  refreshStockWarnings,
+} from '#/api/mes/wm/stock-warning';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import CurrentModal from './modules/current-modal.vue';
+import HandleModal from './modules/handle-modal.vue';
+
+const [CurrentModalComponent, currentModalApi] = useVbenModal({
+  connectedComponent: CurrentModal,
+  destroyOnClose: true,
+});
+
+const [HandleModalComponent, handleModalApi] = useVbenModal({
+  connectedComponent: HandleModal,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 瀹炴椂璁$畻褰撳墠棰勮 */
+function handleCurrent() {
+  currentModalApi.open();
+}
+
+/** 閲嶆柊璁$畻骞惰惤搴� */
+async function handleRefreshWarnings() {
+  const hideLoading = message.loading({
+    content: '姝e湪閲嶆柊璁$畻骞惰惤搴撻璀�...',
+    duration: 0,
+  });
+  try {
+    const count = await refreshStockWarnings();
+    message.success(`閲嶆柊璁$畻瀹屾垚锛屽叡 ${count} 鏉¢璀);
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 澶勭悊棰勮 */
+function handleOpenHandle(row: MesWmStockWarningApi.StockWarning) {
+  handleModalApi.setData({ id: row.id }).open();
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getStockWarningPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MesWmStockWarningApi.StockWarning>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <CurrentModalComponent @success="handleRefresh" />
+    <HandleModalComponent @success="handleRefresh" />
+
+    <Grid table-title="搴撳瓨棰勮鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: '瀹炴椂璁$畻',
+              type: 'primary',
+              icon: ACTION_ICON.SEARCH,
+              auth: ['mes:wm-stock-warning:query'],
+              onClick: handleCurrent,
+            },
+            {
+              label: '閲嶆柊璁$畻',
+              type: 'default',
+              icon: ACTION_ICON.REFRESH,
+              auth: ['mes:wm-stock-warning:update'],
+              onClick: handleRefreshWarnings,
+            },
+          ]"
+        />
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: '澶勭悊',
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mes:wm-stock-warning:update'],
+              ifShow: row.status === MesWmStockWarningStatusEnum.UNHANDLED,
+              onClick: handleOpenHandle.bind(null, row),
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
diff --git a/src/views/wls/stock-warning/modules/current-modal.vue b/src/views/wls/stock-warning/modules/current-modal.vue
new file mode 100644
index 0000000..e806ba1
--- /dev/null
+++ b/src/views/wls/stock-warning/modules/current-modal.vue
@@ -0,0 +1,63 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesWmStockWarningApi } from '#/api/mes/wm/stock-warning';
+
+import { ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getCurrentStockWarnings } from '#/api/mes/wm/stock-warning';
+import { $t } from '#/locales';
+
+import { useCurrentGridColumns } from '../data';
+
+const list = ref<MesWmStockWarningApi.StockWarning[]>([]);
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useCurrentGridColumns(),
+    height: 420,
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async () => {
+          return { list: list.value, total: list.value.length };
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+    },
+  } as VxeTableGridOptions<MesWmStockWarningApi.StockWarning>,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    await modalApi.close();
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      list.value = [];
+      return;
+    }
+    modalApi.lock();
+    try {
+      list.value = await getCurrentStockWarnings();
+      gridApi.reload();
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="$t('ui.actionTitle.view', ['瀹炴椂搴撳瓨棰勮'])" class="w-4/5">
+    <Grid table-title="褰撳墠棰勮锛堝疄鏃惰绠楋紝鏈惤搴擄級" />
+  </Modal>
+</template>
diff --git a/src/views/wls/stock-warning/modules/handle-modal.vue b/src/views/wls/stock-warning/modules/handle-modal.vue
new file mode 100644
index 0000000..dbd9e67
--- /dev/null
+++ b/src/views/wls/stock-warning/modules/handle-modal.vue
@@ -0,0 +1,74 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { handleStockWarning } from '#/api/mes/wm/stock-warning';
+import { $t } from '#/locales';
+
+const emit = defineEmits(['success']);
+const warningId = ref<number>();
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 100,
+  },
+  layout: 'horizontal',
+  schema: [
+    {
+      component: 'Textarea',
+      fieldName: 'handleRemark',
+      label: '澶勭悊澶囨敞',
+      componentProps: {
+        allowClear: true,
+        maxLength: 200,
+        placeholder: '璇疯緭鍏ュ鐞嗗娉�',
+        rows: 3,
+      },
+    },
+  ],
+  showDefaultActions: false,
+  wrapperClass: 'grid-cols-1',
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid || !warningId.value) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      const { handleRemark } = await formApi.getValues();
+      await handleStockWarning({ id: warningId.value, handleRemark });
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      warningId.value = undefined;
+      await formApi.resetForm();
+      return;
+    }
+    const data = modalApi.getData<{ id: number }>();
+    warningId.value = data.id;
+  },
+});
+</script>
+
+<template>
+  <Modal :title="$t('ui.actionTitle.edit', ['搴撳瓨棰勮'])">
+    <Form />
+  </Modal>
+</template>
diff --git a/src/views/wls/stocktaking/task/index.vue b/src/views/wls/stocktaking/task/index.vue
index 4ffefe8..8d52536 100644
--- a/src/views/wls/stocktaking/task/index.vue
+++ b/src/views/wls/stocktaking/task/index.vue
@@ -14,6 +14,7 @@
   deleteStockTaking,
   exportStockTaking,
   getStockTakingPage,
+  processStockTakingDifference,
 } from '#/api/mes/wm/stocktaking/task';
 import { $t } from '#/locales';
 
@@ -60,6 +61,23 @@
   await cancelStockTaking(row.id!);
   message.success('鍙栨秷鎴愬姛');
   handleRefresh();
+}
+
+/** 澶勭悊鐩樼偣宸紓锛堢洏鐩堝叆搴�/鐩樹簭鍑哄簱锛岃皟鏁村簱瀛橈級 */
+async function handleProcessDifference(
+  row: MesWmStockTakingTaskApi.StockTakingTask,
+) {
+  const hideLoading = message.loading({
+    content: '姝e湪澶勭悊鐩樼偣宸紓...',
+    duration: 0,
+  });
+  try {
+    await processStockTakingDifference(row.id!);
+    message.success('澶勭悊鐩樼偣宸紓鎴愬姛');
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
 }
 
 /** 鍒犻櫎鐩樼偣浠诲姟 */
@@ -193,6 +211,17 @@
                 confirm: handleCancel.bind(null, row),
               },
             },
+            {
+              label: '澶勭悊宸紓',
+              type: 'link',
+              icon: ACTION_ICON.AUDIT,
+              auth: ['mes:wm-stock-taking-task:update'],
+              ifShow: row.status === StatusEnum.FINISHED,
+              popConfirm: {
+                title: '纭澶勭悊鐩樼偣宸紓锛熷皢鎸夌洏鐐圭粨鏋滅洏鐩堝叆搴�/鐩樹簭鍑哄簱璋冩暣搴撳瓨銆�',
+                confirm: handleProcessDifference.bind(null, row),
+              },
+            },
           ]"
         />
       </template>
diff --git a/src/views/wls/stocktaking/task/modules/result-list.vue b/src/views/wls/stocktaking/task/modules/result-list.vue
index 0e9c315..b253a77 100644
--- a/src/views/wls/stocktaking/task/modules/result-list.vue
+++ b/src/views/wls/stocktaking/task/modules/result-list.vue
@@ -2,13 +2,14 @@
 import type { VxeTableGridOptions } from '#/adapter/vxe-table';
 import type { MesWmStockTakingResultApi } from '#/api/mes/wm/stocktaking/task/result';
 
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 
 import { useVbenModal } from '@vben/common-ui';
 
-import { message } from 'ant-design-vue';
+import { Button, Input, InputNumber, message } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import { scanStockTaking } from '#/api/mes/wm/stocktaking/task';
 import {
   deleteStockTakingResult,
   getStockTakingResultPage,
@@ -24,6 +25,37 @@
 }>();
 
 const isExecute = computed(() => props.formType === 'execute'); // 鎵ц鐩樼偣鎬佸彲缁存姢鐩樼偣缁撴灉
+
+// PDA 鎵爜鐩樼偣杈撳叆
+const scanCode = ref('');
+const takingQuantity = ref<number | undefined>(undefined);
+const scanning = ref(false);
+
+/** 鎵爜鐩樼偣 */
+async function handleScan() {
+  if (!scanCode.value) {
+    message.warning('璇疯緭鍏ヨ拷婧爜/琚嬬爜/鎵樼洏鐮�/鎵规鍙�');
+    return;
+  }
+  if (takingQuantity.value === undefined || takingQuantity.value === null) {
+    message.warning('璇疯緭鍏ョ洏鐐规暟閲�');
+    return;
+  }
+  scanning.value = true;
+  try {
+    const resultId = await scanStockTaking({
+      taskId: props.taskId,
+      scanCode: scanCode.value,
+      takingQuantity: takingQuantity.value,
+    });
+    message.success(`鎵爜鎴愬姛锛岀洏鐐圭粨鏋滅紪鍙凤細${resultId}`);
+    scanCode.value = '';
+    takingQuantity.value = undefined;
+    handleRefresh();
+  } finally {
+    scanning.value = false;
+  }
+}
 
 const [ResultFormModal, resultFormModalApi] = useVbenModal({
   connectedComponent: ResultForm,
@@ -95,6 +127,35 @@
 <template>
   <div>
     <ResultFormModal @success="handleRefresh" />
+    <!-- PDA 鎵爜鐩樼偣 -->
+    <div
+      v-if="isExecute"
+      class="mb-3 flex flex-wrap items-center gap-2 rounded-md border border-dashed border-yellow-300 bg-yellow-50 px-3 py-2"
+    >
+      <span class="font-medium text-yellow-700">PDA 鎵爜鐩樼偣锛�</span>
+      <Input
+        v-model:value="scanCode"
+        allow-clear
+        class="!w-56"
+        placeholder="杩芥函鐮�/琚嬬爜/鎵樼洏鐮�/鎵规鍙�"
+        @press-enter="handleScan"
+      />
+      <InputNumber
+        v-model:value="takingQuantity"
+        :min="0"
+        :precision="2"
+        class="!w-32"
+        placeholder="鐩樼偣鏁伴噺"
+        @press-enter="handleScan"
+      />
+      <Button
+        type="primary"
+        :loading="scanning"
+        @click="handleScan"
+      >
+        鎵爜鐩樼偣
+      </Button>
+    </div>
     <Grid table-title="鐩樼偣缁撴灉">
       <template v-if="isExecute" #toolbar-tools>
         <TableAction

--
Gitblit v1.9.3