12 小时以前 e1e0d48eb7ab2b305e6062f23610f8be9db3bed6
feat(wms): 新增库存汇总统计与预警功能

- 添加MES库存状态、预警类型和处理状态枚举定义
- 实现库存多维汇总功能,支持按品种、批次、仓库维度统计
- 开发库存预警管理模块,包含实时预警计算和处理功能
- 集成PDA扫码盘点功能到盘点任务界面
- 实现盘点差异处理机制,支持盘盈入库和盘亏出库操作
- 构建完整的库存预警处理流程和界面组件
已添加7个文件
已修改5个文件
940 ■■■■■ 文件已修改
src/api/mes/wm/materialstock/index.ts 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/stock-warning/index.ts 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/stocktaking/task/index.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/packages/constants/src/biz-mes-enum.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stock-summary/data.ts 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stock-summary/index.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stock-warning/data.ts 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stock-warning/index.vue 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stock-warning/modules/current-modal.vue 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stock-warning/modules/handle-modal.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/stocktaking/task/modules/result-list.vue 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 },
  );
}
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; // å¤„理人ID
    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);
}
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 } },
  );
}
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, // åˆæ ¼åº“(正式库存)
  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,
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: '合格库(正式库存)', 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,
  ];
}
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>
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',
    },
  ];
}
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: '正在重新计算并落库预警...',
    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>
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>
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>
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: '正在处理盘点差异...',
    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>
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