已添加1个文件
已修改22个文件
636 ■■■■ 文件已修改
src/api/mes/wm/materialstock/index.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/productsales/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/wls/productsales/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/data.ts 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/mdm/modules/form.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/md/item/modules/form.vue 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/feedback/modules/form.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/components/gantt-chart.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/task/data.ts 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/modules/form.vue 139 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/process-design/route/modules/form.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/itemreceipt/modules/form.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/components/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/components/move-modal.vue 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/components/select-dialog.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/data.ts 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productissue/modules/form.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/productsales/data.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/returnissue/modules/form.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/salesnotice/modules/form.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/materialstock/index.ts
@@ -106,6 +106,21 @@
  return requestClient.post<number>('/mes/wm/material-stock/adjust-out', data);
}
/** åº“存移库 */
export interface MaterialStockMoveParams {
  materialStockId: number; // åº“存记录ID
  toWarehouseId: number; // ç›®æ ‡ä»“库ID
  toLocationId: number; // ç›®æ ‡åº“区ID
  toAreaId: number; // ç›®æ ‡åº“位ID
  quantity: number; // ç§»åº“数量
  remark?: string; // å¤‡æ³¨
}
/** åº“存移库 */
export function moveMaterialStock(data: MaterialStockMoveParams) {
  return requestClient.post<boolean>('/mes/wm/material-stock/move', data);
}
/** åº“存调整(正数=入库,负数=出库)- æ—§æŽ¥å£ï¼Œä¿ç•™å…¼å®¹ */
export interface MaterialStockAdjustParams {
  itemId: number; // ç‰©æ–™ID
src/api/mes/wm/productsales/index.ts
@@ -14,6 +14,7 @@
    clientCode?: string; // å®¢æˆ·ç¼–码
    clientName?: string; // å®¢æˆ·åç§°
    salesOrderCode?: string; // é”€å”®è®¢å•编号
    saleOrderId?: number; // é”€å”®è®¢å•ID
    salesDate?: number; // å‡ºåº“日期
    contactName?: string; // æ”¶è´§äºº
    contactTelephone?: string; // è”系方式
src/api/wls/productsales/index.ts
@@ -14,6 +14,7 @@
    clientCode?: string; // å®¢æˆ·ç¼–码
    clientName?: string; // å®¢æˆ·åç§°
    salesOrderCode?: string; // é”€å”®è®¢å•编号
    saleOrderId?: number; // é”€å”®è®¢å•ID
    salesDate?: number; // å‡ºåº“日期
    contactName?: string; // æ”¶è´§äºº
    contactTelephone?: string; // è”系方式
src/views/basicData/mdm/data.ts
@@ -19,7 +19,7 @@
let unitListPromise: Promise<any[]> | null = null;
/** å°†æ‰å¹³åˆ†ç±»åˆ—表转为 TreeSelect æ‰€éœ€çš„æ ‘形结构 */
function buildCategoryTree(list: any[]): any[] {
export function buildCategoryTree(list: any[]): any[] {
  const map = new Map<number, any>();
  const roots: any[] = [];
  for (const item of list) {
@@ -59,7 +59,7 @@
}
/** æ–°å¢ž/修改的表单 */
export function useFormSchema(formApi?: VbenFormApi): VbenFormSchema[] {
export function useFormSchema(formApi?: VbenFormApi, categoryTreeData: any[] = []): VbenFormSchema[] {
  return [
    {
      component: "Input",
@@ -103,7 +103,7 @@
      rules: "required",
    },
    {
      component: "ApiTreeSelect",
      component: "TreeSelect",
      fieldName: "categoryId",
      label: "物料分类",
      rules: "selectRequired",
@@ -111,10 +111,8 @@
        placeholder: "请选择物料分类",
        allowClear: true,
        treeDefaultExpandAll: true,
        api: async () => {
          const res = await getItemTypeSimpleList();
          return buildCategoryTree(res || []);
        },
        treeData: categoryTreeData,
        fieldNames: { label: 'label', value: 'value', children: 'children' },
      },
    },
    {
src/views/basicData/mdm/modules/form.vue
@@ -11,7 +11,8 @@
import { createItem, getItem, updateItem } from '#/api/mdm/item';
import { $t } from '#/locales';
import { useFormSchema, clearUnitListCache } from '../data';
import { useFormSchema, clearUnitListCache, buildCategoryTree } from '../data';
import { getItemTypeSimpleList } from '#/api/mes/md/item/type';
defineOptions({ name: "ItemForm" });
@@ -56,8 +57,11 @@
    const data = modalApi.getData<MdmItemApi.Item>();
    // æ¸…除单位缓存,确保获取最新数据
    clearUnitListCache();
    // åŠ è½½æœ€æ–°åˆ†ç±»æ•°æ®
    const categoryRes = await getItemTypeSimpleList();
    const categoryTreeData = buildCategoryTree(categoryRes || []);
    // è®¾ç½® schema(formApi å·²åˆå§‹åŒ–)
    formApi.setState({ schema: useFormSchema(formApi) });
    formApi.setState({ schema: useFormSchema(formApi, categoryTreeData) });
    if (data?.id) {
      // ç¼–辑模式
      modalApi.lock();
src/views/erp/sale/order/index.vue
@@ -342,7 +342,7 @@
              type: 'link',
              icon: 'ant-design:file-protect-outlined',
              auth: ['mes:wm-sales-notice:create'],
              ifShow: () => row.status === 20 && (row.canCreateSalesNotice || row.productionStatus === 1),
              ifShow: () => row.status === 20 && row.canCreateSalesNotice,
              onClick: handleGenerateNotice.bind(null, row),
            },
            {
src/views/mes/md/item/modules/form.vue
@@ -82,14 +82,12 @@
      if (formType.value === 'create') {
        const id = await createItem(data);
        formData.value = { ...data, id };
        await formApi.setFieldValue('id', id);
        formType.value = 'update';
        message.success($t('ui.actionMessage.operationSuccess'));
      } else {
        await updateItem(data);
        formData.value = { ...formData.value, ...data };
        message.success($t('ui.actionMessage.operationSuccess'));
      }
      message.success($t('ui.actionMessage.operationSuccess'));
      await modalApi.close();
      emit('success');
    } finally {
      modalApi.unlock();
src/views/mes/pro/feedback/modules/form.vue
@@ -128,6 +128,7 @@
      await updateFeedback(data);
      formData.value = { ...formData.value, ...data };
      message.success($t('common.updateSuccess'));
      await modalApi.close();
    }
    // åˆ·æ–°å¿«ç…§ï¼ˆå·²å¯¹é½æ•°é‡å¹¶è¡¥ id),后续 submit ç”¨äºŽåˆ¤æ–­æ˜¯å¦éœ€è¦å†ä¿å­˜
    originalSnapshot.value = JSON.stringify(data);
src/views/mes/pro/task/components/gantt-chart.vue
@@ -13,7 +13,7 @@
 * 1. æŒ‰è®¢å•分组展示生产任务,订单为 project è¡Œï¼Œä»»åŠ¡ä¸ºå­è¡Œ
 * 2. æ”¯æŒåªè¯»é¢„览和拖拽编辑两种模式
 * 3. æ‹–拽后触发 taskUpdate äº‹ä»¶ï¼Œé€šçŸ¥çˆ¶ç»„件批量保存
 * 4. æ—¶é—´åˆ»åº¦ï¼šå‘¨ â†’ æ—¥ â†’ 8 å°æ—¶ï¼ˆ1 å·¥ä½œæ—¥ = 8 å°æ—¶ï¼‰
 * 4. æ—¶é—´åˆ»åº¦ï¼šå‘¨ â†’ æ—¥ â†’ å°æ—¶
 */
defineOptions({ name: 'GanttChart', inheritAttrs: false });
@@ -47,8 +47,8 @@
  gantt.config.readonly = props.readonly;
  gantt.config.date_format = '%Y-%m-%d %H:%i:%s';
  gantt.config.duration_unit = 'hour'; // æŒç»­æ—¶é—´å•位为小时,配合 duration_step å®žçŽ°å·¥ä½œæ—¥å•ä½
  gantt.config.duration_step = 8; // 1 å·¥ä½œæ—¥ = 8 å°æ—¶
  gantt.config.duration_unit = 'hour'; // æŒç»­æ—¶é—´å•位为小时
  gantt.config.duration_step = 1; // duration å­—段即为实际小时数
  gantt.config.row_height = 36;
  gantt.config.bar_height = 24;
  gantt.config.fit_tasks = true;
@@ -70,7 +70,7 @@
  gantt.config.buttons_left = ['gantt_save_btn'];
  gantt.config.buttons_right = ['gantt_cancel_btn'];
  // æ—¶é—´åˆ»åº¦ï¼šå‘¨ > æ—¥ > 8 å°æ—¶
  // æ—¶é—´åˆ»åº¦ï¼šå‘¨ > æ—¥ > å°æ—¶
  const weekScaleTemplate = (date: Date) => {
    const dateToStr = gantt.date.date_to_str('%M %d');
    const endDate = gantt.date.add(gantt.date.add(date, 1, 'week'), -1, 'day');
@@ -82,7 +82,7 @@
  gantt.config.scales = [
    { unit: 'week', step: 1, format: weekScaleTemplate },
    { unit: 'day', step: 1, format: dayTemplate, css: daysStyle },
    { unit: 'hour', step: 8, format: '%H:%i' },
    { unit: 'hour', step: 1, format: '%H:%i' },
  ];
  gantt.config.scale_height = 50;
  gantt.config.show_task_cells = true;
src/views/mes/pro/task/data.ts
@@ -347,8 +347,8 @@
    },
    {
      field: 'duration',
      title: '生产时长',
      width: 80,
      title: '生产时长(h)',
      width: 120,
    },
    {
      field: 'endTime',
@@ -420,7 +420,7 @@
    },
    {
      fieldName: 'duration',
      label: '生产时长',
      label: '生产时长(h)',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
@@ -456,7 +456,7 @@
  ];
}
/** è®¡ç®—结束时间:开始时间 + ç”Ÿäº§æ—¶é•¿ Ã— 8 å°æ—¶ */
/** è®¡ç®—结束时间:开始时间 + ç”Ÿäº§æ—¶é•¿ï¼ˆå°æ—¶ï¼‰ */
async function recalcEndTime(formApi?: VbenFormApi) {
  if (!formApi) {
    return;
@@ -466,7 +466,7 @@
    const start = Number(values.startTime);
    await formApi.setFieldValue(
      'endTime',
      start + values.duration * 8 * 3600 * 1000,
      start + values.duration * 3600 * 1000,
    );
  }
}
@@ -553,7 +553,7 @@
      width: 170,
      formatter: 'formatDateTime',
    },
    { field: 'duration', title: '生产时长', width: 100 },
    { field: 'duration', title: '生产时长(h)', width: 120 },
    {
      field: 'endTime',
      title: '预计完成时间',
src/views/mes/pro/workorder/index.vue
@@ -203,7 +203,9 @@
              label: '完成',
              type: 'link',
              auth: ['mes:pro-work-order:update'],
              ifShow: row.status === MesProWorkOrderStatusEnum.CONFIRMED,
              ifShow:
                row.status === MesProWorkOrderStatusEnum.CONFIRMED &&
                !!row.quantityProduced,
              onClick: handleFinish.bind(null, row),
            },
            {
src/views/mes/pro/workorder/modules/form.vue
@@ -6,13 +6,13 @@
  import { computed, ref } from "vue";
  import { useVbenModal } from "@vben/common-ui";
  import { confirm, useVbenModal } from "@vben/common-ui";
  import {
    BarcodeBizTypeEnum,
    MesProWorkOrderStatusEnum,
  } from "@vben/constants";
  import { Button, message, Popconfirm, Tabs } from "ant-design-vue";
  import { Button, message, Tabs } from "ant-design-vue";
  import { useVbenForm } from "#/adapter/form";
  import {
@@ -83,10 +83,18 @@
    wrapperClass: "grid-cols-3",
  });
  /** é‡æ–°æŒ‚è½½ schema å¹¶æŒ‰è¡¨å•态控制底部确认按钮 */
  /** é‡æ–°æŒ‚è½½ schema å¹¶æŒ‰è¡¨å•态控制底部按钮 */
  function applySchema() {
    formApi.setState({ schema: useFormSchema(formType.value, formApi) });
    modalApi.setState({ showConfirmButton: isEditable.value });
    const showBtn = isEditable.value || isConfirm.value || isFinish.value;
    modalApi.setState({
      showConfirmButton: showBtn,
      confirmText: isConfirm.value || canConfirm.value
        ? '确认'
        : isFinish.value
          ? '完成'
          : undefined,
    });
  }
  /** æŸ¥çœ‹è®¢å•条码 */
@@ -100,48 +108,6 @@
      formData.value.code,
      formData.value.name
    );
  }
  /** ç¡®è®¤è®¢å•:编辑态有修改时先保存,再确认 */
  async function handleConfirm() {
    const { valid } = await formApi.validate();
    if (!valid || !formData.value?.id) {
      return;
    }
    modalApi.lock();
    try {
      if (isEditable.value) {
        const current = JSON.stringify(await formApi.getValues());
        if (current !== originalSnapshot.value) {
          const data =
            (await formApi.getValues()) as MesProWorkOrderApi.WorkOrder;
          await updateWorkOrder({ ...formData.value, ...data });
          originalSnapshot.value = current;
        }
      }
      await confirmWorkOrder(formData.value.id);
      message.success("订单已确认");
      await modalApi.close();
      emit("success");
    } finally {
      modalApi.unlock();
    }
  }
  /** å®Œæˆè®¢å• */
  async function handleFinish() {
    if (!formData.value?.id) {
      return;
    }
    modalApi.lock();
    try {
      await finishWorkOrder(formData.value.id);
      message.success("订单已完成");
      await modalApi.close();
      emit("success");
    } finally {
      modalApi.unlock();
    }
  }
  /** ä»Ž BOM ç‰©æ–™è¡Œé¢„填子订单:复用当前订单上下文,进入新增态 */
@@ -177,21 +143,70 @@
  const [Modal, modalApi] = useVbenModal({
    async onConfirm() {
      // ---- ç¡®è®¤è®¢å•模式 ----
      if (isConfirm.value || canConfirm.value) {
        const { valid } = await formApi.validate();
        if (!valid || !formData.value?.id) return;
        try {
          await confirm('确认要完成订单编制吗?确认后将不能更改。');
        } catch {
          return;
        }
        modalApi.lock();
        try {
          // ç¼–辑态有修改时先保存
          if (isEditable.value) {
            const current = JSON.stringify(await formApi.getValues());
            if (current !== originalSnapshot.value) {
              const data = (await formApi.getValues()) as MesProWorkOrderApi.WorkOrder;
              await updateWorkOrder({ ...formData.value, ...data });
              originalSnapshot.value = current;
            }
          }
          await confirmWorkOrder(formData.value.id);
          message.success('订单已确认');
          await modalApi.close();
          emit('success');
        } finally {
          modalApi.unlock();
        }
        return;
      }
      // ---- å®Œæˆè®¢å•模式 ----
      if (isFinish.value) {
        if (!formData.value?.id) return;
        try {
          await confirm('确认要完成该订单吗?');
        } catch {
          return;
        }
        modalApi.lock();
        try {
          await finishWorkOrder(formData.value.id);
          message.success('订单已完成');
          await modalApi.close();
          emit('success');
        } finally {
          modalApi.unlock();
        }
        return;
      }
      // ---- ä¿å­˜ï¼ˆæ–°å¢ž / ç¼–辑)----
      if (!isEditable.value) {
        await modalApi.close();
        return;
      }
      const { valid } = await formApi.validate();
      if (!valid) {
        return;
      }
      if (!valid) return;
      modalApi.lock();
      // æäº¤è¡¨å•
      const data = (await formApi.getValues()) as MesProWorkOrderApi.WorkOrder;
      try {
        if (formData.value?.id) {
          await updateWorkOrder({ ...formData.value, ...data });
          formData.value = { ...formData.value, ...data };
          emit("success");
          message.success($t("ui.actionMessage.operationSuccess"));
          await modalApi.close();
        } else {
          const id = await createWorkOrder(data);
          formData.value = {
@@ -200,13 +215,11 @@
            status: MesProWorkOrderStatusEnum.PREPARE,
          };
          formType.value = "update";
          // åˆ›å»ºæˆåŠŸåŽåˆ‡æ¢ç¼–è¾‘æ€ï¼Œé‡æŒ‚ schema è®©ã€Œè®¢å•状态」字段显现
          applySchema();
          await formApi.setValues(formData.value);
          emit("success");
        }
        originalSnapshot.value = JSON.stringify(await formApi.getValues());
        emit("success");
        message.success($t("ui.actionMessage.operationSuccess"));
      } finally {
        modalApi.unlock();
      }
@@ -275,22 +288,10 @@
      </Tabs>
    </template>
    <template #prepend-footer>
      <div class="flex flex-auto items-center gap-2">
        <Popconfirm v-if="canConfirm || isConfirm"
                    title="确认要完成订单编制吗?确认后将不能更改。"
                    @confirm="handleConfirm">
          <Button type="primary">确认</Button>
        </Popconfirm>
        <Popconfirm v-if="isFinish"
                    title="确认要完成该订单吗?"
                    @confirm="handleFinish">
          <Button type="primary">完成</Button>
        </Popconfirm>
        <Button v-if="formType === 'detail' && formData?.id"
                @click="handleBarcode">
          æŸ¥çœ‹æ¡ç 
        </Button>
      </div>
      <Button v-if="formType === 'detail' && formData?.id"
              @click="handleBarcode">
        æŸ¥çœ‹æ¡ç 
      </Button>
    </template>
  </Modal>
  <BarcodeDetail ref="barcodeDetailRef" />
src/views/mes/process-design/route/modules/form.vue
@@ -67,7 +67,12 @@
      } else {
        await updateRoute(data);
        formData.value = { ...formData.value, ...data };
        emit('success');
        message.success($t('ui.actionMessage.operationSuccess'));
        await modalApi.close();
        return;
      }
      // æ–°å¢žï¼šä¿æŒå¼¹çª—打开,切换到编辑模式继续维护
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
    } finally {
src/views/wls/itemreceipt/modules/form.vue
@@ -133,17 +133,21 @@
      if (formData.value?.id) {
        await updateItemReceipt({ ...formData.value, ...data });
        formData.value = { ...formData.value, ...data };
      } else {
        const id = await createItemReceipt(data);
        formData.value = {
          ...data,
          id,
          status: MesWmItemReceiptStatusEnum.PREPARE,
        };
        await formApi.setFieldValue('id', id);
        await formApi.setFieldValue('status', formData.value.status);
        formType.value = 'update';
        emit('success');
        message.success($t('ui.actionMessage.operationSuccess'));
        await modalApi.close();
        return;
      }
      // æ–°å¢žï¼šä¿æŒå¼¹çª—打开,切换到编辑模式
      const id = await createItemReceipt(data);
      formData.value = {
        ...data,
        id,
        status: MesWmItemReceiptStatusEnum.PREPARE,
      };
      await formApi.setFieldValue('id', id);
      await formApi.setFieldValue('status', formData.value.status);
      formType.value = 'update';
      originalSnapshot.value = JSON.stringify(await formApi.getValues());
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
src/views/wls/materialstock/components/index.ts
@@ -1,3 +1,4 @@
export { default as WmMaterialStockSelectDialog } from './select-dialog.vue';
export { default as WmMaterialStockSelect } from './select.vue';
export { default as WmStockAdjustModal } from './stock-adjust-modal.vue';
export { default as WmStockMoveModal } from './move-modal.vue';
src/views/wls/materialstock/components/move-modal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,237 @@
<script lang="ts" setup>
import type { MesWmMaterialStockApi } from '#/api/mes/wm/materialstock';
import type { VbenFormSchema } from '#/adapter/form';
import { markRaw, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { z } from '#/adapter/form';
import { useVbenForm } from '#/adapter/form';
import { moveMaterialStock } from '#/api/mes/wm/materialstock';
import {
  WmWarehouseAreaSelect,
  WmWarehouseLocationSelect,
  WmWarehouseSelect,
} from '#/views/wls/warehouse/components';
defineOptions({ name: 'WmStockMoveModal' });
const emit = defineEmits<{ success: [] }>();
const currentRow = ref<MesWmMaterialStockApi.MaterialStock>();
function useFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'itemCode',
      label: '物料编码',
      component: 'Input',
      componentProps: { disabled: true },
    },
    {
      fieldName: 'itemName',
      label: '物料名称',
      component: 'Input',
      componentProps: { disabled: true },
    },
    {
      fieldName: 'batchCode',
      label: '批次号',
      component: 'Input',
      componentProps: { disabled: true },
    },
    {
      fieldName: 'warehouseName',
      label: '当前仓库',
      component: 'Input',
      componentProps: { disabled: true },
    },
    {
      fieldName: 'locationName',
      label: '当前库区',
      component: 'Input',
      componentProps: { disabled: true },
    },
    {
      fieldName: 'areaName',
      label: '当前库位',
      component: 'Input',
      componentProps: { disabled: true },
    },
    {
      fieldName: 'quantity',
      label: '当前数量',
      component: 'InputNumber',
      componentProps: { disabled: true, class: '!w-full', precision: 2 },
    },
    {
      fieldName: 'toWarehouseId',
      label: '目标仓库',
      component: markRaw(WmWarehouseSelect),
      componentProps: {
        placeholder: '请选择目标仓库',
        onChange: async () => {
          await formApi.setFieldValue('toLocationId', undefined);
          await formApi.setFieldValue('toAreaId', undefined);
        },
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'toLocationId',
      label: '目标库区',
      component: markRaw(WmWarehouseLocationSelect),
      componentProps: { placeholder: '请选择目标库区' },
      dependencies: {
        triggerFields: ['toWarehouseId'],
        componentProps: (values) => ({
          warehouseId: values.toWarehouseId,
          placeholder: '请选择目标库区',
          onChange: async () => {
            await formApi.setFieldValue('toAreaId', undefined);
          },
        }),
        trigger: (values, api) => {
          if (values.toLocationId !== undefined) {
            void api.setFieldValue('toLocationId', undefined);
            void api.setFieldValue('toAreaId', undefined);
          }
        },
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'toAreaId',
      label: '目标库位',
      component: markRaw(WmWarehouseAreaSelect),
      componentProps: { placeholder: '请选择目标库位' },
      dependencies: {
        triggerFields: ['toLocationId'],
        componentProps: (values) => ({
          locationId: values.toLocationId,
          placeholder: '请选择目标库位',
        }),
        trigger: (values, api) => {
          if (values.toAreaId !== undefined) {
            void api.setFieldValue('toAreaId', undefined);
          }
        },
      },
      rules: 'selectRequired',
    },
    {
      fieldName: 'moveQuantity',
      label: '移库数量',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        min: 0.01,
        placeholder: '请输入移库数量',
        precision: 2,
      },
      rules: z.number().positive('移库数量必须大于0'),
    },
    {
      fieldName: 'remark',
      label: '备注',
      component: 'Textarea',
      formItemClass: 'col-span-2',
      componentProps: { placeholder: '请输入备注', rows: 3 },
    },
  ];
}
const [Form, formApi] = useVbenForm({
  commonConfig: {
    componentProps: { class: 'w-full' },
    formItemClass: 'col-span-1',
    labelWidth: 100,
  },
  layout: 'horizontal',
  schema: useFormSchema(),
  showDefaultActions: false,
  wrapperClass: 'grid-cols-2',
});
const [Modal, modalApi] = useVbenModal({
  async onConfirm() {
    const { valid } = await formApi.validate();
    if (!valid) return;
    const row = currentRow.value;
    if (!row?.id) return;
    const values = await formApi.getValues();
    const moveQuantity = values.moveQuantity as number;
    const quantity = (row.quantity || 0) as number;
    const availableQuantity = (row.availableQuantity || 0) as number;
    if (moveQuantity > quantity) {
      message.error(`移库数量不能超过当前库存数量(${quantity.toFixed(2)})`);
      return;
    }
    if (moveQuantity > availableQuantity) {
      message.error(`移库数量不能超过可用量(${availableQuantity.toFixed(2)}),可用量 = åœ¨åº“数量 - å†»ç»“数量 - å ç”¨é‡`);
      return;
    }
    if (row.warehouseId === values.toWarehouseId && row.locationId === values.toLocationId && row.areaId === values.toAreaId) {
      message.error('目标库位与当前库位相同,不允许移库');
      return;
    }
    modalApi.lock();
    try {
      await moveMaterialStock({
        materialStockId: row.id,
        quantity: moveQuantity,
        remark: values.remark,
        toAreaId: values.toAreaId,
        toLocationId: values.toLocationId,
        toWarehouseId: values.toWarehouseId,
      });
      message.success('移库成功');
      emit('success');
      await modalApi.close();
    } finally {
      modalApi.unlock();
    }
  },
  async onOpenChange(isOpen: boolean) {
    if (!isOpen) {
      currentRow.value = undefined;
      return;
    }
    const data = modalApi.getData<MesWmMaterialStockApi.MaterialStock>();
    if (!data?.id) return;
    currentRow.value = data;
    if (data.frozen) {
      message.warning('该库存记录已冻结,不允许移库');
      await modalApi.close();
      return;
    }
    await formApi.setValues({
      areaName: data.areaName,
      batchCode: data.batchCode,
      itemCode: data.itemCode,
      itemName: data.itemName,
      locationName: data.locationName,
      quantity: data.quantity,
      warehouseName: data.warehouseName,
    });
  },
  title: '库存移库',
});
</script>
<template>
  <Modal class="w-3/5">
    <Form class="mx-4" />
  </Modal>
</template>
src/views/wls/materialstock/components/select-dialog.vue
@@ -161,7 +161,7 @@
            // é»˜è®¤æŸ¥æœªå†»ç»“
            frozen: false,
            itemTypeId: searchItemTypeId.value,
            itemId: formValues.mdmItemId ?? props.mdmItemId,
            itemId: props.mdmItemId,
            warehouseId: formValues.warehouseId ?? props.warehouseId,
            virtualFilter:
              props.virtualFilter === 'all' ? undefined : props.virtualFilter,
src/views/wls/materialstock/data.ts
@@ -8,7 +8,6 @@
import { DICT_TYPE } from '@vben/constants';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { MdVendorSelect } from '#/views/mes/md/vendor/components';
import {
  WmWarehouseAreaSelect,
@@ -84,6 +83,7 @@
    newFrozen: boolean,
    row: MesWmMaterialStockApi.MaterialStock,
  ) => Promise<boolean | undefined>,
  onMove?: (row: MesWmMaterialStockApi.MaterialStock) => void,
): VxeTableGridOptions<MesWmMaterialStockApi.MaterialStock>['columns'] {
  const { hasAccessByCodes } = useAccess();
  return [
@@ -190,6 +190,16 @@
        },
      },
    },
    ...(onMove
      ? [
          {
            title: '操作',
            width: 80,
            fixed: 'right' as const,
            slots: { default: 'actions' } as const,
          },
        ]
      : []),
  ];
}
@@ -197,19 +207,21 @@
export function useSelectGridFormSchema(): VbenFormSchema[] {
  return [
    {
      fieldName: 'itemId',
      fieldName: 'itemName',
      label: '物料',
      component: markRaw(MdItemSelect),
      component: 'Input',
      componentProps: {
        placeholder: '请选择物料',
        allowClear: true,
        placeholder: '请输入物料名称',
      },
    },
    {
      fieldName: 'vendorId',
      fieldName: 'vendorName',
      label: '供应商',
      component: markRaw(MdVendorSelect),
      component: 'Input',
      componentProps: {
        placeholder: '请选择供应商',
        allowClear: true,
        placeholder: '请输入供应商名称',
      },
    },
    {
src/views/wls/materialstock/index.vue
@@ -19,7 +19,7 @@
import { MdItemTypeTree } from '#/views/mes/md/item/type/components';
import { WmBatchDetail } from '#/views/wls/batch/components';
import AreaForm from '#/views/wls/warehouse/area/modules/form.vue';
import { WmStockAdjustModal } from './components';
import { WmStockAdjustModal, WmStockMoveModal } from './components';
import { useGridColumns, useGridFormSchema } from './data';
@@ -30,6 +30,11 @@
const [StockAdjustModal, stockAdjustModalApi] = useVbenModal({
  connectedComponent: WmStockAdjustModal,
  destroyOnClose: true,
});
const [StockMoveModal, stockMoveModalApi] = useVbenModal({
  connectedComponent: WmStockMoveModal,
  destroyOnClose: true,
});
@@ -74,6 +79,16 @@
  gridApi.query();
}
/** æ‰“开移库弹窗 */
function handleOpenStockMove(row: MesWmMaterialStockApi.MaterialStock) {
  stockMoveModalApi.setData(row).open();
}
/** ç§»åº“成功回调 */
function handleStockMoveSuccess() {
  gridApi.query();
}
/** æ‰“开批次详情弹窗 */
function handleOpenBatchDetail(row: MesWmMaterialStockApi.MaterialStock) {
  if (!row.batchId) {
@@ -108,7 +123,7 @@
    schema: useGridFormSchema(),
  },
  gridOptions: {
    columns: useGridColumns(handleFrozenChange),
    columns: useGridColumns(handleFrozenChange, handleOpenStockMove),
    height: 'auto',
    keepSource: true,
    proxyConfig: {
@@ -139,6 +154,7 @@
  <Page auto-content-height><AreaModal />
    <WmBatchDetail ref="batchDetailRef" />
    <StockAdjustModal @success="handleStockAdjustSuccess" />
    <StockMoveModal @success="handleStockMoveSuccess" />
    <div class="flex h-full w-full">
      <!-- å·¦ä¾§ç‰©æ–™åˆ†ç±»æ ‘ -->
@@ -192,6 +208,19 @@
            </Button>
            <span v-else>-</span>
          </template>
          <template #actions="{ row }">
            <TableAction
              :actions="[
                {
                  label: '移库',
                  type: 'link',
                  auth: ['mes:wm-material-stock:move'],
                  ifShow: !row.frozen,
                  onClick: handleOpenStockMove.bind(null, row),
                },
              ]"
            />
          </template>
        </Grid>
      </div>
    </div>
src/views/wls/productissue/modules/form.vue
@@ -152,17 +152,21 @@
      if (formData.value?.id) {
        await updateProductIssue({ ...formData.value, ...data });
        formData.value = { ...formData.value, ...data };
      } else {
        const id = await createProductIssue(data);
        formData.value = {
          ...data,
          id,
          status: MesWmProductIssueStatusEnum.PREPARE,
        };
        await formApi.setFieldValue('id', id);
        await formApi.setFieldValue('status', formData.value.status);
        formType.value = 'update';
        emit('success');
        message.success($t('ui.actionMessage.operationSuccess'));
        await modalApi.close();
        return;
      }
      // æ–°å¢žï¼šä¿æŒå¼¹çª—打开,切换到编辑模式
      const id = await createProductIssue(data);
      formData.value = {
        ...data,
        id,
        status: MesWmProductIssueStatusEnum.PREPARE,
      };
      await formApi.setFieldValue('id', id);
      await formApi.setFieldValue('status', formData.value.status);
      formType.value = 'update';
      originalSnapshot.value = JSON.stringify(await formApi.getValues());
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
src/views/wls/productsales/data.ts
@@ -139,14 +139,6 @@
      },
    },
    {
      fieldName: 'salesOrderCode',
      component: 'Input',
      dependencies: {
        triggerFields: [''],
        show: () => false,
      },
    },
    {
      fieldName: 'saleOrderId',
      label: '销售订单编号',
      component: markRaw(ErpSaleOrderSelect),
@@ -163,6 +155,23 @@
        },
      },
      rules: 'selectRequired',
      dependencies: {
        triggerFields: ['salesOrderCode'],
        if: () => formType === 'create',
      },
    },
    {
      fieldName: 'salesOrderCode',
      label: '销售订单编号',
      component: 'Input',
      componentProps: {
        disabled: true,
        placeholder: '销售订单编号',
      },
      dependencies: {
        triggerFields: [''],
        if: () => formType !== 'create',
      },
    },
    {
      fieldName: 'salesDate',
src/views/wls/returnissue/modules/form.vue
@@ -142,17 +142,21 @@
      if (formData.value?.id) {
        await updateReturnIssue({ ...formData.value, ...data });
        formData.value = { ...formData.value, ...data };
      } else {
        const id = await createReturnIssue(data);
        formData.value = {
          ...data,
          id,
          status: MesWmReturnIssueStatusEnum.PREPARE,
        };
        await formApi.setFieldValue('id', id);
        await formApi.setFieldValue('status', formData.value.status);
        formType.value = 'update';
        emit('success');
        message.success($t('ui.actionMessage.operationSuccess'));
        await modalApi.close();
        return;
      }
      // æ–°å¢žï¼šä¿æŒå¼¹çª—打开,切换到编辑模式
      const id = await createReturnIssue(data);
      formData.value = {
        ...data,
        id,
        status: MesWmReturnIssueStatusEnum.PREPARE,
      };
      await formApi.setFieldValue('id', id);
      await formApi.setFieldValue('status', formData.value.status);
      formType.value = 'update';
      originalSnapshot.value = JSON.stringify(await formApi.getValues());
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));
src/views/wls/salesnotice/modules/form.vue
@@ -100,17 +100,21 @@
      if (formData.value?.id) {
        await updateSalesNotice({ ...formData.value, ...data });
        formData.value = { ...formData.value, ...data };
      } else {
        const id = await createSalesNotice(data);
        formData.value = {
          ...data,
          id,
          status: MesWmSalesNoticeStatusEnum.PREPARE,
        };
        await formApi.setFieldValue('id', id);
        await formApi.setFieldValue('status', formData.value.status);
        formType.value = 'update';
        emit('success');
        message.success($t('ui.actionMessage.operationSuccess'));
        await modalApi.close();
        return;
      }
      // æ–°å¢žï¼šä¿æŒå¼¹çª—打开,切换到编辑模式
      const id = await createSalesNotice(data);
      formData.value = {
        ...data,
        id,
        status: MesWmSalesNoticeStatusEnum.PREPARE,
      };
      await formApi.setFieldValue('id', id);
      await formApi.setFieldValue('status', formData.value.status);
      formType.value = 'update';
      originalSnapshot.value = JSON.stringify(await formApi.getValues());
      emit('success');
      message.success($t('ui.actionMessage.operationSuccess'));