gaoluyang
2026-06-12 ac00bb7b8eae1bf6aabb7f1ae67c2e5aa5b8ae90
src/views/salesManagement/salesLedger/index.vue
@@ -48,6 +48,9 @@
                     @click="openForm('add')">
            新增台账
          </el-button>
               <el-button type="primary" plain :disabled="selectedRows.length === 0" @click="handleBatchExportContract">
                  导出合同
               </el-button>
          <el-button type="primary"
                     plain
                     @click="handleImport">导入
@@ -155,6 +158,10 @@
              </el-table-column>
              <el-table-column label="数量"
                               prop="quantity" />
              <el-table-column label="每件数量"
                               prop="singleQuantity" />
              <el-table-column label="总数"
                               prop="totalQuantity" />
              <el-table-column label="待发货数量"
                               prop="noQuantity" />
              <el-table-column label="税率(%)"
@@ -240,7 +247,7 @@
            <el-button link
                       type="primary"
                       @click="openForm('edit', scope.row)"
                       :disabled="!scope.row.isEdit || scope.row.hasProductionRecord || !canEditLedger(scope.row)">编辑
                       :disabled="!canEditLedger(scope.row)">编辑
            </el-button>
            <el-button link
                       type="primary"
@@ -284,10 +291,15 @@
          <el-col :span="12">
            <el-form-item label="销售合同号:"
                          prop="salesContractNo">
              <el-input v-model="form.salesContractNo"
                        placeholder="自动生成"
                        clearable
                        disabled />
              <div style="display: flex; align-items: center; gap: 12px;width: 100%;">
                <el-checkbox v-model="form.autoGenerateContractNo" v-if="operationType === 'add'">自动生成
                </el-checkbox>
                <el-input v-model="form.salesContractNo"
                          :placeholder="form.autoGenerateContractNo ? '自动生成' : '请输入'"
                          clearable
                          :disabled="form.autoGenerateContractNo" />
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -440,6 +452,10 @@
                           prop="unit" />
          <el-table-column label="数量"
                           prop="quantity" />
          <el-table-column label="每件数量"
                           prop="singleQuantity" />
          <el-table-column label="总数"
                           prop="totalQuantity" />
          <el-table-column label="税率(%)"
                           prop="taxRate" />
          <el-table-column label="含税单价(元)"
@@ -676,6 +692,30 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="每件数量:"
                          prop="singleQuantity">
              <el-input-number :step="1"
                               :min="1"
                               v-model="productForm.singleQuantity"
                               placeholder="请输入"
                               clearable
                               :precision="0"
                               @change="calculateFromSingleQuantity"
                               style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="总数:"
                          prop="totalQuantity">
              <el-input v-model="productForm.totalQuantity"
                        placeholder="自动计算"
                        disabled
                        style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="含税总价(元):"
                          prop="taxInclusiveTotalPrice">
              <el-input v-model="productForm.taxInclusiveTotalPrice"
@@ -885,7 +925,7 @@
    <!-- 发货弹框 -->
    <el-dialog v-model="deliveryFormVisible"
               title="发货信息"
               width="40%"
               width="70%"
               @close="closeDeliveryDia">
      <el-form :model="deliveryForm"
               label-width="120px"
@@ -898,7 +938,8 @@
                          prop="type">
              <el-select v-model="deliveryForm.type"
                         placeholder="请选择发货类型"
                         style="width: 100%">
                         style="width: 100%"
                         @change="handleDeliveryTypeChange">
                <el-option label="货车"
                           value="货车" />
                <el-option label="快递"
@@ -914,8 +955,43 @@
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24" v-if="deliveryForm.type === '货车'">
            <el-form-item label="发货车牌号:"
                          prop="shippingCarNumber">
              <el-input v-model="deliveryForm.shippingCarNumber"
                        placeholder="请输入发货车牌号"
                        clearable />
            </el-form-item>
          </el-col>
          <el-col :span="24" v-else>
            <el-form-item label="快递公司:"
                          prop="expressCompany">
              <el-input v-model="deliveryForm.expressCompany"
                        placeholder="请输入快递公司"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30" v-if="deliveryForm.type === '快递'">
          <el-col :span="24">
            <el-form-item label="批号:"
            <el-form-item label="快递单号:"
                          prop="expressNumber">
              <el-input v-model="deliveryForm.expressNumber"
                        placeholder="请输入快递单号"
                        clearable />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="发货图片:">
              <ImageUpload v-model:file-list="deliveryFileList" :limit="9" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="库存:"
                          prop="batchNo">
              <el-table :data="deliveryForm.batchNoList"
                        border
