2026-04-28 1c15c3ddecdb0284a47eb763cbfcf6978fcb186b
Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro
已修改9个文件
1524 ■■■■■ 文件已修改
src/api/productionManagement/productionOrder.js 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/borrow/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/document/index.vue 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fileManagement/return/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 350 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/productionOrder/index.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/productionManagement/workOrderManagement/components/MaterialDialog.vue 502 ●●●● 补丁 | 查看 | 原始文档 | 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/fileManagement/borrow/index.vue
@@ -100,16 +100,14 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="借阅书籍:" prop="documentationId">
               <!-- <el-select v-model="borrowForm.documentationId" placeholder="请选择借阅书籍" style="width: 100%" @change="handleScanContent">
                 <el-option
                   v-for="item in documentList"
                   :key="item.id"
                   :label="item.docName || item.name"
                   :value="item.id"
                 />
               </el-select> -->
               <div style="display: flex; gap: 10px;">
                <el-select v-model="borrowForm.documentationId" placeholder="请选择借阅书籍" style="flex: 1;width: 100px;" @change="handleSelectChange">
                <el-select
                  v-if="borrowOperationType !== 'edit'"
                  v-model="borrowForm.documentationId"
                  placeholder="请选择借阅书籍"
                  style="flex: 1;width: 100px;"
                  @change="handleSelectChange"
                >
                  <el-option 
                    v-for="item in documentList" 
                    :key="item.id" 
@@ -118,6 +116,13 @@
                  />
                </el-select>
                <el-input
                  v-else
                  v-model="currentEditDocName"
                  style="flex: 1;width: 100px;"
                  disabled
                />
                <el-input
                  v-if="borrowOperationType !== 'edit'"
                  v-model="scanContent"
                  placeholder="扫码输入"
                  style="width: 100px;"
@@ -205,6 +210,7 @@
const selectedRows = ref([]);
const documentList = ref([]); // 文档列表,用于借阅书籍选择
const scanContent = ref() // 扫码内容
const currentEditDocName = ref(''); // 编辑时存储的文档名称
// 分页相关
const pagination = reactive({
  currentPage: 1,
@@ -282,6 +288,7 @@
      {
        name: "编辑",
        type: "text",
        disabled: (row) => row.borrowStatus === '归还',
        clickFun: (row) => {
          openBorrowDia('edit', row)
        },
@@ -428,13 +435,16 @@
  if (type === "edit") {
    // 编辑模式,加载现有数据
    Object.assign(borrowForm, data);
    // 存储文档名称用于显示
    currentEditDocName.value = data.docName || '';
  } else {
    // 新增模式,清空表单
    Object.keys(borrowForm).forEach(key => {
      borrowForm[key] = "";
    });
         // 设置默认状态
     borrowForm.borrowStatus = "借阅";
    currentEditDocName.value = ''; // 清空编辑时的文档名称
    // 设置默认状态
    borrowForm.borrowStatus = "借阅";
    // 设置当前日期为借阅日期
    borrowForm.borrowDate = new Date().toISOString().split('T')[0];
  }
@@ -445,6 +455,7 @@
  proxy.$refs.borrowFormRef.resetFields();
  borrowDia.value = false;
  scanContent.value = ''; // 清空扫码内容
  currentEditDocName.value = ''; // 清空编辑时的文档名称
};
// 提交借阅表单
src/views/fileManagement/document/index.vue
@@ -862,12 +862,14 @@
      documentForm[key] = "";
    });
    documentForm.attachments = []; // 新增模式下也清空附件
    // 设置默认值 - 使用字典数据的第一个选项作为默认值
    // 设置默认值 - 文档状态默认设置为"正常"
    if (document_status.value && document_status.value.length > 0) {
      documentForm.docStatus = document_status.value[0].value;
      const normalStatus = document_status.value.find(item => item.label === '正常');
      documentForm.docStatus = normalStatus ? normalStatus.value : document_status.value[0].value;
    }
    if (document_urgency.value && document_urgency.value.length > 0) {
      documentForm.urgencyLevel = document_urgency.value[0].value;
      const normalUrgency = document_urgency.value.find(item => item.label === '普通');
      documentForm.urgencyLevel = normalUrgency ? normalUrgency.value : document_urgency.value[0].value;
    }
  }
};
src/views/fileManagement/return/index.vue
@@ -103,16 +103,14 @@
                 <el-row :gutter="20">
           <el-col :span="12">
             <el-form-item label="文档:" prop="borrowId">
               <!-- <el-select v-model="returnForm.borrowId" placeholder="请选择文档" style="flex: 1;" @change="handleDocumentChange">
                 <el-option
                   v-for="item in documentList"
                   :key="item.id"
                   :label="item.docName || item.name"
                   :value="item.id"
                 />
               </el-select> -->
               <div style="display: flex; gap: 10px;">
                <el-select v-model="returnForm.borrowId" placeholder="请选择文档" style="width: 120px;" @change="handleDocumentChange">
                <el-select
                  v-if="returnOperationType !== 'edit'"
                  v-model="returnForm.borrowId"
                  placeholder="请选择文档"
                  style="width: 120px;"
                  @change="handleDocumentChange"
                >
                  <el-option 
                    v-for="item in documentList" 
                    :key="item.id" 
