gongchunyi
2026-09-03 9840e565b021ca4ed199ea40d2c8b47bc9feeb3f
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -1,298 +1,541 @@
<template>
  <div>
    <el-dialog v-model="dialogVisible" title="领料台账" width="1200px" @close="handleClose">
    <el-dialog v-model="dialogVisible"
               title="领料台账"
               width="1450px"
               @close="handleClose">
      <div class="material-toolbar">
        <el-button type="primary" @click="handleAddMaterialRow">新增</el-button>
        <el-button type="primary"
                   @click="handleAddMaterialRow">新增</el-button>
      </div>
      <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="tempId">
        <el-table-column label="工序名称" min-width="180">
      <el-table v-loading="materialTableLoading"
                :data="materialTableData"
                border
                row-key="tempId">
        <el-table-column label="工序名称"
                         min-width="100">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.processName || "-" }}</span>
            <el-select
              v-else
              v-model="row.processName"
              placeholder="请选择工序"
              clearable
              filterable
              style="width: 100%;"
              @change="val => handleProcessNameChange(row, val)"
            >
              <el-option v-for="item in processOptions" :key="item.id" :label="item.name" :value="item.name" />
            <span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
            <el-select v-else
                       v-model="row.operationName"
                       placeholder="请选择工序"
                       clearable
                       filterable
                       style="width: 100%;"
                       @change="val => handleProcessNameChange(row, val)">
              <el-option v-for="item in processOptions"
                         :key="item.technologyOperationId"
                         :label="item.name"
                         :value="item.name" />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="原料名称" min-width="160">
        <el-table-column label="原料名称"
                         min-width="120">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
            <el-button v-else type="primary" link @click="openMaterialProductSelect(row)">
            <el-button v-else
                       type="primary"
                       link
                       @click="openMaterialProductSelect(row)">
              {{ row.materialName || "选择原料" }}
            </el-button>
          </template>
        </el-table-column>
        <el-table-column label="原料型号" min-width="180">
        <el-table-column label="原料型号"
                         min-width="110">
          <template #default="{ row }">
            {{ row.materialModel || "-" }}
          </template>
        </el-table-column>
        <el-table-column label="需求数量" min-width="120">
        <!-- 批号多选 -->
        <el-table-column min-width="180"
                         label="批号">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.requiredQty ?? "-" }}</span>
            <el-input-number
              v-else
              v-model="row.requiredQty"
              :min="0"
              :precision="3"
              :step="1"
              controls-position="right"
              style="width: 100%;"
              @change="val => handleRequiredQtyChange(row, val)"
            />
            <el-select v-model="row.batchNo"
                       multiple
                       collapse-tags
                       collapse-tags-indicator
                       placeholder="请选择批号"
                       style="width: 100%;">
              <el-option v-for="item in row.batchNoList"
                         :key="item"
                         :label="item"
                         :value="item" />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="计量单位" width="120">
        <el-table-column label="需求数量"
                         min-width="95">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ stripTrailingZeros(row.demandedQuantity) ?? "-" }}</span>
            <el-input-number v-else
                             v-model="row.demandedQuantity"
                             :min="0"
                             :step="1"
                             controls-position="right"
                             :formatter="value => stripTrailingZeros(value)"
                             :parser="value => parseFloat(value) || 0"
                             style="width: 100%;"
                             @change="val => handleRequiredQtyChange(row, val)" />
          </template>
        </el-table-column>
        <el-table-column label="库存余量"
                         min-width="90">
          <template #default="{ row }">
            <span v-if="row.stockQty === null">-</span>
            <span v-else>{{ stripTrailingZeros(row.stockQty) }}</span>
          </template>
        </el-table-column>
        <el-table-column label="已领数量"
                         min-width="90">
          <template #default="{ row }">
            {{ stripTrailingZeros(row.pickedQty) ?? 0 }}
          </template>
        </el-table-column>
        <el-table-column label="剩余可领"
                         min-width="90">
          <template #default="{ row }">
            <span :class="{ 'remaining-empty': Number(row.remainingQty) <= 0 }">
              {{ stripTrailingZeros(row.remainingQty) ?? 0 }}
            </span>
          </template>
        </el-table-column>
        <el-table-column label="计量单位"
                         width="80">
          <template #default="{ row }">
            {{ row.unit || "-" }}
          </template>
        </el-table-column>
        <el-table-column label="领用数量" min-width="120">
        <el-table-column label="领用数量"
                         min-width="120">
          <template #default="{ row }">
            <el-input-number
              v-model="row.pickQty"
              :min="0"
              :precision="3"
              :step="1"
              controls-position="right"
              style="width: 100%;"
            />
            <el-input-number v-model="row.pickQty"
                             :min="0"
                             :step="1"
                             controls-position="right"
                             :formatter="value => stripTrailingZeros(value)"
                             :parser="value => parseFloat(value) || 0"
                             style="width: 100%;" />
          </template>
        </el-table-column>
        <el-table-column label="操作" width="90" fixed="right">
        <el-table-column label="操作"
                         width="90"
                         fixed="right">
          <template #default="{ $index, row }">
            <el-button v-if="row.bom !== true" type="danger" link @click="handleDeleteMaterialRow($index)">删除</el-button>
            <el-button v-if="row.bom !== true || Number(row.pickQty) > 0"
                       type="danger"
                       link
                       @click="handleDeleteMaterialRow($index)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" :loading="materialSaving" @click="handleMaterialSave">保存</el-button>
          <el-button type="primary"
                     :loading="materialSaving"
                     :disabled="isSaveDisabled"
                     @click="handleMaterialSave">保存</el-button>
          <el-button @click="dialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
    <ProductSelectDialog
      v-model="materialProductDialogVisible"
      @confirm="handleMaterialProductConfirm"
      single
      request-url="/stockInventory/rawMaterials"
    />
    <ProductSelectDialog v-model="materialProductDialogVisible"
                         @confirm="handleMaterialProductConfirm"
                         single />
    <!-- request-url="/stockInventory/rawMaterials" -->
  </div>