@@ -925,6 +1001,15 @@
                <el-table-column label="批号"
                                 prop="batchNo"
                                 min-width="180" />
                <el-table-column label="产品大类"
                                 prop="productName"
                                 min-width="100" />
                <el-table-column label="规格型号"
                                 prop="model"
                                 min-width="100" />
                <el-table-column label="单位"
                                 prop="unit"
                                 min-width="100" />
                <el-table-column label="库存数量"
                                 min-width="120"
                                 align="center">
@@ -990,6 +1075,7 @@
  import useFormData from "@/hooks/useFormData.js";
  import dayjs from "dayjs";
  import FileUpload from "@/components/AttachmentUpload/file/index.vue";
  import ImageUpload from "@/components/AttachmentUpload/image/index.vue";
  import { getCurrentDate } from "@/utils/index.js";
  import { listCustomer } from "@/api/basicData/customer.js";
@@ -1017,6 +1103,7 @@
  });
  const total = ref(0);
  const fileList = ref([]);
  const deliveryFileList = ref([]);
  // 用户信息表单弹框数据
  const operationType = ref("");
@@ -1031,6 +1118,7 @@
    },
    form: {
      salesContractNo: "",
      autoGenerateContractNo: true,
      salesman: "",
      customerId: "",
      entryPerson: "",
@@ -1039,7 +1127,6 @@
      maintenanceTime: "",
      productData: [],
      executionDate: "",
      hasProductionRecord: false,
    },
    rules: {
      salesman: [{ required: true, message: "请选择", trigger: "change" }],
@@ -1062,6 +1149,8 @@
      specificationModel: "",
      unit: "",
      quantity: "",
      singleQuantity: 1,
      totalQuantity: "",
      taxInclusiveUnitPrice: "",
      taxRate: "",
      taxInclusiveTotalPrice: "",
@@ -1195,11 +1284,30 @@
        deliveryQuantity: 0,
      }));
  };
  const validateDeliveryShippingCarNumber = (_rule, value, callback) => {
    if (deliveryForm.value.type === "货车" && !value) {
      return callback(new Error("请输入发货车牌号"));
    }
    callback();
  };
  const validateDeliveryExpressCompany = (_rule, value, callback) => {
    if (deliveryForm.value.type === "快递" && !value) {
      return callback(new Error("请输入快递公司"));
    }
    callback();
  };
  const deliveryFormData = reactive({
    deliveryForm: {
      shippingCarNumber: "",
      expressCompany: "",
      expressNumber: "",
      type: "货车", // 货车, 快递
    },
    deliveryRules: {
      shippingCarNumber: [
        { validator: validateDeliveryShippingCarNumber, trigger: "blur" },
      ],
      expressCompany: [{ validator: validateDeliveryExpressCompany, trigger: "blur" }],
      type: [{ required: true, message: "请选择发货类型", trigger: "change" }],
    },
  });