@@ -121,6 +119,13 @@
                  />
                </el-select>
                <el-input
                  v-else
                  v-model="currentEditDocName"
                  style="width: 120px;"
                  disabled
                />
                <el-input
                  v-if="returnOperationType !== 'edit'"
                  v-model="scanContent"
                  placeholder="扫码输入"
                  style="flex: 1;"
@@ -215,6 +220,7 @@
const documentList = ref([]); // 文档列表
const borrowInfoList = ref([]); // 借阅信息列表
const scanContent = ref(); // 扫码内容
const currentEditDocName = ref(''); // 编辑时存储的文档名称
// 分页相关
const pagination = reactive({
@@ -286,6 +292,7 @@
      {
        name: "编辑",
        type: "text",
        disabled: (row) => row.borrowStatus === '归还',
        clickFun: (row) => {
          openReturnDia('edit', row)
        },
@@ -396,15 +403,14 @@
  if (type === "edit") {
    // 编辑模式,加载现有数据
    Object.assign(returnForm, data);
    // 编辑模式下,文档选择后自动填充借阅人和应归还日期
    if (returnForm.borrowId) {
      handleDocumentChange(returnForm.borrowId);
    }
    // 存储文档名称用于显示
    currentEditDocName.value = data.docName || '';
  } else {
    // 新增模式,清空表单
    Object.keys(returnForm).forEach(key => {
      returnForm[key] = "";
    });
    currentEditDocName.value = ''; // 清空编辑时的文档名称
    // 设置默认状态
    returnForm.borrowStatus = "归还";
    // 设置当前日期为归还日期
@@ -418,6 +424,7 @@
  returnDia.value = false;
  scanContent.value = ''; // 清空扫码内容
  borrowInfoList.value = []; // 清空借阅信息列表
  currentEditDocName.value = ''; // 清空编辑时的文档名称
};
// 提交归还表单
src/views/inventoryManagement/stockManagement/New.vue
@@ -1,96 +1,97 @@
<template>
  <div>
    <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="产品名称"
            prop="productModelId"
            :rules="[
    <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="产品名称"
                      prop="productModelId"
                      :rules="[
                {
                required: true,
                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="库存类型"
            prop="type"
            :rules="[
        <el-form-item label="库存类型"
                      prop="type"
                      :rules="[
                {
                required: true,
                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="批号"
            prop="batchNo"
        >
          <el-input v-model="formState.batchNo" placeholder="请输入批号" />
        <el-form-item label="批号"
                      prop="batchNo"
                      :rules="[
                {
                required: true,
                message: '请输入批号',
                trigger: 'blur',
              }
            ]">
          <el-input v-model="formState.batchNo"
                    placeholder="请输入批号" />
        </el-form-item>
        <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%" />
        <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%" />
        </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"
          @confirm="handleProductSelect"
          :top-product-parent-id="props.topProductParentId"
          single
        />
      <ProductSelectDialog v-model="showProductSelectDialog"
                           @confirm="handleProductSelect"
                           :top-product-parent-id="props.topProductParentId"
                           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>
@@ -99,62 +100,27 @@
</template>
<script setup>
import {ref, computed, watch, getCurrentInstance} from "vue";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import {addStockInRecordOnly} from "@/api/inventoryManagement/stockInventory.js";
import {createStockUnInventory} from "@/api/inventoryManagement/stockUninventory.js";
  import { ref, computed, watch, getCurrentInstance } from "vue";
  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
  import { addStockInRecordOnly } from "@/api/inventoryManagement/stockInventory.js";
  import { createStockUnInventory } from "@/api/inventoryManagement/stockUninventory.js";
const props = defineProps({
  visible: {
    type: Boolean,
    required: true,
  },
  topProductParentId: {
    type: Number,
    default: undefined,
    required: false,
  }
});
  const props = defineProps({
    visible: {
      type: Boolean,
      required: true,
    },
    topProductParentId: {
      type: Number,
      default: undefined,
      required: false,
    },
  });
const emit = defineEmits(['update:visible', 'completed']);
  const emit = defineEmits(["update:visible", "completed"]);
// 响应式数据(替代选项式的 data)
const formState = ref({
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
  unit: "",
  type: undefined,
  qualitity: 0,
  batchNo: null,
  warnNum: 0,
  remark: '',
});
const isShow = computed({
  get() {
    return props.visible;
  },
  set(val) {
    emit('update:visible', val);
  },
});
const showProductSelectDialog = ref(false);
// 批号为空时转为 null
watch(() => formState.value.batchNo, (val) => {
  if (val === '') {
    formState.value.batchNo = null;
  }
});
let { proxy } = getCurrentInstance()
const closeModal = () => {
  // 重置表单数据
  formState.value = {
  // 响应式数据(替代选项式的 data)
  const formState = ref({
    productId: undefined,
    productModelId: undefined,
    productName: "",
@@ -164,65 +130,101 @@
    qualitity: 0,
    batchNo: null,
    warnNum: 0,
    remark: '',
  };
  isShow.value = false;
};
    remark: "",
  });
// 产品选择处理
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
    formState.value.productModelId = product.id;
    formState.value.unit = product.unit;
    showProductSelectDialog.value = false;
    // 触发表单验证更新
    proxy.$refs["formRef"]?.validateField('productModelId');
  }
};
  const isShow = computed({
    get() {
      return props.visible;
    },
    set(val) {
      emit("update:visible", val);
    },
  });
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // 验证是否选择了产品和规格
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择产品");
        return;
      }
      if (!formState.value.productModelId) {
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (formState.value.type === 'qualified') {
        addStockInRecordOnly(formState.value).then(res => {
          // 关闭模态框
          isShow.value = false;
          // 告知父组件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      } else {
        formState.value.warnNum = 0;
        createStockUnInventory(formState.value).then(res => {
          // 关闭模态框
          isShow.value = false;
          // 告知父组件已完成
          emit('completed');
          proxy.$modal.msgSuccess("提交成功");
        })
      }
  const showProductSelectDialog = ref(false);
  // 批号为空时转为 null
  watch(
    () => formState.value.batchNo,
    val => {
      if (val === "") {
        formState.value.batchNo = null;
      }
    }
  })
};
  );
  let { proxy } = getCurrentInstance();
defineExpose({
  closeModal,
  handleSubmit,
  isShow,
});
  const closeModal = () => {
    // 重置表单数据
    formState.value = {
      productId: undefined,
      productModelId: undefined,
      productName: "",
      productModelName: "",
      unit: "",
      type: undefined,
      qualitity: 0,
      batchNo: null,
      warnNum: 0,
      remark: "",
    };
    isShow.value = false;
  };
  // 产品选择处理
  const handleProductSelect = async products => {
    if (products && products.length > 0) {
      const product = products[0];
      formState.value.productId = product.productId;
      formState.value.productName = product.productName;
      formState.value.productModelName = product.model;
      formState.value.productModelId = product.id;
      formState.value.unit = product.unit;
      showProductSelectDialog.value = false;
      // 触发表单验证更新
      proxy.$refs["formRef"]?.validateField("productModelId");
    }
  };
  const handleSubmit = () => {
    proxy.$refs["formRef"].validate(valid => {
      if (valid) {
        // 验证是否选择了产品和规格
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择产品");
          return;
        }
        if (!formState.value.productModelId) {
          proxy.$modal.msgError("请选择规格");
          return;
        }
        if (formState.value.type === "qualified") {
          addStockInRecordOnly(formState.value).then(res => {
            // 关闭模态框
            isShow.value = false;
            // 告知父组件已完成
            emit("completed");
            proxy.$modal.msgSuccess("提交成功");
          });
        } else {
          formState.value.warnNum = 0;
          createStockUnInventory(formState.value).then(res => {
            // 关闭模态框
            isShow.value = false;
            // 告知父组件已完成
            emit("completed");
            proxy.$modal.msgSuccess("提交成功");
          });
        }
      }
    });
  };
  defineExpose({
    closeModal,
    handleSubmit,
    isShow,
  });
