From cd1f2d9605c2e208bb7e70f5d3bc83b45a9ec8a6 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 18 六月 2026 15:16:53 +0800
Subject: [PATCH] feat: 产品规格带出产品大类
---
src/views/salesManagement/salesLedger/index.vue | 3374 ++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 2,882 insertions(+), 492 deletions(-)
diff --git a/src/views/salesManagement/salesLedger/index.vue b/src/views/salesManagement/salesLedger/index.vue
index 9455101..303271b 100644
--- a/src/views/salesManagement/salesLedger/index.vue
+++ b/src/views/salesManagement/salesLedger/index.vue
@@ -1,5 +1,721 @@
<template>
<div class="app-container">
+ <template v-if="isFormPageMode">
+ <div class="sales-ledger-page-header">
+ <div class="sales-ledger-page-header-content">
+ <div class="sales-ledger-page-title">{{ pageFormTitle }}</div>
+ <div class="sales-ledger-page-subtitle">{{ pageFormSubtitle }}</div>
+ </div>
+ <el-button class="sales-ledger-page-back"
+ @click="exitFormPage()">杩斿洖鍙拌处</el-button>
+ </div>
+ <div class="sales-ledger-page-form">
+ <el-form :model="form"
+ label-width="140px"
+ label-position="top"
+ :rules="rules"
+ @keydown.capture="handleTabScrollFollow"
+ ref="formRef">
+ <el-row v-if="operationType === 'add'"
+ style="margin-bottom: 10px;">
+ <el-col :span="24"
+ style="text-align: right;">
+ <el-button type="primary"
+ plain
+ @click="openQuotationDialog">
+ 浠庨攢鍞姤浠峰鍏�
+ </el-button>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="閿�鍞悎鍚屽彿锛�"
+ prop="salesContractNo">
+ <el-input v-model="form.salesContractNo"
+ placeholder="鑷姩鐢熸垚"
+ clearable
+ disabled />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="涓氬姟鍛橈細"
+ prop="salesman">
+ <el-select v-model="form.salesman"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="isReviewedEdit">
+ <el-option v-for="item in userList"
+ :key="item.nickName"
+ :label="item.nickName"
+ :value="item.nickName" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="瀹㈡埛鍚嶇О锛�"
+ prop="customerId">
+ <el-select v-model="form.customerId"
+ filterable
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="isReviewedEdit">
+ <el-option v-for="item in customerOption"
+ :key="item.id"
+ :label="item.customerName"
+ :value="item.id">
+ {{ item.customerName + (item.taxpayerIdentificationNumber ? "鈥斺��" + item.taxpayerIdentificationNumber : "") }}
+ </el-option>
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="椤圭洰鍚嶇О锛�"
+ prop="projectName">
+ <el-input v-model="form.projectName"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :disabled="isReviewedEdit" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="绛捐鏃ユ湡锛�"
+ prop="executionDate">
+ <el-date-picker style="width: 100%"
+ v-model="form.executionDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="isReviewedEdit" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="浜よ揣鏃ユ湡锛�"
+ prop="deliveryDate">
+ <el-date-picker style="width: 100%"
+ v-model="form.deliveryDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="isReviewedEdit" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="12">
+ <el-form-item label="褰曞叆浜猴細"
+ prop="entryPerson">
+ <el-select v-model="form.entryPerson"
+ filterable
+ default-first-option
+ :reserve-keyword="false"
+ placeholder="璇烽�夋嫨"
+ clearable
+ @change="changs"
+ :disabled="isReviewedEdit">
+ <el-option v-for="item in userList"
+ :key="item.userId"
+ :label="item.nickName"
+ :value="item.userId" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="褰曞叆鏃ユ湡锛�"
+ prop="entryDate">
+ <el-date-picker style="width: 100%"
+ v-model="form.entryDate"
+ value-format="YYYY-MM-DD"
+ format="YYYY-MM-DD"
+ type="date"
+ placeholder="璇烽�夋嫨"
+ clearable
+ :disabled="isReviewedEdit" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row>
+ <el-form-item label="浜у搧淇℃伅锛�"
+ prop="entryDate">
+ <el-button plain
+ type="danger"
+ :disabled="isReviewedEdit"
+ @click="deleteProduct">鍒犻櫎</el-button>
+ <el-button plain
+ type="primary"
+ :disabled="isReviewedEdit"
+ @click="pinSelectedProductRow">鍥哄畾</el-button>
+ </el-form-item>
+ </el-row>
+ <el-table :data="productData"
+ border
+ class="compact-product-table"
+ @selection-change="productSelected"
+ show-summary
+ :summary-method="summarizeProductTable">
+ <el-table-column align="center"
+ type="selection"
+ width="55"
+ :selectable="(row) => !isProductShipped(row)" />
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column label="浜у搧澶х被"
+ prop="productCategory"
+ min-width="120">
+ <template #default="scope">
+ <el-tree-select v-if="scope.row.__editing"
+ v-model="scope.row.__productCategoryId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ filterable
+ check-strictly
+ :data="productOptions"
+ :render-after-expand="false"
+ style="width: 100%"
+ :filter-node-method="filterProductCategoryNode"
+ @change="(val) => handleInlineProductCategoryChange(scope.row, val)"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.productCategory ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="瑙勬牸鍨嬪彿"
+ prop="specificationModel"
+ min-width="140">
+ <template #default="scope">
+ <el-select v-if="scope.row.__editing"
+ v-model="scope.row.productModelId"
+ placeholder="璇烽�夋嫨"
+ clearable
+ filterable
+ style="width: 100%"
+ @change="(val) => handleInlineProductModelChange(scope.row, val)"
+ :disabled="isReviewedEdit">
+ <el-option v-for="item in modelOptions"
+ :key="item.id"
+ :label="item.model"
+ :value="item.id" />
+ </el-select>
+ <span v-else>{{ scope.row.specificationModel ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍘氬害(mm)"
+ prop="thickness"
+ min-width="95">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.thickness"
+ :min="0"
+ :step="1"
+ :precision="2"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.thickness ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="妤煎眰缂栧彿"
+ prop="floorCode"
+ min-width="120"
+ show-overflow-tooltip>
+ <template #default="scope">
+ <el-input v-if="scope.row.__editing"
+ v-model="scope.row.floorCode"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.floorCode ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍚◣鍗曚环(鍏�)"
+ prop="taxInclusiveUnitPrice"
+ min-width="85">
+ <template #default="scope">
+ <el-input-number v-if="scope.row.__editing"
+ :controls="false"
+ :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="85">
+ <template #default="scope">
+ <el-input-number v-if="scope.row.__editing"
+ :controls="false"
+ v-model="scope.row.width"
+ :min="0"
+ :step="1"
+ :precision="2"
+ style="width:100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineSizeChange(scope.row)"
+ @input="() => handleInlineSizeChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.width ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="楂�(mm)"
+ prop="height"
+ min-width="85">
+ <template #default="scope">
+ <el-input-number v-if="scope.row.__editing"
+ :controls="false"
+ v-model="scope.row.height"
+ :min="0"
+ :step="1"
+ :precision="2"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineSizeChange(scope.row)"
+ @input="() => handleInlineSizeChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.height ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏁伴噺"
+ prop="quantity"
+ min-width="85">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.quantity"
+ :step="1"
+ :min="0"
+ :precision="0"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineQuantityChange(scope.row)"
+ @input="() => handleInlineQuantityChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.quantity ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="缁撶畻鍗曠墖闈㈢Н(銕�)"
+ prop="settlePieceArea"
+ min-width="120">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.settlePieceArea"
+ :min="0"
+ :step="1"
+ :precision="4"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineSettleAreaChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <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="110">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.actualTotalArea"
+ :min="0"
+ :step="1"
+ :precision="4"
+ style="width: 100%"
+ placeholder="鑷姩璁$畻"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.actualTotalArea ? Number(scope.row.actualTotalArea).toFixed(4) : "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="绋庣巼(%)"
+ prop="taxRate"
+ min-width="80">
+ <template #default="scope">
+ <el-select v-if="scope.row.__editing"
+ v-model="scope.row.taxRate"
+ placeholder="璇烽�夋嫨"
+ clearable
+ style="width: 100%"
+ @change="() => handleInlineTaxRateChange(scope.row)"
+ :disabled="isReviewedEdit">
+ <el-option label="1"
+ value="1" />
+ <el-option label="3"
+ value="3" />
+ <el-option label="6"
+ value="6" />
+ <el-option label="9"
+ value="9" />
+ <el-option label="13"
+ value="13" />
+ </el-select>
+ <span v-else>{{ scope.row.taxRate ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍚◣鎬讳环(鍏�)"
+ prop="taxInclusiveTotalPrice"
+ :formatter="formattedNumber"
+ min-width="100" />
+ <el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
+ prop="taxExclusiveTotalPrice"
+ :formatter="formattedNumber"
+ min-width="100" />
+ <el-table-column label="鍔犲伐瑕佹眰"
+ prop="processRequirement"
+ min-width="100"
+ show-overflow-tooltip>
+ <template #default="scope">
+ <el-input v-if="scope.row.__editing"
+ v-model="scope.row.processRequirement"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.processRequirement ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍙戠エ绫诲瀷"
+ prop="invoiceType"
+ min-width="90">
+ <template #default="scope">
+ <el-select v-if="scope.row.__editing"
+ v-model="scope.row.invoiceType"
+ placeholder="璇烽�夋嫨"
+ clearable
+ style="width: 100%"
+ :disabled="isReviewedEdit">
+ <el-option label="澧炴櫘绁�"
+ value="澧炴櫘绁�" />
+ <el-option label="澧炰笓绁�"
+ value="澧炰笓绁�" />
+ </el-select>
+ <span v-else>{{ scope.row.invoiceType ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="澶囨敞"
+ prop="remark"
+ min-width="100"
+ show-overflow-tooltip>
+ <template #default="scope">
+ <el-input v-if="scope.row.__editing"
+ v-model="scope.row.remark"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.remark ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="閲嶇"
+ prop="heavyBox"
+ min-width="80">
+ <template #default="scope">
+ <el-input v-if="scope.row.__editing"
+ v-model="scope.row.heavyBox"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.heavyBox ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column fixed="right"
+ label="鎿嶄綔"
+ min-width="150"
+ align="center">
+ <template #default="scope">
+ <template v-if="scope.row.__editing">
+ <el-button link
+ type="primary"
+ size="small"
+ @click="saveProductInline(scope.row, scope.$index)">淇濆瓨</el-button>
+ <el-button link
+ type="danger"
+ size="small"
+ @click="cancelProductInline(scope.row, scope.$index)">鍙栨秷</el-button>
+ <el-popover :width="560"
+ trigger="click"
+ :hide-after="0"
+ :visible="scope.row.__otherAmountPopoverVisible"
+ @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)">
+ <template #reference>
+ <el-button link
+ type="primary"
+ size="small"
+ @click="openOtherAmountInline(scope.row)">
+ 棰濆鍔犲伐({{ (scope.row.salesProductProcessList || []).length || 0 }})
+ </el-button>
+ </template>
+ <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;">
+ <div style="font-weight: 600; color:#303133;">
+ 棰濆鍔犲伐
+ </div>
+ <el-button type="primary"
+ plain
+ size="small"
+ :disabled="isReviewedEdit"
+ @click="startAddOtherAmountForRow(scope.row)">
+ 鏂板
+ </el-button>
+ </div>
+ <div v-if="scope.row.__inlineOtherAmountAdding"
+ style="display:flex; flex-direction:column; gap: 8px; margin-bottom: 10px;"
+ @click.stop>
+ <el-select v-model="scope.row.__inlineOtherAmountAddId"
+ filterable
+ clearable
+ placeholder="璇烽�夋嫨棰濆鍔犲伐椤圭洰"
+ style="width: 100%;">
+ <el-option v-for="item in otherAmountSelectOptions"
+ :key="item.id"
+ :label="item.processName"
+ :value="item.id" />
+ </el-select>
+ <div style="display:flex; justify-content:flex-end; gap: 8px;">
+ <el-button size="small"
+ @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null">
+ 鍙栨秷
+ </el-button>
+ <el-button type="primary"
+ size="small"
+ :disabled="isReviewedEdit || scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''"
+ @click="confirmAddOtherAmountForRow(scope.row)">
+ 纭娣诲姞
+ </el-button>
+ </div>
+ </div>
+ <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0"
+ style="display:flex; flex-wrap:wrap; gap: 8px;">
+ <div v-for="(item, idx) in scope.row.salesProductProcessList"
+ :key="String(item.id) + '_' + idx"
+ style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);">
+ <el-tag type="info"
+ style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
+ {{ item.processName }}
+ </el-tag>
+ <el-input-number :controls="false" v-model="item.quantity"
+ :min="0"
+ :step="1"
+ :precision="0"
+ style="width: 120px;"
+ placeholder="鏁伴噺"
+ :disabled="isReviewedEdit"
+ @change="handleOtherAmountQuantityChange(scope.row)" />
+ <el-button type="danger"
+ link
+ size="small"
+ :disabled="isReviewedEdit"
+ @click="removeOtherAmountAtForRow(scope.row, idx)">
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <div v-else
+ style="color:#909399; font-size: 13px;">
+ 鏆傛棤棰濆鍔犲伐
+ </div>
+ </el-popover>
+ </template>
+ <template v-else>
+ <el-button v-if="isReviewedEdit"
+ link
+ type="primary"
+ size="small"
+ :disabled="isProductShipped(scope.row)"
+ @click="editPriceOnly(scope.row)">
+ 淇敼鍗曚环
+ </el-button>
+ <template v-if="!isReviewedEdit">
+ <el-button link
+ type="primary"
+ size="small"
+ :disabled="isProductShipped(scope.row)"
+ @click="editProductInline(scope.row, scope.$index)">
+ 缂栬緫
+ </el-button>
+ <el-button link
+ type="primary"
+ size="small"
+ :disabled="isProductShipped(scope.row)"
+ @click="copyProductInline(scope.row, scope.$index)">
+ 澶嶅埗
+ </el-button>
+ </template>
+ <el-popover :width="560"
+ trigger="click"
+ :hide-after="0"
+ :visible="scope.row.__otherAmountPopoverVisible"
+ @update:visible="(val) => handleOtherAmountPopoverVisibleChange(scope.row, val)">
+ <template #reference>
+ <el-button link
+ type="primary"
+ size="small"
+ :disabled="isProductShipped(scope.row)"
+ @click="openOtherAmountInline(scope.row)">
+ 棰濆鍔犲伐({{ (scope.row.salesProductProcessList || []).length || 0 }})
+ </el-button>
+ </template>
+ <div style="display:flex; align-items:center; justify-content:space-between; gap: 10px; margin-bottom: 8px;">
+ <div style="font-weight: 600; color:#303133;">
+ 棰濆鍔犲伐
+ </div>
+ <el-button type="primary"
+ plain
+ size="small"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit"
+ @click="startAddOtherAmountForRow(scope.row)">
+ 鏂板
+ </el-button>
+ </div>
+ <div v-if="scope.row.__inlineOtherAmountAdding"
+ style="display:flex; flex-direction:column; gap: 8px; margin-bottom: 10px;"
+ @click.stop>
+ <el-select v-model="scope.row.__inlineOtherAmountAddId"
+ filterable
+ clearable
+ placeholder="璇烽�夋嫨棰濆鍔犲伐椤圭洰"
+ style="width: 100%;"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit">
+ <el-option v-for="item in otherAmountSelectOptions"
+ :key="item.id"
+ :label="item.processName"
+ :value="item.id" />
+ </el-select>
+ <div style="display:flex; justify-content:flex-end; gap: 8px;">
+ <el-button size="small"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit"
+ @click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null">
+ 鍙栨秷
+ </el-button>
+ <el-button type="primary"
+ size="small"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit || scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''"
+ @click="confirmAddOtherAmountForRow(scope.row)">
+ 纭娣诲姞
+ </el-button>
+ </div>
+ </div>
+ <div v-if="Array.isArray(scope.row.salesProductProcessList) && scope.row.salesProductProcessList.length > 0"
+ style="display:flex; flex-wrap:wrap; gap: 8px;">
+ <div v-for="(item, idx) in scope.row.salesProductProcessList"
+ :key="String(item.id) + '_' + idx"
+ style="display:flex; align-items:center; gap: 8px; flex: 0 0 calc(50% - 4px); max-width: calc(50% - 4px);">
+ <el-tag type="info"
+ style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
+ {{ item.processName }}
+ </el-tag>
+ <el-input-number :controls="false" v-model="item.quantity"
+ :min="0"
+ :step="1"
+ :precision="0"
+ style="width: 120px;"
+ placeholder="鏁伴噺"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit"
+ @change="handleOtherAmountQuantityChange(scope.row)" />
+ <el-button type="danger"
+ link
+ size="small"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit"
+ @click="removeOtherAmountAtForRow(scope.row, idx)">
+ 鍒犻櫎
+ </el-button>
+ </div>
+ </div>
+ <div v-else
+ style="color:#909399; font-size: 13px;">
+ 鏆傛棤棰濆鍔犲伐
+ </div>
+ </el-popover>
+ </template>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="澶囨敞锛�"
+ prop="remarks">
+ <el-input v-model="form.remarks"
+ placeholder="璇疯緭鍏�"
+ clearable
+ type="textarea"
+ :rows="2"
+ :disabled="isReviewedEdit" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="瀹㈡埛澶囨敞锛�"
+ prop="customerRemarks">
+ <el-input v-model="form.customerRemarks"
+ placeholder="璇疯緭鍏�"
+ clearable
+ type="textarea"
+ :rows="2"
+ :disabled="isReviewedEdit" />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="30">
+ <el-col :span="24">
+ <el-form-item label="闄勪欢鏉愭枡锛�"
+ prop="salesLedgerFiles">
+ <el-upload v-model:file-list="fileList"
+ :action="upload.url"
+ multiple
+ ref="fileUpload"
+ auto-upload
+ :headers="upload.headers"
+ :before-upload="handleBeforeUpload"
+ :on-error="handleUploadError"
+ :on-success="handleUploadSuccess"
+ :on-remove="handleRemove">
+ <el-button type="primary">涓婁紶</el-button>
+ <template #tip>
+ <div class="el-upload__tip">
+ 鏂囦欢鏍煎紡鏀寔
+ doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
+ </div>
+ </template>
+ </el-upload>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <div class="sales-ledger-page-actions">
+ <el-button type="primary" @click="submitForm">鎻愪氦</el-button>
+ </div>
+ </el-form>
+ </div>
+ </template>
+ <template v-else>
+ <div class="sales-ledger-status-bar">
+ <button v-for="tab in salesLedgerStatusTabs"
+ :key="tab.key"
+ type="button"
+ class="sales-ledger-status-tab"
+ :class="{ 'is-active': activeStatusTab === tab.key }"
+ @click="handleStatusTabChange(tab.key)">
+ {{ tab.label }}
+ </button>
+ </div>
+ <ReverseAuditHistory v-if="activeStatusTab === 'reverseReviewed'" />
+ <div v-else>
<div class="search_form">
<el-form :model="searchForm"
:inline="true">
@@ -32,6 +748,20 @@
prefix-icon="Search"
@change="handleQuery" />
</el-form-item>
+ <el-form-item label="浜у搧瀹�(mm)锛�">
+ <el-input v-model="searchForm.width"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
+ <el-form-item label="浜у搧楂�(mm)锛�">
+ <el-input v-model="searchForm.height"
+ placeholder="璇疯緭鍏�"
+ clearable
+ prefix-icon="Search"
+ @change="handleQuery" />
+ </el-form-item>
<el-form-item label="褰曞叆鏃ユ湡锛�">
<el-date-picker v-model="searchForm.entryDate"
value-format="YYYY-MM-DD"
@@ -50,10 +780,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="鍏ュ簱鐘舵�侊細">
@@ -63,7 +797,20 @@
style="width: 140px">
<el-option label="鏈叆搴�"
:value="0" />
+ <el-option label="閮ㄥ垎鍏ュ簱"
+ :value="1" />
<el-option label="宸插叆搴�"
+ :value="2" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璁㈠崟鐘舵�侊細">
+ <el-select v-model="searchForm.orderStatus"
+ placeholder="璇烽�夋嫨"
+ clearable
+ style="width: 140px">
+ <el-option label="杩涜涓�"
+ :value="0" />
+ <el-option label="宸插畬鎴�"
:value="1" />
</el-select>
</el-form-item>
@@ -74,218 +821,173 @@
</el-form>
</div>
<div class="table_list">
- <div class="actions">
- <div>
+ <div class="actions sales-ledger-toolbar">
+ <div class="sales-ledger-toolbar-group">
<OtherAmountMaintenanceButton />
<ProcessFlowMaintenanceButton />
</div>
<ProcessFlowConfigSelectDialog v-model:visible="processFlowSelectDialogVisible"
:default-route-id="processFlowSelectDefaultRouteId"
+ :default-record-list="processFlowSelectDefaultRecordList"
:bound-route-name="processFlowSelectBoundRouteName"
@confirm="handleProcessFlowSelectConfirm" />
- <el-space wrap>
- <el-button type="primary"
- @click="handleSalesStock">鍏ュ簱</el-button>
- <el-button type="primary"
- @click="openForm('add')">鏂板鍙拌处</el-button>
- <el-button type="primary"
- @click="handleBulkDelivery">鍙戣揣</el-button>
- <el-button type="primary"
- plain
- @click="handleImport">瀵煎叆</el-button>
- <el-button @click="handleOut">瀵煎嚭</el-button>
- <el-button type="danger"
- plain
- @click="handleDelete">鍒犻櫎</el-button>
- <el-dropdown @command="handlePrintCommand">
+ <div class="sales-ledger-toolbar-actions">
+ <el-space v-if="activeStatusTab === 'pendingReview'"
+ wrap
+ class="sales-ledger-toolbar-group">
<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="finishedProcessCard">鐢熶骇娴佺▼鍗★紙鎴愬搧锛�</el-dropdown-item>
- <el-dropdown-item command="salesOrder">閿�鍞鍗�</el-dropdown-item>
- <el-dropdown-item command="salesDeliveryNote">閿�鍞彂璐у崟</el-dropdown-item>
- </el-dropdown-menu>
- </template>
- </el-dropdown>
- <el-button type="primary"
- plain
- @click="handlePrintLabel">鎵撳嵃鏍囩</el-button>
- </el-space>
+ @click="handleAudit"
+ :disabled="isBatchButtonDisabled('audit')">瀹℃牳</el-button>
+ <el-button type="danger"
+ plain
+ @click="handleDelete"
+ :disabled="isBatchButtonDisabled('delete')">鍒犻櫎</el-button>
+ <el-button type="primary"
+ @click="openForm('add')">鏂板鍙拌处</el-button>
+ <el-button type="primary"
+ plain
+ @click="handleImport">瀵煎叆</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </el-space>
+ <el-space v-else-if="activeStatusTab === 'reviewed'"
+ wrap
+ class="sales-ledger-toolbar-group">
+ <el-button type="primary"
+ @click="handleReverseAudit"
+ :disabled="isBatchButtonDisabled('reverseAudit')">鍙嶅</el-button>
+ <el-button type="warning"
+ @click="handleMarkCompleted"
+ :disabled="isBatchButtonDisabled('markCompleted')">鏍囪瀹屾垚</el-button>
+ <el-button type="primary"
+ @click="handleSalesStock"
+ :disabled="isBatchButtonDisabled('stock')">鍏ュ簱</el-button>
+ <el-button type="primary"
+ @click="handleBulkDelivery"
+ :disabled="isBatchButtonDisabled('delivery')">鍙戣揣</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-date-picker v-model="processRouteExportDateRange"
+ type="datetimerange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫椂闂�"
+ end-placeholder="缁撴潫鏃堕棿"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ format="YYYY-MM-DD HH:mm:ss"
+ clearable
+ style="width: 340px;" />
+ <el-button @click="handleExportProcessRoute"
+ :disabled="isBatchButtonDisabled('export')">瀵煎嚭宸ヨ壓璺嚎</el-button>
+ </el-space>
+ <el-space v-else-if="activeStatusTab === 'stocked'"
+ wrap
+ class="sales-ledger-toolbar-group">
+ <el-button type="primary"
+ @click="handleReverseAudit"
+ :disabled="isBatchButtonDisabled('reverseAudit')">鍙嶅</el-button>
+ <el-button type="warning"
+ @click="handleMarkCompleted"
+ :disabled="isBatchButtonDisabled('markCompleted')">鏍囪瀹屾垚</el-button>
+ <el-button type="primary"
+ @click="handleBulkDelivery"
+ :disabled="isBatchButtonDisabled('delivery')">鍙戣揣</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </el-space>
+ <el-space v-else-if="activeStatusTab === 'delivered'"
+ wrap
+ class="sales-ledger-toolbar-group">
+ <el-button type="warning"
+ @click="handleMarkCompleted"
+ :disabled="isBatchButtonDisabled('markCompleted')">鏍囪瀹屾垚</el-button>
+ <el-button type="primary"
+ @click="handleReverseAudit"
+ :disabled="isBatchButtonDisabled('reverseAudit')">鍙嶅</el-button>
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </el-space>
+ <el-space v-else-if="activeStatusTab === 'completed'"
+ wrap
+ class="sales-ledger-toolbar-group">
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ <el-button type="primary"
+ plain
+ @click="handlePrintCommand('salesOrder')"
+ :disabled="isBatchButtonDisabled('print')">鎵撳嵃鍗曟嵁</el-button>
+ <el-button type="primary"
+ plain
+ @click="handlePrintLabel"
+ :disabled="isBatchButtonDisabled('print')">鎵撳嵃鏍囩</el-button>
+ </el-space>
+ <el-space v-else-if="activeStatusTab === 'all'"
+ wrap
+ class="sales-ledger-toolbar-group">
+ <el-button @click="handleOut">瀵煎嚭</el-button>
+ </el-space>
+ <el-space wrap class="sales-ledger-toolbar-group sales-ledger-toolbar-group--muted">
+ <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-space>
+ <el-space wrap class="sales-ledger-toolbar-group sales-ledger-toolbar-group--muted">
+ <el-dropdown @command="handlePrintCommand">
+ <el-button type="primary"
+ plain
+ :disabled="isBatchButtonDisabled('print')">
+ 鎵撳嵃鍗曟嵁<el-icon class="el-icon--right">
+ <ArrowDown />
+ </el-icon>
+ </el-button>
+ <template #dropdown>
+ <el-dropdown-menu>
+ <el-dropdown-item command="finishedProcessCard">鐢熶骇娴佺▼鍗★紙鎴愬搧锛�</el-dropdown-item>
+ <el-dropdown-item command="salesOrder">閿�鍞鍗�</el-dropdown-item>
+ <el-dropdown-item command="salesDeliveryNote">閿�鍞彂璐у崟</el-dropdown-item>
+ </el-dropdown-menu>
+ </template>
+ </el-dropdown>
+ <el-button type="primary"
+ plain
+ @click="handlePrintLabel"
+ :disabled="isBatchButtonDisabled('print')">鎵撳嵃鏍囩</el-button>
+ </el-space>
+ </div>
</div>
<el-table :data="tableData"
border
v-loading="tableLoading"
@selection-change="handleSelectionChange"
- :expand-row-keys="expandedRowKeys"
:row-key="(row) => row.id"
:row-class-name="tableRowClassName"
show-summary
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"
fixed="left" />
- <el-table-column type="expand"
- width="60"
- fixed="left">
- <template #default="props">
- <el-table :data="props.row.children"
- border
- show-summary
- :summary-method="summarizeChildrenTable">
- <el-table-column align="center"
- label="搴忓彿"
- type="index" />
- <el-table-column label="妤煎眰缂栧彿"
- prop="floorCode"
- min-width="100"
- show-overflow-tooltip />
- <el-table-column label="浜у搧澶х被"
- prop="productCategory" />
- <el-table-column label="瑙勬牸鍨嬪彿"
- prop="specificationModel" />
- <el-table-column label="鍘氬害"
- prop="thickness"
- min-width="90">
- <template #default="scope">
- {{ scope.row.thickness ?? "" }}
- </template>
- </el-table-column>
- <el-table-column label="瀹�(mm)"
- prop="width"
- min-width="80">
- <template #default="scope">
- {{ scope.row.width ?? "" }}
- </template>
- </el-table-column>
- <el-table-column label="楂�(mm)"
- prop="height"
- min-width="80">
- <template #default="scope">
- {{ scope.row.height ?? "" }}
- </template>
- </el-table-column>
- <el-table-column label="鍛ㄩ暱(cm)"
- prop="perimeter"
- min-width="90">
- <template #default="scope">
- {{ scope.row.perimeter ?? "" }}
- </template>
- </el-table-column>
- <el-table-column label="鎬婚潰绉�(m虏)"
- prop="actualTotalArea"
- min-width="100">
- <template #default="scope">
- {{ scope.row.actualTotalArea ?? "" }}
- </template>
- </el-table-column>
- <el-table-column label="鍔犲伐瑕佹眰"
- prop="processRequirement"
- min-width="120"
- show-overflow-tooltip />
- <el-table-column label="澶囨敞"
- prop="remark"
- min-width="120"
- show-overflow-tooltip />
- <el-table-column label="閲嶇"
- prop="heavyBox"
- min-width="80">
- <template #default="scope">
- {{ scope.row.heavyBox ?? "" }}
- </template>
- </el-table-column>
- <el-table-column label="浜у搧鐘舵��"
- width="100px"
- align="center">
- <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 === 0 && (scope.row.shippingDate || scope.row.shippingCarNumber)"
- type="success">宸插嚭搴�</el-tag>
- <el-tag v-else
- type="danger">涓嶈冻</el-tag>
- </template>
- </el-table-column>
- <!-- <el-table-column label="鍙戣揣鐘舵��" width="140" align="center">
- <template #default="scope">
- <el-tag :type="getShippingStatusType(scope.row)" size="small">
- {{ getShippingStatusText(scope.row) }}
- </el-tag>
- </template>
- </el-table-column> -->
- <el-table-column label="蹇�掑叕鍙�"
- prop="expressCompany"
- show-overflow-tooltip />
- <el-table-column label="蹇�掑崟鍙�"
- prop="expressNumber"
- show-overflow-tooltip />
- <el-table-column label="鍙戣揣杞︾墝"
- minWidth="100px"
- align="center">
- <template #default="scope">
- <div>
- <el-tag type="success"
- v-if="scope.row.shippingCarNumber">{{ scope.row.shippingCarNumber }}</el-tag>
- <el-tag v-else
- type="info">-</el-tag>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="鍙戣揣鏃ユ湡"
- minWidth="100px"
- align="center">
- <template #default="scope">
- <div>
- <div v-if="scope.row.shippingDate">{{ scope.row.shippingDate }}</div>
- <el-tag v-else
- type="info">-</el-tag>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="鏁伴噺"
- prop="quantity" />
- <el-table-column label="绋庣巼(%)"
- prop="taxRate" />
- <el-table-column label="鍚◣鍗曚环(鍏�)"
- prop="taxInclusiveUnitPrice"
- :formatter="formattedNumber" />
- <el-table-column label="鍚◣鎬讳环(鍏�)"
- prop="taxInclusiveTotalPrice"
- :formatter="formattedNumber" />
- <el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
- prop="taxExclusiveTotalPrice"
- :formatter="formattedNumber" />
- <!--鎿嶄綔-->
- <!-- <el-table-column Width="60px" label="鎿嶄綔" align="center">
- <template #default="scope">
- <el-button
- link
- type="primary"
- :disabled="!canShip(scope.row)"
- @click="openDeliveryForm(scope.row)">
- 鍙戣揣
- </el-button>
- </template>
- </el-table-column> -->
- </el-table>
- </template>
- </el-table-column>
<el-table-column align="center"
label="搴忓彿"
type="index"
width="60" />
- <el-table-column label="閿�鍞悎鍚屽彿"
+ <el-table-column label="璁㈠崟鍙�"
prop="salesContractNo"
width="180"
- show-overflow-tooltip />
+ show-overflow-tooltip>
+ <template #default="scope">
+ <el-button link
+ type="primary"
+ @click="openForm('view', scope.row)">{{ scope.row.salesContractNo || "-" }}</el-button>
+ </template>
+ </el-table-column>
<el-table-column label="瀹㈡埛鍚嶇О"
prop="customerName"
width="300"
@@ -294,15 +996,19 @@
prop="salesman"
width="100"
show-overflow-tooltip />
- <el-table-column label="椤圭洰鍚嶇О"
- prop="projectName"
- width="180"
- show-overflow-tooltip />
<el-table-column label="鍚堝悓閲戦(鍏�)"
prop="contractAmount"
width="220"
show-overflow-tooltip
:formatter="formattedNumber" />
+ <el-table-column label="闈㈢Н"
+ prop="productTotalArea"
+ width="120"
+ show-overflow-tooltip />
+ <el-table-column label="鏁伴噺"
+ prop="productTotalQuantity"
+ width="120"
+ show-overflow-tooltip />
<el-table-column label="鍙戣揣鐘舵��"
width="140"
align="center">
@@ -317,8 +1023,22 @@
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>
+ </el-table-column>
+ <el-table-column label="瀹℃牳鐘舵��"
+ width="120"
+ align="center">
+ <template #default="scope">
+ <el-tag v-if="Number(scope.row.reviewStatus) === 0"
+ type="warning">寰呭鏍�</el-tag>
+ <el-tag v-else-if="Number(scope.row.reviewStatus) === 1"
+ type="success">宸插鏍�</el-tag>
+ <el-tag v-else
+ type="info">寰呭鏍�</el-tag>
</template>
</el-table-column>
<el-table-column label="鍏ュ簱鐘舵��"
@@ -328,9 +1048,37 @@
<el-tag v-if="Number(scope.row.stockStatus) === 0"
type="info">鏈叆搴�</el-tag>
<el-tag v-else-if="Number(scope.row.stockStatus) === 1"
+ 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>
+ </el-table-column>
+ <el-table-column label="璁㈠崟鐘舵��"
+ width="120"
+ align="center">
+ <template #default="scope">
+ <el-tag v-if="Number(scope.row.orderStatus) === 1"
+ type="success">宸插畬鎴�</el-tag>
+ <el-tag v-else
+ type="info">杩涜涓�</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏍囩鎵撳嵃"
+ width="90"
+ align="center">
+ <template #default="scope">
+ <span>{{ scope.row.labelPrintCount ?? 0 }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍗曟嵁鎵撳嵃"
+ width="90"
+ align="center">
+ <template #default="scope">
+ <span>{{ scope.row.documentPrintCount ?? 0 }}</span>
</template>
</el-table-column>
<el-table-column label="褰曞叆浜�"
@@ -339,10 +1087,6 @@
show-overflow-tooltip />
<el-table-column label="褰曞叆鏃ユ湡"
prop="entryDate"
- width="120"
- show-overflow-tooltip />
- <el-table-column label="绛捐鏃ユ湡"
- prop="executionDate"
width="120"
show-overflow-tooltip />
<el-table-column label="浜や粯鏃ユ湡"
@@ -359,20 +1103,24 @@
show-overflow-tooltip />
<el-table-column fixed="right"
label="鎿嶄綔"
- width="200"
+ width="280"
align="center">
<template #default="scope">
<el-button link
type="primary"
@click="openForm('edit', scope.row)"
- :disabled="!scope.row.isEdit">缂栬緫</el-button>
+ :disabled="Number(scope.row.orderStatus) === 1">缂栬緫</el-button>
<el-button link
type="primary"
@click="openProcessFlowSelect(scope.row)"
- :disabled="!scope.row.isEdit">宸ヨ壓璺嚎</el-button>
+ :disabled="Number(scope.row.reviewStatus) !== 1 || Number(scope.row.orderStatus) === 1">宸ヨ壓璺嚎</el-button>
<el-button link
type="primary"
@click="downLoadFile(scope.row)">闄勪欢</el-button>
+ <el-button link
+ type="primary"
+ @click="openLedgerQrDialog(scope.row)"
+ :disabled="Number(scope.row.orderStatus) === 1">浜岀淮鐮�</el-button>
</template>
</el-table-column>
</el-table>
@@ -383,10 +1131,13 @@
:limit="page.size"
@pagination="paginationChange" />
</div>
- <FormDialog v-model="dialogFormVisible"
- :title="operationType === 'add' ? '鏂板閿�鍞彴璐﹂〉闈�' : '缂栬緫閿�鍞彴璐﹂〉闈�'"
+ </div>
+ </template>
+ <FormDialog v-if="!isFormPageMode"
+ v-model="dialogFormVisible"
+ :title="isCompletedOrder ? '鏌ョ湅閿�鍞彴璐﹂〉闈紙宸插畬鎴愶級' : operationType === 'add' ? '鏂板閿�鍞彴璐﹂〉闈�' : operationType === 'view' ? '鏌ョ湅閿�鍞彴璐﹂〉闈�' : '缂栬緫閿�鍞彴璐﹂〉闈�'"
:width="'70%'"
- :operation-type="operationType"
+ :operation-type="isCompletedOrder || operationType === 'view' ? 'detail' : operationType"
@close="closeDia"
@confirm="submitForm"
@cancel="closeDia">
@@ -394,6 +1145,7 @@
label-width="140px"
label-position="top"
:rules="rules"
+ @keydown.capture="handleTabScrollFollow"
ref="formRef">
<!-- 鎶ヤ环鍗曞鍏ュ叆鍙o細鏀惧湪琛ㄥ崟椤堕儴锛岄�夋嫨鍚庡弽鏄惧鎴�/涓氬姟鍛樼瓑 -->
<el-row v-if="operationType === 'add'"
@@ -423,7 +1175,7 @@
<el-select v-model="form.salesman"
placeholder="璇烽�夋嫨"
clearable
- :disabled="operationType === 'view'">
+ :disabled="operationType === 'view' || isReviewedEdit">
<el-option v-for="item in userList"
:key="item.nickName"
:label="item.nickName"
@@ -440,14 +1192,12 @@
filterable
placeholder="璇烽�夋嫨"
clearable
- :disabled="operationType === 'view'">
+ :disabled="operationType === 'view' || isReviewedEdit">
<el-option v-for="item in customerOption"
:key="item.id"
:label="item.customerName"
:value="item.id">
- {{
- item.customerName + "鈥斺��" + item.taxpayerIdentificationNumber
- }}
+ {{ item.customerName + (item.taxpayerIdentificationNumber ? "鈥斺��" + item.taxpayerIdentificationNumber : "") }}
</el-option>
</el-select>
</el-form-item>
@@ -458,7 +1208,7 @@
<el-input v-model="form.projectName"
placeholder="璇疯緭鍏�"
clearable
- :disabled="operationType === 'view'" />
+ :disabled="operationType === 'view' || isReviewedEdit" />
</el-form-item>
</el-col>
</el-row>
@@ -473,7 +1223,7 @@
type="date"
placeholder="璇烽�夋嫨"
clearable
- :disabled="operationType === 'view'" />
+ :disabled="operationType === 'view' || isReviewedEdit" />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -485,7 +1235,8 @@
format="YYYY-MM-DD"
type="date"
placeholder="璇烽�夋嫨"
- clearable />
+ clearable
+ :disabled="operationType === 'view' || isReviewedEdit" />
</el-form-item>
</el-col>
</el-row>
@@ -499,7 +1250,8 @@
:reserve-keyword="false"
placeholder="璇烽�夋嫨"
clearable
- @change="changs">
+ @change="changs"
+ :disabled="operationType === 'view' || isReviewedEdit">
<el-option v-for="item in userList"
:key="item.userId"
:label="item.nickName"
@@ -516,7 +1268,8 @@
format="YYYY-MM-DD"
type="date"
placeholder="璇烽�夋嫨"
- clearable />
+ clearable
+ :disabled="operationType === 'view' || isReviewedEdit" />
</el-form-item>
</el-col>
</el-row>
@@ -524,22 +1277,23 @@
<el-form-item label="浜у搧淇℃伅锛�"
prop="entryDate">
<el-button v-if="operationType !== 'view'"
- type="primary"
- :disabled="hasEditingProductRow()"
- @click="addProductInline">
- 娣诲姞
- </el-button>
- <el-button v-if="operationType !== 'view'"
plain
type="danger"
+ :disabled="isReviewedEdit"
@click="deleteProduct">鍒犻櫎</el-button>
+ <el-button v-if="operationType !== 'view'"
+ plain
+ type="primary"
+ :disabled="isReviewedEdit"
+ @click="pinSelectedProductRow">鍥哄畾</el-button>
</el-form-item>
</el-row>
<el-table :data="productData"
border
+ class="compact-product-table"
@selection-change="productSelected"
show-summary
- :summary-method="summarizeMainTable">
+ :summary-method="summarizeProductTable">
<el-table-column align="center"
type="selection"
width="55"
@@ -551,7 +1305,7 @@
width="60" />
<el-table-column label="浜у搧澶х被"
prop="productCategory"
- min-width="160">
+ min-width="120">
<template #default="scope">
<el-tree-select v-if="scope.row.__editing"
v-model="scope.row.__productCategoryId"
@@ -563,13 +1317,14 @@
:render-after-expand="false"
style="width: 100%"
:filter-node-method="filterProductCategoryNode"
- @change="(val) => handleInlineProductCategoryChange(scope.row, val)" />
+ @change="(val) => handleInlineProductCategoryChange(scope.row, val)"
+ :disabled="isReviewedEdit" />
<span v-else>{{ scope.row.productCategory ?? "" }}</span>
</template>
</el-table-column>
<el-table-column label="瑙勬牸鍨嬪彿"
prop="specificationModel"
- min-width="160">
+ min-width="140">
<template #default="scope">
<el-select v-if="scope.row.__editing"
v-model="scope.row.productModelId"
@@ -577,7 +1332,8 @@
clearable
filterable
style="width: 100%"
- @change="(val) => handleInlineProductModelChange(scope.row, val)">
+ @change="(val) => handleInlineProductModelChange(scope.row, val)"
+ :disabled="isReviewedEdit">
<el-option v-for="item in modelOptions"
:key="item.id"
:label="item.model"
@@ -588,105 +1344,39 @@
</el-table-column>
<el-table-column label="鍘氬害(mm)"
prop="thickness"
- min-width="160">
+ min-width="95">
<template #default="scope">
- <el-input-number v-if="scope.row.__editing"
+ <el-input-number :controls="false" v-if="scope.row.__editing"
v-model="scope.row.thickness"
:min="0"
- :step="0.000000000000001"
- :precision="15"
+ :step="1"
+ :precision="2"
style="width: 100%"
placeholder="璇疯緭鍏�"
- clearable />
+ clearable
+ :disabled="isReviewedEdit" />
<span v-else>{{ scope.row.thickness ?? "" }}</span>
</template>
</el-table-column>
- <el-table-column label="瀹�(mm)"
- prop="width"
- min-width="160">
+ <el-table-column label="妤煎眰缂栧彿"
+ prop="floorCode"
+ min-width="120"
+ show-overflow-tooltip>
<template #default="scope">
- <el-input-number v-if="scope.row.__editing"
- v-model="scope.row.width"
- :min="0"
- :step="1"
- :precision="2"
- style="width:100%"
- placeholder="璇疯緭鍏�"
- clearable
- @change="() => handleInlineSizeChange(scope.row)"
- @input="() => handleInlineSizeChange(scope.row)" />
- <span v-else>{{ scope.row.width ?? "" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="楂�(mm)"
- prop="height"
- min-width="160">
- <template #default="scope">
- <el-input-number v-if="scope.row.__editing"
- v-model="scope.row.height"
- :min="0"
- :step="1"
- :precision="2"
- style="width: 100%"
- placeholder="璇疯緭鍏�"
- clearable
- @change="() => handleInlineSizeChange(scope.row)"
- @input="() => handleInlineSizeChange(scope.row)" />
- <span v-else>{{ scope.row.height ?? "" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="缁撶畻鍗曠墖闈㈢Н(銕�)"
- prop="settlePieceArea"
- min-width="160">
- <template #default="scope">
- <el-input-number v-if="scope.row.__editing"
- v-model="scope.row.settlePieceArea"
- :min="0"
- :step="0.00001"
- :precision="5"
- style="width: 100%"
- placeholder="璇疯緭鍏�"
- clearable
- @change="() => handleInlineSettleAreaChange(scope.row)" />
- <span v-else>{{ scope.row.settlePieceArea ?? "" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="鏁伴噺"
- prop="quantity"
- min-width="150">
- <template #default="scope">
- <el-input-number v-if="scope.row.__editing"
- v-model="scope.row.quantity"
- :step="0.1"
- :min="0"
- :precision="2"
- style="width: 100%"
- placeholder="璇疯緭鍏�"
- clearable
- @change="() => handleInlineQuantityChange(scope.row)"
- @input="() => handleInlineQuantityChange(scope.row)" />
- <span v-else>{{ scope.row.quantity ?? "" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="闈㈢Н(m虏)"
- prop="actualTotalArea"
- min-width="160">
- <template #default="scope">
- <el-input-number v-if="scope.row.__editing"
- v-model="scope.row.actualTotalArea"
- :min="0"
- :step="0.00001"
- :precision="5"
- style="width: 100%"
- placeholder="鑷姩璁$畻" />
- <span v-else>{{ scope.row.actualTotalArea ?? "" }}</span>
+ <el-input v-if="scope.row.__editing"
+ v-model="scope.row.floorCode"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.floorCode ?? "" }}</span>
</template>
</el-table-column>
<el-table-column label="鍚◣鍗曚环(鍏�)"
prop="taxInclusiveUnitPrice"
- min-width="140">
+ min-width="105">
<template #default="scope">
- <el-input-number v-if="scope.row.__editing"
+ <el-input-number :controls="false" v-if="scope.row.__editing"
:step="0.01"
:min="0"
:precision="2"
@@ -696,19 +1386,106 @@
clearable
@change="() => handleInlineUnitPriceChange(scope.row)"
@input="() => handleInlineUnitPriceChange(scope.row)" />
- <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice ?? 0) }}</span>
+ <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹�(mm)"
+ prop="width"
+ min-width="85">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.width"
+ :min="0"
+ :step="1"
+ :precision="2"
+ style="width:100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineSizeChange(scope.row)"
+ @input="() => handleInlineSizeChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.width ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="楂�(mm)"
+ prop="height"
+ min-width="85">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.height"
+ :min="0"
+ :step="1"
+ :precision="2"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineSizeChange(scope.row)"
+ @input="() => handleInlineSizeChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.height ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鏁伴噺"
+ prop="quantity"
+ min-width="85">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.quantity"
+ :step="1"
+ :min="0"
+ :precision="0"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineQuantityChange(scope.row)"
+ @input="() => handleInlineQuantityChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.quantity ?? "" }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="缁撶畻鍗曠墖闈㈢Н(銕�)"
+ prop="settlePieceArea"
+ min-width="120">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.settlePieceArea"
+ :min="0"
+ :step="1"
+ :precision="4"
+ style="width: 100%"
+ placeholder="璇疯緭鍏�"
+ clearable
+ @change="() => handleInlineSettleAreaChange(scope.row)"
+ :disabled="isReviewedEdit" />
+ <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="110">
+ <template #default="scope">
+ <el-input-number :controls="false" v-if="scope.row.__editing"
+ v-model="scope.row.actualTotalArea"
+ :min="0"
+ :step="1"
+ :precision="4"
+ style="width: 100%"
+ placeholder="鑷姩璁$畻"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.actualTotalArea ? Number(scope.row.actualTotalArea).toFixed(4) : "" }}</span>
</template>
</el-table-column>
<el-table-column label="绋庣巼(%)"
prop="taxRate"
- min-width="120">
+ min-width="80">
<template #default="scope">
<el-select v-if="scope.row.__editing"
v-model="scope.row.taxRate"
placeholder="璇烽�夋嫨"
clearable
style="width: 100%"
- @change="() => handleInlineTaxRateChange(scope.row)">
+ @change="() => handleInlineTaxRateChange(scope.row)"
+ :disabled="isReviewedEdit">
<el-option label="1"
value="1" />
<el-option label="3"
@@ -726,20 +1503,35 @@
<el-table-column label="鍚◣鎬讳环(鍏�)"
prop="taxInclusiveTotalPrice"
:formatter="formattedNumber"
- min-width="120" />
+ min-width="100" />
<el-table-column label="涓嶅惈绋庢�讳环(鍏�)"
prop="taxExclusiveTotalPrice"
:formatter="formattedNumber"
- min-width="120" />
+ min-width="100" />
+ <el-table-column label="鍔犲伐瑕佹眰"
+ prop="processRequirement"
+ min-width="100"
+ show-overflow-tooltip>
+ <template #default="scope">
+ <el-input v-if="scope.row.__editing"
+ v-model="scope.row.processRequirement"
+ placeholder="璇疯緭鍏�"
+ clearable
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
+ <span v-else>{{ scope.row.processRequirement ?? "" }}</span>
+ </template>
+ </el-table-column>
<el-table-column label="鍙戠エ绫诲瀷"
prop="invoiceType"
- min-width="120">
+ min-width="90">
<template #default="scope">
<el-select v-if="scope.row.__editing"
v-model="scope.row.invoiceType"
placeholder="璇烽�夋嫨"
clearable
- style="width: 100%">
+ style="width: 100%"
+ :disabled="isReviewedEdit">
<el-option label="澧炴櫘绁�"
value="澧炴櫘绁�" />
<el-option label="澧炰笓绁�"
@@ -748,60 +1540,36 @@
<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"
+ min-width="100"
show-overflow-tooltip>
<template #default="scope">
<el-input v-if="scope.row.__editing"
v-model="scope.row.remark"
placeholder="璇疯緭鍏�"
clearable
- style="width: 100%" />
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
<span v-else>{{ scope.row.remark ?? "" }}</span>
- </template>
- </el-table-column>
- <el-table-column label="妤煎眰缂栧彿"
- prop="floorCode"
- min-width="140"
- 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="heavyBox"
- min-width="100">
+ min-width="80">
<template #default="scope">
<el-input v-if="scope.row.__editing"
v-model="scope.row.heavyBox"
placeholder="璇疯緭鍏�"
clearable
- style="width: 100%" />
+ style="width: 100%"
+ :disabled="isReviewedEdit" />
<span v-else>{{ scope.row.heavyBox ?? "" }}</span>
</template>
</el-table-column>
<el-table-column fixed="right"
label="鎿嶄綔"
- min-width="220"
+ min-width="150"
align="center"
v-if="operationType !== 'view'">
<template #default="scope">
@@ -873,7 +1641,7 @@
style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
{{ item.processName }}
</el-tag>
- <el-input-number v-model="item.quantity"
+ <el-input-number :controls="false" v-model="item.quantity"
:min="0"
:step="1"
:precision="0"
@@ -896,13 +1664,32 @@
</el-popover>
</template>
<template v-else>
- <el-button link
+ <!-- 宸插鏍告ā寮忥細鍙兘淇敼鍗曚环 -->
+ <el-button v-if="isReviewedEdit"
+ link
type="primary"
size="small"
:disabled="isProductShipped(scope.row)"
- @click="editProductInline(scope.row, scope.$index)">
- 缂栬緫
+ @click="editPriceOnly(scope.row)">
+ 淇敼鍗曚环
</el-button>
+ <!-- 鏈鏍告ā寮忥細瀹屾暣缂栬緫 -->
+ <template v-if="!isReviewedEdit">
+ <el-button link
+ type="primary"
+ size="small"
+ :disabled="isProductShipped(scope.row)"
+ @click="editProductInline(scope.row, scope.$index)">
+ 缂栬緫
+ </el-button>
+ <el-button link
+ type="primary"
+ size="small"
+ :disabled="isProductShipped(scope.row)"
+ @click="copyProductInline(scope.row, scope.$index)">
+ 澶嶅埗
+ </el-button>
+ </template>
<el-popover :width="560"
trigger="click"
:hide-after="0"
@@ -924,7 +1711,7 @@
<el-button type="primary"
plain
size="small"
- :disabled="isProductShipped(scope.row)"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit"
@click="startAddOtherAmountForRow(scope.row)">
鏂板
</el-button>
@@ -937,7 +1724,7 @@
clearable
placeholder="璇烽�夋嫨棰濆鍔犲伐椤圭洰"
style="width: 100%;"
- :disabled="isProductShipped(scope.row)">
+ :disabled="isProductShipped(scope.row) || isReviewedEdit">
<el-option v-for="item in otherAmountSelectOptions"
:key="item.id"
:label="item.processName"
@@ -945,13 +1732,13 @@
</el-select>
<div style="display:flex; justify-content:flex-end; gap: 8px;">
<el-button size="small"
- :disabled="isProductShipped(scope.row)"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit"
@click="scope.row.__inlineOtherAmountAdding = false; scope.row.__inlineOtherAmountAddId = null">
鍙栨秷
</el-button>
<el-button type="primary"
size="small"
- :disabled="isProductShipped(scope.row) || scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit || scope.row.__inlineOtherAmountAddId === null || scope.row.__inlineOtherAmountAddId === undefined || scope.row.__inlineOtherAmountAddId === ''"
@click="confirmAddOtherAmountForRow(scope.row)">
纭娣诲姞
</el-button>
@@ -966,18 +1753,18 @@
style="max-width: 170px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
{{ item.processName }}
</el-tag>
- <el-input-number v-model="item.quantity"
+ <el-input-number :controls="false" v-model="item.quantity"
:min="0"
:step="1"
:precision="0"
style="width: 120px;"
placeholder="鏁伴噺"
- :disabled="operationType === 'view' || isProductShipped(scope.row)"
+ :disabled="operationType === 'view' || isProductShipped(scope.row) || isReviewedEdit"
@change="handleOtherAmountQuantityChange(scope.row)" />
<el-button type="danger"
link
size="small"
- :disabled="isProductShipped(scope.row)"
+ :disabled="isProductShipped(scope.row) || isReviewedEdit"
@click="removeOtherAmountAtForRow(scope.row, idx)">
鍒犻櫎
</el-button>
@@ -1001,7 +1788,7 @@
clearable
type="textarea"
:rows="2"
- :disabled="operationType === 'view'" />
+ :disabled="operationType === 'view' || isReviewedEdit" />
</el-form-item>
</el-col>
</el-row>
@@ -1014,7 +1801,7 @@
clearable
type="textarea"
:rows="2"
- :disabled="operationType === 'view'" />
+ :disabled="operationType === 'view' || isReviewedEdit" />
</el-form-item>
</el-col>
</el-row>
@@ -1033,9 +1820,9 @@
:on-success="handleUploadSuccess"
:on-remove="handleRemove">
<el-button type="primary"
- v-if="operationType !== 'view'">涓婁紶</el-button>
+ v-if="operationType !== 'view' || isCompletedOrder">涓婁紶</el-button>
<template #tip
- v-if="operationType !== 'view'">
+ v-if="operationType !== 'view' || isCompletedOrder">
<div class="el-upload__tip">
鏂囦欢鏍煎紡鏀寔
doc锛宒ocx锛寈ls锛寈lsx锛宲pt锛宲ptx锛宲df锛宼xt锛寈ml锛宩pg锛宩peg锛宲ng锛実if锛宐mp锛宺ar锛寊ip锛�7z
@@ -1172,7 +1959,7 @@
<el-col :span="8">
<el-form-item label="鍘氬害锛�"
prop="thickness">
- <el-input-number v-model="productForm.thickness"
+ <el-input-number :controls="false" v-model="productForm.thickness"
:min="0"
:step="0.000000000000001"
:precision="15"
@@ -1187,7 +1974,7 @@
<el-col :span="8">
<el-form-item label="鍚◣鍗曚环(鍏�)锛�"
prop="taxInclusiveUnitPrice">
- <el-input-number :step="0.01"
+ <el-input-number :controls="false" :step="0.01"
:min="0"
v-model="productForm.taxInclusiveUnitPrice"
style="width: 100%"
@@ -1221,7 +2008,7 @@
<el-col :span="8">
<el-form-item label="鏁伴噺锛�"
prop="quantity">
- <el-input-number :step="0.1"
+ <el-input-number :controls="false" :step="0.1"
:min="0"
v-model="productForm.quantity"
placeholder="璇疯緭鍏�"
@@ -1272,7 +2059,7 @@
<el-col :span="8">
<el-form-item label="瀹�(mm)锛�"
prop="width">
- <el-input-number v-model="productForm.width"
+ <el-input-number :controls="false" v-model="productForm.width"
:min="0"
:step="1"
:precision="2"
@@ -1285,7 +2072,7 @@
<el-col :span="8">
<el-form-item label="楂�(mm)锛�"
prop="height">
- <el-input-number v-model="productForm.height"
+ <el-input-number :controls="false" v-model="productForm.height"
:min="0"
:step="1"
:precision="2"
@@ -1298,7 +2085,7 @@
<el-col :span="8">
<el-form-item label="鍛ㄩ暱(cm)锛�"
prop="perimeter">
- <el-input-number v-model="productForm.perimeter"
+ <el-input-number :controls="false" v-model="productForm.perimeter"
:min="0"
:step="0.01"
:precision="2"
@@ -1314,10 +2101,10 @@
<el-col :span="8">
<el-form-item label="瀹為檯鍗曠墖闈㈢Н(銕�)锛�"
prop="actualPieceArea">
- <el-input-number v-model="productForm.actualPieceArea"
+ <el-input-number :controls="false" v-model="productForm.actualPieceArea"
:min="0"
- :step="0.00001"
- :precision="5"
+ :step="0.0001"
+ :precision="4"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable
@@ -1327,10 +2114,10 @@
<el-col :span="8">
<el-form-item label="瀹為檯鎬婚潰绉�(銕�)锛�"
prop="actualTotalArea">
- <el-input-number v-model="productForm.actualTotalArea"
+ <el-input-number :controls="false" v-model="productForm.actualTotalArea"
:min="0"
- :step="0.00001"
- :precision="5"
+ :step="0.0001"
+ :precision="4"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable />
@@ -1339,10 +2126,10 @@
<el-col :span="8">
<el-form-item label="缁撶畻鍗曠墖闈㈢Н(銕�)锛�"
prop="settlePieceArea">
- <el-input-number v-model="productForm.settlePieceArea"
+ <el-input-number :controls="false" v-model="productForm.settlePieceArea"
:min="0"
- :step="0.00001"
- :precision="5"
+ :step="0.0001"
+ :precision="4"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable
@@ -1352,10 +2139,10 @@
<el-col :span="8">
<el-form-item label="缁撶畻鎬婚潰绉�(銕�)锛�"
prop="settleTotalArea">
- <el-input-number v-model="productForm.settleTotalArea"
+ <el-input-number :controls="false" v-model="productForm.settleTotalArea"
:min="0"
- :step="0.00001"
- :precision="5"
+ :step="0.0001"
+ :precision="4"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable />
@@ -1364,10 +2151,10 @@
<el-col :span="8">
<el-form-item label="缁撶畻鎬婚潰绉�(銕�)锛�"
prop="settleTotalArea">
- <el-input-number v-model="productForm.settleTotalArea"
+ <el-input-number :controls="false" v-model="productForm.settleTotalArea"
:min="0"
- :step="0.00001"
- :precision="5"
+ :step="0.0001"
+ :precision="4"
style="width: 100%"
placeholder="璇疯緭鍏�"
clearable />
@@ -1447,7 +2234,7 @@
</el-tag>
</div>
<div style="flex: 1;">
- <el-input-number v-model="item.quantity"
+ <el-input-number :controls="false" v-model="item.quantity"
:min="0"
:step="1"
:precision="0"
@@ -1509,9 +2296,10 @@
<FormDialog v-model="importUpload.open"
:title="importUpload.title"
:width="'600px'"
- @close="importUpload.open = false"
+ :loading="importUpload.isUploading"
+ @close="onClose"
@confirm="submitImportFile"
- @cancel="importUpload.open = false">
+ @cancel="onClose">
<el-upload ref="importUploadRef"
:limit="1"
accept=".xlsx,.xls"
@@ -1522,6 +2310,7 @@
:on-error="importUpload.onError"
:on-progress="importUpload.onProgress"
:on-change="importUpload.onChange"
+ :on-exceed="importUpload.onExceed"
:auto-upload="false"
drag>
<i class="el-icon-upload"></i>
@@ -1613,16 +2402,157 @@
</div>
</template>
</el-dialog>
+ <!-- 鍏ュ簱浜у搧閫夋嫨寮圭獥 -->
+ <el-dialog v-model="stockDialogVisible"
+ 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
+ v-loading="stockLoading"
+ height="400px"
+ @selection-change="val => selectedStockProductIds = val.map(item => item.id)">
+ <el-table-column type="selection"
+ width="55"
+ align="center" />
+ <el-table-column align="center"
+ label="搴忓彿"
+ type="index"
+ width="60" />
+ <el-table-column prop="productCategory"
+ label="浜у搧澶х被"
+ show-overflow-tooltip />
+ <el-table-column prop="specificationModel"
+ label="瑙勬牸鍨嬪彿"
+ show-overflow-tooltip />
+ <el-table-column prop="quantity"
+ label="鏁伴噺"
+ width="100" />
+ <el-table-column prop="stockedQuantity"
+ label="宸插叆搴撴暟閲�"
+ width="120"
+ align="center"
+ show-overflow-tooltip />
+ <el-table-column prop="floorCode"
+ label="妤煎眰缂栧彿"
+ show-overflow-tooltip />
+ </el-table>
+ <template #footer>
+ <el-button @click="stockDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary"
+ @click="submitStock"
+ :disabled="selectedStockProductIds.length === 0">
+ 纭鍏ュ簱
+ </el-button>
+ </template>
+ </el-dialog>
+ <el-dialog v-model="ledgerQrDialogVisible"
+ title="閿�鍞鍗曚簩缁寸爜"
+ width="360px"
+ draggable
+ :close-on-click-modal="false">
+ <div class="ledger-qr-dialog">
+ <img v-if="ledgerQrCompositeUrl"
+ :src="ledgerQrCompositeUrl"
+ alt="閿�鍞鍗曚簩缁寸爜"
+ class="ledger-qr-composite-img" />
+ <el-button type="primary"
+ class="ledger-qr-save-btn"
+ :disabled="!ledgerQrCompositeUrl"
+ @click="downloadLedgerQrCode">
+ 淇濆瓨鍥剧墖
+ </el-button>
+ </div>
+ </el-dialog>
+ <!-- 鍙嶅鏍稿脊绐� -->
+ <el-dialog v-model="reverseAuditDialogVisible"
+ title="鍙嶅鏍哥‘璁�"
+ width="500px"
+ :close-on-click-modal="false"
+ class="reverse-audit-dialog">
+ <el-form label-width="110px"
+ label-position="left">
+ <el-form-item label="鍙嶅鏍哥被鍨�">
+ <el-radio-group v-model="reverseAuditForm.counterReviewType">
+ <el-radio :label="1">
+ <span>浣滃簾璁㈠崟</span>
+ <span style="color: #909399; font-size: 12px; margin-left: 4px;">锛堜笉鐢熸垚鏂拌鍗曪級</span>
+ </el-radio>
+ <el-radio :label="2">
+ <span>閲嶆柊鐢熸垚</span>
+ <span style="color: #909399; font-size: 12px; margin-left: 4px;">锛堣烦杞埌鏂板椤甸潰缂栬緫锛�</span>
+ </el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="鍙嶅鏍稿師鍥�">
+ <el-input v-model="reverseAuditForm.counterReviewDesc"
+ type="textarea"
+ :rows="4"
+ placeholder="璇疯緭鍏ュ弽瀹℃牳鍘熷洜" />
+ </el-form-item>
+ </el-form>
+ <div class="reverse-audit-warning">
+ <el-icon style="color: #E6A23C; margin-right: 4px;"><WarningFilled /></el-icon>
+ 娉ㄦ剰锛氬弽瀹℃牳灏嗚嚜鍔ㄤ綔搴熻璁㈠崟瀵瑰簲鐨勬墍鏈夊叆搴撱�佸嚭搴撱�佸彂璐у崟鎹�
+ </div>
+ <template #footer>
+ <el-button @click="reverseAuditDialogVisible = false">鍙栨秷</el-button>
+ <el-button type="danger"
+ @click="confirmReverseAudit"
+ :disabled="!reverseAuditForm.counterReviewType || !reverseAuditForm.counterReviewDesc.trim()">纭鍙嶅鏍�</el-button>
+ </template>
+ </el-dialog>
</div>
</template>
<script setup>
import { getToken } from "@/utils/auth";
import pagination from "@/components/PIMTable/Pagination.vue";
- import { onMounted, ref, getCurrentInstance, watch, nextTick } from "vue";
+ import { onMounted, ref, reactive, getCurrentInstance, watch, nextTick, computed } from "vue";
import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js";
import { ElMessageBox, ElMessage } from "element-plus";
- import { ArrowDown } from "@element-plus/icons-vue";
+ import { ArrowDown, WarningFilled } from "@element-plus/icons-vue";
import useUserStore from "@/store/modules/user";
import { approveUserList } from "@/api/collaborativeApproval/approvalProcess.js";
import { userListNoPage } from "@/api/system/user.js";
@@ -1631,6 +2561,7 @@
import OtherAmountMaintenanceButton from "./components/OtherAmountMaintenanceButton.vue";
import ProcessFlowMaintenanceButton from "./components/ProcessFlowMaintenanceButton.vue";
import ProcessFlowConfigSelectDialog from "./components/ProcessFlowConfigSelectDialog.vue";
+ import ReverseAuditHistory from "../reverseAuditHistory/index.vue";
import { getQuotationList } from "@/api/salesManagement/salesQuotation.js";
import {
ledgerListPage,
@@ -1651,6 +2582,9 @@
getSalesInvoices,
getSalesLabel,
salesStock,
+ counterReview,
+ markOrderCompleted,
+ incrementPrintCount,
} from "@/api/salesManagement/salesLedger.js";
import { modelList, productTreeList } from "@/api/basicData/product.js";
import useFormData from "@/hooks/useFormData.js";
@@ -1660,14 +2594,19 @@
import { printSalesOrder } from "./components/salesOrderPrint.js";
import { printSalesDeliveryNote } from "./components/salesDeliveryPrint.js";
import { printSalesLabel } from "./components/salesLabelPrint.js";
+ import QRCode from "qrcode";
+ import { useRoute, useRouter } from "vue-router";
// import { salesLedgerProductSetProcessFlowConfig } from "@/api/salesManagement/salesProcessFlowConfig.js";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
+ const route = useRoute();
+ const router = useRouter();
const tableData = ref([]);
const productData = ref([]);
const selectedRows = ref([]);
const productSelectedRows = ref([]);
+ const pinnedProductTemplate = ref(null);
const userList = ref([]);
const userListApprove = ref([]);
const customerOption = ref([]);
@@ -1680,27 +2619,178 @@
});
const total = ref(0);
const fileList = ref([]);
+ const processRouteExportDateRange = ref([]);
// 宸ヨ壓璺嚎閰嶇疆閫夋嫨寮圭獥锛堢粦瀹氬埌鍙拌处浜у搧锛�
const processFlowSelectDialogVisible = ref(false);
const processFlowSelectLedgerRow = ref(null);
const processFlowSelectDefaultRouteId = ref(null);
+ const processFlowSelectDefaultRecordList = ref([]);
const processFlowSelectBoundRouteId = ref(null);
const processFlowSelectBoundRouteName = ref("");
+
+ // 鍏ュ簱寮圭獥鐩稿叧
+ const stockDialogVisible = ref(false);
+ const stockProductList = ref([]);
+ 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("");
+ const ledgerQrDownloadBaseName = ref("");
+ const pendingAddPrefillDetail = ref(null);
+ const pageMode = computed(() => String(route.query.mode || ""));
+ const isFormPageMode = computed(() =>
+ pageMode.value === "add" || pageMode.value === "edit"
+ );
+ const pageFormTitle = computed(() =>
+ pageMode.value === "edit" ? "缂栬緫閿�鍞彴璐�" : "鏂板閿�鍞彴璐�"
+ );
+ const pageFormSubtitle = computed(() =>
+ pageMode.value === "edit"
+ ? "缂栬緫瀹屾垚鍚庡皢鑷姩杩斿洖閿�鍞彴璐﹀垪琛�"
+ : "鏂板瀹屾垚鍚庡皢鑷姩杩斿洖閿�鍞彴璐﹀垪琛�"
+ );
+
+ const sanitizeLedgerQrFilename = s =>
+ String(s)
+ .replace(/[\\/:*?"<>|]/g, "_")
+ .trim()
+ .slice(0, 80) || "ledger";
+
+ const wrapLedgerQrTextLines = (ctx, text, maxWidth) => {
+ const chars = [...text];
+ const lines = [];
+ let line = "";
+ for (const ch of chars) {
+ const test = line + ch;
+ if (ctx.measureText(test).width > maxWidth && line.length) {
+ lines.push(line);
+ line = ch;
+ } else {
+ line = test;
+ }
+ }
+ if (line) lines.push(line);
+ return lines;
+ };
+
+ const buildLedgerQrCompositeDataUrl = row =>
+ new Promise((resolve, reject) => {
+ const payload = JSON.stringify({
+ id: row.id,
+ salesContractNo: (row.salesContractNo ?? "").trim(),
+ type: "XS",
+ });
+ QRCode.toDataURL(payload, { width: 220, margin: 2 })
+ .then(qrDataUrl => {
+ const contract = (row.salesContractNo ?? "").trim() || "鈥�";
+ const img = new Image();
+ img.onload = () => {
+ const QR_SIZE = 220;
+ const padTop = 16;
+ const gapAfterQr = 14;
+ const bottomPad = 48;
+ const horizontalPad = 20;
+ const lineHeight = 20;
+ const fontSize = 14;
+ const label = `閿�鍞悎鍚屽彿锛�${contract}`;
+
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+ canvas.width = Math.max(QR_SIZE + horizontalPad * 2, 280);
+ ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`;
+ const lines = wrapLedgerQrTextLines(
+ ctx,
+ label,
+ canvas.width - horizontalPad * 2
+ );
+ const textBlockHeight = lines.length * lineHeight;
+ canvas.height =
+ padTop + QR_SIZE + gapAfterQr + textBlockHeight + bottomPad;
+
+ ctx.fillStyle = "#ffffff";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ const qrX = (canvas.width - QR_SIZE) / 2;
+ ctx.drawImage(img, qrX, padTop, QR_SIZE, QR_SIZE);
+
+ ctx.fillStyle = "#606266";
+ ctx.font = `${fontSize}px "Microsoft YaHei", "PingFang SC", sans-serif`;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "top";
+ const textY0 = padTop + QR_SIZE + gapAfterQr;
+ lines.forEach((ln, i) => {
+ ctx.fillText(ln, canvas.width / 2, textY0 + i * lineHeight);
+ });
+
+ const baseName = sanitizeLedgerQrFilename(
+ contract !== "鈥�" ? contract : String(row.id)
+ );
+ resolve({ dataUrl: canvas.toDataURL("image/png"), baseName });
+ };
+ img.onerror = () => reject(new Error("浜岀淮鐮佸浘鐗囧姞杞藉け璐�"));
+ img.src = qrDataUrl;
+ })
+ .catch(reject);
+ });
+
+ const openLedgerQrDialog = async row => {
+ if (row?.id === undefined || row?.id === null || row?.id === "") {
+ ElMessage.warning("鏃犳硶鐢熸垚浜岀淮鐮侊細缂哄皯鍙拌处 ID");
+ return;
+ }
+ ledgerQrCompositeUrl.value = "";
+ ledgerQrDownloadBaseName.value = "";
+ try {
+ const { dataUrl, baseName } = await buildLedgerQrCompositeDataUrl(row);
+ ledgerQrCompositeUrl.value = dataUrl;
+ ledgerQrDownloadBaseName.value = baseName;
+ ledgerQrDialogVisible.value = true;
+ } catch {
+ ElMessage.error("浜岀淮鐮佺敓鎴愬け璐�");
+ }
+ };
+
+ const downloadLedgerQrCode = () => {
+ if (!ledgerQrCompositeUrl.value) return;
+ const a = document.createElement("a");
+ a.href = ledgerQrCompositeUrl.value;
+ a.download = `閿�鍞彴璐︿簩缁寸爜-${ledgerQrDownloadBaseName.value}.png`;
+ a.click();
+ };
// 鐢ㄦ埛淇℃伅琛ㄥ崟寮规鏁版嵁
const operationType = ref("");
const dialogFormVisible = ref(false);
+ // 宸插畬鎴愯鍗曟爣璁帮細view 妯″紡浣嗛檮浠朵笂浼犱粛鍙敤
+ const isCompletedOrder = ref(false);
+ // 宸插鏍歌鍗曠紪杈戞ā寮忥細鍙兘淇敼鍗曚环锛屽叾浠栧瓧娈电鐢�
+ const isReviewedEdit = ref(false);
const data = reactive({
searchForm: {
customerName: "", // 瀹㈡埛鍚嶇О
customerId: "", // 瀹㈡埛ID锛堟煡璇笅鎷夛級
salesContractNo: "", // 閿�鍞悎鍚岀紪鍙�
+ width: undefined, // 浜у搧瀹�(mm)
+ height: undefined, // 浜у搧楂�(mm)
entryDate: null, // 褰曞叆鏃ユ湡
entryDateStart: undefined,
entryDateEnd: undefined,
- deliveryStatus: undefined, // 鍙戣揣鐘舵�侊細1鏈彂璐� 2瀹℃壒涓� 3瀹℃壒澶辫触 4宸插彂璐�
- stockStatus: undefined, // 鍏ュ簱鐘舵�侊細0鏈叆搴� 1宸插叆搴�
+ deliveryStatus: undefined, // 鍙戣揣鐘舵�侊細1鏈彂璐� 2瀹℃壒涓� 3瀹℃壒涓嶉�氳繃 4瀹℃壒閫氳繃 5宸插彂璐� 6閮ㄥ垎鍙戣揣
+ stockStatus: undefined, // 鍏ュ簱鐘舵�侊細0鏈叆搴� 1閮ㄥ垎鍏ュ簱 2宸插叆搴�
+ reviewStatus: undefined, // 瀹℃牳鐘舵�侊細0寰呭鏍� 1宸插鏍� 2宸插弽瀹�
+ orderStatus: undefined, // 璁㈠崟鐘舵�侊細0杩涜涓� 1宸插畬鎴�
},
form: {
salesContractNo: "",
@@ -1712,6 +2802,8 @@
maintenanceTime: "",
productData: [],
executionDate: "",
+ reviewStatus: undefined,
+ stockStatus: undefined,
},
rules: {
salesman: [{ required: true, message: "璇烽�夋嫨", trigger: "change" }],
@@ -1840,48 +2932,249 @@
return (productData.value || []).some(r => r && r.__editing);
};
+ const isPlaceholderRowDirty = row => {
+ if (!row || !row.__placeholder) return false;
+ const fieldsToCheck = [
+ "width",
+ "height",
+ "quantity",
+ "settlePieceArea",
+ "taxInclusiveUnitPrice",
+ "taxRate",
+ "invoiceType",
+ "floorCode",
+ "processRequirement",
+ "remark",
+ "heavyBox",
+ ];
+ return fieldsToCheck.some(key => {
+ const value = row[key];
+ return value !== null && value !== undefined && value !== "";
+ });
+ };
+
+ const getPlaceholderRowIndex = () =>
+ (productData.value || []).findIndex(r => r && r.__placeholder);
+
+ const discardPlaceholderRowIfPristine = () => {
+ const index = getPlaceholderRowIndex();
+ if (index === -1) return true;
+ const row = productData.value[index];
+ if (isPlaceholderRowDirty(row)) {
+ proxy.$modal.msgWarning("璇峰厛淇濆瓨鎴栧彇娑堝綋鍓嶅緟褰曞叆琛�");
+ return false;
+ }
+ productData.value.splice(index, 1);
+ if (editingProductRow.value === row) {
+ editingProductRow.value = null;
+ }
+ return true;
+ };
+
+ const buildEmptyInlineProductRow = (prefill = {}) => ({
+ id: null,
+ __tempKey: `__temp_${Date.now()}_${Math.random().toString(16).slice(2)}`,
+ __editing: true,
+ __isNew: true,
+ __placeholder: true,
+ __productCategoryId: prefill.__productCategoryId ?? null,
+ productCategory: prefill.productCategory ?? "",
+ productModelId: prefill.productModelId ?? null,
+ specificationModel: prefill.specificationModel ?? "",
+ thickness: prefill.thickness ?? null,
+ quantity: null,
+ taxInclusiveUnitPrice: null,
+ taxRate: "",
+ taxInclusiveTotalPrice: null,
+ taxExclusiveTotalPrice: null,
+ invoiceType: "",
+ width: null,
+ height: null,
+ perimeter: null,
+ actualPieceArea: null,
+ actualTotalArea: null,
+ settlePieceArea: null,
+ settleTotalArea: null,
+ processRequirement: "",
+ remark: "",
+ salesProductProcessList: [],
+ processFlowConfigId: null,
+ floorCode: "",
+ heavyBox: "",
+ });
+
+ const getPinnedProductPrefill = async () => {
+ await getProductOptions();
+ if (!pinnedProductTemplate.value) {
+ const models = await modelList({});
+ modelOptions.value = models || [];
+ return {};
+ }
+ if (pinnedProductTemplate.value.__productCategoryId) {
+ const models = await modelList({
+ id: pinnedProductTemplate.value.__productCategoryId,
+ });
+ modelOptions.value = models || [];
+ } else {
+ const models = await modelList({});
+ modelOptions.value = models || [];
+ }
+ return { ...pinnedProductTemplate.value };
+ };
+
+ const appendEditablePlaceholderRow = async () => {
+ if (operationType.value === "view" || isReviewedEdit.value) return;
+ const existingPlaceholder = (productData.value || []).find(
+ r => r && r.__placeholder
+ );
+ if (existingPlaceholder) {
+ if (!existingPlaceholder.__editing) existingPlaceholder.__editing = true;
+ editingProductRow.value = existingPlaceholder;
+ productForm.value = existingPlaceholder;
+ return;
+ }
+ if (hasEditingProductRow()) return;
+ await getProductOptions();
+ await fetchOtherAmountSelectOptions(true);
+ const prefill = await getPinnedProductPrefill();
+ const row = buildEmptyInlineProductRow(prefill);
+ productData.value.push(row);
+ editingProductRow.value = row;
+ productForm.value = row;
+ };
+
+ const pinSelectedProductRow = async () => {
+ if (operationType.value === "view" || isReviewedEdit.value) return;
+ if (!productSelectedRows.value || productSelectedRows.value.length !== 1) {
+ proxy.$modal.msgWarning("璇烽�夋嫨涓�鏉′骇鍝佹暟鎹繘琛屽浐瀹�");
+ return;
+ }
+ const selectedRow = productSelectedRows.value[0];
+ const categoryId =
+ selectedRow.__productCategoryId ??
+ findNodeIdByLabel(productOptions.value, selectedRow.productCategory) ??
+ null;
+ if (!selectedRow.productCategory || !selectedRow.specificationModel) {
+ proxy.$modal.msgWarning("璇峰厛閫夋嫨瀹屾暣鐨勪骇鍝佸ぇ绫诲拰瑙勬牸鍨嬪彿鍐嶅浐瀹�");
+ return;
+ }
+ if (!categoryId || !selectedRow.productModelId) {
+ proxy.$modal.msgWarning("璇峰厛淇濆瓨褰撳墠浜у搧鍚庡啀鍥哄畾");
+ return;
+ }
+ pinnedProductTemplate.value = {
+ __productCategoryId: categoryId,
+ productCategory: selectedRow.productCategory ?? "",
+ productModelId: selectedRow.productModelId ?? null,
+ specificationModel: selectedRow.specificationModel ?? "",
+ thickness:
+ selectedRow.thickness !== null &&
+ selectedRow.thickness !== undefined &&
+ selectedRow.thickness !== ""
+ ? Number(selectedRow.thickness)
+ : null,
+ };
+ proxy.$modal.msgSuccess("鍥哄畾鎴愬姛锛屽悗缁細鑷姩甯﹀嚭璇ヤ骇鍝佸拰瑙勬牸鍨嬪彿");
+ const editingRow = (productData.value || []).find(r => r?.__editing);
+ if (
+ editingRow &&
+ !editingRow.productCategory &&
+ !editingRow.productModelId &&
+ !editingRow.specificationModel
+ ) {
+ Object.assign(editingRow, await getPinnedProductPrefill());
+ }
+ };
+
const addProductInline = async () => {
if (operationType.value === "view") return;
if (hasEditingProductRow()) {
proxy.$modal.msgWarning("璇峰厛淇濆瓨鎴栧彇娑堝綋鍓嶇紪杈戣");
return;
}
+ await appendEditablePlaceholderRow();
+ };
+
+ const copyProductInline = async row => {
+ if (operationType.value === "view") return;
+ if (!row) return;
+ if (isProductShipped(row)) {
+ proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳藉鍒�");
+ return;
+ }
+ const hasBlockingEditingRow = (productData.value || []).some(
+ item => item && item.__editing && !item.__placeholder
+ );
+ if (hasBlockingEditingRow) {
+ proxy.$modal.msgWarning("璇峰厛淇濆瓨鎴栧彇娑堝綋鍓嶇紪杈戣");
+ return;
+ }
+ if (!discardPlaceholderRowIfPristine()) return;
await getProductOptions();
await fetchOtherAmountSelectOptions(true);
- const row = {
- id: null,
- __tempKey: `__temp_${Date.now()}_${Math.random().toString(16).slice(2)}`,
- __editing: true,
- __isNew: true,
- __productCategoryId: null,
- productCategory: "",
- productModelId: null,
- specificationModel: "",
- thickness: null,
- quantity: 0,
- taxInclusiveUnitPrice: 0,
- taxRate: "",
- taxInclusiveTotalPrice: 0,
- taxExclusiveTotalPrice: 0,
- invoiceType: "",
- width: 0,
- height: 0,
- perimeter: 0,
- actualPieceArea: 0,
- actualTotalArea: 0,
- settlePieceArea: 0,
- settleTotalArea: 0,
- processRequirement: "",
- remark: "",
- salesProductProcessList: [],
- processFlowConfigId: null,
- floorCode: "",
- heavyBox: "",
- };
- productData.value.push(row);
- editingProductRow.value = row;
- // 璁╃幇鏈夌殑璁$畻/鍏朵粬閲戦閫昏緫澶嶇敤褰撳墠琛�
- productForm.value = row;
+
+ const copied = buildEmptyInlineProductRow();
+ copied.__placeholder = false;
+ copied.__productCategoryId =
+ row.__productCategoryId ??
+ findNodeIdByLabel(productOptions.value, row.productCategory) ??
+ null;
+ copied.productCategory = row.productCategory ?? "";
+ copied.productModelId = row.productModelId ?? null;
+ copied.specificationModel = row.specificationModel ?? "";
+ copied.thickness =
+ row.thickness !== null &&
+ row.thickness !== undefined &&
+ row.thickness !== ""
+ ? Number(row.thickness)
+ : null;
+ copied.floorCode = row?.floorCode ?? row?.floor_code ?? "";
+
+ 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.taxInclusiveTotalPrice = null;
+ copied.taxExclusiveTotalPrice = null;
+ copied.width = null;
+ copied.height = null;
+ copied.perimeter = null;
+ copied.actualPieceArea = null;
+ copied.actualTotalArea = null;
+ copied.settlePieceArea = null;
+ copied.settleTotalArea = null;
+
+ // 澶嶅埗鏃舵寜鈥滀骇鍝佸ぇ绫� + 瑙勬牸鍨嬪彿鍚嶇О鈥濆弽鏌ュ瀷鍙� id锛岀‘淇濅笅鎷夎兘姝g‘鍥炴樉
+ try {
+ if (copied.__productCategoryId) {
+ const models = await modelList({ id: copied.__productCategoryId });
+ modelOptions.value = models || [];
+ const matchedModel = (modelOptions.value || []).find(
+ m => m.model === copied.specificationModel
+ );
+ copied.productModelId = matchedModel?.id ?? copied.productModelId ?? null;
+ }
+ } catch (e) {
+ console.error("澶嶅埗鏃跺姞杞戒骇鍝佽鏍煎瀷鍙峰け璐�", e);
+ }
+
+ productData.value.push(copied);
+ editingProductRow.value = copied;
+ productForm.value = copied;
+ };
+
+ /** 宸插鏍歌鍗曪細浠呬慨鏀瑰惈绋庡崟浠� */
+ const editPriceOnly = (row) => {
+ if (operationType.value === "view") return;
+ if (!row) return;
+ stopOtherEditingRows();
+ row.__editing = true;
+ row.__priceOnly = true; // 鏍囪鍙慨鏀瑰崟浠�
};
const editProductInline = async (row, index) => {
@@ -1891,6 +3184,7 @@
proxy.$modal.msgWarning("宸插彂璐ф垨瀹℃牳閫氳繃鐨勪骇鍝佷笉鑳界紪杈�");
return;
}
+ if (!discardPlaceholderRowIfPristine()) return;
stopOtherEditingRows();
await getProductOptions();
await fetchOtherAmountSelectOptions(true);
@@ -1978,10 +3272,6 @@
proxy.$modal.msgWarning("闈㈢Н蹇呴』澶т簬0");
return false;
}
- if (row.taxInclusiveUnitPrice <= 0) {
- proxy.$modal.msgWarning("鍚◣鍗曚环蹇呴』澶т簬0");
- return false;
- }
if (!row.productModelId) {
proxy.$modal.msgWarning("璇烽�夋嫨瑙勬牸鍨嬪彿");
return false;
@@ -2038,11 +3328,13 @@
if (model?.model) row.specificationModel = model.model;
if (operationType.value === "edit") {
+ row.__placeholder = false;
// 鍙拌处宸插瓨鍦細璧板師鎺ュ彛淇濆瓨鍒板悗绔紝鍐嶅洖鎷夊埛鏂�
const payload = { ...row, salesLedgerId: currentId.value, type: 1 };
delete payload.__backup;
delete payload.__editing;
delete payload.__isNew;
+ delete payload.__placeholder;
delete payload.__productCategoryId;
delete payload.__tempKey;
await addOrUpdateSalesLedgerProduct(payload);
@@ -2052,19 +3344,22 @@
productData.value = res.productData;
}
);
+ stopOtherEditingRows();
+ await appendEditablePlaceholderRow();
} else {
// 鏂板鍙拌处锛氫粎鍦ㄦ湰鍦� productData 鐢熸晥锛屾渶缁堥殢鍙拌处涓�璧锋彁浜�
row.__isNew = false;
+ row.__placeholder = false;
row.__editing = false;
delete row.__backup;
+ stopOtherEditingRows();
+ await appendEditablePlaceholderRow();
}
-
- stopOtherEditingRows();
};
- const cancelProductInline = (row, index) => {
+ const cancelProductInline = async (row, index) => {
if (!row) return;
- if (row.__isNew) {
+ if (row.__placeholder) {
productData.value.splice(index, 1);
} else if (row.__backup) {
const restored = JSON.parse(JSON.stringify(row.__backup));
@@ -2074,9 +3369,11 @@
Object.assign(row, restored);
row.id = keepId;
row.__editing = false;
+ row.__placeholder = false;
delete row.__backup;
}
stopOtherEditingRows();
+ await appendEditablePlaceholderRow();
};
const openOtherAmountInline = async row => {
@@ -2513,6 +3810,13 @@
onChange: (file, fileList) => {
console.log("鏂囦欢鐘舵�佹敼鍙�", file, fileList);
},
+ onExceed: (files, fileList) => {
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ const file = files[0];
+ importUploadRef.value.handleStart(file);
+ }
+ },
onProgress: (event, file, fileList) => {
console.log("涓婁紶涓�...", event.percent);
},
@@ -2536,7 +3840,66 @@
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 activeStatusTab = ref("all");
+ const salesLedgerStatusTabs = [
+ { key: "all", label: "鍏ㄩ儴" },
+ { key: "pendingReview", label: "鏈鏍�" },
+ { key: "reviewed", label: "宸插鏍�" },
+ { key: "reverseReviewed", label: "鍙嶅鏍�" },
+ { key: "stocked", label: "宸插叆搴�" },
+ { key: "delivered", label: "宸插彂璐�" },
+ { key: "completed", label: "宸插畬鎴�" },
+ ];
+ const resetStatusFilters = () => {
+ searchForm.reviewStatus = undefined;
+ searchForm.stockStatus = undefined;
+ searchForm.deliveryStatus = undefined;
+ searchForm.orderStatus = undefined;
+ };
+
+ const handleStatusTabChange = tabKey => {
+ activeStatusTab.value = tabKey;
+ resetStatusFilters();
+ switch (tabKey) {
+ case "all":
+ break;
+ case "pendingReview":
+ searchForm.reviewStatus = 0;
+ break;
+ case "reviewed":
+ searchForm.reviewStatus = 1;
+ break;
+ case "reverseReviewed":
+ searchForm.reviewStatus = 2;
+ break;
+ case "stocked":
+ searchForm.stockStatus = 2;
+ break;
+ case "delivered":
+ searchForm.deliveryStatus = 5;
+ break;
+ case "completed":
+ searchForm.orderStatus = 1;
+ break;
+ default:
+ break;
+ }
+ handleQuery();
+ };
const changeDaterange = value => {
if (value) {
searchForm.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD");
@@ -2546,6 +3909,43 @@
searchForm.entryDateEnd = undefined;
}
handleQuery();
+ };
+
+ /** 鎵归噺鎸夐挳绂佺敤鍒ゆ柇锛氭牴鎹�変腑琛岀殑瀹℃牳鐘舵�佸拰璁㈠崟鐘舵�佹帶鍒舵寜閽彲鐢ㄦ��
+ * 鏈鏍�(0)锛氬彧鑳藉鏍搞�佸垹闄ゃ�佺紪杈�
+ * 宸插鏍�(1)锛氬彲浠ュ弽瀹°�佸叆搴撱�佸彂璐с�佸鍑恒�佹墦鍗般�佸伐鑹鸿矾绾�
+ * 宸插畬鎴�(orderStatus=1)锛氭墍鏈夋搷浣滄寜閽鐢�
+ * 鏈�変腑浠讳綍琛屾椂鎵�鏈夋壒閲忔寜閽鐢�
+ */
+ const isBatchButtonDisabled = (action) => {
+ if (selectedRows.value.length === 0) return true;
+ const hasCompleted = selectedRows.value.some(r => Number(r.orderStatus) === 1);
+ // 宸插畬鎴愯鍗曪細鎵�鏈夋搷浣滄寜閽鐢�
+ if (hasCompleted && action !== 'markCompleted') return true;
+ const statuses = selectedRows.value.map(r => Number(r.reviewStatus));
+ const allUnreviewed = statuses.every(s => s === 0);
+ const allReviewed = statuses.every(s => s === 1);
+ switch (action) {
+ case 'audit':
+ return !allUnreviewed;
+ case 'reverseAudit':
+ return !allReviewed;
+ case 'stock':
+ return !allReviewed;
+ case 'delivery':
+ return !allReviewed;
+ case 'export':
+ return !allReviewed;
+ case 'delete':
+ return !allUnreviewed;
+ case 'print':
+ return !allReviewed;
+ case 'markCompleted':
+ // 鍙湁宸插鏍镐笖鏈畬鎴愮殑璁㈠崟鎵嶈兘鏍囪瀹屾垚
+ return !allReviewed || hasCompleted;
+ default:
+ return false;
+ }
};
// 鏌ヨ鍒楄〃
@@ -2559,6 +3959,168 @@
expandedRowKeys.value = [];
getList();
};
+
+ /** 瀹℃牳鎸夐挳鎿嶄綔 */
+ const handleAudit = async () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸鏍哥殑鏁版嵁");
+ return;
+ }
+ const canNotAudit = selectedRows.value.filter(
+ row => Number(row.reviewStatus) !== 0
+ );
+ if (canNotAudit.length > 0) {
+ proxy.$modal.msgWarning("閫変腑鐨勬暟鎹腑鍖呭惈闈炲緟瀹℃牳椤癸紝璇烽噸鏂伴�夋嫨");
+ return;
+ }
+
+ // 褰曞叆浜轰笉鑳藉鏍歌鏉℃暟鎹�
+ const isEntryPerson = selectedRows.value.some(
+ row => String(row.entryPerson) === String(userStore.id)
+ );
+ if (isEntryPerson) {
+ proxy.$modal.msgWarning("褰曞叆浜轰笉鑳藉鏍歌嚜宸辨彁浜ょ殑鏁版嵁");
+ return;
+ }
+
+ try {
+ await ElMessageBox.confirm("鏄惁纭瀹℃牳閫変腑鐨勯攢鍞彴璐︼紵", "鎻愮ず", {
+ confirmButtonText: "纭畾",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ });
+ for (const row of selectedRows.value) {
+ await addOrUpdateSalesLedger({ ...row, reviewStatus: 1 });
+ }
+ proxy.$modal.msgSuccess("瀹℃牳鎴愬姛");
+ getList();
+ } catch (error) {
+ console.log(error);
+ }
+ };
+
+ /** 鍙嶅鏍稿脊绐楃姸鎬� */
+ const reverseAuditDialogVisible = ref(false);
+ const reverseAuditForm = reactive({
+ counterReviewType: null,
+ counterReviewDesc: "",
+ });
+
+ /** 鍙嶅鎸夐挳鎿嶄綔 鈥� 鎵撳紑寮圭獥 */
+ const handleReverseAudit = () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佸弽瀹℃牳鐨勬暟鎹�");
+ return;
+ }
+ const canNotReverse = selectedRows.value.filter(
+ row => Number(row.reviewStatus) !== 1
+ );
+ if (canNotReverse.length > 0) {
+ proxy.$modal.msgWarning("閫変腑鐨勬暟鎹腑鍖呭惈闈炲凡瀹℃牳椤癸紝璇烽噸鏂伴�夋嫨");
+ return;
+ }
+ // 閲嶇疆寮圭獥琛ㄥ崟
+ reverseAuditForm.counterReviewType = null;
+ reverseAuditForm.counterReviewDesc = "";
+ reverseAuditDialogVisible.value = true;
+ };
+
+ /** 鍙嶅鏍哥‘璁ゆ搷浣� */
+ const confirmReverseAudit = async () => {
+ if (!reverseAuditForm.counterReviewType) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鍙嶅鏍哥被鍨�");
+ return;
+ }
+ if (!reverseAuditForm.counterReviewDesc.trim()) {
+ proxy.$modal.msgWarning("璇疯緭鍏ュ弽瀹℃牳鍘熷洜");
+ return;
+ }
+ try {
+ const ids = selectedRows.value.map(row => row.id);
+ const res = await counterReview({
+ ids,
+ counterReviewType: reverseAuditForm.counterReviewType,
+ counterReviewDesc: reverseAuditForm.counterReviewDesc,
+ });
+ reverseAuditDialogVisible.value = false;
+ proxy.$modal.msgSuccess("鍙嶅鏍告垚鍔�");
+
+ // 閲嶆柊鐢熸垚绫诲瀷锛氳烦杞埌鏂板鍙拌处椤甸潰锛堥濉暟鎹級
+ if (reverseAuditForm.counterReviewType === 2 && res.newLedgerIds && res.newLedgerIds.length > 0) {
+ // 鍗曟潯鍙嶅鏃惰烦杞埌鏂板椤甸潰缂栬緫鏂板彴璐�
+ if (res.newLedgerIds.length === 1) {
+ const newId = res.newLedgerIds[0];
+ getSalesLedgerWithProducts({ id: newId, type: 1 }).then(detail => {
+ enterAddPage(detail);
+ });
+ }
+ }
+ getList();
+ } catch (error) {
+ console.log(error);
+ }
+ };
+
+ /** 鏍囪瀹屾垚鎿嶄綔 */
+ const handleMarkCompleted = async () => {
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇烽�夋嫨瑕佹爣璁板畬鎴愮殑鏁版嵁");
+ return;
+ }
+ const cannotComplete = selectedRows.value.filter(
+ row => Number(row.reviewStatus) !== 1 || Number(row.orderStatus) === 1
+ );
+ if (cannotComplete.length > 0) {
+ proxy.$modal.msgWarning("閫変腑鐨勬暟鎹腑鍖呭惈闈炲凡瀹℃牳鎴栧凡瀹屾垚鐨勯」锛岃閲嶆柊閫夋嫨");
+ return;
+ }
+ try {
+ await ElMessageBox.confirm(
+ "鏍囪瀹屾垚鍚庤鍗曞皢鍙樹负鍙锛屽彧鑳芥煡鐪嬪拰涓婁紶涓嬭浇闄勪欢锛屼笉鍙挙閿�銆傛槸鍚︾‘璁わ紵",
+ "鎻愮ず",
+ { confirmButtonText: "纭畾", cancelButtonText: "鍙栨秷", type: "warning" }
+ );
+ const ids = selectedRows.value.map(row => row.id);
+ await markOrderCompleted({ ids });
+ proxy.$modal.msgSuccess("鏍囪瀹屾垚鎴愬姛");
+ getList();
+ } catch {
+ // 鐢ㄦ埛鍙栨秷
+ }
+ };
+
+ /** 鐢ㄩ濉暟鎹墦寮�鏂板鍙拌处椤甸潰锛堥噸鏂扮敓鎴愬満鏅級 */
+ const openFormWithPreFill = async (detail, keepPageMode = false) => {
+ operationType.value = "add";
+ form.value = {};
+ productData.value = [];
+ selectedQuotation.value = null;
+ fileList.value = [];
+ pinnedProductTemplate.value = null;
+ let userLists = await userListNoPage();
+ userList.value = userLists.data;
+ customerList().then(res => {
+ customerOption.value = res;
+ });
+ // 棰勫~鍘熷彴璐︽暟鎹埌鏂板琛ㄥ崟
+ form.value.customerId = detail.customerId;
+ form.value.customerName = detail.customerName;
+ form.value.projectName = detail.projectName;
+ form.value.salesman = detail.salesman;
+ form.value.entryPerson = Number(userStore.id);
+ form.value.entryDate = getCurrentDate();
+ form.value.executionDate = detail.executionDate || getCurrentDate();
+ form.value.deliveryDate = detail.deliveryDate;
+ form.value.paymentMethod = detail.paymentMethod;
+ form.value.contractAmount = detail.contractAmount;
+ form.value.remarks = detail.remarks;
+ form.value.customerRemarks = detail.customerRemarks ?? detail.customer_remarks ?? "";
+ productData.value = detail.productData || [];
+ form.value.deliveryDate = dayjs(form.value.entryDate).add(7, "day").format("YYYY-MM-DD");
+ await appendEditablePlaceholderRow();
+ dialogFormVisible.value = !keepPageMode;
+ };
+
const paginationChange = obj => {
page.current = obj.page;
page.size = obj.limit;
@@ -2568,7 +4130,7 @@
tableLoading.value = true;
const { entryDate, ...rest } = searchForm;
// 灏嗚寖鍥存棩鏈熷瓧娈典紶閫掔粰鍚庣
- const params = { ...rest, ...page };
+ const params = { ...rest, ...page, reviewStatusList: [0, 1, 2] };
// 绉婚櫎褰曞叆鏃ユ湡鐨勯粯璁ゅ�艰缃紝鍙繚鐣欒寖鍥存棩鏈熷瓧娈�
delete params.entryDate;
// 鏌ヨ瀹㈡埛鍚嶇О涓庢柊澧炰繚鎸佷竴鑷达細鍏堥�� customerId锛屽啀鏄犲皠涓� customerName 鏌ヨ
@@ -2586,14 +4148,42 @@
delete params.customerName;
}
}
+ const widthValue = params.width != null ? String(params.width).trim() : "";
+ if (widthValue) {
+ params.width = widthValue;
+ } else {
+ delete params.width;
+ }
+ const heightValue = params.height != null ? String(params.height).trim() : "";
+ if (heightValue) {
+ params.height = heightValue;
+ } else {
+ delete params.height;
+ }
+ const shouldAutoExpandBySize = Boolean(params.width || params.height);
delete params.customerId;
return ledgerListPage(params)
- .then(res => {
+ .then(async res => {
tableLoading.value = false;
tableData.value = res.records;
tableData.value.map(item => {
item.children = [];
});
+ if (shouldAutoExpandBySize && tableData.value.length > 0) {
+ const loadChildrenTasks = tableData.value.map(item =>
+ productList({ salesLedgerId: item.id, type: 1 })
+ .then(productRes => {
+ item.children = Array.isArray(productRes?.data)
+ ? productRes.data
+ : [];
+ })
+ .catch(() => {
+ item.children = [];
+ })
+ );
+ await Promise.all(loadChildrenTasks);
+ expandedRowKeys.value = tableData.value.map(item => item.id);
+ }
total.value = res.total;
return res;
})
@@ -2609,17 +4199,62 @@
return;
}
const row = selectedRows.value[0] || {};
+ if (Number(row.reviewStatus) !== 1) {
+ ElMessage.warning("鍙湁宸插鏍哥殑鍙拌处鎵嶈兘杩涜鍏ュ簱鎿嶄綔");
+ return;
+ }
const id = row?.id;
if (!id) {
ElMessage.warning("鎵�閫夋暟鎹己灏慽d锛屾棤娉曞叆搴�");
return;
}
- if (Number(row.stockStatus) === 1) {
- ElMessage.info("璇ュ彴璐﹀凡鍏ュ簱锛屾棤闇�閲嶅鎿嶄綔");
+ if (Number(row.stockStatus) === 2) {
+ ElMessage.info("璇ュ彴璐﹀凡鍏ㄩ儴鍏ュ簱锛屾棤闇�閲嶅鎿嶄綔");
return;
}
+
+ currentStockLedgerId.value = id;
+ selectedStockProductIds.value = [];
+ stockProductList.value = [];
+ stockApproverNodes.value = [{ id: 1, userId: null }];
+ nextStockApproverId = 2;
+ stockDialogVisible.value = true;
+ stockLoading.value = true;
+
try {
- await ElMessageBox.confirm("纭瀵规墍閫夊彴璐︽墽琛屽叆搴擄紵", "鎻愮ず", {
+ 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?.("鑾峰彇浜у搧鎴栧鎵逛汉澶辫触");
+ } 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;
+ }
+
+ try {
+ await ElMessageBox.confirm("纭瀵规墍閫変骇鍝佹墽琛屽叆搴擄紵", "鎻愮ず", {
confirmButtonText: "纭畾",
cancelButtonText: "鍙栨秷",
type: "warning",
@@ -2627,10 +4262,29 @@
} catch {
return;
}
+
proxy?.$modal?.loading?.("姝e湪鍏ュ簱锛岃绋嶅��...");
try {
- await salesStock({ id });
+ 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;
await getList();
} catch (e) {
proxy?.$modal?.msgError?.("鍏ュ簱澶辫触锛岃绋嶅悗閲嶈瘯");
@@ -2642,10 +4296,15 @@
// 鎵撳紑鈥滃伐鑹鸿矾绾块厤缃�濋�夋嫨寮圭獥锛堝繀椤绘樉寮忛�夋嫨锛�
const openProcessFlowSelect = async ledgerRow => {
if (!ledgerRow) return;
- if (!ledgerRow.isEdit) return;
+ if (Number(ledgerRow.reviewStatus) !== 1) {
+ proxy.$modal.msgWarning("鍙湁宸插鏍哥殑鍙拌处鎵嶈兘閫夋嫨宸ヨ壓璺嚎");
+ return;
+ }
+ // if (!ledgerRow.isEdit) return;
processFlowSelectLedgerRow.value = ledgerRow;
processFlowSelectDefaultRouteId.value = null;
+ processFlowSelectDefaultRecordList.value = [];
processFlowSelectBoundRouteId.value = null;
processFlowSelectBoundRouteName.value = "";
@@ -2655,25 +4314,29 @@
const boundId = info?.processRouteId ?? info?.routeId ?? info?.id ?? null;
const boundName =
info?.processRouteName ?? info?.routeName ?? info?.name ?? "";
+ const recordList = Array.isArray(info?.recordList) ? info.recordList : [];
processFlowSelectBoundRouteId.value = boundId;
processFlowSelectBoundRouteName.value = boundName;
processFlowSelectDefaultRouteId.value = boundId;
+ processFlowSelectDefaultRecordList.value = recordList;
} catch (e) {
// 鏌ヨ澶辫触鏃舵寜鏈粦瀹氬鐞嗭紝涓嶉樆濉炲脊绐�
processFlowSelectBoundRouteId.value = null;
processFlowSelectBoundRouteName.value = "";
processFlowSelectDefaultRouteId.value = null;
+ processFlowSelectDefaultRecordList.value = [];
}
processFlowSelectDialogVisible.value = true;
};
// 缁戝畾宸ヨ壓璺嚎鍒板綋鍓嶅彴璐︽暟鎹�
- const handleProcessFlowSelectConfirm = async routeId => {
+ const handleProcessFlowSelectConfirm = async payload => {
const ledgerRow = processFlowSelectLedgerRow.value;
if (!ledgerRow?.id) return;
- const finalRouteId = routeId ?? null;
+ const finalRouteId = payload?.routeId ?? payload ?? null;
+ const recordList = Array.isArray(payload?.recordList) ? payload.recordList : [];
if (!finalRouteId) return;
const oldRouteId = processFlowSelectBoundRouteId.value;
@@ -2703,6 +4366,7 @@
await saleProcessBind({
salesLedgerId: ledgerRow.id,
processRouteId: finalRouteId,
+ recordList,
});
proxy?.$modal?.msgSuccess?.("宸ヨ壓璺嚎缁戝畾鎴愬姛");
@@ -2725,24 +4389,90 @@
});
};
const formattedNumber = (row, column, cellValue) => {
- return parseFloat(cellValue).toFixed(2);
+ if (cellValue === null || cellValue === undefined || cellValue === "") {
+ return "";
+ }
+ const num = Number(cellValue);
+ return Number.isFinite(num) ? num.toFixed(2) : "";
+ };
+
+ const scrollElementIntoVisibleArea = target => {
+ if (!target || !(target instanceof HTMLElement)) return;
+ let parent = target.parentElement;
+ while (parent && parent !== document.body) {
+ const style = window.getComputedStyle(parent);
+ const canScrollX =
+ (style.overflowX === "auto" ||
+ style.overflowX === "scroll" ||
+ style.overflowX === "overlay") &&
+ parent.scrollWidth > parent.clientWidth;
+ const canScrollY =
+ (style.overflowY === "auto" ||
+ style.overflowY === "scroll" ||
+ style.overflowY === "overlay") &&
+ parent.scrollHeight > parent.clientHeight;
+
+ if (canScrollX || canScrollY) {
+ const parentRect = parent.getBoundingClientRect();
+ const targetRect = target.getBoundingClientRect();
+ if (canScrollX) {
+ const targetCenterX = targetRect.left + targetRect.width / 2;
+ const parentCenterX = parentRect.left + parentRect.width / 2;
+ const deltaX = targetCenterX - parentCenterX;
+ if (Math.abs(deltaX) > 2) {
+ parent.scrollLeft += deltaX;
+ }
+ }
+
+ if (canScrollY) {
+ const targetCenterY = targetRect.top + targetRect.height / 2;
+ const parentCenterY = parentRect.top + parentRect.height / 2;
+ const deltaY = targetCenterY - parentCenterY;
+ if (Math.abs(deltaY) > 2) {
+ parent.scrollTop += deltaY;
+ }
+ }
+ }
+
+ parent = parent.parentElement;
+ }
+ };
+
+ const handleTabScrollFollow = e => {
+ if (!e || e.key !== "Tab") return;
+ requestAnimationFrame(() => {
+ const active = document.activeElement;
+ if (active instanceof HTMLElement) {
+ scrollElementIntoVisibleArea(active);
+ }
+ });
};
// 鑾峰彇tree瀛愭暟鎹�
const getModels = value => {
- // 浜у搧澶х被鍙樺寲鏃讹紝閲嶇疆瑙勬牸鍨嬪彿涓庡帤搴︼紝閬垮厤鏃у�兼畫鐣�
- productForm.value.productModelId = null;
- productForm.value.specificationModel = "";
- productForm.value.thickness = null;
-
if (!value) {
productForm.value.productCategory = "";
- modelOptions.value = [];
+ modelList({}).then(res => {
+ modelOptions.value = res || [];
+ });
return;
}
productForm.value.productCategory = findNodeById(productOptions.value, value);
modelList({ id: value }).then(res => {
modelOptions.value = res || [];
+ const currentModelId = productForm.value.productModelId;
+ if (currentModelId) {
+ const isValid = modelOptions.value.some(item => item.id === currentModelId);
+ if (!isValid) {
+ productForm.value.productModelId = null;
+ productForm.value.specificationModel = "";
+ productForm.value.thickness = null;
+ }
+ } else {
+ productForm.value.productModelId = null;
+ productForm.value.specificationModel = "";
+ productForm.value.thickness = null;
+ }
});
};
const getProductModel = value => {
@@ -2761,6 +4491,12 @@
modelThickness === ""
? null
: Number(modelThickness);
+
+ const categoryId = selectedModel?.productId;
+ if (categoryId && productForm.value.__productCategoryId !== categoryId) {
+ productForm.value.__productCategoryId = categoryId;
+ productForm.value.productCategory = findNodeById(productOptions.value, categoryId);
+ }
} else {
productForm.value.specificationModel = null;
productForm.value.thickness = null;
@@ -2866,6 +4602,8 @@
const summarizeMainTable = param => {
return proxy.summarizeTable(param, [
"contractAmount",
+ "productTotalQuantity",
+ "productTotalArea",
"taxInclusiveTotalPrice",
"taxExclusiveTotalPrice",
]);
@@ -2878,12 +4616,130 @@
"taxExclusiveTotalPrice",
]);
};
+ const summarizeProductTable = param => {
+ return proxy.summarizeTable(param, [
+ "quantity",
+ "settlePieceArea",
+ "actualTotalArea",
+ "taxInclusiveTotalPrice",
+ "taxExclusiveTotalPrice",
+ ]);
+ };
+
+ const initAddFormState = async () => {
+ operationType.value = "add";
+ isCompletedOrder.value = false;
+ isReviewedEdit.value = false;
+ form.value = {};
+ productData.value = [];
+ fileList.value = [];
+ selectedQuotation.value = null;
+ pinnedProductTemplate.value = null;
+
+ const userLists = await userListNoPage();
+ userList.value = userLists.data;
+ customerList().then(res => {
+ customerOption.value = res;
+ });
+
+ form.value.entryPerson = userStore.id;
+ form.value.entryDate = getCurrentDate();
+ form.value.executionDate = getCurrentDate();
+ form.value.customerRemarks = "";
+ form.value.deliveryDate = dayjs(form.value.entryDate)
+ .add(7, "day")
+ .format("YYYY-MM-DD");
+ await appendEditablePlaceholderRow();
+ };
+
+ const initEditFormState = async rowId => {
+ operationType.value = "edit";
+ isCompletedOrder.value = false;
+ form.value = {};
+ productData.value = [];
+ fileList.value = [];
+ selectedQuotation.value = null;
+ pinnedProductTemplate.value = null;
+
+ const userLists = await userListNoPage();
+ userList.value = userLists.data;
+ customerList().then(res => {
+ customerOption.value = res;
+ });
+
+ currentId.value = rowId;
+ const res = await getSalesLedgerWithProducts({ id: rowId, type: 1 });
+ form.value = { ...res };
+ form.value.entryPerson = Number(res.entryPerson);
+ form.value.customerRemarks =
+ res?.customerRemarks ?? res?.customer_remarks ?? "";
+ productData.value = form.value.productData || [];
+ fileList.value = form.value.salesLedgerFiles || [];
+ isReviewedEdit.value = Number(res?.reviewStatus) === 1;
+ await appendEditablePlaceholderRow();
+ };
+
+ const enterAddPage = async detail => {
+ pendingAddPrefillDetail.value = detail || null;
+ const query = { ...route.query, mode: "add" };
+ await router.push({ path: route.path, query });
+ };
+
+ const enterEditPage = async row => {
+ const query = { ...route.query, mode: "edit", id: row?.id };
+ await router.push({ path: route.path, query });
+ };
+
+ const exitFormPage = async (shouldRefresh = false) => {
+ const query = { ...route.query };
+ delete query.mode;
+ delete query.id;
+ pendingAddPrefillDetail.value = null;
+ await router.push({ path: route.path, query });
+ closeDia(false);
+ if (shouldRefresh) {
+ getList();
+ }
+ };
+
+ watch(
+ pageMode,
+ async enabled => {
+ if (enabled === "add") {
+ await initAddFormState();
+ if (pendingAddPrefillDetail.value) {
+ await openFormWithPreFill(pendingAddPrefillDetail.value, true);
+ pendingAddPrefillDetail.value = null;
+ }
+ return;
+ }
+ if (enabled === "edit" && route.query.id) {
+ await initEditFormState(route.query.id);
+ }
+ },
+ { immediate: true }
+ );
+
// 鎵撳紑寮规
const openForm = async (type, row) => {
- operationType.value = type;
+ if (type === "add") {
+ await enterAddPage();
+ return;
+ }
+ if (type === "edit") {
+ await enterEditPage(row);
+ return;
+ }
+ // 宸插畬鎴愯鍗曞己鍒朵负鍙妯″紡锛屼絾闄勪欢涓婁紶浠嶅彲鐢�
+ const isCompleted = Number(row?.orderStatus) === 1;
+ const effectiveType = isCompleted ? 'view' : type;
+ operationType.value = effectiveType;
+ isCompletedOrder.value = isCompleted;
form.value = {};
productData.value = [];
selectedQuotation.value = null;
+ // 宸插鏍歌鍗曠紪杈戞椂锛屾爣璁板彧鑳戒慨鏀瑰崟浠凤紙宸插畬鎴愯鍗曞凡寮哄埗view锛屾澶勪笉浼氬啀鍛戒腑锛�
+ isReviewedEdit.value = effectiveType === "edit" && Number(row?.reviewStatus) === 1;
let userLists = await userListNoPage();
userList.value = userLists.data;
customerList().then(res => {
@@ -3087,18 +4943,21 @@
console.log("productData.value--", productData.value);
// 琛屽唴缂栬緫鏈繚瀛樻椂涓嶅厑璁告彁浜わ紝閬垮厤鑴忔暟鎹�/涓存椂瀛楁杩涘叆鍚庣
const hasEditingRow = (productData.value || []).some(
- r => r && r.__editing
+ r => r && r.__editing && !r.__placeholder
);
if (hasEditingRow) {
proxy.$modal.msgWarning("浜у搧淇℃伅瀛樺湪鏈繚瀛樼殑缂栬緫琛岋紝璇峰厛淇濆瓨鎴栧彇娑�");
return;
}
if (productData.value !== null && productData.value.length > 0) {
- const cleanedProducts = (productData.value || []).map(p => {
+ const cleanedProducts = (productData.value || [])
+ .filter(p => p && !p.__placeholder)
+ .map(p => {
if (!p || typeof p !== "object") return p;
const {
__editing,
__isNew,
+ __placeholder,
__backup,
__productCategoryId,
__tempKey,
@@ -3109,6 +4968,10 @@
rest.quantity = Number(rest.quantity ?? 0) || 0;
return rest;
});
+ if (cleanedProducts.length === 0) {
+ proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
+ return;
+ }
form.value.productData = proxy.HaveJson(cleanedProducts);
} else {
proxy.$modal.msgWarning("璇锋坊鍔犱骇鍝佷俊鎭�");
@@ -3120,10 +4983,16 @@
}
form.value.tempFileIds = tempFileIds;
form.value.type = 1;
+ form.value.reviewStatus = form.value.reviewStatus ?? 0; // 榛樿瀹℃牳鐘舵�佷负寰呭鏍�
+ form.value.stockStatus = 0; // 榛樿鍏ュ簱鐘舵�佷负鏈叆搴�
const submitPayload = { ...form.value };
delete submitPayload.paymentMethod;
addOrUpdateSalesLedger(submitPayload).then(res => {
proxy.$modal.msgSuccess("鎻愪氦鎴愬姛");
+ if (isFormPageMode.value) {
+ exitFormPage(true);
+ return;
+ }
closeDia();
getList();
});
@@ -3131,9 +5000,16 @@
});
};
// 鍏抽棴寮规
- const closeDia = () => {
- proxy.resetForm("formRef");
+ const closeDia = (resetFormRef = true) => {
+ if (resetFormRef) {
+ proxy.resetForm("formRef");
+ }
dialogFormVisible.value = false;
+ isCompletedOrder.value = false;
+ isReviewedEdit.value = false;
+ if (!isFormPageMode.value) {
+ fileList.value = [];
+ }
};
const productIndex = ref(0);
@@ -3320,10 +5196,35 @@
productData.value.splice(index, 1);
}
});
+ appendEditablePlaceholderRow();
} else {
+ const placeholderRows = productSelectedRows.value.filter(
+ item => item?.__placeholder
+ );
+ if (placeholderRows.length > 0) {
+ placeholderRows.forEach(selectedRow => {
+ const index = productData.value.findIndex(product => {
+ if (!product || !selectedRow) return false;
+ return (
+ product.__tempKey &&
+ selectedRow.__tempKey &&
+ String(product.__tempKey) === String(selectedRow.__tempKey)
+ );
+ });
+ if (index !== -1) {
+ productData.value.splice(index, 1);
+ }
+ });
+ appendEditablePlaceholderRow();
+ }
let ids = [];
- if (productSelectedRows.value.length > 0) {
- ids = productSelectedRows.value.map(item => item.id);
+ const persistedRows = productSelectedRows.value.filter(
+ item => !item?.__placeholder && item?.id !== null && item?.id !== undefined
+ );
+ if (persistedRows.length > 0) {
+ ids = persistedRows.map(item => item.id);
+ } else {
+ return;
}
ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚垹闄わ紝鏄惁纭鍒犻櫎锛�", "瀵煎嚭", {
confirmButtonText: "纭",
@@ -3353,18 +5254,51 @@
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 = () => {
+ importUpload.open = false;
+ if (importUploadRef.value) {
+ importUploadRef.value.clearFiles();
+ }
};
// 鎻愪氦瀵煎叆鏂囦欢
@@ -3373,19 +5307,65 @@
proxy.$refs["importUploadRef"].submit();
};
- // 瀵煎嚭
+ // 瀵煎嚭锛堟寜褰撳墠鏌ヨ鏉′欢瀵煎嚭锛�
const handleOut = () => {
- ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
- confirmButtonText: "纭",
- cancelButtonText: "鍙栨秷",
- type: "warning",
- })
- .then(() => {
- proxy.download("/sales/ledger/export", {}, "閿�鍞彴璐�.xlsx");
- })
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
- });
+ // 鏋勫缓鏌ヨ鍙傛暟锛堜笌 getList 淇濇寔涓�鑷达級
+ const { entryDate, ...rest } = searchForm;
+ const params = { ...rest };
+
+ // 澶勭悊褰曞叆鏃ユ湡鑼冨洿
+ if (entryDate && entryDate.length === 2) {
+ params.entryDateStart = entryDate[0];
+ params.entryDateEnd = entryDate[1];
+ }
+
+ // 澶勭悊瀹㈡埛鍚嶇О鏌ヨ
+ const selectedCustomer = (customerOption.value || []).find(
+ item => String(item?.id ?? "") === String(params.customerId ?? "")
+ );
+ if (selectedCustomer?.customerName) {
+ params.customerName = String(selectedCustomer.customerName).trim();
+ }
+ delete params.customerId;
+
+ // 澶勭悊浜у搧瀹介珮鏌ヨ鍙傛暟
+ const widthValue = params.width != null ? String(params.width).trim() : "";
+ const heightValue = params.height != null ? String(params.height).trim() : "";
+ if (!widthValue) delete params.width;
+ if (!heightValue) delete params.height;
+
+ proxy.download("/sales/ledger/exportWithProducts", params, "閿�鍞彴璐�.xlsx");
+ };
+
+ const handleExportProcessRoute = () => {
+ if (selectedRows.value.length === 0) {
+ proxy?.$modal?.msgWarning?.("璇烽�夋嫨瑕佸鍑虹殑閿�鍞彴璐�");
+ return;
+ }
+ const salesLedgerIds = selectedRows.value
+ .map(item => item.id)
+ .filter(id => id !== null && id !== undefined && id !== "");
+ if (salesLedgerIds.length === 0) {
+ proxy?.$modal?.msgWarning?.("璇烽�夋嫨瑕佸鍑虹殑閿�鍞彴璐�");
+ return;
+ }
+
+ const params = {
+ salesLedgerIds: salesLedgerIds.join(","),
+ };
+ if (
+ Array.isArray(processRouteExportDateRange.value) &&
+ processRouteExportDateRange.value.length === 2
+ ) {
+ params.completedTimeStart = processRouteExportDateRange.value[0];
+ params.completedTimeEnd = processRouteExportDateRange.value[1];
+ }
+
+ proxy.download(
+ "/sales/ledger/exportProcessRoute",
+ params,
+ "閿�鍞彴璐﹀伐鑹鸿矾绾垮鍑�.xlsx"
+ );
};
/** 鍒ゆ柇鍗曚釜浜у搧鏄惁宸插彂璐э紙鏍规嵁shippingStatus鍒ゆ柇锛屽凡鍙戣揣鎴栧鏍搁�氳繃涓嶅彲缂栬緫鍜屽垹闄わ級 */
const isProductShipped = product => {
@@ -3416,6 +5396,15 @@
return;
}
const ids = selectedRows.value.map(item => item.id);
+
+ // 妫�鏌ユ槸鍚︽湁宸插鏍哥殑鍙拌处
+ const audited = selectedRows.value.filter(
+ row => Number(row.reviewStatus) === 1
+ );
+ if (audited.length > 0) {
+ proxy.$modal.msgWarning("閫変腑鐨勬暟鎹腑鍖呭惈宸插鏍搁」锛屼笉鑳藉垹闄�");
+ return;
+ }
// 妫�鏌ユ槸鍚︽湁宸茶繘琛屽彂璐ф垨鍙戣揣瀹屾垚鐨勯攢鍞鍗曪紝鑻ユ湁鍒欎笉鍏佽鍒犻櫎
const cannotDeleteNames = [];
@@ -3464,11 +5453,20 @@
command !== "salesDeliveryNote"
)
return;
+
+ if (selectedRows.value.length === 0) {
+ proxy.$modal.msgWarning("璇疯嚦灏戦�夋嫨涓�鏉¢攢鍞彴璐︽暟鎹繘琛屾墦鍗�");
+ return;
+ }
+ const hasUnapproved = selectedRows.value.some(
+ row => Number(row.reviewStatus) !== 1
+ );
+ if (hasUnapproved) {
+ proxy.$modal.msgWarning("閫変腑鐨勬暟鎹腑鍖呭惈鏈鏍搁」锛屾棤娉曟墦鍗�");
+ return;
+ }
+
if (command === "salesDeliveryNote") {
- if (selectedRows.value.length === 0) {
- proxy.$modal.msgWarning("璇疯嚦灏戦�夋嫨涓�鏉¢攢鍞彴璐︽暟鎹繘琛屾墦鍗�");
- return;
- }
const customerNames = Array.from(
new Set(
selectedRows.value.map(item => String(item?.customerName ?? "").trim())
@@ -3503,7 +5501,11 @@
try {
const res = await getSalesInvoices(selectedIds);
const salesInvoiceData = res?.data ?? {};
- printSalesDeliveryNote(salesInvoiceData, selectedRow);
+ await printSalesDeliveryNote(salesInvoiceData, selectedRow, selectedIds);
+ // 鎵撳嵃鎴愬姛鍚庨�掑鍗曟嵁鎵撳嵃娆℃暟
+ selectedIds.forEach(id => {
+ incrementPrintCount({ id, printType: 'document' }).catch(() => {});
+ });
} catch (error) {
console.error("鎵撳嵃閿�鍞彂璐у崟澶辫触:", error);
proxy.$modal.msgError("鎵撳嵃澶辫触锛岃绋嶅悗閲嶈瘯");
@@ -3529,10 +5531,45 @@
const res = await getSalesOrder(selectedId);
const salesOrderData = res?.data ?? {};
printSalesOrder(salesOrderData);
+ // 鎵撳嵃鎴愬姛鍚庨�掑鍗曟嵁鎵撳嵃娆℃暟
+ incrementPrintCount({ id: selectedId, printType: 'document' }).catch(() => {});
} else {
const res = await getProcessCard(selectedId);
const processCardData = res?.data ?? {};
- printFinishedProcessCard(processCardData);
+ // 琛ラ綈浜岀淮鐮佹墍闇�鐨勫彴璐︽爣璇嗭紙鍚庣鏁版嵁鏈夋椂涓嶅甫 id锛�
+ if (processCardData && typeof processCardData === "object") {
+ processCardData.salesLedgerId =
+ processCardData.salesLedgerId ?? selectedId;
+ processCardData.salesContractNo =
+ (processCardData.salesContractNo ?? "").trim() ||
+ String(selectedRow?.salesContractNo ?? "").trim();
+ }
+ const routeNodes = processCardData?.routeNodes;
+ const isProcessRouteEmpty =
+ !Array.isArray(routeNodes) || routeNodes.length === 0;
+ if (isProcessRouteEmpty) {
+ proxy.$modal.closeLoading();
+ try {
+ await ElMessageBox.confirm(
+ "褰撳墠璁㈠崟鏈粦瀹氬伐鑹鸿矾绾夸篃娌℃湁璁剧疆榛樿鐨勫伐鑹鸿矾绾匡紝鏄惁浠嶈鎵撳嵃锛�",
+ "鎻愮ず",
+ {
+ confirmButtonText: "鎵撳嵃",
+ cancelButtonText: "鍙栨秷",
+ type: "warning",
+ }
+ );
+ } catch {
+ return;
+ }
+ await printFinishedProcessCard(processCardData);
+ // 鎵撳嵃鎴愬姛鍚庨�掑鍗曟嵁鎵撳嵃娆℃暟
+ incrementPrintCount({ id: selectedId, printType: 'document' }).catch(() => {});
+ } else {
+ await printFinishedProcessCard(processCardData);
+ // 鎵撳嵃鎴愬姛鍚庨�掑鍗曟嵁鎵撳嵃娆℃暟
+ incrementPrintCount({ id: selectedId, printType: 'document' }).catch(() => {});
+ }
}
} catch (error) {
console.error(
@@ -3555,7 +5592,12 @@
return;
}
- const selectedId = selectedRows.value[0]?.id;
+ const selectedRow = selectedRows.value[0];
+ if (Number(selectedRow?.reviewStatus) !== 1) {
+ proxy.$modal.msgWarning("鍙湁宸插鏍哥殑鍙拌处鎵嶈兘杩涜鏍囩鎵撳嵃");
+ return;
+ }
+ const selectedId = selectedRow?.id;
if (!selectedId) {
proxy.$modal.msgWarning("褰撳墠閫夋嫨鏁版嵁缂哄皯ID锛屾棤娉曟墦鍗版爣绛�");
return;
@@ -3570,6 +5612,8 @@
return;
}
printSalesLabel(labelList);
+ // 鎵撳嵃鎴愬姛鍚庨�掑鏍囩鎵撳嵃娆℃暟
+ incrementPrintCount({ id: selectedId, printType: 'label' }).catch(() => {});
} catch (error) {
console.error("鎵撳嵃鏍囩澶辫触:", error);
proxy.$modal.msgError("鎵撳嵃鏍囩澶辫触锛岃绋嶅悗閲嶈瘯");
@@ -3924,15 +5968,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;
}
// 鑾峰彇鍙戣揣鐘舵��
@@ -3948,25 +5992,94 @@
return statusStr === "寰呭彂璐�" || statusStr === "瀹℃牳鎷掔粷";
};
+ const getLedgerDisplayName = ledger =>
+ String(ledger?.salesContractNo || "").trim() ||
+ String(ledger?.projectName || "").trim() ||
+ `ID:${ledger?.id ?? "-"}`;
+
+ const validateLedgersStockedBeforeDelivery = async ledgers => {
+ const invalidLedgers = [];
+ for (const ledger of ledgers || []) {
+ const ledgerId = ledger?.id;
+ const ledgerName = getLedgerDisplayName(ledger);
+ if (!ledgerId) {
+ invalidLedgers.push(`${ledgerName}(缂哄皯鍙拌处ID)`);
+ continue;
+ }
+ let products = [];
+ try {
+ const res = await productList({ salesLedgerId: ledgerId, type: 1 });
+ products = Array.isArray(res?.data) ? res.data : [];
+ } catch (e) {
+ invalidLedgers.push(`${ledgerName}(鏄庣粏鍔犺浇澶辫触)`);
+ continue;
+ }
+ const unstockedProducts = products.filter(
+ item => Number(item?.productStockStatus) !== 2
+ );
+ if (unstockedProducts.length > 0) {
+ invalidLedgers.push(
+ `${ledgerName}(鏈叏閮ㄥ叆搴�${unstockedProducts.length}鏉�)`
+ );
+ }
+ }
+ return invalidLedgers;
+ };
+
const handleBulkDelivery = async () => {
if (selectedRows.value.length === 0) {
proxy.$modal.msgWarning("璇烽�夋嫨鏁版嵁");
return;
}
- // 鍙厑璁搞�愭湭鍙戣揣/瀹℃壒澶辫触銆戣繘鍏ュ彂璐ф祦绋�
- const canDeliveryLedgers = selectedRows.value.filter(r => {
- const status = Number(r.deliveryStatus);
- return status === 1 || status === 3;
+ // 鍏佽锛�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 (!ledgerAllowsDelivery(row.deliveryStatus)) {
+ proxy.$modal.msgWarning(
+ "浠呮湭鍙戣揣銆佸鎵逛笉閫氳繃銆佸鎵归�氳繃鎴栭儴鍒嗗彂璐х殑鍙拌处鍙互鍙戣揣"
+ );
+ isTrue = false;
+ return;
+ }
+ if (row.deliveryStatus !== statusItem) {
+ proxy.$modal.msgWarning("璇烽�夋嫨鐩稿悓鐘舵�佺殑閿�鍞彴璐�");
+ isTrue = false;
+ return;
+ }
});
- if (canDeliveryLedgers.length === 0) {
- proxy.$modal.msgWarning("浠呮湭鍙戣揣鎴栧鎵瑰け璐ョ殑鍙拌处鍙互鍙戣揣");
+ if (!isTrue) {
return;
}
- // 宸插彂璐у彴璐︼細寮圭獥鎻愰啋锛屼笉鑳藉啀娆″彂璐э紙4 瑙嗕负宸插彂璐э級
+ proxy.$modal.loading("姝e湪鏍¢獙鏄庣粏鍏ュ簱鐘舵�侊紝璇风◢鍊�...");
+ const invalidLedgers = await validateLedgersStockedBeforeDelivery(
+ selectedRows.value
+ );
+ proxy.$modal.closeLoading();
+ if (invalidLedgers.length > 0) {
+ try {
+ await ElMessageBox.alert(
+ `浠ヤ笅閿�鍞彴璐﹀瓨鍦ㄦ湭鍏ㄩ儴鍏ュ簱鐨勬槑缁嗭紝鏆備笉鍙彂璐э細\n${invalidLedgers.join(
+ "\n"
+ )}`,
+ "鎻愮ず",
+ {
+ type: "warning",
+ confirmButtonText: "鐭ラ亾浜�",
+ }
+ );
+ } catch {
+ /* 鍏抽棴寮圭獥 */
+ }
+ return;
+ }
+
+ // 宸插叏閮ㄥ彂璐э紙5锛夌殑鍙拌处锛氬脊绐楁彁閱掞紝涓嶈兘鍐嶆鍙戣揣
const shippedLedgers = selectedRows.value.filter(
- r => Number(r.deliveryStatus) === 4
+ r => Number(r.deliveryStatus) === 5
);
if (shippedLedgers.length === selectedRows.value.length) {
try {
@@ -4023,9 +6136,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}锛岃烦杩囧彂璐
);
@@ -4080,11 +6193,31 @@
};
// 鎵撳紑鍙戣揣寮规锛堝崟鏉★級
- const openDeliveryForm = row => {
- // 鍙厑璁搞�愭湭鍙戣揣/瀹℃壒澶辫触銆戝彂璐э紱宸插彂璐�/瀹℃壒涓笉鍏佽
+ 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;
+ }
+
+ proxy.$modal.loading("姝e湪鏍¢獙鏄庣粏鍏ュ簱鐘舵�侊紝璇风◢鍊�...");
+ const invalidLedgers = await validateLedgersStockedBeforeDelivery([row]);
+ proxy.$modal.closeLoading();
+ if (invalidLedgers.length > 0) {
+ try {
+ await ElMessageBox.alert(
+ `褰撳墠閿�鍞彴璐﹀瓨鍦ㄦ湭鍏ㄩ儴鍏ュ簱鐨勬槑缁嗭紝鏆備笉鍙彂璐э細\n${invalidLedgers[0]}`,
+ "鎻愮ず",
+ {
+ type: "warning",
+ confirmButtonText: "鐭ラ亾浜�",
+ }
+ );
+ } catch {
+ /* 鍏抽棴寮圭獥 */
+ }
return;
}
@@ -4128,6 +6261,7 @@
const run = async () => {
for (const salesLedgerId of uniqueLedgerIds) {
await addShippingInfo({
+ scanOutbound: false,
salesLedgerId,
type: deliveryForm.value.type,
approveUserIds,
@@ -4180,7 +6314,7 @@
currentFactoryName.value = res.user.currentFactoryName;
};
onMounted(() => {
- getList();
+ handleStatusTabChange(activeStatusTab.value);
customerList().then(res => {
customerOption.value = res;
});
@@ -4230,9 +6364,265 @@
margin-top: unset;
}
+ .sales-ledger-status-bar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 12px 14px;
+ margin-bottom: 14px;
+ background: linear-gradient(180deg, #f9fbff 0%, #f2f6ff 100%);
+ border: 1px solid #dbe6ff;
+ border-radius: 12px;
+ }
+
+ .sales-ledger-status-tab {
+ min-width: 88px;
+ height: 34px;
+ padding: 0 14px;
+ border: 1px solid #d7def0;
+ border-radius: 9px;
+ background: #fff;
+ color: #4b5567;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.18s ease;
+ }
+
+ .sales-ledger-status-tab:hover {
+ border-color: #8fb3ff;
+ color: #1d4ed8;
+ }
+
+ .sales-ledger-status-tab.is-active {
+ border-color: #1d4ed8;
+ background: linear-gradient(135deg, #2f67f6 0%, #1d4ed8 100%);
+ color: #fff;
+ box-shadow: 0 8px 18px rgba(29, 78, 216, 0.16);
+ }
+
.actions {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
+
+ .sales-ledger-toolbar {
+ align-items: flex-start;
+ gap: 14px;
+ padding: 10px 12px;
+ background: #fff;
+ border: 1px solid #e7edf7;
+ border-radius: 12px;
+ }
+
+ .sales-ledger-toolbar-actions {
+ display: flex;
+ flex: 1;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ gap: 10px;
+ }
+
+ .sales-ledger-toolbar-group {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 4px;
+ background: #f7f9fc;
+ border: 1px solid #edf1f7;
+ border-radius: 10px;
+ }
+
+ .sales-ledger-toolbar-group--muted {
+ background: #fbfcfe;
+ }
+
+ .sales-ledger-page-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 16px;
+ margin-bottom: 16px;
+ }
+
+ .sales-ledger-page-header-content {
+ flex: 1;
+ }
+
+ .sales-ledger-page-title {
+ font-size: 22px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 1.3;
+ }
+
+ .sales-ledger-page-back {
+ flex-shrink: 0;
+ }
+
+ .sales-ledger-page-subtitle {
+ margin-top: 6px;
+ font-size: 13px;
+ color: #909399;
+ }
+
+ .sales-ledger-page-form {
+ padding: 20px;
+ background: #fff;
+ border-radius: 8px;
+ }
+
+ .sales-ledger-page-actions {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+ margin-top: 24px;
+ }
+
+ .compact-product-table {
+ :deep(.el-table__cell) {
+ padding: 4px 0;
+ font-size: 12px;
+ }
+
+ :deep(.cell) {
+ line-height: 1.2;
+ }
+
+ :deep(.el-input__wrapper) {
+ padding: 0 8px;
+ }
+
+ :deep(.el-input-number .el-input__wrapper) {
+ padding-left: 8px;
+ padding-right: 8px;
+ }
+
+ :deep(.el-input-number),
+ :deep(.el-select),
+ :deep(.el-input),
+ :deep(.el-tree-select) {
+ font-size: 12px;
+ }
+
+ :deep(.el-input-number .el-input__inner) {
+ height: 28px;
+ font-size: 12px;
+ text-align: left;
+ }
+
+ :deep(.el-select .el-input__inner),
+ :deep(.el-input .el-input__inner) {
+ height: 28px;
+ }
+
+ :deep(.el-button--small),
+ :deep(.el-button.is-link) {
+ font-size: 12px;
+ padding: 2px 4px;
+ }
+ }
+
+ .ledger-qr-dialog {
+ text-align: center;
+ padding-bottom: 8px;
+ }
+
+ .ledger-qr-composite-img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+ margin: 0 auto 28px;
+ }
+
+ .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) {
+ .sales-ledger-status-tab {
+ min-width: unset;
+ flex: 1 1 calc(50% - 8px);
+ }
+
+ .sales-ledger-toolbar,
+ .sales-ledger-page-header {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .sales-ledger-toolbar-actions {
+ width: 100%;
+ justify-content: flex-start;
+ }
+
+ .approver-node-item {
+ flex: 0 0 100%;
+ }
+ }
+
+ .reverse-audit-warning {
+ display: flex;
+ align-items: flex-start;
+ padding: 10px 12px;
+ margin-top: 8px;
+ background-color: #fdf6ec;
+ border: 1px solid #faecd8;
+ border-radius: 4px;
+ color: #e6a23c;
+ font-size: 13px;
+ line-height: 1.6;
+ }
+
+ .reverse-audit-dialog .el-radio {
+ display: flex;
+ align-items: center;
+ height: 36px;
+ margin-right: 0;
+ margin-bottom: 8px;
+ }
</style>
--
Gitblit v1.9.3