gongchunyi
6 天以前 aa802302cfe650e08806c8b87d5a8c5d3a79ffe1
src/views/salesManagement/salesLedger/index.vue
@@ -64,10 +64,14 @@
                       :value="1" />
            <el-option label="审批中"
                       :value="2" />
            <el-option label="审批失败"
            <el-option label="审批不通过"
                       :value="3" />
            <el-option label="已发货"
            <el-option label="审批通过"
                       :value="4" />
            <el-option label="已发货"
                       :value="5" />
            <el-option label="部分发货"
                       :value="6" />
          </el-select>
        </el-form-item>
        <el-form-item label="入库状态:">
@@ -109,6 +113,20 @@
          <el-button type="primary"
                     plain
                     @click="handleImport">导入</el-button>
          <el-dropdown @command="handleHistoryImportCommand">
            <el-button type="primary"
                       plain>
              历史迁移<el-icon class="el-icon--right">
                <ArrowDown />
              </el-icon>
            </el-button>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item command="notShipped">未出库</el-dropdown-item>
                <el-dropdown-item command="shipped">已出库</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
          <el-button @click="handleOut">导出</el-button>
          <el-button type="danger"
                     plain
@@ -144,7 +162,7 @@
                style="width: 100%"
                :summary-method="summarizeMainTable"
                @expand-change="expandChange"
                height="calc(100vh - 18.5em)">
                height="calc(100vh - 22em)">
        <el-table-column align="center"
                         type="selection"
                         width="55"
@@ -224,6 +242,8 @@
                <template #default="scope">
                  <el-tag v-if="scope.row.approveStatus === 1 && (!scope.row.shippingDate || !scope.row.shippingCarNumber)"
                          type="success">充足</el-tag>
                  <el-tag v-else-if="scope.row.approveStatus === 1 && scope.row.shippingDate && scope.row.shippingCarNumber"
                          type="success">已出库</el-tag>
                  <el-tag v-else-if="scope.row.approveStatus === 0 && (scope.row.shippingDate || scope.row.shippingCarNumber)"
                          type="success">已出库</el-tag>
                  <el-tag v-else
@@ -355,6 +375,8 @@
                    type="primary">审批通过</el-tag>
            <el-tag v-else-if="Number(scope.row.deliveryStatus) === 5"
                    type="success">已发货</el-tag>
            <el-tag v-else-if="Number(scope.row.deliveryStatus) === 6"
                    type="warning">部分发货</el-tag>
            <el-tag v-else
                    type="info">-</el-tag>
          </template>
@@ -369,6 +391,8 @@
                    type="success">部分入库</el-tag>
            <el-tag v-else-if="Number(scope.row.stockStatus) === 2"
                    type="success">已入库</el-tag>
            <el-tag v-else-if="Number(scope.row.stockStatus) === 3"
                    type="warning">审批中</el-tag>
            <el-tag v-else
                    type="info">-</el-tag>
          </template>
