zhangwencui
2026-04-28 b7e3bc6bbe2f6464f4f92e457212fac7ea61758d
领料功能
已添加1个文件
已修改6个文件
835 ■■■■ 文件已修改
src/api/productionManagement/productionOrder.js 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 152 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue 138 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/productionManagement/productionOrder.js
@@ -72,23 +72,47 @@
}
// ç”Ÿäº§è®¢å•-保存领料台账
// export function saveMaterialPickingLedger(data) {
//   return request({
//     url: "/productOrderMaterial/save",
//     method: "post",
//     data,
//   });
// }
export function saveMaterialPickingLedger(data) {
  return request({
    url: "/productOrderMaterial/save",
    url: "/productionOrderPick/savePick",
    method: "post",
    data,
  });
}
// ç”Ÿäº§è®¢å•-领料详情列表
export function listMaterialPickingDetail(query) {
export function updateMaterialPickingLedger(data) {
  return request({
    url: "/productOrderMaterial/detailList",
    method: "get",
    params: query,
    url: "/productionOrderPick/updatePick",
    method: "post",
    data,
  });
}
// ç”Ÿäº§è®¢å•-领料详情列表
// export function listMaterialPickingDetail(query) {
//   return request({
//     url: "/productOrderMaterial/detailList",
//     method: "get",
//     params: query,
//   });
// }
export function listMaterialPickingBom(productionOrderId) {
  return request({
    url: "/productionOrder/pick/" + productionOrderId,
    method: "get",
  });
}
export function listMaterialPickingDetail(productionOrderId) {
  return request({
    url: "/productionOrderPick/detail/" + productionOrderId,
    method: "get",
  });
}
// ç”Ÿäº§è®¢å•-补料记录列表
export function listMaterialSupplementRecord(query) {
  return request({
src/views/inventoryManagement/stockManagement/New.vue
@@ -1,14 +1,14 @@
<template>
  <div>
    <el-dialog
        v-model="isShow"
    <el-dialog v-model="isShow"
        title="新增库存"
        width="800"
        @close="closeModal"
    >
      <el-form label-width="140px" :model="formState" label-position="top" ref="formRef">
        <el-form-item
            label="产品名称"
               @close="closeModal">
      <el-form label-width="140px"
               :model="formState"
               label-position="top"
               ref="formRef">
        <el-form-item label="产品名称"
            prop="productModelId"
            :rules="[
                {
@@ -16,29 +16,23 @@
                message: '请选择产品',
                trigger: 'change',
              }
            ]"
        >
          <el-button type="primary" @click="showProductSelectDialog = true">
            ]">
          <el-button type="primary"
                     @click="showProductSelectDialog = true">
            {{ formState.productName ? formState.productName : '选择产品' }}
          </el-button>
        </el-form-item>
        <el-form-item
            label="规格"
            prop="productModelName"
        >
          <el-input v-model="formState.productModelName"  disabled />
        <el-form-item label="规格"
                      prop="productModelName">
          <el-input v-model="formState.productModelName"
                    disabled />
        </el-form-item>
        <el-form-item
            label="单位"
            prop="unit"
        >
          <el-input v-model="formState.unit"  disabled />
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formState.unit"
                    disabled />
        </el-form-item>
        <el-form-item
            label="库存类型"
        <el-form-item label="库存类型"
            prop="type"
            :rules="[
                {
@@ -46,51 +40,58 @@
                message: '请选择库存类型',
                trigger: 'change',
              }
            ]"
        >
          <el-select v-model="formState.type" placeholder="请选择库存类型">
            <el-option label="合格库存" value="qualified" />
            <el-option label="不合格库存" value="unqualified" />
            ]">
          <el-select v-model="formState.type"
                     placeholder="请选择库存类型">
            <el-option label="合格库存"
                       value="qualified" />
            <el-option label="不合格库存"
                       value="unqualified" />
          </el-select>
        </el-form-item>
        <el-form-item
            label="库存数量"
            prop="qualitity"
        >
          <el-input-number v-model="formState.qualitity" :step="1" :min="1" style="width: 100%" />
        <el-form-item label="库存数量"
                      prop="qualitity">
          <el-input-number v-model="formState.qualitity"
                           :step="1"
                           :min="1"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item
            label="批号"
        <el-form-item label="批号"
            prop="batchNo"
        >
          <el-input v-model="formState.batchNo" placeholder="请输入批号" />
                      :rules="[
                {
                required: true,
                message: '请输入批号',
                trigger: 'blur',
              }
            ]">
          <el-input v-model="formState.batchNo"
                    placeholder="请输入批号" />
        </el-form-item>
        <el-form-item
            v-if="formState.type === 'qualified'"
        <el-form-item v-if="formState.type === 'qualified'"
            label="库存预警数量"
            prop="warnNum"
        >
          <el-input-number v-model="formState.warnNum" :step="1" :min="0" :max="formState.qualitity" style="width: 100%" />
                      prop="warnNum">
          <el-input-number v-model="formState.warnNum"
                           :step="1"
                           :min="0"
                           :max="formState.qualitity"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="formState.remark" type="textarea" />
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="formState.remark"
                    type="textarea" />
        </el-form-item>
      </el-form>
      <!-- äº§å“é€‰æ‹©å¼¹çª— -->
      <ProductSelectDialog
          v-model="showProductSelectDialog"
      <ProductSelectDialog v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          :top-product-parent-id="props.topProductParentId"
          single
        />
                           single />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button type="primary"
                     @click="handleSubmit">确认</el-button>
          <el-button @click="closeModal">取消</el-button>
        </div>
      </template>