@@ -1521,7 +1629,7 @@
    selectedQuotation.value = null;
    let userLists = await userListNoPage();
    userList.value = userLists.data;
    listCustomer({ current: -1, size: -1 }).then(res => {
    listCustomer({ current: -1, size: -1, type: 0 }).then(res => {
      customerOption.value = res.data.records;
    });
    form.value.entryPerson = userStore.id;
@@ -1530,6 +1638,8 @@
      form.value.entryDate = getCurrentDate();
      // 签订日期默认为当天
      form.value.executionDate = getCurrentDate();
      // 默认自动生成销售合同号
      form.value.autoGenerateContractNo = true;
    } else {
      currentId.value = row.id;
      getSalesLedgerWithProducts({ id: row.id, type: 1 }).then(res => {
@@ -1537,6 +1647,8 @@
        form.value.entryPerson = Number(res.entryPerson);
        productData.value = form.value.productData;
        fileList.value = form.value.storageBlobVOs;
        // 编辑时设置自动生成为false,允许手动修改
        form.value.autoGenerateContractNo = false;
      });
    }
    // let userAll = await userStore.getInfo()
@@ -1630,6 +1742,7 @@
    productData.value = products.map(p => {
      const quantity = Number(p.quantity ?? 0) || 0;
      const unitPrice = Number(p.unitPrice ?? 0) || 0;
      const singleQuantity = 1;
      const taxRate = "13"; // 默认 13%,便于直接提交(如需可在产品中自行修改)
      const taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
      const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(
@@ -1642,12 +1755,16 @@
        specificationModel: p.specification || "",
        unit: p.unit || "",
        quantity: quantity,
        singleQuantity: singleQuantity,
        totalQuantity: String(quantity * singleQuantity),
        taxRate: taxRate,
        taxInclusiveUnitPrice: unitPrice.toFixed(2),
        taxInclusiveTotalPrice: taxInclusiveTotalPrice,
        taxExclusiveTotalPrice: taxExclusiveTotalPrice,
        invoiceType: "增普票",
        isProduction: true,
        productId: p.productId,
        productModelId: p.productModelId
      };
    });
@@ -1671,6 +1788,9 @@
        }
        form.value.storageBlobDTOs = fileList;
        form.value.type = 1;
        if (form.value.autoGenerateContractNo) {
          form.value.salesContractNo = '';
        }
        addOrUpdateSalesLedger(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
@@ -1704,6 +1824,12 @@
    if (type === "edit") {
      productForm.value = { ...row };
      productIndex.value = index;
      // 如果没有 totalQuantity,自动计算:总数 = 每件数量 * 数量
      if (!productForm.value.totalQuantity && productForm.value.quantity) {
        const singleQuantity = parseInt(productForm.value.singleQuantity) || 1;
        const quantity = parseFloat(productForm.value.quantity);
        productForm.value.totalQuantity = (quantity * singleQuantity).toFixed(0);
      }
      // 编辑时根据产品大类名称反查 tree 节点 id,并加载规格型号列表
      try {
        const options =
@@ -2385,6 +2511,10 @@
        );
    }
    // 计算总数 = 每件数量 * 数量
    const singleQuantity = parseInt(productForm.value.singleQuantity) || 1;
    productForm.value.totalQuantity = (quantity * singleQuantity).toFixed(0);
    isCalculating.value = false;
  };
@@ -2418,6 +2548,10 @@
      inclusiveTotalPrice / quantity
    ).toFixed(2);
    // 计算总数 = 每件数量 * 数量
    const singleQuantity = parseInt(productForm.value.singleQuantity) || 1;
    productForm.value.totalQuantity = (quantity * singleQuantity).toFixed(0);
    isCalculating.value = false;
  };
@@ -2438,7 +2572,7 @@
    isCalculating.value = true;
    // 计算含税总价
    // 计算含税总价 = 单价 * 数量
    productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    // 如果有税率,计算不含税总价
@@ -2449,6 +2583,10 @@
          productForm.value.taxRate
        );
    }
    // 计算总数 = 每件数量 * 数量
    const singleQuantity = parseInt(productForm.value.singleQuantity) || 1;
    productForm.value.totalQuantity = (quantity * singleQuantity).toFixed(0);
    isCalculating.value = false;
  };
@@ -2470,7 +2608,7 @@
    isCalculating.value = true;
    // 计算含税总价
    // 计算含税总价 = 单价 * 数量
    productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    // 如果有税率,计算不含税总价
@@ -2481,6 +2619,25 @@
          productForm.value.taxRate
        );
    }
    isCalculating.value = false;
  };
  // 根据每件数量变化计算总数
  const calculateFromSingleQuantity = () => {
    if (isCalculating.value) return;
    const quantity = parseFloat(productForm.value.quantity);
    if (!quantity || quantity <= 0) {
      return;
    }
    isCalculating.value = true;
    // 计算总数 = 每件数量 * 数量
    const singleQuantity = parseInt(productForm.value.singleQuantity) || 1;
    productForm.value.totalQuantity = (quantity * singleQuantity).toFixed(0);
    isCalculating.value = false;
  };
@@ -2537,6 +2694,7 @@
      审核拒绝: "审核拒绝",
      审核通过: "审核通过",
      已发货: "已发货",
      部分发货: "部分发货",
    };
    return statusTextMap[statusStr] || "待发货";
  };
@@ -2568,6 +2726,7 @@
      审核拒绝: "danger",
      审核通过: "success",
      已发货: "success",
      部分发货: "warning",
    };
    return typeTextMap[statusStr] || "info";
  };