</template>
<script setup>
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
import {
  listMaterialPickingDetail,
  listMaterialPickingLedger,
  saveMaterialPickingLedger,
} from "@/api/productionManagement/productionOrder.js";
  import { computed, ref, watch } from "vue";
  import { ElMessage } from "element-plus";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { findProductProcessRouteItemList } from "@/api/productionManagement/productProcessRoute.js";
  import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js";
  import {
    listMaterialPickingDetail,
    listMaterialPickingBom,
    saveMaterialPickingLedger,
  } from "@/api/productionManagement/productionOrder.js";
const props = defineProps({
  modelValue: { type: Boolean, default: false },
  orderRow: { type: Object, default: null },
});
const emit = defineEmits(["update:modelValue", "saved"]);
const dialogVisible = computed({
  get: () => props.modelValue,
  set: val => emit("update:modelValue", val),
});
const materialProductDialogVisible = ref(false);
const materialTableLoading = ref(false);
const materialSaving = ref(false);
const materialTableData = ref([]);
const processOptions = ref([]);
const currentMaterialSelectRowIndex = ref(-1);
let materialTempId = 0;
const createMaterialRow = (row = {}) => ({
  tempId: row.id || `temp_${++materialTempId}`,
  id: row.id,
  processId: row.processId,
  productProcessId: row.productProcessId || row.processId,
  processName: row.processName || "",
  bom: row.bom === true,
  materialModelId: row.materialModelId,
  materialName: row.materialName || "",
  materialModel: row.materialModel || "",
  requiredQty: Number(row.requiredQty ?? 0),
  unit: row.unit || "",
  pickQty: Number(row.pickQty ?? row.requiredQty ?? 0),
});
const getProcessOptions = async () => {
  if (!props.orderRow?.id) return;
  const res = await findProductProcessRouteItemList({ orderId: props.orderRow.id });
  const routeList = Array.isArray(res?.data) ? res.data : res?.data?.records || [];
  const processMap = new Map();
  routeList.forEach(item => {
    const processId = item.processId;
    const processName = item.processName;
    if (!processId || !processName) return;
    const key = `${processId}_${processName}`;
    if (!processMap.has(key)) {
      processMap.set(key, {
        id: processId,
        name: processName,
      });
    }
  const props = defineProps({
    modelValue: { type: Boolean, default: false },
    orderRow: { type: Object, default: null },
  });
  processOptions.value = Array.from(processMap.values());
};
  const emit = defineEmits(["update:modelValue", "saved"]);
const loadMaterialData = async () => {
  if (!props.orderRow?.id) return;
  materialTableLoading.value = true;
  materialTableData.value = [];
  await getProcessOptions();
  try {
    const detailRes = await listMaterialPickingDetail({ orderId: props.orderRow.id });
    const detailList = Array.isArray(detailRes?.data)
      ? detailRes.data
      : detailRes?.data?.records || [];
    if (detailList.length > 0) {
      materialTableData.value = detailList.map(item => createMaterialRow(item));
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
  const materialProductDialogVisible = ref(false);
  const materialTableLoading = ref(false);
  const materialSaving = ref(false);
  const materialTableData = ref([]);
  const isSaveDisabled = computed(() => {
    if (materialTableLoading.value) return true;
    if (materialTableData.value.length === 0) return true;
    // 只有存在本次领用数量大于0的行才允许保存
    return !materialTableData.value.some(item => Number(item.pickQty) > 0);
  });
  const processOptions = ref([]);
  const currentMaterialSelectRowIndex = ref(-1);
  let materialTempId = 0;
  const toNumber = val => {
    const n = Number(val);
    return Number.isFinite(n) ? n : 0;
  };
  // 保留6位小数,避免浮点误差
  const roundQuantity = val => Math.round(toNumber(val) * 1e6) / 1e6;
  // 需求数量分组键:与后端保持一致(工序ID优先,缺失时用工序名称)
  const buildDemandKey = (technologyOperationId, operationName, productModelId) =>
    `${technologyOperationId != null && technologyOperationId !== undefined ? "T" + technologyOperationId : "N" + (operationName || "")}#${productModelId ?? ""}`;
  const stripTrailingZeros = val => {
    if (val === null || val === undefined || val === "") return "0";
    const str = String(val);
    if (str.includes(".")) {
      return parseFloat(str).toString();
    }
    return str;
  };
  const createMaterialRow = (row = {}) => {
    const batchNoList = Array.isArray(row.batchNoList) ? row.batchNoList : [];
    const pickedEntry = row.pickedEntry || null;
    const pickedQty = pickedEntry ? roundQuantity(pickedEntry.pickedQty) : 0;
    const returnedQty = pickedEntry ? roundQuantity(pickedEntry.returnedQty) : 0;
    const demandedQuantity = roundQuantity(row.demandedQuantity ?? 0);
    const remainingQty = Math.max(roundQuantity(demandedQuantity - pickedQty + returnedQty), 0);
    return {
      tempId: `temp_${++materialTempId}`,
      id: null,
      bom: row.bom === true,
      technologyOperationId: row.technologyOperationId ?? null,
      operationName: row.operationName || "",
      materialModelId: row.materialModelId ?? null,
      materialName: row.materialName || "",
      materialModel: row.materialModel || "",
      unit: row.unit || "",
      demandedQuantity,
      pickedQty,
      returnedQty,
      remainingQty,
      pickQty: 0,
      stockQty: null,
      batchNo: [],
      batchNoList,
    };
  };
  const getProcessOptions = async () => {
    if (!props.orderRow?.id) return;
    const res = await findProductProcessRouteItemList({
      orderId: props.orderRow.id,
    });
    const routeList = Array.isArray(res?.data)
      ? res.data
      : res?.data?.records || [];
    const processMap = new Map();
    routeList.forEach(item => {
      const processId = item.technologyOperationId;
      const operationName = item.operationName;
      if (!processId || !operationName) return;
      const key = `${processId}_${operationName}`;
      if (!processMap.has(key)) {
        processMap.set(key, {
          id: processId,
          technologyOperationId: processId,
          name: operationName,
        });
      }
    });
    processOptions.value = Array.from(processMap.values());
  };
  const loadMaterialData = async () => {
    if (!props.orderRow?.id) return;
    materialTableLoading.value = true;
    materialTableData.value = [];
    try {
      await getProcessOptions();
      // 同时获取BOM需求数据与已领料明细,用于计算“已领数量/剩余可领”
      const [bomRes, detailRes] = await Promise.all([
        listMaterialPickingBom(props.orderRow.id),
        listMaterialPickingDetail(props.orderRow.id),
      ]);
      const bomList = Array.isArray(bomRes?.data)
        ? bomRes.data
        : bomRes?.data?.records || [];
      const detailList = Array.isArray(detailRes?.data)
        ? detailRes.data
        : detailRes?.data?.records || [];
      // 按“工序+原料”汇总历史领料:已领数量、退料数量
      const pickedMap = new Map();
      detailList.forEach(item => {
        if (!item) return;
        const key = buildDemandKey(
          item.technologyOperationId,
          item.operationName,
          item.productModelId
        );
        const entry = pickedMap.get(key) || {
          pickedQty: 0,
          returnedQty: 0,
          manualDemand: 0,
          technologyOperationId: item.technologyOperationId,
          operationName: item.operationName || "",
          materialModelId: item.productModelId,
          materialName: item.productName || "",
          materialModel: item.model || "",
          unit: item.unit || "",
          batchNoList: Array.isArray(item.batchNoList)
            ? item.batchNoList
            : [],
        };
        entry.pickedQty = roundQuantity(entry.pickedQty + toNumber(item.pickQuantity));
        entry.returnedQty = roundQuantity(entry.returnedQty + toNumber(item.returnQty));
        // 手工新增行(非BOM)的需求数量以领料快照为准
        if (item.bom !== true && item.demandedQuantity != null) {
          entry.manualDemand = Math.max(
            toNumber(entry.manualDemand),
            toNumber(item.demandedQuantity)
          );
        }
        pickedMap.set(key, entry);
      });
      const rows = [];
      const bomKeySet = new Set();
      // BOM行:需求数量取BOM,已领数量取历史领料汇总
      bomList.forEach(item => {
        const materialModelId = item.productModelId ?? item.materialModelId;
        const key = buildDemandKey(
          item.technologyOperationId,
          item.operationName,
          materialModelId
        );
        bomKeySet.add(key);
        rows.push(
          createMaterialRow({
            bom: true,
            technologyOperationId: item.technologyOperationId,
            operationName: item.operationName || "",
            materialModelId,
            materialName: item.productName || item.materialName || "",
            materialModel: item.model || item.materialModel || "",
            unit: item.unit || "",
            demandedQuantity: item.demandedQuantity ?? item.requiredQty ?? 0,
            pickedEntry: pickedMap.get(key),
            batchNoList: item.batchNoList || [],
          })
        );
      });
      // 不在BOM中的历史领料(手工新增行),也展示出来支持继续领料
      pickedMap.forEach((entry, key) => {
        if (bomKeySet.has(key)) return;
        rows.push(
          createMaterialRow({
            bom: false,
            technologyOperationId: entry.technologyOperationId,
            operationName: entry.operationName,
            materialModelId: entry.materialModelId,
            materialName: entry.materialName,
            materialModel: entry.materialModel,
            unit: entry.unit,
            demandedQuantity: entry.manualDemand,
            pickedEntry: entry,
            batchNoList: entry.batchNoList,
          })
        );
      });
      // 先查出库存余量再入表,保证首次打开即展示(rows 尚未转为响应式代理,
      // 入表后再改原始对象不会触发视图更新)
      await loadStockQtyForRows(rows);
      materialTableData.value = rows;
    } finally {
      materialTableLoading.value = false;
    }
  };
  watch(
    () => dialogVisible.value,
    visible => {
      if (visible) {
        loadMaterialData();
      }
    }
  );
  const handleClose = () => {
    materialTableData.value = [];
    currentMaterialSelectRowIndex.value = -1;
  };
  // 按型号汇总所有批次的原料可用库存余量
  const loadStockQtyForRows = async rows => {
    const modelIds = [
      ...new Set(rows.map(row => row.materialModelId).filter(Boolean)),
    ];
    const stockMap = new Map();
    await Promise.all(
      modelIds.map(async modelId => {
        try {
          const res = await getStockInventoryByModelId(modelId);
          const list = Array.isArray(res?.data)
            ? res.data
            : res?.data?.records || res?.data?.rows || [];
          const total = list.reduce(
            (sum, item) => sum + toNumber(item?.qualitity ?? item?.quantity),
            0
          );
          stockMap.set(modelId, roundQuantity(total));
        } catch (e) {
          stockMap.set(modelId, 0);
        }
      })
    );
    rows.forEach(row => {
      if (!row.materialModelId) {
        row.stockQty = null;
        return;
      }
      row.stockQty = stockMap.has(row.materialModelId)
        ? stockMap.get(row.materialModelId)
        : 0;
    });
  };
  const handleAddMaterialRow = () => {
    materialTableData.value.push(createMaterialRow());
  };
  const handleDeleteMaterialRow = index => {
    materialTableData.value.splice(index, 1);
  };
  const handleProcessNameChange = (row, operationName) => {
    const process = processOptions.value.find(
      item => item.name === operationName
    );
    row.technologyOperationId = process?.technologyOperationId;
  };
  const handleRequiredQtyChange = (row, val) => {
    const required = roundQuantity(val);
    row.demandedQuantity = required;
    row.remainingQty = Math.max(
      roundQuantity(required - toNumber(row.pickedQty) + toNumber(row.returnedQty)),
      0
    );
  };
  const openMaterialProductSelect = row => {
    currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(
      item => item.tempId === row.tempId
    );
    materialProductDialogVisible.value = true;
  };
  const handleMaterialProductConfirm = products => {
    if (!products || products.length === 0) return;
    const index = currentMaterialSelectRowIndex.value;
    if (index < 0 || !materialTableData.value[index]) return;
    const product = products[0];
    const row = materialTableData.value[index];
    row.materialModelId =
      product.materialModelId || product.modelId || product.id;
    row.materialName =
      product.materialName || product.productName || product.name || "";
    row.materialModel = product.materialModel || product.model || "";
    row.unit = product.unit || product.measureUnit || "";
    row.batchNoList = Array.isArray(product.batchNoList) ? product.batchNoList : [];
    row.batchNo = [];
    currentMaterialSelectRowIndex.value = -1;
    materialProductDialogVisible.value = false;
    loadStockQtyForRows([row]);
  };
  const validateMaterialRows = () => {
    if (materialTableData.value.length === 0) {
      return { valid: false, message: "请先新增领料数据" };
    }
    const pickRows = materialTableData.value.filter(
      item => toNumber(item.pickQty) > 0
    );
    if (pickRows.length === 0) {
      return { valid: false, message: "请至少填写一行大于0的领用数量" };
    }
    for (let i = 0; i < materialTableData.value.length; i++) {
      const item = materialTableData.value[i];
      const rowNo = i + 1;
      if (toNumber(item.pickQty) <= 0) continue;
      if (item.bom !== true && (!item.operationName || !item.materialName)) {
        return { valid: false, message: `第${rowNo}行的工序名称和原料名称为必填项` };
      }
      if (!Array.isArray(item.batchNo) || item.batchNo.length === 0) {
        return { valid: false, message: `第${rowNo}行请选择批号` };
      }
    }
    return { valid: true, message: "" };
  };
  const handleMaterialSave = async () => {
    if (!props.orderRow?.id) return;
    const validateResult = validateMaterialRows();
    if (!validateResult.valid) {
      ElMessage.warning(validateResult.message);
      return;
    }
    const ledgerRes = await listMaterialPickingLedger({ orderId: props.orderRow.id });
    const ledgerList = Array.isArray(ledgerRes?.data)
      ? ledgerRes.data
      : ledgerRes?.data?.records || [];
    materialTableData.value = ledgerList.map(item => createMaterialRow(item));
  } finally {
    materialTableLoading.value = false;
  }
};
    materialSaving.value = true;
    try {
      // 每次保存都是一次独立的领料:只提交本次领用数量大于0的行,
      // 出库台账按本次领料数量记账,历史领料记录保持不变。
      const pickRows = materialTableData.value.filter(
        item => toNumber(item.pickQty) > 0
      );
      await saveMaterialPickingLedger({
        productionOrderId: props.orderRow.id,
        productionOrderPickDto: pickRows.map(item => ({
          technologyOperationId: item.technologyOperationId,
          operationName: item.operationName,
          bom: item.bom === true,
          productModelId: item.materialModelId,
          demandedQuantity: item.demandedQuantity,
          unit: item.unit,
          pickQuantity: toNumber(item.pickQty),
          batchNo: Array.isArray(item.batchNo)
            ? item.batchNo.join(",")
            : item.batchNo,
        })),
      });
watch(
  () => dialogVisible.value,
  visible => {
    if (visible) {
      loadMaterialData();
      ElMessage({ message: "领料成功", type: "success" });
      emit("saved");
      dialogVisible.value = false;
    } finally {
      materialSaving.value = false;
    }
  }
);
const handleClose = () => {
  materialTableData.value = [];
  currentMaterialSelectRowIndex.value = -1;
};
const handleAddMaterialRow = () => {
  materialTableData.value.push(createMaterialRow());
};
const handleDeleteMaterialRow = index => {
  materialTableData.value.splice(index, 1);
};
const handleProcessNameChange = (row, processName) => {
  const process = processOptions.value.find(item => item.name === processName);
  row.productProcessId = process?.id;
};
const handleRequiredQtyChange = (row, val) => {
  const required = Number(val ?? 0);
  row.requiredQty = required;
  if (!row.pickQty || Number(row.pickQty) === 0) {
    row.pickQty = required;
  }
};
const openMaterialProductSelect = row => {
  currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(item => item.tempId === row.tempId);
  materialProductDialogVisible.value = true;
};
const handleMaterialProductConfirm = products => {
  if (!products || products.length === 0) return;
  const index = currentMaterialSelectRowIndex.value;
  if (index < 0 || !materialTableData.value[index]) return;
  const product = products[0];
  const row = materialTableData.value[index];
  row.materialModelId = product.materialModelId || product.modelId || product.id;
  row.materialName = product.materialName || product.productName || product.name || "";
  row.materialModel = product.materialModel || product.model || "";
  row.unit = product.unit || product.measureUnit || "";
  currentMaterialSelectRowIndex.value = -1;
  materialProductDialogVisible.value = false;
};
const validateMaterialRows = () => {
  if (materialTableData.value.length === 0) {
    return { valid: false, message: "请先新增领料数据" };
  }
  const invalidNewRow = materialTableData.value.find(
    item => item.bom !== true && (!item.processName || !item.materialName)
  );
  if (invalidNewRow) {
    return { valid: false, message: "新增行的工序名称和原料名称为必填项" };
  }
  const invalidRow = materialTableData.value.find(
    item =>
      !item.processName ||
      !item.materialName ||
      item.requiredQty === null ||
      item.requiredQty === undefined ||
      item.pickQty === null ||
      item.pickQty === undefined
  );
  if (invalidRow) {
    return { valid: false, message: "请完善工序、原料和数量后再保存" };
  }
  return { valid: true, message: "" };
};
const handleMaterialSave = async () => {
  if (!props.orderRow?.id) return;
  const validateResult = validateMaterialRows();
  if (!validateResult.valid) {
    ElMessage.warning(validateResult.message);
    return;
  }
  materialSaving.value = true;
  try {
    await saveMaterialPickingLedger({
      orderId: props.orderRow.id,
      items: materialTableData.value.map(item => ({
        id: item.id,
        processId: item.processName,
        productProcessId: item.productProcessId,
        processName: item.processName,
        bom: item.bom === true,
        materialModelId: item.materialModelId,
        materialName: item.materialName,
        materialModel: item.materialModel,
        requiredQty: item.requiredQty,
        unit: item.unit,
        pickQty: item.pickQty,
      })),
    });
    emit("saved");
    dialogVisible.value = false;
  } finally {
    materialSaving.value = false;
  }
};
  };
</script>
<style scoped lang="scss">
.material-toolbar {
  margin-bottom: 12px;
  text-align: right;
}
  .material-toolbar {
    margin-bottom: 12px;
    text-align: right;
  }
  .remaining-empty {
    color: #f56c6c;
    font-weight: 600;
  }
</style>