@@ -113,10 +114,10 @@
    type: Number,
    default: undefined,
    required: false,
  }
    },
});
const emit = defineEmits(['update:visible', 'completed']);
  const emit = defineEmits(["update:visible", "completed"]);
// å“åº”式数据(替代选项式的 data)
const formState = ref({
@@ -129,7 +130,7 @@
  qualitity: 0,
  batchNo: null,
  warnNum: 0,
  remark: '',
    remark: "",
});
const isShow = computed({
@@ -137,20 +138,23 @@
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
      emit("update:visible", val);
  },
});
const showProductSelectDialog = ref(false);
// æ‰¹å·ä¸ºç©ºæ—¶è½¬ä¸º null
watch(() => formState.value.batchNo, (val) => {
  if (val === '') {
  watch(
    () => formState.value.batchNo,
    val => {
      if (val === "") {
    formState.value.batchNo = null;
  }
});
    }
  );
let { proxy } = getCurrentInstance()
  let { proxy } = getCurrentInstance();
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
@@ -164,13 +168,13 @@
    qualitity: 0,
    batchNo: null,
    warnNum: 0,
    remark: '',
      remark: "",
  };
  isShow.value = false;
};
// äº§å“é€‰æ‹©å¤„理
const handleProductSelect = async (products) => {
  const handleProductSelect = async products => {
  if (products && products.length > 0) {
    const product = products[0];
    formState.value.productId = product.productId;
@@ -180,7 +184,7 @@
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // è§¦å‘表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
      proxy.$refs["formRef"]?.validateField("productModelId");
  }
};
@@ -196,29 +200,27 @@
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (formState.value.type === 'qualified') {
        if (formState.value.type === "qualified") {
        addStockInRecordOnly(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
            emit("completed");
          proxy.$modal.msgSuccess("提交成功");
        })
          });
      } else {
        formState.value.warnNum = 0;
        createStockUnInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
          // å‘ŠçŸ¥çˆ¶ç»„件已完成
          emit('completed');
            emit("completed");
          proxy.$modal.msgSuccess("提交成功");
        })
          });
      }
    }
  })
    });
};
defineExpose({
  closeModal,
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
@@ -1,44 +1,82 @@
<template>
  <div>
    <el-dialog v-model="dialogVisible" title="领料详情" width="1400px" @close="handleClose">
      <el-table v-loading="materialDetailLoading" :data="materialDetailTableData" border row-key="id">
        <el-table-column label="工序名称" prop="processName" min-width="180" />
        <el-table-column label="原料名称" prop="materialName" min-width="160" />
        <el-table-column label="原料型号" prop="materialModel" min-width="180" />
        <el-table-column label="需求数量" prop="requiredQty" min-width="110" />
        <el-table-column label="计量单位" prop="unit" width="100" />
        <el-table-column label="领用数量" prop="pickQty" min-width="110" />
        <el-table-column label="补料数量" min-width="120">
    <el-dialog v-model="dialogVisible"
               title="领料详情"
               width="1400px"
               @close="handleClose">
      <el-table v-loading="materialDetailLoading"
                :data="materialDetailTableData"
                border
                row-key="id">
        <el-table-column label="工序名称"
                         prop="operationName"
                         min-width="180" />
        <el-table-column label="原料名称"
                         prop="productName"
                         min-width="160" />
        <el-table-column label="原料型号"
                         prop="model"
                         min-width="180" />
        <el-table-column label="批号"
                         prop="batchNo"
                         min-width="150" />
        <el-table-column label="需求数量"
                         prop="demandedQuantity"
                         min-width="110" />
        <el-table-column label="计量单位"
                         prop="unit"
                         width="100" />
        <el-table-column label="领用数量"
                         prop="pickQuantity"
                         min-width="110" />
        <el-table-column label="补料数量"
                         min-width="120">
          <template #default="{ row }">
            <el-button type="primary" link @click="handleViewSupplementRecord(row)">
            <el-button type="primary"
                       link
                       @click="handleViewSupplementRecord(row)">
              {{ row.supplementQty ?? 0 }}
            </el-button>
          </template>
        </el-table-column>
        <el-table-column label="退料数量" prop="returnQty" min-width="110" />
        <el-table-column label="实际数量" prop="actualQty" min-width="110" />
        <el-table-column label="退料数量"
                         prop="returnQty"
                         min-width="110" />
        <el-table-column label="实际数量"
                         prop="actualQty"
                         min-width="110" />
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button
            type="warning"
          <el-button type="warning"
            :loading="materialReturnConfirming"
            :disabled="!canOpenReturnSummary"
            @click="openReturnSummaryDialog"
          >
                     @click="openReturnSummaryDialog">
            é€€æ–™ç¡®è®¤
          </el-button>
          <el-button @click="dialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="supplementRecordDialogVisible" title="补料记录" width="800px">
      <el-table v-loading="supplementRecordLoading" :data="supplementRecordTableData" border row-key="id">
        <el-table-column label="补料数量" prop="supplementQty" min-width="120" />
        <el-table-column label="补料人" prop="supplementUserName" min-width="120" />
        <el-table-column label="补料日期" prop="supplementTime" min-width="160" />
        <el-table-column label="补料原因" prop="supplementReason" min-width="200" />
    <el-dialog v-model="supplementRecordDialogVisible"
               title="补料记录"
               width="800px">
      <el-table v-loading="supplementRecordLoading"
                :data="supplementRecordTableData"
                border
                row-key="id">
        <el-table-column label="补料数量"
                         prop="supplementQty"
                         min-width="120" />
        <el-table-column label="补料人"
                         prop="supplementUserName"
                         min-width="120" />
        <el-table-column label="补料日期"
                         prop="supplementTime"
                         min-width="160" />
        <el-table-column label="补料原因"
                         prop="supplementReason"
                         min-width="200" />
      </el-table>
      <template #footer>
        <span class="dialog-footer">
@@ -46,18 +84,30 @@
        </span>
      </template>
    </el-dialog>
    <el-dialog v-model="returnSummaryDialogVisible" title="退料汇总确认" width="900px">
      <el-table :data="returnSummaryList" border row-key="summaryKey">
        <el-table-column label="原料名称" prop="materialName" min-width="180" />
        <el-table-column label="原料型号" prop="materialModel" min-width="180" />
        <el-table-column label="计量单位" prop="unit" min-width="100" />
        <el-table-column label="退料汇总数量" prop="returnQtyTotal" min-width="140" />
    <el-dialog v-model="returnSummaryDialogVisible"
               title="退料汇总确认"
               width="900px">
      <el-table :data="returnSummaryList"
                border
                row-key="summaryKey">
        <el-table-column label="原料名称"
                         prop="materialName"
                         min-width="180" />
        <el-table-column label="原料型号"
                         prop="materialModel"
                         min-width="180" />
        <el-table-column label="计量单位"
                         prop="unit"
                         min-width="100" />
        <el-table-column label="退料汇总数量"
                         prop="returnQtyTotal"
                         min-width="140" />
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" :loading="materialReturnConfirming" @click="handleReturnConfirm">确认提交</el-button>
          <el-button type="primary"
                     :loading="materialReturnConfirming"
                     @click="handleReturnConfirm">确认提交</el-button>
          <el-button @click="returnSummaryDialogVisible = false">取消</el-button>
        </span>
      </template>
@@ -68,7 +118,11 @@
<script setup>
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { listMaterialPickingDetail, listMaterialSupplementRecord, confirmMaterialReturn } from "@/api/productionManagement/productionOrder.js";
  import {
    listMaterialPickingDetail,
    listMaterialSupplementRecord,
    confirmMaterialReturn,
  } from "@/api/productionManagement/productionOrder.js";
const props = defineProps({
  modelValue: { type: Boolean, default: false },
@@ -90,7 +144,9 @@
const returnSummaryDialogVisible = ref(false);
const returnSummaryList = ref([]);
const calcReturnQty = item =>
  Number(item.pickQty || 0) + Number(item.supplementQty || 0) - Number(item.actualQty || 0);
    Number(item.pickQuantity || 0) +
    Number(item.supplementQty || 0) -
    Number(item.actualQty || 0);
const canOpenReturnSummary = computed(() =>
  materialDetailTableData.value.some(item => calcReturnQty(item) > 0)
);
@@ -100,7 +156,7 @@
  materialDetailLoading.value = true;
  materialDetailTableData.value = [];
  try {
    const res = await listMaterialPickingDetail({ orderId: props.orderRow.id });
      const res = await listMaterialPickingDetail(props.orderRow.id);
    materialDetailTableData.value = res.data || [];
  } finally {
    materialDetailLoading.value = false;
@@ -126,7 +182,9 @@
  supplementRecordLoading.value = true;
  supplementRecordTableData.value = [];
  try {
    const res = await listMaterialSupplementRecord({ materialDetailId: row.id });
      const res = await listMaterialSupplementRecord({
        materialDetailId: row.id,
      });
    supplementRecordTableData.value = res.data || [];
  } finally {
    supplementRecordLoading.value = false;
@@ -138,11 +196,13 @@
  materialDetailTableData.value.forEach(item => {
    const returnQty = calcReturnQty(item);
    if (returnQty <= 0) return;
    const key = `${item.materialModelId || ""}_${item.materialName || ""}_${item.materialModel || ""}_${item.unit || ""}`;
      const key = `${item.productModelId || ""}_${item.productName || ""}_${
        item.model || ""
      }_${item.unit || ""}`;
    const old = map.get(key) || {
      summaryKey: key,
      materialName: item.materialName || "",
      materialModel: item.materialModel || "",
        materialName: item.productName || "",
        materialModel: item.model || "",
      unit: item.unit || "",
      returnQtyTotal: 0,
    };
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -13,25 +13,25 @@
                border
                row-key="tempId">
        <el-table-column label="工序名称"
                         min-width="180">
                         min-width="140">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.processName || "-" }}</span>
            <span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
            <el-select v-else
                       v-model="row.processName"
                       v-model="row.operationName"
                       placeholder="请选择工序"
                       clearable
                       filterable
                       style="width: 100%;"
                       @change="val => handleProcessNameChange(row, val)">
              <el-option v-for="item in processOptions"
                         :key="item.id"
                         :key="item.technologyOperationId"
                         :label="item.name"
                         :value="item.name" />
            </el-select>
          </template>
        </el-table-column>
        <el-table-column label="原料名称"
                         min-width="160">
                         min-width="140">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
            <el-button v-else
@@ -43,17 +43,37 @@
          </template>
        </el-table-column>
        <el-table-column label="原料型号"
                         min-width="180">
                         min-width="140">
          <template #default="{ row }">
            {{ row.materialModel || "-" }}
          </template>
        </el-table-column>
        <!-- æ‰¹å·å¤šé€‰ -->
        <el-table-column min-width="200">
          <template #header>
            <span style="color: #f56c6c; margin-right: 4px;">*</span>
            <span>批号</span>
          </template>
          <template #default="{ row }">
            <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="需求数量"
                         min-width="120">
          <template #default="{ row }">
            <span v-if="row.bom === true">{{ row.requiredQty ?? "-" }}</span>
            <span v-if="row.bom === true">{{ row.demandedQuantity ?? "-" }}</span>
            <el-input-number v-else
                             v-model="row.requiredQty"
                             v-model="row.demandedQuantity"
                             :min="0"
                             :precision="3"
                             :step="1"
@@ -63,7 +83,7 @@
          </template>
        </el-table-column>
        <el-table-column label="计量单位"
                         width="120">
                         width="100">
          <template #default="{ row }">
            {{ row.unit || "-" }}
          </template>
@@ -110,12 +130,18 @@
  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 {
    findProductProcessRouteItemList,
    listMain,
  } from "@/api/productionManagement/productProcessRoute.js";
  import {
    listMaterialPickingDetail,
    listMaterialPickingBom,
    listMaterialPickingLedger,
    saveMaterialPickingLedger,
    updateMaterialPickingLedger,
  } from "@/api/productionManagement/productionOrder.js";
  import { queryList2 } from "@/api/productionManagement/productStructure.js";
  const props = defineProps({
    modelValue: { type: Boolean, default: false },
@@ -139,16 +165,22 @@
  const createMaterialRow = (row = {}) => ({
    tempId: row.id || `temp_${++materialTempId}`,
    id: row.id,
    processId: row.processId,
    productProcessId: row.productProcessId || row.processId,
    processName: row.processName || "",
    processId: row.processId || row.technologyOperationId,
    technologyOperationId: row.technologyOperationId || row.processId,
    operationName: row.operationName || "",
    bom: row.bom === true,
    materialModelId: row.materialModelId,
    materialName: row.materialName || "",
    materialModel: row.materialModel || "",
    requiredQty: Number(row.requiredQty ?? 0),
    materialModelId: row.materialModelId || row.productModelId,
    materialName: row.materialName || row.productName || "",
    materialModel: row.materialModel || row.model || "",
    demandedQuantity: Number(row.requiredQty ?? row.demandedQuantity ?? 0),
    unit: row.unit || "",
    pickQty: Number(row.pickQty ?? row.requiredQty ?? 0),
    pickQty: Number(row.pickQty ?? row.pickQuantity ?? 0),
    batchNo: row.batchNo
      ? typeof row.batchNo === "string"
        ? row.batchNo.split(",")
        : row.batchNo
      : [],
    batchNoList: row.batchNoList || [],
  });
  const getProcessOptions = async () => {
@@ -161,19 +193,20 @@
      : 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}`;
      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,
          name: processName,
          name: operationName,
        });
      }
    });
    processOptions.value = Array.from(processMap.values());
  };
  const isDetail = ref(true);
  const loadMaterialData = async () => {
    if (!props.orderRow?.id) return;
@@ -181,23 +214,23 @@
    materialTableData.value = [];
    await getProcessOptions();
    try {
      const detailRes = await listMaterialPickingDetail({
        orderId: props.orderRow.id,
      });
      const detailRes = await listMaterialPickingDetail(props.orderRow.id);
      const detailList = Array.isArray(detailRes?.data)
        ? detailRes.data
        : detailRes?.data?.records || [];
      if (detailList.length > 0) {
        isDetail.value = true;
        materialTableData.value = detailList.map(item => createMaterialRow(item));
        return;
      } else {
        isDetail.value = false;
        const bomRes = await listMaterialPickingBom(props.orderRow.id);
        const bomList = Array.isArray(bomRes?.data)
          ? bomRes.data
          : bomRes?.data?.records || [];
        materialTableData.value = bomList.map(item => createMaterialRow(item));
        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;
    }
@@ -225,14 +258,16 @@
    materialTableData.value.splice(index, 1);
  };
  const handleProcessNameChange = (row, processName) => {
    const process = processOptions.value.find(item => item.name === processName);
    row.productProcessId = process?.id;
  const handleProcessNameChange = (row, operationName) => {
    const process = processOptions.value.find(
      item => item.name === operationName
    );
    row.technologyOperationId = process?.technologyOperationId;
  };
  const handleRequiredQtyChange = (row, val) => {
    const required = Number(val ?? 0);
    row.requiredQty = required;
    row.demandedQuantity = required;
    if (!row.pickQty || Number(row.pickQty) === 0) {
      row.pickQty = required;
    }
@@ -246,6 +281,8 @@
  };
  const handleMaterialProductConfirm = products => {
    console.log(products, "products");
    if (!products || products.length === 0) return;
    const index = currentMaterialSelectRowIndex.value;
    if (index < 0 || !materialTableData.value[index]) return;
@@ -257,6 +294,7 @@
      product.materialName || product.productName || product.name || "";
    row.materialModel = product.materialModel || product.model || "";
    row.unit = product.unit || product.measureUnit || "";
    row.batchNoList = product.batchNoList;
    currentMaterialSelectRowIndex.value = -1;
    materialProductDialogVisible.value = false;
  };
@@ -266,22 +304,24 @@
      return { valid: false, message: "请先新增领料数据" };
    }
    const invalidNewRow = materialTableData.value.find(
      item => item.bom !== true && (!item.processName || !item.materialName)
      item => item.bom !== true && (!item.operationName || !item.materialName)
    );
    if (invalidNewRow) {
      return { valid: false, message: "新增行的工序名称和原料名称为必填项" };
    }
    const invalidRow = materialTableData.value.find(
      item =>
        !item.processName ||
        !item.operationName ||
        !item.materialName ||
        item.requiredQty === null ||
        item.requiredQty === undefined ||
        !item.batchNo ||
        item.batchNo.length === 0 ||
        item.demandedQuantity === null ||
        item.demandedQuantity === undefined ||
        item.pickQty === null ||
        item.pickQty === undefined
    );
    if (invalidRow) {
      return { valid: false, message: "请完善工序、原料和数量后再保存" };
      return { valid: false, message: "请完善工序、原料、批号和数量后再保存" };
    }
    return { valid: true, message: "" };
  };
@@ -295,22 +335,49 @@
    }
    materialSaving.value = true;
    try {
      await saveMaterialPickingLedger({
        orderId: props.orderRow.id,
        items: materialTableData.value.map(item => ({
      if (isDetail.value) {
        await updateMaterialPickingLedger({
          productionOrderId: props.orderRow.id,
          productionOrderPickDto: materialTableData.value.map(item => ({
          id: item.id,
          processId: item.processName,
          productProcessId: item.productProcessId,
          processName: item.processName,
            // processId: item.operationName,
            technologyOperationId: item.technologyOperationId,
            operationName: item.operationName,
          bom: item.bom === true,
          materialModelId: item.materialModelId,
          materialName: item.materialName,
          materialModel: item.materialModel,
          requiredQty: item.requiredQty,
            productModelId: item.materialModelId,
            // materialName: item.materialName,
            // materialModel: item.materialModel,
            demandedQuantity: item.demandedQuantity,
          unit: item.unit,
          pickQty: item.pickQty,
            pickQuantity: item.pickQty,
            batchNo: Array.isArray(item.batchNo)
              ? item.batchNo.join(",")
              : item.batchNo,
        })),
      });
      } else {
        await saveMaterialPickingLedger({
          productionOrderId: props.orderRow.id,
          productionOrderPickDto: materialTableData.value.map(item => ({
            id: item.id,
            // processId: item.operationName,
            technologyOperationId: item.technologyOperationId,
            operationName: item.operationName,
            bom: item.bom === true,
            productModelId: item.materialModelId,
            // materialName: item.materialName,
            // materialModel: item.materialModel,
            demandedQuantity: item.demandedQuantity,
            unit: item.unit,
            pickQuantity: item.pickQty,
            batchNo: Array.isArray(item.batchNo)
              ? item.batchNo.join(",")
              : item.batchNo,
          })),
        });
      }
      ElMessage({ message: "领料成功", type: "success" });
      emit("saved");
      dialogVisible.value = false;
    } finally {
src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,159 @@
<template>
  <el-dialog v-model="dialogVisible"
             title="补料"
             width="1200px"
             @close="handleClose">
    <el-table v-loading="loading"
              :data="tableData"
              border
              row-key="id">
      <el-table-column label="工序名称"
                       prop="operationName"
                       min-width="140" />
      <el-table-column label="原料名称"
                       prop="productName"
                       min-width="140" />
      <el-table-column label="原料型号"
                       prop="model"
                       min-width="140" />
      <el-table-column label="计量单位"
                       prop="unit"
                       width="100" />
      <el-table-column label="领用数量"
                       prop="pickQuantity"
                       width="100" />
      <el-table-column label="补料数量"
                       min-width="150">
        <template #default="{ row }">
          <el-input-number v-model="row.newSupplementQty"
                           :min="0"
                           :precision="3"
                           :step="1"
                           controls-position="right"
                           placeholder="输入补料数量"
                           style="width: 100%;" />
        </template>
      </el-table-column>
      <el-table-column label="补料原因"
                       min-width="200">
        <template #default="{ row }">
          <el-input v-model="row.newSupplementReason"
                    placeholder="输入补料原因"
                    maxlength="200"
                    show-word-limit />
        </template>
      </el-table-column>
    </el-table>
    <template #footer>
      <span class="dialog-footer">
        <el-button type="primary"
                   :loading="submitting"
                   @click="handleSubmit">ç¡® å®š</el-button>
        <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
      </span>
    </template>
  </el-dialog>
</template>
<script setup>
  import { computed, ref, watch } from "vue";
  import { ElMessage } from "element-plus";
  import {
    listMaterialPickingDetail,
    updateMaterialPickingLedger,
  } 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 loading = ref(false);
  const submitting = ref(false);
  const tableData = ref([]);
  const loadData = async () => {
    if (!props.orderRow?.id) return;
    loading.value = true;
    try {
      const res = await listMaterialPickingDetail(props.orderRow.id);
      tableData.value = (res.data || []).map(item => ({
        ...item,
        newSupplementQty: 0,
        newSupplementReason: "",
      }));
    } catch (e) {
      console.error("获取物料明细失败:", e);
      ElMessage.error("获取物料明细失败");
    } finally {
      loading.value = false;
    }
  };
  watch(
    () => dialogVisible.value,
    visible => {
      if (visible) {
        loadData();
      }
    }
  );
  const handleClose = () => {
    tableData.value = [];
  };
  const handleSubmit = async () => {
    const supplementList = tableData.value.filter(
      item => item.newSupplementQty > 0
    );
    if (supplementList.length === 0) {
      ElMessage.warning("请至少输入一条补料数量");
      return;
    }
    const invalidRow = supplementList.find(item => !item.newSupplementReason);
    if (invalidRow) {
      ElMessage.warning("请输入补料原因");
      return;
    }
    submitting.value = true;
    try {
      await updateMaterialPickingLedger({
        productionOrderId: props.orderRow.id,
        productionOrderPickDto: tableData.value.map(item => ({
          id: item.id,
          technologyOperationId: item.technologyOperationId,
          operationName: item.operationName,
          bom: item.bom === true,
          productModelId: item.productModelId,
          demandedQuantity: item.demandedQuantity,
          unit: item.unit,
          pickQuantity: item.pickQuantity,
          batchNo: item.batchNo,
          feedingQuantity: item.newSupplementQty || 0,
          feedingReason: item.newSupplementReason || "",
          pickType: 2,
        })),
      });
      ElMessage.success("补料成功");
      dialogVisible.value = false;
      emit("saved");
    } catch (e) {
      console.error("补料失败:", e);
      ElMessage.error("补料失败");
    } finally {
      submitting.value = false;
    }
  };
</script>
<style scoped lang="scss">
</style>
src/views/productionManagement/productionOrder/index.vue
@@ -304,7 +304,7 @@
      label: "操作",
      align: "center",
      fixed: "right",
      width: 340,
      width: 360,
      operation: [
        {
          name: "工艺路线",
@@ -337,27 +337,20 @@
            showSourceData(row);
          },
        },
        // {
        //   name: "产品结构",
        //   type: "text",
        //   clickFun: row => {
        //     showProductStructure(row);
        //   },
        // },
        // {
        //   name: "领料",
        //   type: "text",
        //   clickFun: row => {
        //     openMaterialDialog(row);
        //   },
        // },
        // {
        //   name: "领料详情",
        //   type: "text",
        //   clickFun: row => {
        //     openMaterialDetailDialog(row);
        //   },
        // },
        {
          name: "领料",
          type: "text",
          clickFun: row => {
            openMaterialDialog(row);
          },
        },
        {
          name: "领料详情",
          type: "text",
          clickFun: row => {
            openMaterialDetailDialog(row);
          },
        },
      ],
    },
  ]);
src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue
@@ -1,86 +1,119 @@
<template>
  <div>
    <el-dialog
      v-model="dialogVisible"
    <el-dialog v-model="dialogVisible"
      title="物料"
      width="1200px"
      @close="handleCloseMaterialDialog"
    >
      <el-table v-loading="materialTableLoading" :data="materialTableData" border row-key="id">
        <el-table-column label="工序名称" prop="processName" min-width="140" />
        <el-table-column label="原料名称" prop="materialName" min-width="140" />
        <el-table-column label="原料型号" prop="materialModel" min-width="140" />
        <el-table-column label="计量单位" prop="unit" min-width="100" />
        <el-table-column label="线边仓数量" prop="pickQty" min-width="100" />
        <el-table-column label="补料数量" prop="supplementQty" min-width="100" />
        <el-table-column label="实际数量" min-width="140">
               @close="handleCloseMaterialDialog">
      <el-table v-loading="materialTableLoading"
                :data="materialTableData"
                border
                row-key="id">
        <el-table-column label="工序名称"
                         prop="processName"
                         min-width="140" />
        <el-table-column label="原料名称"
                         prop="materialName"
                         min-width="140" />
        <el-table-column label="原料型号"
                         prop="materialModel"
                         min-width="140" />
        <el-table-column label="计量单位"
                         prop="unit"
                         min-width="100" />
        <el-table-column label="线边仓数量"
                         prop="pickQty"
                         min-width="100" />
        <el-table-column label="补料数量"
                         prop="supplementQty"
                         min-width="100" />
        <el-table-column label="实际数量"
                         min-width="140">
          <template #default="{ row }">
            <el-input-number
              v-model="row.actualQty"
            <el-input-number v-model="row.actualQty"
              :min="0"
              :precision="3"
              :step="1"
              controls-position="right"
              style="width: 100%;"
            />
                             style="width: 100%;" />
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" fixed="right" width="180">
        <el-table-column label="操作"
                         align="center"
                         fixed="right"
                         width="180">
          <template #default="{ row }">
            <el-button type="primary" link @click="openSupplementDialog(row)">补料</el-button>
            <el-button type="info" link @click="openSupplementRecordDialog(row)">补料记录</el-button>
            <el-button type="primary"
                       link
                       @click="openSupplementDialog(row)">补料</el-button>
            <el-button type="info"
                       link
                       @click="openSupplementRecordDialog(row)">补料记录</el-button>
          </template>
        </el-table-column>
      </el-table>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" :loading="pickSubmitting" @click="handleSubmitPick">领用</el-button>
          <el-button type="primary"
                     :loading="pickSubmitting"
                     @click="handleSubmitPick">领用</el-button>
          <el-button @click="dialogVisible = false">取消</el-button>
        </span>
      </template>
    </el-dialog>
    <FormDialog
      v-model="supplementDialogVisible"
    <FormDialog v-model="supplementDialogVisible"
      title="补料"
      width="500px"
      @confirm="handleSubmitSupplement"
    >
      <el-form ref="supplementFormRef" :model="supplementForm" :rules="supplementRules" label-width="100px">
        <el-form-item label="补料数量" prop="supplementQty">
          <el-input-number
            v-model="supplementForm.supplementQty"
                @confirm="handleSubmitSupplement">
      <el-form ref="supplementFormRef"
               :model="supplementForm"
               :rules="supplementRules"
               label-width="100px">
        <el-form-item label="补料数量"
                      prop="supplementQty">
          <el-input-number v-model="supplementForm.supplementQty"
            :min="0.001"
            :precision="3"
            :step="1"
            style="width: 100%;"
          />
                           style="width: 100%;" />
        </el-form-item>
        <el-form-item label="补料原因" prop="supplementReason">
          <el-input
            v-model="supplementForm.supplementReason"
        <el-form-item label="补料原因"
                      prop="supplementReason">
          <el-input v-model="supplementForm.supplementReason"
            type="textarea"
            :rows="3"
            maxlength="200"
            show-word-limit
            placeholder="请输入补料原因"
          />
                    placeholder="请输入补料原因" />
        </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" :loading="supplementSubmitting" @click="handleSubmitSupplement">确定</el-button>
          <el-button type="primary"
                     :loading="supplementSubmitting"
                     @click="handleSubmitSupplement">确定</el-button>
          <el-button @click="supplementDialogVisible = false">取消</el-button>
        </span>
      </template>
    </FormDialog>
    <el-dialog v-model="supplementRecordDialogVisible" title="补料记录" width="900px">
      <el-table v-loading="supplementRecordLoading" :data="supplementRecordTableData" border row-key="id">
        <el-table-column label="补料数量" prop="supplementQty" min-width="100" />
        <el-table-column label="补料原因" prop="supplementReason" min-width="200" />
        <el-table-column label="补料人" prop="supplementUserName" min-width="120" />
        <el-table-column label="补料日期" prop="supplementTime" min-width="160" />
    <el-dialog v-model="supplementRecordDialogVisible"
               title="补料记录"
               width="900px">
      <el-table v-loading="supplementRecordLoading"
                :data="supplementRecordTableData"
                border
                row-key="id">
        <el-table-column label="补料数量"
                         prop="supplementQty"
                         min-width="100" />
        <el-table-column label="补料原因"
                         prop="supplementReason"
                         min-width="200" />
        <el-table-column label="补料人"
                         prop="supplementUserName"
                         min-width="120" />
        <el-table-column label="补料日期"
                         prop="supplementTime"
                         min-width="160" />
      </el-table>
      <template #footer>
        <span class="dialog-footer">
@@ -139,8 +172,12 @@
const supplementRecordTableData = ref([]);
const supplementRules = {
  supplementQty: [{ required: true, message: "请输入补料数量", trigger: "blur" }],
  supplementReason: [{ required: true, message: "请输入补料原因", trigger: "blur" }],
    supplementQty: [
      { required: true, message: "请输入补料数量", trigger: "blur" },
    ],
    supplementReason: [
      { required: true, message: "请输入补料原因", trigger: "blur" },
    ],
};
const loadMaterialTable = async row => {
  if (!row?.id) return;
@@ -235,7 +272,12 @@
  if (materialTableData.value.length === 0) {
    return { valid: false, message: "暂无可领用物料" };
  }
  const invalidRow = materialTableData.value.find(item => item.actualQty === null || item.actualQty === undefined || item.actualQty === "");
    const invalidRow = materialTableData.value.find(
      item =>
        item.actualQty === null ||
        item.actualQty === undefined ||
        item.actualQty === ""
    );
  if (invalidRow) {
    return { valid: false, message: "请填写实际数量后再领用" };
  }