@@ -2593,7 +2752,7 @@
    // 发货状态必须是"待发货"或"审核拒绝"
    const statusStr = shippingStatus ? String(shippingStatus).trim() : "";
    return statusStr === "待发货" || statusStr === "审核拒绝";
    return statusStr === "待发货" || statusStr === "审核拒绝" || statusStr === "部分发货";
  };
  // 打开附件弹窗
@@ -2605,7 +2764,40 @@
    recordId.value = row.id;
    fileDialogVisible.value = true;
  };
   const handleBatchExportContract = () => {
      if (selectedRows.value.length === 0) {
         proxy.$modal.msgWarning("请选择要导出的台账");
         return;
      }
      const firstCustomerKey =
         selectedRows.value[0].customerId ?? selectedRows.value[0].customerName ?? "";
      const hasDifferentCustomer = selectedRows.value.some((row) => {
         const customerKey = row.customerId ?? row.customerName ?? "";
         return String(customerKey) !== String(firstCustomerKey);
      });
      if (hasDifferentCustomer) {
         proxy.$modal.msgWarning("仅支持同一客户同时导出合同");
         return;
      }
      const ids = selectedRows.value
         .map((row) => row.id)
         .filter((id) => id !== null && id !== undefined);
      if (ids.length === 0) {
         proxy.$modal.msgWarning("未获取到合同ID");
         return;
      }
      proxy.download(
         "/sales/ledger/exportContract",
         ids,
         `销售合同_批量_${dayjs().format("YYYYMMDDHHmmss")}.docx`,
         {
            method: "post",
            filename: `销售合同_批量_${dayjs().format("YYYYMMDDHHmmss")}.docx`,
            headers: { "Content-Type": "application/json;charset=utf-8" },
            transformRequest: [(data) => JSON.stringify(data)],
         }
      );
   }
  // 打开发货弹框
  const openDeliveryForm = async row => {
    // 检查是否可以发货
@@ -2620,11 +2812,31 @@
    const batchNoList = await getDeliveryBatchNoList(
      row.productModelId || row.modelId
    );
    // 自动分配发货数量:按照待发货数量,从上到下依次分配
    const noQuantity = Number(row?.noQuantity || 0);
    let remainingQuantity = noQuantity;
    batchNoList.forEach(item => {
      if (remainingQuantity <= 0) {
        item.deliveryQuantity = 0;
        return;
      }
      const batchStockQuantity = getDeliveryBatchQuantity(item);
      // 当前批次可分配的数量:取剩余待发货数量和库存数量的较小值
      const assignQuantity = Math.min(remainingQuantity, batchStockQuantity);
      item.deliveryQuantity = assignQuantity;
      remainingQuantity -= assignQuantity;
    });
    deliveryForm.value = {
      shippingCarNumber: "",
      expressCompany: "",
      expressNumber: "",
      type: "货车",
      batchNo: [],
      batchNoList,
    };
    deliveryFileList.value = [];
    deliveryFormVisible.value = true;
  };
@@ -2662,6 +2874,19 @@
          salesLedgerId: salesLedgerId,
          salesLedgerProductId: currentDeliveryRow.value.id,
          type: deliveryForm.value.type,
          shippingCarNumber:
            deliveryForm.value.type === "货车"
              ? deliveryForm.value.shippingCarNumber
              : "",
          expressCompany:
            deliveryForm.value.type === "快递"
              ? deliveryForm.value.expressCompany
              : "",
          expressNumber:
            deliveryForm.value.type === "快递"
              ? deliveryForm.value.expressNumber
              : "",
          storageBlobDTOs: deliveryFileList.value || [],
          batchNo: deliveryForm.value.batchNo,
          batchNoDetailList: selectedBatchRows.map(item => ({
            stockInventoryId: item.id,
@@ -2701,8 +2926,18 @@
  };
  // 关闭发货弹框
  const handleDeliveryTypeChange = val => {
    if (val === "货车") {
      deliveryForm.value.expressCompany = "";
      deliveryForm.value.expressNumber = "";
    } else {
      deliveryForm.value.shippingCarNumber = "";
    }
  };
  const closeDeliveryDia = () => {
    proxy.resetForm("deliveryFormRef");
    deliveryFileList.value = [];
    deliveryFormVisible.value = false;
    currentDeliveryRow.value = null;
  };
@@ -2919,4 +3154,4 @@
      page-break-after: avoid;
    }
  }
</style>
</style>