</script>
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"
            :loading="materialReturnConfirming"
            :disabled="!canOpenReturnSummary"
            @click="openReturnSummaryDialog"
          >
          <el-button type="warning"
                     :loading="materialReturnConfirming"
                     :disabled="!canOpenReturnSummary"
                     @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>
@@ -66,116 +116,126 @@
</template>
<script setup>
import { computed, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { listMaterialPickingDetail, listMaterialSupplementRecord, confirmMaterialReturn } from "@/api/productionManagement/productionOrder.js";
  import { computed, ref, watch } from "vue";
  import { ElMessage } from "element-plus";
  import {
    listMaterialPickingDetail,
    listMaterialSupplementRecord,
    confirmMaterialReturn,
  } from "@/api/productionManagement/productionOrder.js";
const props = defineProps({
  modelValue: { type: Boolean, default: false },
  orderRow: { type: Object, default: null },
});
const emit = defineEmits(["update:modelValue", "confirmed"]);
const dialogVisible = computed({
  get: () => props.modelValue,
  set: val => emit("update:modelValue", val),
});
const materialDetailLoading = ref(false);
const materialDetailTableData = ref([]);
const materialReturnConfirming = ref(false);
const supplementRecordDialogVisible = ref(false);
const supplementRecordLoading = ref(false);
const supplementRecordTableData = ref([]);
const returnSummaryDialogVisible = ref(false);
const returnSummaryList = ref([]);
const calcReturnQty = item =>
  Number(item.pickQty || 0) + Number(item.supplementQty || 0) - Number(item.actualQty || 0);
const canOpenReturnSummary = computed(() =>
  materialDetailTableData.value.some(item => calcReturnQty(item) > 0)
);
const loadDetailList = async () => {
  if (!props.orderRow?.id) return;
  materialDetailLoading.value = true;
  materialDetailTableData.value = [];
  try {
    const res = await listMaterialPickingDetail({ orderId: props.orderRow.id });
    materialDetailTableData.value = res.data || [];
  } finally {
    materialDetailLoading.value = false;
  }
};
watch(
  () => dialogVisible.value,
  visible => {
    if (visible) {
      loadDetailList();
    }
  }
);
const handleClose = () => {
  materialDetailTableData.value = [];
};
const handleViewSupplementRecord = async row => {
  if (!row?.id) return;
  supplementRecordDialogVisible.value = true;
  supplementRecordLoading.value = true;
  supplementRecordTableData.value = [];
  try {
    const res = await listMaterialSupplementRecord({ materialDetailId: row.id });
    supplementRecordTableData.value = res.data || [];
  } finally {
    supplementRecordLoading.value = false;
  }
};
const buildReturnSummary = () => {
  const map = new Map();
  materialDetailTableData.value.forEach(item => {
    const returnQty = calcReturnQty(item);
    if (returnQty <= 0) return;
    const key = `${item.materialModelId || ""}_${item.materialName || ""}_${item.materialModel || ""}_${item.unit || ""}`;
    const old = map.get(key) || {
      summaryKey: key,
      materialName: item.materialName || "",
      materialModel: item.materialModel || "",
      unit: item.unit || "",
      returnQtyTotal: 0,
    };
    old.returnQtyTotal += returnQty;
    map.set(key, old);
  const props = defineProps({
    modelValue: { type: Boolean, default: false },
    orderRow: { type: Object, default: null },
  });
  return Array.from(map.values());
};
  const emit = defineEmits(["update:modelValue", "confirmed"]);
const openReturnSummaryDialog = async () => {
  if (!canOpenReturnSummary.value) {
    ElMessage.warning("退料数量=领用数量+补料数量-实际数量,且需大于0");
    return;
  }
  returnSummaryList.value = buildReturnSummary();
  returnSummaryDialogVisible.value = true;
};
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
const handleReturnConfirm = async () => {
  if (!props.orderRow?.id) return;
  materialReturnConfirming.value = true;
  try {
    await confirmMaterialReturn({
      orderId: props.orderRow.id,
      returnSummaryList: returnSummaryList.value,
  const materialDetailLoading = ref(false);
  const materialDetailTableData = ref([]);
  const materialReturnConfirming = ref(false);
  const supplementRecordDialogVisible = ref(false);
  const supplementRecordLoading = ref(false);
  const supplementRecordTableData = ref([]);
  const returnSummaryDialogVisible = ref(false);
  const returnSummaryList = ref([]);
  const calcReturnQty = item =>
    Number(item.pickQuantity || 0) +
    Number(item.supplementQty || 0) -
    Number(item.actualQty || 0);
  const canOpenReturnSummary = computed(() =>
    materialDetailTableData.value.some(item => calcReturnQty(item) > 0)
  );
  const loadDetailList = async () => {
    if (!props.orderRow?.id) return;
    materialDetailLoading.value = true;
    materialDetailTableData.value = [];
    try {
      const res = await listMaterialPickingDetail(props.orderRow.id);
      materialDetailTableData.value = res.data || [];
    } finally {
      materialDetailLoading.value = false;
    }
  };
  watch(
    () => dialogVisible.value,
    visible => {
      if (visible) {
        loadDetailList();
      }
    }
  );
  const handleClose = () => {
    materialDetailTableData.value = [];
  };
  const handleViewSupplementRecord = async row => {
    if (!row?.id) return;
    supplementRecordDialogVisible.value = true;
    supplementRecordLoading.value = true;
    supplementRecordTableData.value = [];
    try {
      const res = await listMaterialSupplementRecord({
        materialDetailId: row.id,
      });
      supplementRecordTableData.value = res.data || [];
    } finally {
      supplementRecordLoading.value = false;
    }
  };
  const buildReturnSummary = () => {
    const map = new Map();
    materialDetailTableData.value.forEach(item => {
      const returnQty = calcReturnQty(item);
      if (returnQty <= 0) return;
      const key = `${item.productModelId || ""}_${item.productName || ""}_${
        item.model || ""
      }_${item.unit || ""}`;
      const old = map.get(key) || {
        summaryKey: key,
        materialName: item.productName || "",
        materialModel: item.model || "",
        unit: item.unit || "",
        returnQtyTotal: 0,
      };
      old.returnQtyTotal += returnQty;
      map.set(key, old);
    });
    returnSummaryDialogVisible.value = false;
    dialogVisible.value = false;
    emit("confirmed");
  } finally {
    materialReturnConfirming.value = false;
  }
};
    return Array.from(map.values());
  };
  const openReturnSummaryDialog = async () => {
    if (!canOpenReturnSummary.value) {
      ElMessage.warning("退料数量=领用数量+补料数量-实际数量,且需大于0");
      return;
    }
    returnSummaryList.value = buildReturnSummary();
    returnSummaryDialogVisible.value = true;
  };
  const handleReturnConfirm = async () => {
    if (!props.orderRow?.id) return;
    materialReturnConfirming.value = true;
    try {
      await confirmMaterialReturn({
        orderId: props.orderRow.id,
        returnSummaryList: returnSummaryList.value,
      });
      returnSummaryDialogVisible.value = false;
      dialogVisible.value = false;
      emit("confirmed");
    } finally {
      materialReturnConfirming.value = false;
    }
  };
</script>
<style scoped lang="scss"></style>
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 => ({
          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,
        })),
      });
      if (isDetail.value) {
        await updateMaterialPickingLedger({
          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,
          })),
        });
      } 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/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"
      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">
    <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">
          <template #default="{ row }">
            <el-input-number
              v-model="row.actualQty"
              :min="0"
              :precision="3"
              :step="1"
              controls-position="right"
              style="width: 100%;"
            />
            <el-input-number v-model="row.actualQty"
                             :min="0"
                             :precision="3"
                             :step="1"
                             controls-position="right"
                             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"
      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"
            :min="0.001"
            :precision="3"
            :step="1"
            style="width: 100%;"
          />
    <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"
                           :min="0.001"
                           :precision="3"
                           :step="1"
                           style="width: 100%;" />
        </el-form-item>
        <el-form-item label="补料原因" prop="supplementReason">
          <el-input
            v-model="supplementForm.supplementReason"
            type="textarea"
            :rows="3"
            maxlength="200"
            show-word-limit
            placeholder="请输入补料原因"
          />
        <el-form-item label="补料原因"
                      prop="supplementReason">
          <el-input v-model="supplementForm.supplementReason"
                    type="textarea"
                    :rows="3"
                    maxlength="200"
                    show-word-limit
                    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">