@@ -644,6 +668,36 @@
              <span v-else>{{ scope.row.thickness ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="楼层编号"
                           prop="floorCode"
                           min-width="250"
                           show-overflow-tooltip>
            <template #default="scope">
              <el-input v-if="scope.row.__editing"
                        v-model="scope.row.floorCode"
                        placeholder="请输入"
                        clearable
                        style="width: 100%" />
              <span v-else>{{ scope.row.floorCode ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="含税单价(元)"
                           prop="taxInclusiveUnitPrice"
                           min-width="160">
            <template #default="scope">
              <el-input-number v-if="scope.row.__editing"
                               :step="0.01"
                               :min="0"
                               :precision="2"
                               style="width: 100%"
                               v-model="scope.row.taxInclusiveUnitPrice"
                               placeholder="请输入"
                               clearable
                               @change="() => handleInlineUnitPriceChange(scope.row)"
                               @input="() => handleInlineUnitPriceChange(scope.row)" />
              <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice) }}</span>
            </template>
          </el-table-column>
          <el-table-column label="宽(mm)"
                           prop="width"
                           min-width="160">
@@ -680,23 +734,6 @@
              <span v-else>{{ scope.row.height ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="结算单片面积(㎡)"
                           prop="settlePieceArea"
                           min-width="200">
            <template #default="scope">
              <el-input-number v-if="scope.row.__editing"
                               controls-position="right"
                               v-model="scope.row.settlePieceArea"
                               :min="0"
                               :step="1"
                               :precision="4"
                               style="width: 100%"
                               placeholder="请输入"
                               clearable
                               @change="() => handleInlineSettleAreaChange(scope.row)" />
              <span v-else>{{ scope.row.settlePieceArea ? Number(scope.row.settlePieceArea).toFixed(4) : "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="数量"
                           prop="quantity"
                           min-width="150">
@@ -715,6 +752,23 @@
              <span v-else>{{ scope.row.quantity ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="结算单片面积(㎡)"
                           prop="settlePieceArea"
                           min-width="200">
            <template #default="scope">
              <el-input-number v-if="scope.row.__editing"
                               controls-position="right"
                               v-model="scope.row.settlePieceArea"
                               :min="0"
                               :step="1"
                               :precision="4"
                               style="width: 100%"
                               placeholder="请输入"
                               clearable
                               @change="() => handleInlineSettleAreaChange(scope.row)" />
              <span v-else>{{ scope.row.settlePieceArea ? Number(scope.row.settlePieceArea).toFixed(4) : "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="面积(m²)"
                           prop="actualTotalArea"
                           min-width="200">
@@ -728,23 +782,6 @@
                               style="width: 100%"
                               placeholder="自动计算" />
              <span v-else>{{ scope.row.actualTotalArea ? Number(scope.row.actualTotalArea).toFixed(4) : "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="含税单价(元)"
                           prop="taxInclusiveUnitPrice"
                           min-width="160">
            <template #default="scope">
              <el-input-number v-if="scope.row.__editing"
                               :step="0.01"
                               :min="0"
                               :precision="2"
                               style="width: 100%"
                               v-model="scope.row.taxInclusiveUnitPrice"
                               placeholder="请输入"
                               clearable
                               @change="() => handleInlineUnitPriceChange(scope.row)"
                               @input="() => handleInlineUnitPriceChange(scope.row)" />
              <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice) }}</span>
            </template>
          </el-table-column>
          <el-table-column label="税率(%)"
@@ -779,6 +816,19 @@
                           prop="taxExclusiveTotalPrice"
                           :formatter="formattedNumber"
                           min-width="120" />
          <el-table-column label="加工要求"
                           prop="processRequirement"
                           min-width="160"
                           show-overflow-tooltip>
            <template #default="scope">
              <el-input v-if="scope.row.__editing"
                        v-model="scope.row.processRequirement"
                        placeholder="请输入"
                        clearable
                        style="width: 100%" />
              <span v-else>{{ scope.row.processRequirement ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="发票类型"
                           prop="invoiceType"
                           min-width="120">
@@ -796,19 +846,6 @@
              <span v-else>{{ scope.row.invoiceType ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="加工要求"
                           prop="processRequirement"
                           min-width="160"
                           show-overflow-tooltip>
            <template #default="scope">
              <el-input v-if="scope.row.__editing"
                        v-model="scope.row.processRequirement"
                        placeholder="请输入"
                        clearable
                        style="width: 100%" />
              <span v-else>{{ scope.row.processRequirement ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="备注"
                           prop="remark"
                           min-width="140"
@@ -820,19 +857,6 @@
                        clearable
                        style="width: 100%" />
              <span v-else>{{ scope.row.remark ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="楼层编号"
                           prop="floorCode"
                           min-width="250"
                           show-overflow-tooltip>
            <template #default="scope">
              <el-input v-if="scope.row.__editing"
                        v-model="scope.row.floorCode"
                        placeholder="请输入"
                        clearable
                        style="width: 100%" />
              <span v-else>{{ scope.row.floorCode ?? "" }}</span>
            </template>
          </el-table-column>
          <el-table-column label="重箱"
@@ -1675,6 +1699,46 @@
               title="选择入库产品"
               width="60%"
               :close-on-click-modal="false">
      <div style="margin-bottom: 12px;">
        <el-form>
          <el-form-item required>
            <template #label>
              <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
                <span>审批人选择:</span>
                <el-button type="primary"
                           size="small"
                           @click="addStockApproverNode"
                           icon="Plus">新增节点</el-button>
              </div>
            </template>
            <div class="approver-nodes-container">
              <div v-for="(node, index) in stockApproverNodes"
                   :key="node.id"
                   class="approver-node-item">
                <div class="approver-node-header">
                  <span class="approver-node-label">审批节点 {{ index + 1 }}</span>
                  <el-button v-if="stockApproverNodes.length > 1"
                             type="danger"
                             size="small"
                             text
                             @click="removeStockApproverNode(index)"
                             icon="Delete">删除</el-button>
                </div>
                <el-select v-model="node.userId"
                           placeholder="请选择审批人"
                           filterable
                           clearable
                           style="width: 100%;">
                  <el-option v-for="item in stockApproverOptions"
                             :key="item.userId"
                             :label="item.userName"
                             :value="item.userId" />
                </el-select>
              </div>
            </div>
          </el-form-item>
        </el-form>
      </div>
      <el-table :data="stockProductList"
                border
                stripe
@@ -1815,6 +1879,15 @@
  const selectedStockProductIds = ref([]);
  const stockLoading = ref(false);
  const currentStockLedgerId = ref(null);
  const stockApproverOptions = ref([]);
  const stockApproverNodes = ref([{ id: 1, userId: null }]);
  let nextStockApproverId = 2;
  const addStockApproverNode = () => {
    stockApproverNodes.value.push({ id: nextStockApproverId++, userId: null });
  };
  const removeStockApproverNode = index => {
    stockApproverNodes.value.splice(index, 1);
  };
  const ledgerQrDialogVisible = ref(false);
  const ledgerQrCompositeUrl = ref("");
@@ -1936,7 +2009,7 @@
      entryDate: null, // 录入日期
      entryDateStart: undefined,
      entryDateEnd: undefined,
      deliveryStatus: undefined, // 发货状态:1未发货 2审批中 3审批失败 4已发货
      deliveryStatus: undefined, // 发货状态:1未发货 2审批中 3审批不通过 4审批通过 5已发货 6部分发货
      stockStatus: undefined, // 入库状态:0未入库 1部分入库 2已入库
    },
    form: {
@@ -2149,10 +2222,17 @@
      row.thickness !== null && row.thickness !== undefined && row.thickness !== ""
        ? Number(row.thickness)
        : null;
    copied.floorCode = row?.floorCode ?? row?.floor_code ?? "";
    // 复制新建仅带出产品大类与规格型号,其他数字字段全部留空,避免出现 0.00
    const srcUnit = row?.taxInclusiveUnitPrice;
    const unitNum =
      srcUnit !== null && srcUnit !== undefined && srcUnit !== ""
        ? Number(srcUnit)
        : NaN;
    copied.taxInclusiveUnitPrice = Number.isFinite(unitNum) ? unitNum : null;
    // 复制新建带出:产品大类、规格型号、厚度、楼层编号、单价;其余数量/面积/总价等留空,避免出现 0.00
    copied.quantity = null;
    copied.taxInclusiveUnitPrice = null;
    copied.taxInclusiveTotalPrice = null;
    copied.taxExclusiveTotalPrice = null;
    copied.width = null;
@@ -2841,7 +2921,19 @@
      proxy.$modal.msgError("导入失败,请重试");
    },
  });
  const HISTORY_IMPORT_URL_MAP = {
    notShipped: "/sales/ledger/salesHistory/notShippingImport",
    shipped: "/sales/ledger/salesHistory/shippingImport",
  };
  const HISTORY_IMPORT_TEMPLATE_URL_MAP = {
    notShipped: "/sales/ledger/salesHistory/notShippingImportTemplate",
    shipped: "/sales/ledger/salesHistory/shippingImportTemplate",
  };
  const HISTORY_IMPORT_TEMPLATE_FILE_NAME_MAP = {
    notShipped: "销售发货历史数据导入模板-未发货.xlsx",
    shipped: "销售发货历史数据导入模板-已发货.xlsx",
  };
  const currentImportCommand = ref("default");
  const changeDaterange = value => {
    if (value) {
      searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
@@ -2957,22 +3049,36 @@
    currentStockLedgerId.value = id;
    selectedStockProductIds.value = [];
    stockProductList.value = [];
    stockApproverNodes.value = [{ id: 1, userId: null }];
    nextStockApproverId = 2;
    stockDialogVisible.value = true;
    stockLoading.value = true;
    try {
      const approverRes = await approveUserList({ approveType: 9 });
      stockApproverOptions.value = Array.isArray(approverRes?.data)
        ? approverRes.data.map(item => ({
            userId: item.userId,
            userName: item.userName,
          }))
        : [];
      const res = await productList({ salesLedgerId: id, type: 1 });
      stockProductList.value = [];
      stockProductList.value =
        res.data.filter(item => item.productStockStatus == 0 || item.productStockStatus == 1) || [];
    } catch (e) {
      proxy?.$modal?.msgError?.("获取产品列表失败");
      proxy?.$modal?.msgError?.("获取产品或审批人失败");
    } finally {
      stockLoading.value = false;
    }
  };
  const submitStock = async () => {
    const hasEmptyApprover = stockApproverNodes.value.some(node => !node.userId);
    if (hasEmptyApprover) {
      ElMessage.warning("请为所有审批节点选择审批人");
      return;
    }
    if (selectedStockProductIds.value.length === 0) {
      ElMessage.warning("请选择至少一个产品进行入库");
      return;
@@ -2990,9 +3096,16 @@
    proxy?.$modal?.loading?.("正在入库,请稍候...");
    try {
      const approveUserIds = stockApproverNodes.value.map(node => node.userId).join(",");
      const approveUserName = stockApproverNodes.value
        .map(node => stockApproverOptions.value.find(item => String(item.userId) === String(node.userId))?.userName)
        .filter(Boolean)
        .join(",");
      await salesStock({
        salesLedgerId: currentStockLedgerId.value,
        salesLedgerProducts: selectedStockProductIds.value,
        approveUserIds,
        approveUserName,
      });
      proxy?.$modal?.msgSuccess?.("入库成功");
      stockDialogVisible.value = false;
@@ -3776,17 +3889,40 @@
    otherAmountAddDialogVisible.value = false;
    otherAmountAddId.value = null;
  };
  // 导入
  const handleImport = () => {
    importUpload.title = "导入销售台账";
  const openImportDialog = (title, url) => {
    importUpload.title = title;
    importUpload.url = import.meta.env.VITE_APP_BASE_API + url;
    importUpload.open = true;
    importUpload.isUploading = false;
    if (importUploadRef.value) {
      importUploadRef.value.clearFiles();
    }
  };
  // 导入
  const handleImport = () => {
    currentImportCommand.value = "default";
    openImportDialog("导入销售台账", "/sales/ledger/import");
  };
  // 历史迁移
  const handleHistoryImportCommand = command => {
    const url = HISTORY_IMPORT_URL_MAP[command];
    if (!url) return;
    currentImportCommand.value = command;
    const title = command === "shipped" ? "历史迁移-已出库" : "历史迁移-未出库";
    openImportDialog(title, url);
  };
  // 下载导入模板
  const downloadTemplate = () => {
    const command = currentImportCommand.value;
    if (command && command !== "default") {
      const templateUrl = HISTORY_IMPORT_TEMPLATE_URL_MAP[command];
      const fileName = HISTORY_IMPORT_TEMPLATE_FILE_NAME_MAP[command];
      if (templateUrl) {
        proxy.download(templateUrl, {}, fileName || "销售发货历史数据导入模板.xlsx");
        return;
      }
    }
    proxy.download("/sales/ledger/exportTemplate", {}, "销售台账导入模板.xlsx");
  };
  const onClose = () => {
@@ -4381,15 +4517,15 @@
      return false;
    }
    // 如果后端返回了台账级发货状态(deliveryStatus)
    // 1=已发货,则禁止再次发货
    // 台账级发货状态(deliveryStatus):2审批中、5已发货 时不可再发起本行发货;6部分发货仍可按明细继续发
    const deliveryStatus = row.deliveryStatus;
    if (
      deliveryStatus !== null &&
      deliveryStatus !== undefined &&
      String(deliveryStatus).trim() !== ""
    ) {
      if (Number(deliveryStatus) === 1) return false;
      const ds = Number(deliveryStatus);
      if (ds === 2 || ds === 5) return false;
    }
    // 获取发货状态
@@ -4445,12 +4581,16 @@
      return;
    }
    // 只允许【未发货/审批失败】进入发货流程
    // 允许:1未发货、3审批不通过、4审批通过、6部分发货;不允许:2审批中、5已发货
    const statusItem = selectedRows.value[0].deliveryStatus;
    const ledgerAllowsDelivery = s =>
      [1, 3, 4, 6].includes(Number(s));
    let isTrue = true;
    selectedRows.value.forEach(row => {
      if (row.deliveryStatus != 1 && row.deliveryStatus != 3) {
        proxy.$modal.msgWarning("仅未发货或审批失败的台账可以发货");
      if (!ledgerAllowsDelivery(row.deliveryStatus)) {
        proxy.$modal.msgWarning(
          "仅未发货、审批不通过、审批通过或部分发货的台账可以发货"
        );
        isTrue = false;
        return;
      }
@@ -4487,9 +4627,9 @@
      return;
    }
    // 已发货台账:弹窗提醒,不能再次发货(4 视为已发货)
    // 已全部发货(5)的台账:弹窗提醒,不能再次发货
    const shippedLedgers = selectedRows.value.filter(
      r => Number(r.deliveryStatus) === 4
      r => Number(r.deliveryStatus) === 5
    );
    if (shippedLedgers.length === selectedRows.value.length) {
      try {
@@ -4546,9 +4686,9 @@
    try {
      const targets = [];
      for (const ledger of selectedRows.value) {
        //如果已经是“审批中(2)”或“已发货(4)”,则跳过,不允许重复操作
        // 审批中(2)、已全部发货(5) 跳过;部分发货(6) 等仍收集可发明细
        const status = Number(ledger.deliveryStatus);
        if (status === 2 || status === 4) {
        if (status === 2 || status === 5) {
          console.warn(
            `台账编号 ${ledger.salesContractNo} 状态为 ${status},跳过发货`
          );
@@ -4604,10 +4744,11 @@
  // 打开发货弹框(单条)
  const openDeliveryForm = async row => {
    // 只允许【未发货/审批失败】发货;已发货/审批中不允许
    const status = Number(row.deliveryStatus);
    if (status !== 1 && status !== 3) {
      proxy.$modal.msgWarning("只有发货状态为未发货或审批失败的记录才可以发货");
    if (![1, 3, 4, 6].includes(status)) {
      proxy.$modal.msgWarning(
        "只有发货状态为未发货、审批不通过、审批通过或部分发货的记录才可以发货"
      );
      return;
    }
@@ -4670,6 +4811,7 @@
        const run = async () => {
          for (const salesLedgerId of uniqueLedgerIds) {
            await addShippingInfo({
              scanOutbound:  false,
              salesLedgerId,
              type: deliveryForm.value.type,
              approveUserIds,
@@ -4793,4 +4935,54 @@
  .ledger-qr-save-btn {
    margin-bottom: 12px;
  }
  .approver-nodes-container {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    padding: 16px;
    background-color: #f8f9fa;
    border-radius: 4px;
    border: 1px solid #e4e7ed;
  }
  .approver-node-item {
    flex: 0 0 calc(33.333% - 12px);
    min-width: 200px;
    padding: 12px;
    background-color: #fff;
    border-radius: 4px;
    border: 1px solid #dcdfe6;
    transition: all 0.3s;
  }
  .approver-node-item:hover {
    border-color: #409eff;
    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
  }
  .approver-node-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 8px;
  }
  .approver-node-label {
    font-size: 13px;
    font-weight: 500;
    color: #606266;
  }
  @media (max-width: 1200px) {
    .approver-node-item {
      flex: 0 0 calc(50% - 8px);
    }
  }
  @media (max-width: 768px) {
    .approver-node-item {
      flex: 0 0 100%;
    }
  }
</style>