gaoluyang
2026-05-18 da57fbd8e7fa021614fb32502fb1520ea4e34e1e
src/views/inventoryManagement/dispatchLog/Record.vue
@@ -31,6 +31,7 @@
        >
      </div>
      <div>
        <el-button type="primary" @click="handleAdd">新增</el-button>
        <el-button type="primary" @click="handleBatchApprove">审批</el-button>
        <el-button @click="handleOut">导出</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
@@ -67,6 +68,7 @@
          show-overflow-tooltip
        />
        <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
        <el-table-column label="库位" prop="warehouseName" show-overflow-tooltip />
        <el-table-column label="批号" prop="batchNo" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" show-overflow-tooltip />
        <el-table-column
@@ -94,6 +96,17 @@
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="120" align="center" fixed="right">
          <template #default="scope">
            <el-button
              v-if="scope.row.approvalStatus !== 1 && scope.row.approvalStatus !== '1' && scope.row.approvalStatus !== 'approved' && scope.row.approvalStatus !== 'APPROVED'"
              link
              type="primary"
              size="small"
              @click="handleEdit(scope.row)">编辑</el-button>
            <span v-else style="color: #999; font-size: 12px;">已通过</span>
          </template>
        </el-table-column>
      </el-table>
      <pagination
        v-show="total > 0"
@@ -104,24 +117,181 @@
        @pagination="paginationChange"
      />
    </div>
    <!-- 新增/编辑对话框 -->
    <el-dialog v-model="dialogVisible"
               :title="dialogTitle"
               width="800"
               @close="closeDialog">
      <el-form ref="formRef"
               :model="formState"
               label-width="140px"
               label-position="top">
        <el-form-item label="产品名称"
                      prop="productModelId"
                      :rules="[
                        {
                          required: true,
                          message: '请选择产品',
                          trigger: 'change',
                        }
                      ]">
          <el-button type="primary"
                     @click="showProductSelect = 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>
        <el-form-item label="单位"
                      prop="unit">
          <el-input v-model="formState.unit" disabled />
        </el-form-item>
        <el-form-item label="仓库"
                      prop="warehouseInfoId"
                      :rules="[
                        {
                          required: true,
                          message: '请选择仓库',
                          trigger: 'change',
                        }
                      ]">
          <el-select v-model="formState.warehouseInfoId"
                     placeholder="请选择仓库"
                     clearable
                     @change="handleWarehouseChange"
                     style="width: 100%">
            <el-option v-for="warehouse in warehouseList"
                       :key="warehouse.id"
                       :label="warehouse.warehouseName"
                       :value="warehouse.id" />
          </el-select>
        </el-form-item>
        <el-form-item label="批号"
                      prop="batchNo"
                      :rules="[
                        {
                          required: true,
                          message: '请选择批号',
                          trigger: 'change',
                        }
                      ]">
          <el-select v-model="formState.batchNo"
                     placeholder="请选择批号"
                     clearable
                     @change="handleBatchNoChange"
                     style="width: 100%">
            <el-option v-for="batch in batchNoList"
                       :key="batch"
                       :label="batch + ' (库存: ' + (batchNoStockMap[batch] || 0) + ')'"
                       :value="batch" />
          </el-select>
        </el-form-item>
        <el-form-item v-if="formState.batchNo && batchNoStockMap[formState.batchNo] !== undefined"
                      label="当前批号库存"
                      prop="currentStock">
          <el-input :model-value="batchNoStockMap[formState.batchNo]" disabled />
        </el-form-item>
        <el-form-item label="出库数量"
                      prop="qualitity"
                      :rules="[
                        {
                          required: true,
                          message: '请输入出库数量',
                          trigger: 'blur',
                        },
                        {
                          validator: (rule, value, callback) => {
                            if (formState.maxStock > 0 && value > formState.maxStock) {
                              callback('出库数量不能超过当前批号库存 ' + formState.maxStock);
                            } else {
                              callback();
                            }
                          },
                          trigger: 'blur',
                        }
                      ]">
          <el-input-number v-model="formState.qualitity"
                           :step="1"
                           :min="1"
                           :max="formState.maxStock > 0 ? formState.maxStock : undefined"
                           style="width: 100%" />
        </el-form-item>
        <el-form-item v-if="isEdit"
                      label="来源"
                      prop="recordType">
          <el-select v-model="formState.recordType"
                     placeholder="请选择来源"
                     disabled>
            <el-option v-for="item in stockRecordTypeOptions"
                       :key="item.value"
                       :label="item.label"
                       :value="item.value" />
          </el-select>
        </el-form-item>
        <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>
        </el-form-item>
        <el-form-item label="备注"
                      prop="remark">
          <el-input v-model="formState.remark"
                    type="textarea" />
        </el-form-item>
      </el-form>
      <!-- 产品选择弹窗 -->
      <ProductSelectDialog v-model="showProductSelect"
                           @confirm="handleProductSelect"
                           :top-product-parent-id="props.topParentProductId"
                           request-url="/basic/product/pageModelAndQua"
                           single />
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleSubmit">确认</el-button>
          <el-button @click="closeDialog">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import pagination from "@/components/PIMTable/Pagination.vue";