@@ -92,187 +125,196 @@
</template>
<script setup>
import { computed, nextTick, reactive, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import FormDialog from "@/components/Dialog/FormDialog.vue";
import {
  listWorkOrderMaterialLedger,
  addWorkOrderMaterialSupplement,
  listWorkOrderMaterialSupplementRecord,
  pickWorkOrderMaterial,
} from "@/api/productionManagement/workOrder.js";
  import { computed, nextTick, reactive, ref, watch } from "vue";
  import { ElMessage } from "element-plus";
  import FormDialog from "@/components/Dialog/FormDialog.vue";
  import {
    listWorkOrderMaterialLedger,
    addWorkOrderMaterialSupplement,
    listWorkOrderMaterialSupplementRecord,
    pickWorkOrderMaterial,
  } from "@/api/productionManagement/workOrder.js";
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false,
  },
  rowData: {
    type: Object,
    default: () => null,
  },
});
const emit = defineEmits(["update:modelValue", "refresh"]);
const dialogVisible = computed({
  get: () => props.modelValue,
  set: val => emit("update:modelValue", val),
});
const materialTableLoading = ref(false);
const materialTableData = ref([]);
const currentMaterialRow = ref(null);
const currentMaterialOrderRow = ref(null);
const pickSubmitting = ref(false);
const supplementDialogVisible = ref(false);
const supplementSubmitting = ref(false);
const supplementFormRef = ref(null);
const supplementForm = reactive({
  supplementQty: null,
  supplementReason: "",
});
const supplementRecordDialogVisible = ref(false);
const supplementRecordLoading = ref(false);
const supplementRecordTableData = ref([]);
const supplementRules = {
  supplementQty: [{ required: true, message: "请输入补料数量", trigger: "blur" }],
  supplementReason: [{ required: true, message: "请输入补料原因", trigger: "blur" }],
};
const loadMaterialTable = async row => {
  if (!row?.id) return;
  currentMaterialOrderRow.value = row;
  materialTableLoading.value = true;
  materialTableData.value = [];
  try {
    const res = await listWorkOrderMaterialLedger({
      workOrderId: row.id,
      processId: row.processId,
      productProcessRouteItemId: row.productProcessRouteItemId,
    });
    materialTableData.value = res.data || [];
  } catch (e) {
    console.error("获取物料台账失败", e);
    ElMessage.error("获取物料台账失败");
  } finally {
    materialTableLoading.value = false;
  }
};
watch(
  () => props.modelValue,
  visible => {
    if (visible && props.rowData) {
      loadMaterialTable(props.rowData);
    }
  }
);
const handleCloseMaterialDialog = () => {
  materialTableData.value = [];
  currentMaterialRow.value = null;
  currentMaterialOrderRow.value = null;
};
const openSupplementDialog = row => {
  currentMaterialRow.value = row;
  supplementForm.supplementQty = null;
  supplementForm.supplementReason = "";
  supplementDialogVisible.value = true;
  nextTick(() => {
    supplementFormRef.value?.clearValidate();
  const props = defineProps({
    modelValue: {
      type: Boolean,
      default: false,
    },
    rowData: {
      type: Object,
      default: () => null,
    },
  });
};
const handleSubmitSupplement = () => {
  supplementFormRef.value?.validate(async valid => {
    if (!valid || !currentMaterialRow.value?.id) {
      ElMessage.warning("缺少物料明细ID");
  const emit = defineEmits(["update:modelValue", "refresh"]);
  const dialogVisible = computed({
    get: () => props.modelValue,
    set: val => emit("update:modelValue", val),
  });
  const materialTableLoading = ref(false);
  const materialTableData = ref([]);
  const currentMaterialRow = ref(null);
  const currentMaterialOrderRow = ref(null);
  const pickSubmitting = ref(false);
  const supplementDialogVisible = ref(false);
  const supplementSubmitting = ref(false);
  const supplementFormRef = ref(null);
  const supplementForm = reactive({
    supplementQty: null,
    supplementReason: "",
  });
  const supplementRecordDialogVisible = ref(false);
  const supplementRecordLoading = ref(false);
  const supplementRecordTableData = ref([]);
  const supplementRules = {
    supplementQty: [
      { required: true, message: "请输入补料数量", trigger: "blur" },
    ],
    supplementReason: [
      { required: true, message: "请输入补料原因", trigger: "blur" },
    ],
  };
  const loadMaterialTable = async row => {
    if (!row?.id) return;
    currentMaterialOrderRow.value = row;
    materialTableLoading.value = true;
    materialTableData.value = [];
    try {
      const res = await listWorkOrderMaterialLedger({
        workOrderId: row.id,
        processId: row.processId,
        productProcessRouteItemId: row.productProcessRouteItemId,
      });
      materialTableData.value = res.data || [];
    } catch (e) {
      console.error("获取物料台账失败", e);
      ElMessage.error("获取物料台账失败");
    } finally {
      materialTableLoading.value = false;
    }
  };
  watch(
    () => props.modelValue,
    visible => {
      if (visible && props.rowData) {
        loadMaterialTable(props.rowData);
      }
    }
  );
  const handleCloseMaterialDialog = () => {
    materialTableData.value = [];
    currentMaterialRow.value = null;
    currentMaterialOrderRow.value = null;
  };
  const openSupplementDialog = row => {
    currentMaterialRow.value = row;
    supplementForm.supplementQty = null;
    supplementForm.supplementReason = "";
    supplementDialogVisible.value = true;
    nextTick(() => {
      supplementFormRef.value?.clearValidate();
    });
  };
  const handleSubmitSupplement = () => {
    supplementFormRef.value?.validate(async valid => {
      if (!valid || !currentMaterialRow.value?.id) {
        ElMessage.warning("缺少物料明细ID");
        return;
      }
      supplementSubmitting.value = true;
      try {
        await addWorkOrderMaterialSupplement({
          materialLedgerId: currentMaterialRow.value.id,
          supplementQty: Number(supplementForm.supplementQty),
          supplementReason: supplementForm.supplementReason,
          workOrderId: currentMaterialOrderRow.value?.id,
        });
        supplementDialogVisible.value = false;
        await loadMaterialTable(currentMaterialOrderRow.value);
        ElMessage.success("补料成功");
        emit("refresh");
      } catch (e) {
        console.error("补料失败", e);
        ElMessage.error("补料失败");
      } finally {
        supplementSubmitting.value = false;
      }
    });
  };
  const openSupplementRecordDialog = async row => {
    supplementRecordDialogVisible.value = true;
    supplementRecordLoading.value = true;
    supplementRecordTableData.value = [];
    try {
      const res = await listWorkOrderMaterialSupplementRecord({
        materialLedgerId: row.id,
      });
      supplementRecordTableData.value = res.data || [];
    } catch (e) {
      console.error("获取补料记录失败", e);
      ElMessage.error("获取补料记录失败");
    } finally {
      supplementRecordLoading.value = false;
    }
  };
  const validatePickRows = () => {
    if (materialTableData.value.length === 0) {
      return { valid: false, message: "暂无可领用物料" };
    }
    const invalidRow = materialTableData.value.find(
      item =>
        item.actualQty === null ||
        item.actualQty === undefined ||
        item.actualQty === ""
    );
    if (invalidRow) {
      return { valid: false, message: "请填写实际数量后再领用" };
    }
    const exceedRow = materialTableData.value.find(item => {
      const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0);
      return Number(item.actualQty || 0) > maxQty;
    });
    if (exceedRow) {
      return { valid: false, message: "实际数量不能大于领用数量+补料数量" };
    }
    return { valid: true, message: "" };
  };
  const handleSubmitPick = async () => {
    if (!currentMaterialOrderRow.value?.id) return;
    const validateResult = validatePickRows();
    if (!validateResult.valid) {
      ElMessage.warning(validateResult.message);
      return;
    }
    supplementSubmitting.value = true;
    pickSubmitting.value = true;
    try {
      await addWorkOrderMaterialSupplement({
        materialLedgerId: currentMaterialRow.value.id,
        supplementQty: Number(supplementForm.supplementQty),
        supplementReason: supplementForm.supplementReason,
        workOrderId: currentMaterialOrderRow.value?.id,
      await pickWorkOrderMaterial({
        workOrderId: currentMaterialOrderRow.value.id,
        items: materialTableData.value.map(item => ({
          materialLedgerId: item.id,
          actualQty: Number(item.actualQty || 0),
        })),
      });
      supplementDialogVisible.value = false;
      ElMessage.success("领用成功");
      await loadMaterialTable(currentMaterialOrderRow.value);
      ElMessage.success("补料成功");
      emit("refresh");
    } catch (e) {
      console.error("补料失败", e);
      ElMessage.error("补料失败");
      console.error("领用失败", e);
      ElMessage.error("领用失败");
    } finally {
      supplementSubmitting.value = false;
      pickSubmitting.value = false;
    }
  });
};
const openSupplementRecordDialog = async row => {
  supplementRecordDialogVisible.value = true;
  supplementRecordLoading.value = true;
  supplementRecordTableData.value = [];
  try {
    const res = await listWorkOrderMaterialSupplementRecord({
      materialLedgerId: row.id,
    });
    supplementRecordTableData.value = res.data || [];
  } catch (e) {
    console.error("获取补料记录失败", e);
    ElMessage.error("获取补料记录失败");
  } finally {
    supplementRecordLoading.value = false;
  }
};
const validatePickRows = () => {
  if (materialTableData.value.length === 0) {
    return { valid: false, message: "暂无可领用物料" };
  }
  const invalidRow = materialTableData.value.find(item => item.actualQty === null || item.actualQty === undefined || item.actualQty === "");
  if (invalidRow) {
    return { valid: false, message: "请填写实际数量后再领用" };
  }
  const exceedRow = materialTableData.value.find(item => {
    const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0);
    return Number(item.actualQty || 0) > maxQty;
  });
  if (exceedRow) {
    return { valid: false, message: "实际数量不能大于领用数量+补料数量" };
  }
  return { valid: true, message: "" };
};
const handleSubmitPick = async () => {
  if (!currentMaterialOrderRow.value?.id) return;
  const validateResult = validatePickRows();
  if (!validateResult.valid) {
    ElMessage.warning(validateResult.message);
    return;
  }
  pickSubmitting.value = true;
  try {
    await pickWorkOrderMaterial({
      workOrderId: currentMaterialOrderRow.value.id,
      items: materialTableData.value.map(item => ({
        materialLedgerId: item.id,
        actualQty: Number(item.actualQty || 0),
      })),
    });
    ElMessage.success("领用成功");
    await loadMaterialTable(currentMaterialOrderRow.value);
    emit("refresh");
  } catch (e) {
    console.error("领用失败", e);
    ElMessage.error("领用失败");
  } finally {
    pickSubmitting.value = false;
  }
};
  };
</script>