From 7c2cdcbc7f5585b96fba76a07b0e4417a09c4d7e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 02 六月 2026 11:06:10 +0800
Subject: [PATCH] 新疆马铃薯 1.过程检验下载添加3个模版

---
 src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue |  427 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 427 insertions(+), 0 deletions(-)

diff --git a/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
new file mode 100644
index 0000000..09e7421
--- /dev/null
+++ b/src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
@@ -0,0 +1,427 @@
+<template>
+  <div>
+    <el-dialog v-model="dialogVisible"
+               title="棰嗘枡鍙拌处"
+               width="1200px"
+               @close="handleClose">
+      <div class="material-toolbar">
+        <el-button type="primary"
+                   @click="handleAddMaterialRow">鏂板</el-button>
+      </div>
+      <el-table v-loading="materialTableLoading"
+                :data="materialTableData"
+                border
+                row-key="tempId">
+        <el-table-column label="宸ュ簭鍚嶇О"
+                         min-width="140">
+          <template #default="{ row }">
+            <span v-if="row.bom === true">{{ row.operationName || "-" }}</span>
+            <el-select v-else
+                       v-model="row.operationName"
+                       placeholder="璇烽�夋嫨宸ュ簭"
+                       clearable
+                       filterable
+                       style="width: 100%;"
+                       @change="val => handleProcessNameChange(row, val)">
+              <el-option v-for="item in processOptions"
+                         :key="item.technologyOperationId"
+                         :label="item.name"
+                         :value="item.name" />
+            </el-select>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍘熸枡鍚嶇О"
+                         min-width="140">
+          <template #default="{ row }">
+            <span v-if="row.bom === true">{{ row.materialName || "-" }}</span>
+            <el-button v-else
+                       type="primary"
+                       link
+                       @click="openMaterialProductSelect(row)">
+              {{ row.materialName || "閫夋嫨鍘熸枡" }}
+            </el-button>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍘熸枡鍨嬪彿"
+                         min-width="140">
+          <template #default="{ row }">
+            {{ row.materialModel || "-" }}
+          </template>
+        </el-table-column>
+        <!-- 鎵瑰彿澶氶�� -->
+        <el-table-column min-width="200"
+                         label="鎵瑰彿">
+          <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.demandedQuantity ?? "-" }}</span>
+            <el-input-number v-else
+                             v-model="row.demandedQuantity"
+                             :min="0"
+                             :precision="3"
+                             :step="1"
+                             controls-position="right"
+                             style="width: 100%;"
+                             @change="val => handleRequiredQtyChange(row, val)" />
+          </template>
+        </el-table-column>
+        <el-table-column label="璁¢噺鍗曚綅"
+                         width="100">
+          <template #default="{ row }">
+            {{ row.unit || "-" }}
+          </template>
+        </el-table-column>
+        <el-table-column label="棰嗙敤鏁伴噺"
+                         min-width="120">
+          <template #default="{ row }">
+            <el-input-number v-model="row.pickQty"
+                             :min="0"
+                             :precision="3"
+                             :step="1"
+                             controls-position="right"
+                             style="width: 100%;" />
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔"
+                         width="90"
+                         fixed="right">
+          <template #default="{ $index, row }">
+            <el-button v-if="row.bom !== true"
+                       type="danger"
+                       link
+                       @click="handleDeleteMaterialRow($index)">鍒犻櫎</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button type="primary"
+                     :loading="materialSaving"
+                     :disabled="isSaveDisabled"
+                     @click="handleMaterialSave">淇濆瓨</el-button>
+          <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+        </span>
+      </template>
+    </el-dialog>
+    <ProductSelectDialog v-model="materialProductDialogVisible"
+                         @confirm="handleMaterialProductConfirm"
+                         single />
+    <!-- request-url="/stockInventory/rawMaterials" -->
+  </div>
+</template>
+
+<script setup>
+  import { computed, ref, watch } from "vue";
+  import { ElMessage } from "element-plus";
+  import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
+  import {
+    findProductProcessRouteItemList,
+    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 },
+    orderRow: { type: Object, default: null },
+  });
+  const emit = defineEmits(["update:modelValue", "saved"]);
+
+  const dialogVisible = computed({
+    get: () => props.modelValue,
+    set: val => emit("update:modelValue", val),
+  });
+
+  const materialProductDialogVisible = ref(false);
+  const materialTableLoading = ref(false);
+  const materialSaving = ref(false);
+  const materialTableData = ref([]);
+
+  const isSaveDisabled = computed(() => {
+    if (materialTableData.value.length === 0) return true;
+    return !materialTableData.value.some(row => {
+      // 妫�鏌ユ槸鍚︽湁浠讳綍鐢ㄦ埛杈撳叆鍐呭
+      const hasBatch = Array.isArray(row.batchNo) && row.batchNo.length > 0;
+      const hasPickQty =
+        row.pickQty !== null && row.pickQty !== undefined && row.pickQty !== 0;
+
+      if (row.bom) {
+        // 瀵逛簬鏉ヨ嚜BOM鐨勮锛岃緭鍏ユ鍙湁鈥滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
+        return hasBatch || hasPickQty;
+      } else {
+        // 瀵逛簬鏂板琛岋紝杈撳叆妗嗗寘鎷�滃伐搴忊�濄�佲�滃師鏂欌�濄�佲�滈渶姹傛暟閲忊�濄�佲�滄壒鍙封�濆拰鈥滈鐢ㄦ暟閲忊��
+        const hasOperation = !!row.operationName;
+        const hasMaterial = !!row.materialName;
+        const hasDemanded =
+          row.demandedQuantity !== null &&
+          row.demandedQuantity !== undefined &&
+          row.demandedQuantity !== 0;
+        return (
+          hasBatch || hasPickQty || hasOperation || hasMaterial || hasDemanded
+        );
+      }
+    });
+  });
+
+  const processOptions = ref([]);
+  const currentMaterialSelectRowIndex = ref(-1);
+  let materialTempId = 0;
+
+  const createMaterialRow = (row = {}) => ({
+    tempId: row.id || `temp_${++materialTempId}`,
+    id: row.id,
+    processId: row.processId || row.technologyOperationId,
+    technologyOperationId: row.technologyOperationId || row.processId,
+    operationName: row.operationName || "",
+    bom: row.bom === true,
+    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.pickQuantity ?? 0),
+    batchNo: row.batchNo
+      ? typeof row.batchNo === "string"
+        ? row.batchNo.split(",")
+        : row.batchNo
+      : [],
+    batchNoList: row.batchNoList || [],
+  });
+
+  const getProcessOptions = async () => {
+    if (!props.orderRow?.id) return;
+    const res = await findProductProcessRouteItemList({
+      orderId: props.orderRow.id,
+    });
+    const routeList = Array.isArray(res?.data)
+      ? res.data
+      : res?.data?.records || [];
+    const processMap = new Map();
+    routeList.forEach(item => {
+      const processId = item.technologyOperationId;
+      const operationName = item.operationName;
+      if (!processId || !operationName) return;
+      const key = `${processId}_${operationName}`;
+      if (!processMap.has(key)) {
+        processMap.set(key, {
+          id: processId,
+          name: operationName,
+        });
+      }
+    });
+    processOptions.value = Array.from(processMap.values());
+  };
+  const isDetail = ref(true);
+
+  const loadMaterialData = async () => {
+    if (!props.orderRow?.id) return;
+    materialTableLoading.value = true;
+    materialTableData.value = [];
+    await getProcessOptions();
+    try {
+      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;
+      }
+    } finally {
+      materialTableLoading.value = false;
+    }
+  };
+
+  watch(
+    () => dialogVisible.value,
+    visible => {
+      if (visible) {
+        loadMaterialData();
+      }
+    }
+  );
+
+  const handleClose = () => {
+    materialTableData.value = [];
+    currentMaterialSelectRowIndex.value = -1;
+  };
+
+  const handleAddMaterialRow = () => {
+    materialTableData.value.push(createMaterialRow());
+  };
+
+  const handleDeleteMaterialRow = index => {
+    materialTableData.value.splice(index, 1);
+  };
+
+  const handleProcessNameChange = (row, operationName) => {
+    const process = processOptions.value.find(
+      item => item.name === operationName
+    );
+    row.technologyOperationId = process?.technologyOperationId;
+  };
+
+  const handleRequiredQtyChange = (row, val) => {
+    const required = Number(val ?? 0);
+    row.demandedQuantity = required;
+    if (!row.pickQty || Number(row.pickQty) === 0) {
+      row.pickQty = required;
+    }
+  };
+
+  const openMaterialProductSelect = row => {
+    currentMaterialSelectRowIndex.value = materialTableData.value.findIndex(
+      item => item.tempId === row.tempId
+    );
+    materialProductDialogVisible.value = true;
+  };
+
+  const handleMaterialProductConfirm = products => {
+    console.log(products, "products");
+
+    if (!products || products.length === 0) return;
+    const index = currentMaterialSelectRowIndex.value;
+    if (index < 0 || !materialTableData.value[index]) return;
+    const product = products[0];
+    const row = materialTableData.value[index];
+    row.materialModelId =
+      product.materialModelId || product.modelId || product.id;
+    row.materialName =
+      product.materialName || product.productName || product.name || "";
+    row.materialModel = product.materialModel || product.model || "";
+    row.unit = product.unit || product.measureUnit || "";
+    row.batchNoList = product.batchNoList;
+    currentMaterialSelectRowIndex.value = -1;
+    materialProductDialogVisible.value = false;
+  };
+
+  const validateMaterialRows = () => {
+    if (materialTableData.value.length === 0) {
+      return { valid: false, message: "璇峰厛鏂板棰嗘枡鏁版嵁" };
+    }
+    const invalidNewRow = materialTableData.value.find(
+      item => item.bom !== true && (!item.operationName || !item.materialName)
+    );
+    if (invalidNewRow) {
+      return { valid: false, message: "鏂板琛岀殑宸ュ簭鍚嶇О鍜屽師鏂欏悕绉颁负蹇呭~椤�" };
+    }
+    const invalidRow = materialTableData.value.find(
+      item =>
+        !item.operationName ||
+        !item.materialName ||
+        (Number(item.pickQty) > 0 &&
+          (!item.batchNo || item.batchNo.length === 0)) ||
+        (item.batchNo && item.batchNo.length > 0 && Number(item.pickQty) <= 0) ||
+        item.demandedQuantity === null ||
+        item.demandedQuantity === undefined ||
+        item.pickQty === null ||
+        item.pickQty === undefined
+    );
+    if (invalidRow) {
+      if (
+        invalidRow.batchNo &&
+        invalidRow.batchNo.length > 0 &&
+        Number(invalidRow.pickQty) <= 0
+      ) {
+        return { valid: false, message: "閫夋嫨浜嗘壒鍙锋椂锛岄鐢ㄦ暟閲忓繀椤诲ぇ浜庨浂" };
+      }
+      return { valid: false, message: "璇峰畬鍠勫伐搴忋�佸師鏂欍�佹壒鍙峰拰鏁伴噺鍚庡啀淇濆瓨" };
+    }
+    return { valid: true, message: "" };
+  };
+
+  const handleMaterialSave = async () => {
+    if (!props.orderRow?.id) return;
+    const validateResult = validateMaterialRows();
+    if (!validateResult.valid) {
+      ElMessage.warning(validateResult.message);
+      return;
+    }
+    materialSaving.value = true;
+    try {
+      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 {
+      materialSaving.value = false;
+    }
+  };
+</script>
+
+<style scoped lang="scss">
+  .material-toolbar {
+    margin-bottom: 12px;
+    text-align: right;
+  }
+</style>

--
Gitblit v1.9.3