import { ref } from "vue";
import { ElMessageBox } from "element-plus";
import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue";
import { ref, reactive, toRefs, computed, getCurrentInstance, watch, onMounted } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import useUserStore from "@/store/modules/user";
import { getCurrentDate } from "@/utils/index.js";
import {
  getStockOutPage,
  delPendingStockOut,
  batchApproveStockOutRecords,
  updateStockOutRecord,
} from "@/api/inventoryManagement/stockOut.js";
import {
  findAllQualifiedStockOutRecordTypeOptions,
  findAllUnQualifiedStockOutRecordTypeOptions,
} from "@/api/basicData/enum.js";
import { addStockOutRecordOnly, getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js";
import { addUnqualifiedStockOutRecordOnly } from "@/api/inventoryManagement/stockUninventory.js";
import { getWarehouseList } from "@/api/inventoryManagement/warehouse.js";
import { productModelListByUrl } from "@/api/basicData/productModel.js";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
@@ -130,6 +300,14 @@
const tableLoading = ref(false);
// 来源类型选项
const stockRecordTypeOptions = ref([]);
// 批号列表(从batchNoMaps获取)
const batchNoList = ref([]);
// 批号库存映射
const batchNoStockMap = ref({});
// 仓库列表
const warehouseList = ref([]);
// 原始batchNoMaps数据
const rawBatchNoMaps = ref({});
const page = reactive({
  current: 1,
  size: 100,
@@ -161,6 +339,41 @@
  },
});
const { searchForm } = toRefs(data);
// 对话框相关
const dialogVisible = ref(false);
const dialogType = ref('add'); // 'add' 或 'edit'
const dialogTitle = computed(() => dialogType.value === 'add' ? '新增出库记录' : '编辑出库记录');
const isEdit = computed(() => dialogType.value === 'edit');
const formRef = ref();
const showProductSelect = ref(false);
// 表单数据
const formState = ref({
  id: undefined,
  productId: undefined,
  productModelId: undefined,
  productName: "",
  productModelName: "",
  unit: "",
  warehouseInfoId: null, // 仓库ID
  type: undefined,
  qualitity: 0,
  batchNo: null,
  recordType: "",
  remark: "",
  maxStock: 0, // 当前选中批号的最大库存
});
// 批号为空时转为 null
watch(
  () => formState.value.batchNo,
  val => {
    if (val === "") {
      formState.value.batchNo = null;
    }
  }
);
// 查询列表
/** 搜索按钮操作 */
@@ -239,6 +452,349 @@
  return "warning";
};
// 新增
const handleAdd = () => {
  dialogType.value = 'add';
  resetForm();
  // 根据当前tab设置默认库存类型
  formState.value.type = props.type === '0' ? 'qualified' : 'unqualified';
  dialogVisible.value = true;
};
// 编辑
const handleEdit = async (row) => {
  dialogType.value = 'edit';
  resetForm();
  // 先加载所有数据,最后再赋值 formState
  let loadedWarehouseList = [];
  let loadedBatchNoList = [];
  let loadedBatchNoStockMap = {};
  let loadedMaxStock = 0;
  let loadedRawBatchNoMaps = {};
  // 编辑时加载仓库列表
  if (row.warehouseInfoId) {
    const allWarehouses = await loadWarehouseList();
    const currentWarehouse = allWarehouses.find(w => String(w.id) === String(row.warehouseInfoId));
    if (currentWarehouse) {
      loadedWarehouseList = [{
        id: currentWarehouse.id,
        warehouseName: currentWarehouse.warehouseName || currentWarehouse.name || currentWarehouse.warehouseCode || `仓库${currentWarehouse.id}`
      }];
    }
  }
  // 编辑时查询产品库存
  if (row.productModelId) {
    try {
      console.log('编辑时查询库存,productModelId:', row.productModelId);
      console.log('当前row数据:', row);
      const res = await productModelListByUrl('/basic/product/pageModelAndQua', {
        id: row.productModelId,
        page: 1,
        size: 1
      });
      console.log('查询库存接口返回:', res);
      // 接口直接返回 {records: [], total: ...},没有 data 层和 code
      const records = res.records || (res.data && res.data.records) || [];
      if (records.length > 0) {
        const product = records[0];
        console.log('产品数据:', product);
        console.log('batchNoMaps:', product.batchNoMaps);
        if (product.batchNoMaps && Object.keys(product.batchNoMaps).length > 0) {
          loadedRawBatchNoMaps = product.batchNoMaps;
          // 获取所有仓库信息用于反显名称
          const allWarehouses = await loadWarehouseList();
          const warehouseMap = {};
          allWarehouses.forEach(w => {
            warehouseMap[w.id] = w.warehouseName || w.name || w.warehouseCode || `仓库${w.id}`;
          });
          // 构建仓库列表,确保包含当前记录的仓库
          const warehouseIds = Object.keys(product.batchNoMaps);
          // 如果当前记录的仓库不在 product.batchNoMaps 中,添加进去
          if (row.warehouseInfoId && !warehouseIds.some(id => String(id) === String(row.warehouseInfoId))) {
            warehouseIds.push(String(row.warehouseInfoId));
          }
          loadedWarehouseList = warehouseIds.map(warehouseInfoId => ({
            id: warehouseInfoId,
            warehouseName: warehouseMap[warehouseInfoId] || `仓库${warehouseInfoId}`
          }));
          console.log('当前仓库ID:', row.warehouseInfoId);
          console.log('该仓库的batchNoMaps:', product.batchNoMaps[row.warehouseInfoId]);
          // 如果当前有仓库ID,解析该仓库的批号库存(处理类型不匹配问题)
          let batchArray = null;
          if (row.warehouseInfoId) {
            // 尝试多种方式获取批号数据
            batchArray = product.batchNoMaps[row.warehouseInfoId] ||
                        product.batchNoMaps[String(row.warehouseInfoId)] ||
                        product.batchNoMaps[Number(row.warehouseInfoId)];
          }
          if (batchArray) {
            console.log('batchArray:', batchArray);
            const batchMap = {};
            const batches = [];
            batchArray.forEach(item => {
              const batchNo = Object.keys(item)[0];
              const stock = item[batchNo];
              console.log('批号:', batchNo, '库存:', stock);
              batches.push(batchNo);
              batchMap[batchNo] = stock;
            });
            loadedBatchNoList = batches;
            loadedBatchNoStockMap = batchMap;
            console.log('batchMap:', batchMap);
            console.log('当前批号:', row.batchNo);
            // 设置当前批号的库存
            if (row.batchNo && batchMap[row.batchNo] !== undefined) {
              loadedMaxStock = batchMap[row.batchNo];
              console.log('设置maxStock为:', loadedMaxStock);
            } else {
              console.log('未找到当前批号的库存');
              loadedMaxStock = 0;
            }
          } else {
            console.log('未找到当前仓库的batchNoMaps');
            loadedBatchNoList = row.batchNo ? [row.batchNo] : [];
            loadedBatchNoStockMap = {};
            loadedMaxStock = 0;
          }
        } else {
          console.log('产品没有batchNoMaps');
          loadedBatchNoList = row.batchNo ? [row.batchNo] : [];
          loadedBatchNoStockMap = {};
          loadedMaxStock = 0;
        }
      } else {
        console.log('接口返回数据异常:', res);
        loadedBatchNoList = row.batchNo ? [row.batchNo] : [];
        loadedBatchNoStockMap = {};
        loadedMaxStock = 0;
      }
    } catch (error) {
      console.error('查询产品库存失败', error);
      loadedBatchNoList = row.batchNo ? [row.batchNo] : [];
      loadedBatchNoStockMap = {};
      loadedMaxStock = 0;
    }
  } else {
    console.log('没有productModelId');
    loadedBatchNoList = row.batchNo ? [row.batchNo] : [];
    loadedBatchNoStockMap = {};
    loadedMaxStock = 0;
  }
  // 所有数据加载完成后,一次性赋值
  warehouseList.value = loadedWarehouseList;
  batchNoList.value = loadedBatchNoList;
  batchNoStockMap.value = loadedBatchNoStockMap;
  rawBatchNoMaps.value = loadedRawBatchNoMaps;
  // 最后赋值 formState,确保仓库列表已经准备好
  // 注意:将 warehouseInfoId 转换为字符串,确保与 warehouseList 中的 id 类型匹配
  formState.value = {
    id: row.id,
    productId: row.productId,
    productModelId: row.productModelId,
    productName: row.productName,
    productModelName: row.model,
    unit: row.unit,
    type: props.type === '0' ? 'qualified' : 'unqualified',
    qualitity: row.stockOutNum,
    batchNo: row.batchNo,
    warehouseInfoId: row.warehouseInfoId != null ? String(row.warehouseInfoId) : null,
    recordType: row.recordType,
    remark: row.remark || "",
    maxStock: loadedMaxStock,
  };
  // 所有数据加载完成后再显示弹窗
  dialogVisible.value = true;
};
// 重置表单
const resetForm = () => {
  formState.value = {
    id: undefined,
    productId: undefined,
    productModelId: undefined,
    productName: "",
    productModelName: "",
    unit: "",
    warehouseInfoId: null,
    type: undefined,
    qualitity: 0,
    batchNo: null,
    recordType: "",
    remark: "",
    maxStock: 0,
  };
  warehouseList.value = [];
  batchNoList.value = [];
  batchNoStockMap.value = {};
  rawBatchNoMaps.value = {};
};
// 关闭对话框
const closeDialog = () => {
  dialogVisible.value = false;
  resetForm();
};
// 加载仓库列表
const loadWarehouseList = async () => {
  try {
    const res = await getWarehouseList();
    if (res.code === 200) {
      return res.data || [];
    }
  } catch (error) {
    console.error('加载仓库列表失败', error);
  }
  return [];
};
// 产品选择处理
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;
    // 解析batchNoMaps数据,格式为:{ 仓库ID: [{批号: 库存}, {批号: 库存}] }
    warehouseList.value = [];
    batchNoList.value = [];
    batchNoStockMap.value = {};
    rawBatchNoMaps.value = {};
    formState.value.warehouseInfoId = null;
    formState.value.batchNo = null;
    formState.value.maxStock = 0;
    if (product.batchNoMaps && Object.keys(product.batchNoMaps).length > 0) {
      rawBatchNoMaps.value = product.batchNoMaps;
      // 获取所有仓库信息用于反显名称
      const allWarehouses = await loadWarehouseList();
      const warehouseMap = {};
      allWarehouses.forEach(w => {
        warehouseMap[w.id] = w.warehouseName || w.name || w.warehouseCode || `仓库${w.id}`;
      });
      // 构建仓库列表
      warehouseList.value = Object.keys(product.batchNoMaps).map(warehouseInfoId => ({
        id: warehouseInfoId,
        warehouseName: warehouseMap[warehouseInfoId] || `仓库${warehouseInfoId}`
      }));
    }
    showProductSelect.value = false;
    // 触发表单验证更新
    proxy.$refs["formRef"]?.validateField("productModelId");
  }
};
// 仓库选择变化处理
const handleWarehouseChange = (warehouseInfoId) => {
  batchNoList.value = [];
  batchNoStockMap.value = {};
  formState.value.batchNo = null;
  formState.value.maxStock = 0;
  if (warehouseInfoId && rawBatchNoMaps.value[warehouseInfoId]) {
    // 解析该仓库下的批号数据,格式为:[{批号: 库存}, {批号: 库存}]
    const batchArray = rawBatchNoMaps.value[warehouseInfoId];
    const batchMap = {};
    const batches = [];
    batchArray.forEach(item => {
      const batchNo = Object.keys(item)[0];
      const stock = item[batchNo];
      batches.push(batchNo);
      batchMap[batchNo] = stock;
    });
    batchNoList.value = batches;
    batchNoStockMap.value = batchMap;
  }
};
// 批号选择变化处理
const handleBatchNoChange = (batchNo) => {
  if (batchNo && batchNoStockMap.value[batchNo]) {
    formState.value.maxStock = batchNoStockMap.value[batchNo];
    // 如果当前出库数量超过最大库存,自动调整为最大库存
    if (formState.value.qualitity > formState.value.maxStock) {
      formState.value.qualitity = formState.value.maxStock;
    }
  } else {
    formState.value.maxStock = 0;
  }
};
// 提交表单
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      // 验证是否选择了产品
      if (!formState.value.productModelId) {
        ElMessage.error("请选择产品");
        return;
      }
      if (dialogType.value === 'add') {
        submitAdd();
      } else {
        submitEdit();
      }
    }
  });
};
// 提交新增
const submitAdd = () => {
  const params = { ...formState.value };
  if (formState.value.type === "qualified") {
    addStockOutRecordOnly(params).then(res => {
      ElMessage.success("新增成功");
      closeDialog();
      getList();
    }).catch(() => {
      ElMessage.error("新增失败");
    });
  } else {
    addUnqualifiedStockOutRecordOnly(params).then(res => {
      ElMessage.success("新增成功");
      closeDialog();
      getList();
    }).catch(() => {
      ElMessage.error("新增失败");
    });
  }
};
// 提交编辑
const submitEdit = () => {
  const params = {
    productId: formState.value.productId,
    productModelId: formState.value.productModelId,
    productName: formState.value.productName,
    model: formState.value.productModelName,
    unit: formState.value.unit,
    batchNo: formState.value.batchNo,
    stockOutNum: formState.value.qualitity,
    recordType: formState.value.recordType,
    remark: formState.value.remark,
  };
  updateStockOutRecord(formState.value.id, params).then(() => {
    ElMessage.success("编辑成功");
    closeDialog();
    getList();
  }).catch(() => {
    ElMessage.error("编辑失败");
  });
};
// 获取来源类型选项
const fetchStockRecordTypeOptions = () => {
  if (props.type === "0") {