| | |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="客户名称:"> |
| | | <el-input v-model="searchForm.customerName" placeholder="请输入" clearable prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | <el-select |
| | | v-model="searchForm.customerId" |
| | | filterable |
| | | placeholder="请选择客户名称" |
| | | clearable |
| | | style="width: 220px" |
| | | @change="handleQuery" |
| | | > |
| | | <el-option |
| | | v-for="item in customerOption" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.id" |
| | | > |
| | | {{ item.customerName + "——" + item.taxpayerIdentificationNumber }} |
| | | </el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="销售合同号:"> |
| | | <el-input v-model="searchForm.salesContractNo" placeholder="请输入" clearable prefix-icon="Search" |
| | |
| | | <el-form-item label="录入日期:"> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="请选择" clearable @change="changeDaterange" /> |
| | | </el-form-item> |
| | | <el-form-item label="发货状态:"> |
| | | <el-select v-model="searchForm.deliveryStatus" placeholder="请选择" clearable style="width: 140px"> |
| | | <el-option label="未发货" :value="1" /> |
| | | <el-option label="审批中" :value="2" /> |
| | | <el-option label="审批失败" :value="3" /> |
| | | <el-option label="已发货" :value="4" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="入库状态:"> |
| | | <el-select v-model="searchForm.stockStatus" placeholder="请选择" clearable style="width: 140px"> |
| | | <el-option label="未入库" :value="0" /> |
| | | <el-option label="已入库" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery"> 搜索 </el-button> |
| | |
| | | :bound-route-name="processFlowSelectBoundRouteName" |
| | | @confirm="handleProcessFlowSelectConfirm" |
| | | /> |
| | | <div> |
| | | <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"> |
| | | <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-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | </div> |
| | | <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"> |
| | | <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> |
| | | </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%" |
| | |
| | | <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"> |
| | |
| | | {{ 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="总面积(cm²)" 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"> |
| | |
| | | <el-tag v-else type="danger">不足</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="发货状态" width="140" align="center"> |
| | | <!-- <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> --> |
| | | <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"> |
| | |
| | | <el-table-column label="客户名称" prop="customerName" width="300" show-overflow-tooltip /> |
| | | <el-table-column label="业务员" prop="salesman" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="项目名称" prop="projectName" width="180" show-overflow-tooltip /> |
| | | <el-table-column label="付款方式" prop="paymentMethod" show-overflow-tooltip /> |
| | | <el-table-column label="合同金额(元)" prop="contractAmount" width="220" show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="发货状态" width="140" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag v-if="Number(scope.row.deliveryStatus) === 1" type="info">未发货</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.deliveryStatus) === 2" type="warning">审批中</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.deliveryStatus) === 3" type="danger">审批不通过</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.deliveryStatus) === 4" type="primary">审批通过</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.deliveryStatus) === 5" type="success">已发货</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.stockStatus) === 0" type="info">未入库</el-tag> |
| | | <el-tag v-else-if="Number(scope.row.stockStatus) === 1" type="success">已入库</el-tag> |
| | | <el-tag v-else type="info">-</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="录入人" prop="entryPersonName" width="100" 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-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客户名称:" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="请选择" clearable :disabled="operationType === 'view'"> |
| | | <el-select v-model="form.customerId" filterable placeholder="请选择" clearable :disabled="operationType === 'view'"> |
| | | <el-option v-for="item in customerOption" :key="item.id" :label="item.customerName" :value="item.id"> |
| | | {{ |
| | | item.customerName + "——" + item.taxpayerIdentificationNumber |
| | |
| | | format="YYYY-MM-DD" type="date" placeholder="请选择" clearable :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="付款方式"> |
| | | <el-input v-model="form.paymentMethod" placeholder="请输入" clearable :disabled="operationType === 'view'" /> |
| | | </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 /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="交货日期:" prop="entryDate"> |
| | | <el-date-picker style="width: 100%" v-model="form.deliveryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" |
| | | type="date" placeholder="请选择" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-form-item label="产品信息:" prop="entryDate"> |
| | | <el-button v-if="operationType !== 'view'" type="primary" @click="openProductForm('add')">添加</el-button> |
| | | <el-button |
| | | v-if="operationType !== 'view'" |
| | | type="primary" |
| | | :disabled="hasEditingProductRow()" |
| | | @click="addProductInline" |
| | | > |
| | | 添加 |
| | | </el-button> |
| | | <el-button v-if="operationType !== 'view'" plain type="danger" @click="deleteProduct" >删除</el-button> |
| | | </el-form-item> |
| | | </el-row> |
| | |
| | | <el-table-column align="center" type="selection" width="55" v-if="operationType !== 'view'" |
| | | :selectable="(row) => !isProductShipped(row)" /> |
| | | <el-table-column align="center" label="序号" type="index" width="60" /> |
| | | <el-table-column label="产品大类" prop="productCategory" /> |
| | | <el-table-column label="规格型号" prop="specificationModel" /> |
| | | <el-table-column label="厚度" prop="thickness" min-width="90"> |
| | | <el-table-column label="产品大类" prop="productCategory" min-width="160"> |
| | | <template #default="scope"> |
| | | {{ scope.row.thickness ?? "" }} |
| | | <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)" |
| | | /> |
| | | <span v-else>{{ scope.row.productCategory ?? "" }}</span> |
| | | </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 fixed="right" label="操作" min-width="60" align="center" v-if="operationType !== 'view'"> |
| | | <el-table-column label="规格型号" prop="specificationModel" min-width="160"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="openProductForm('edit', scope.row,scope.$index)">编辑</el-button> |
| | | <el-select |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.productModelId" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | @change="(val) => handleInlineProductModelChange(scope.row, val)" |
| | | > |
| | | <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="160"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.thickness" |
| | | :min="0" |
| | | :step="0.000000000000001" |
| | | :precision="15" |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | /> |
| | | <span v-else>{{ scope.row.thickness ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="宽(mm)" prop="width" min-width="160"> |
| | | <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> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" min-width="140"> |
| | | <template #default="scope"> |
| | | <el-input-number |
| | | v-if="scope.row.__editing" |
| | | :step="0.01" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | v-model="scope.row.taxInclusiveUnitPrice" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="() => handleInlineUnitPriceChange(scope.row)" |
| | | @input="() => handleInlineUnitPriceChange(scope.row)" |
| | | /> |
| | | <span v-else>{{ formattedNumber(null, null, scope.row.taxInclusiveUnitPrice ?? 0) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="税率(%)" prop="taxRate" min-width="120"> |
| | | <template #default="scope"> |
| | | <el-select |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.taxRate" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="() => handleInlineTaxRateChange(scope.row)" |
| | | > |
| | | <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="120"/> |
| | | <el-table-column label="不含税总价(元)" prop="taxExclusiveTotalPrice" :formatter="formattedNumber" min-width="120"/> |
| | | <el-table-column label="发票类型" prop="invoiceType" min-width="120"> |
| | | <template #default="scope"> |
| | | <el-select |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.invoiceType" |
| | | placeholder="请选择" |
| | | clearable |
| | | style="width: 100%" |
| | | > |
| | | <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="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" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-input |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.remark" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | <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"> |
| | | <template #default="scope"> |
| | | <el-input |
| | | v-if="scope.row.__editing" |
| | | v-model="scope.row.heavyBox" |
| | | placeholder="请输入" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | <span v-else>{{ scope.row.heavyBox ?? "" }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="操作" min-width="220" align="center" v-if="operationType !== 'view'"> |
| | | <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" @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="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 |
| | | v-model="item.quantity" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="0" |
| | | style="width: 120px;" |
| | | placeholder="数量" |
| | | :disabled="operationType === 'view'" |
| | | @change="handleOtherAmountQuantityChange(scope.row)" |
| | | /> |
| | | <el-button type="danger" link size="small" @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 |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @click="editProductInline(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" |
| | | :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)" |
| | | @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)" |
| | | > |
| | | <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)" |
| | | @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 === ''" |
| | | @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 |
| | | v-model="item.quantity" |
| | | :min="0" |
| | | :step="1" |
| | | :precision="0" |
| | | style="width: 120px;" |
| | | placeholder="数量" |
| | | :disabled="operationType === 'view' || isProductShipped(scope.row)" |
| | | @change="handleOtherAmountQuantityChange(scope.row)" |
| | | /> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | :disabled="isProductShipped(scope.row)" |
| | | @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> |
| | |
| | | v-model="productForm.productCategory" |
| | | placeholder="请选择" |
| | | clearable |
| | | filterable |
| | | :filter-node-method="filterProductCategoryNode" |
| | | check-strictly |
| | | @change="getModels" |
| | | :data="productOptions" |
| | |
| | | <!-- 每行三个:税率/含税单价/数量 --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="税率(%):" prop="taxRate"> |
| | | <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%"> |
| | | <el-option label="1" value="1" /> |
| | | <el-option label="6" value="6" /> |
| | | <el-option label="13" value="13" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="含税单价(元):" prop="taxInclusiveUnitPrice"> |
| | | <el-input-number |
| | | :step="0.01" |
| | |
| | | clearable |
| | | @change="calculateFromUnitPrice" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="税率(%):" prop="taxRate"> |
| | | <el-select v-model="productForm.taxRate" placeholder="请选择" clearable @change="calculateFromTaxRate" style="width: 100%"> |
| | | <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> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | |
| | | style="width: 100%" |
| | | placeholder="请输入" |
| | | clearable |
| | | @change="recalcAreaTotals" |
| | | @change="() => { recalcAreaTotals(); calculateFromUnitPrice(true); }" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-input-number |
| | | v-model="item.quantity" |
| | | :min="0" |
| | | :step="0.1" |
| | | :precision="2" |
| | | :step="1" |
| | | :precision="0" |
| | | style="width: 100%;" |
| | | placeholder="请输入数量" |
| | | :disabled="operationType === 'view'" |
| | | @change="calculateFromUnitPrice(true)" |
| | | /> |
| | | </div> |
| | | <el-button |
| | |
| | | <script setup> |
| | | import { getToken } from "@/utils/auth"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import {onMounted, ref, getCurrentInstance} from "vue"; |
| | | import {onMounted, ref, getCurrentInstance, watch, nextTick} from "vue"; |
| | | import { addShippingInfo } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | | import { ArrowDown } from "@element-plus/icons-vue"; |
| | |
| | | saleProcessBind, |
| | | getSaleProcessBindInfo, |
| | | getProcessCard, |
| | | getSalesOrder, |
| | | getSalesInvoices, |
| | | getSalesLabel, |
| | | salesStock, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import { printFinishedProcessCard } from "./components/processCardPrint.js"; |
| | | import { printSalesOrder } from "./components/salesOrderPrint.js"; |
| | | import { printSalesDeliveryNote } from "./components/salesDeliveryPrint.js"; |
| | | import { printSalesLabel } from "./components/salesLabelPrint.js"; |
| | | // import { salesLedgerProductSetProcessFlowConfig } from "@/api/salesManagement/salesProcessFlowConfig.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | customerName: "", // 客户名称 |
| | | customerId: "", // 客户ID(查询下拉) |
| | | salesContractNo: "", // 销售合同编号 |
| | | entryDate: null, // 录入日期 |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | deliveryStatus: undefined, // 发货状态:1未发货 2审批中 3审批失败 4已发货 |
| | | stockStatus: undefined, // 入库状态:0未入库 1已入库 |
| | | }, |
| | | form: { |
| | | salesContractNo: "", |
| | |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const { form: searchForm } = useFormData(data.searchForm); |
| | | |
| | | // 新增台账:录入日期变更时,交货日期默认保持为录入日期后第 7 天 |
| | | watch( |
| | | () => [operationType.value, form.value?.entryDate], |
| | | () => { |
| | | if (operationType.value !== "add") return; |
| | | const ed = form.value?.entryDate; |
| | | if (!ed) return; |
| | | form.value.deliveryDate = dayjs(ed).add(7, "day").format("YYYY-MM-DD"); |
| | | } |
| | | ); |
| | | // 产品表单弹框数据 |
| | | const productFormVisible = ref(false); |
| | | const productOperationType = ref(""); |
| | |
| | | productRules: { |
| | | productCategory: [{ required: true, message: "请选择", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "请选择", trigger: "change" }], |
| | | specificationModel: [ |
| | | { required: true, message: "请选择", trigger: "change" }, |
| | | ], |
| | | thickness: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | | { required: true, message: "请输入", trigger: "blur" }, |
| | | ], |
| | | taxRate: [{ required: true, message: "请选择", trigger: "change" }], |
| | | taxInclusiveTotalPrice: [ |
| | | { required: true, message: "请输入", trigger: "blur" }, |
| | | ], |
| | | taxExclusiveTotalPrice: [ |
| | | { required: true, message: "请输入", trigger: "blur" }, |
| | | ], |
| | | invoiceType: [{ required: true, message: "请选择", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { productForm, productRules } = toRefs(productFormData); |
| | | // 防止循环计算的标志 |
| | | const isCalculating = ref(false); |
| | | |
| | | // 产品行内编辑:只允许同时编辑一行 |
| | | const editingProductRow = ref(null); |
| | | |
| | | const ensureProductRowDefaults = (row) => { |
| | | if (!row || typeof row !== "object") return; |
| | | if (!Array.isArray(row.salesProductProcessList)) row.salesProductProcessList = []; |
| | | if (row.__otherAmountPopoverVisible === undefined || row.__otherAmountPopoverVisible === null) row.__otherAmountPopoverVisible = false; |
| | | if (row.__inlineOtherAmountAdding === undefined || row.__inlineOtherAmountAdding === null) row.__inlineOtherAmountAdding = false; |
| | | if (row.__inlineOtherAmountAddId === undefined) row.__inlineOtherAmountAddId = null; |
| | | if (row.width === undefined || row.width === null) row.width = 0; |
| | | if (row.height === undefined || row.height === null) row.height = 0; |
| | | if (row.perimeter === undefined || row.perimeter === null) row.perimeter = 0; |
| | | if (row.actualPieceArea === undefined || row.actualPieceArea === null) row.actualPieceArea = 0; |
| | | if (row.actualTotalArea === undefined || row.actualTotalArea === null) row.actualTotalArea = 0; |
| | | if (row.settlePieceArea === undefined || row.settlePieceArea === null) row.settlePieceArea = 0; |
| | | if (row.settleTotalArea === undefined || row.settleTotalArea === null) row.settleTotalArea = 0; |
| | | if (row.processRequirement === undefined || row.processRequirement === null) row.processRequirement = ""; |
| | | if (row.remark === undefined || row.remark === null) row.remark = ""; |
| | | if (row.floorCode === undefined || row.floorCode === null) row.floorCode = ""; |
| | | if (row.invoiceType === undefined || row.invoiceType === null) row.invoiceType = ""; |
| | | if (row.taxRate === undefined || row.taxRate === null) row.taxRate = ""; |
| | | if (row.quantity === undefined || row.quantity === null) row.quantity = 0; |
| | | if (row.taxInclusiveUnitPrice === undefined || row.taxInclusiveUnitPrice === null) row.taxInclusiveUnitPrice = 0; |
| | | if (row.taxInclusiveTotalPrice === undefined || row.taxInclusiveTotalPrice === null) row.taxInclusiveTotalPrice = 0; |
| | | if (row.taxExclusiveTotalPrice === undefined || row.taxExclusiveTotalPrice === null) row.taxExclusiveTotalPrice = 0; |
| | | }; |
| | | |
| | | const stopOtherEditingRows = () => { |
| | | (productData.value || []).forEach((r) => { |
| | | if (r && r.__editing) r.__editing = false; |
| | | }); |
| | | editingProductRow.value = null; |
| | | }; |
| | | |
| | | const hasEditingProductRow = () => { |
| | | return (productData.value || []).some((r) => r && r.__editing); |
| | | }; |
| | | |
| | | const addProductInline = async () => { |
| | | if (operationType.value === "view") return; |
| | | if (hasEditingProductRow()) { |
| | | proxy.$modal.msgWarning("请先保存或取消当前编辑行"); |
| | | 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 editProductInline = async (row, index) => { |
| | | if (operationType.value === "view") return; |
| | | if (!row) return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | stopOtherEditingRows(); |
| | | await getProductOptions(); |
| | | await fetchOtherAmountSelectOptions(true); |
| | | ensureProductRowDefaults(row); |
| | | // 产品大类 tree-select 回显:名称 -> id |
| | | row.__productCategoryId = findNodeIdByLabel(productOptions.value, row.productCategory); |
| | | |
| | | // 兼容后端字段命名(保持原逻辑) |
| | | row.actualPieceArea = row?.actualPieceArea ?? row?.actual_piece_area ?? 0; |
| | | row.actualTotalArea = row?.actualTotalArea ?? row?.actual_total_area ?? 0; |
| | | row.settlePieceArea = row?.settlePieceArea ?? row?.settle_piece_area ?? 0; |
| | | row.settleTotalArea = row?.settleTotalArea ?? row?.settle_total_area ?? 0; |
| | | row.processRequirement = row?.processRequirement ?? row?.process_requirement ?? ""; |
| | | row.remark = row?.remark ?? row?.remarks ?? ""; |
| | | row.floorCode = row?.floorCode ?? row?.floor_code ?? ""; |
| | | row.processFlowConfigId = row?.processFlowConfigId ?? row?.process_flow_config_id ?? null; |
| | | row.perimeter = row?.perimeter ?? row?.heavyBoxPerimeter ?? row?.heavyboxPerimeter ?? 0; |
| | | row.thickness = row?.thickness; |
| | | |
| | | row.salesProductProcessList = normalizeOtherAmountsFromRow(row); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | |
| | | // 备份用于取消 |
| | | row.__backup = JSON.parse(JSON.stringify(row)); |
| | | row.__editing = true; |
| | | editingProductRow.value = row; |
| | | productForm.value = row; |
| | | |
| | | // 根据产品大类名称反查 tree 节点 id,并加载规格型号列表 |
| | | try { |
| | | const options = productOptions.value && productOptions.value.length > 0 ? productOptions.value : await getProductOptions(); |
| | | const categoryId = findNodeIdByLabel(options, row.productCategory); |
| | | if (categoryId) { |
| | | const models = await modelList({ id: categoryId }); |
| | | modelOptions.value = models || []; |
| | | const currentModel = (modelOptions.value || []).find((m) => m.model === row.specificationModel); |
| | | if (currentModel) row.productModelId = currentModel.id; |
| | | } |
| | | } catch (e) { |
| | | console.error("加载产品规格型号失败", e); |
| | | } |
| | | |
| | | // 同步计算一次 |
| | | recalcPerimeterFromWidthHeight(); |
| | | recalcAreaFromWidthHeight(); |
| | | }; |
| | | |
| | | const validateInlineProductRow = (row) => { |
| | | if (!row) return false; |
| | | if (!row.productCategory) { |
| | | proxy.$modal.msgWarning("请选择产品大类"); |
| | | return false; |
| | | } |
| | | if (!row.productModelId) { |
| | | proxy.$modal.msgWarning("请选择规格型号"); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | const saveProductInline = async (row, index) => { |
| | | if (operationType.value === "view") return; |
| | | if (!row) return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | // 确保 productForm 指向当前行,以复用计算逻辑 |
| | | productForm.value = row; |
| | | ensureProductRowDefaults(row); |
| | | |
| | | if (!validateInlineProductRow(row)) return; |
| | | |
| | | // 厚度精度处理 |
| | | if (row.thickness !== null && row.thickness !== undefined && row.thickness !== "") { |
| | | row.thickness = Number(Number(row.thickness).toFixed(15)); |
| | | } |
| | | |
| | | // 提交前兜底计算一次(沿用原逻辑) |
| | | recalcAreaTotals(); |
| | | // 提交兜底:税率/数量未填时按数字 0 传递 |
| | | row.taxRate = Number(row.taxRate ?? 0) || 0; |
| | | row.quantity = Number(row.quantity ?? 0) || 0; |
| | | |
| | | // 规范化其他金额提交结构 |
| | | row.salesProductProcessList = (Array.isArray(row.salesProductProcessList) ? row.salesProductProcessList : []) |
| | | .map((it) => ({ |
| | | id: it?.id, |
| | | processName: it?.processName ?? "", |
| | | unitPrice: Number(it?.unitPrice ?? 0) || 0, |
| | | quantity: Number(it?.quantity ?? 0) || 0, |
| | | })) |
| | | .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); |
| | | |
| | | // 规格型号:根据 productModelId 回填名称 |
| | | const model = (modelOptions.value || []).find((m) => String(m.id) === String(row.productModelId)); |
| | | if (model?.model) row.specificationModel = model.model; |
| | | |
| | | if (operationType.value === "edit") { |
| | | // 台账已存在:走原接口保存到后端,再回拉刷新 |
| | | const payload = { ...row, salesLedgerId: currentId.value, type: 1 }; |
| | | delete payload.__backup; |
| | | delete payload.__editing; |
| | | delete payload.__isNew; |
| | | delete payload.__productCategoryId; |
| | | delete payload.__tempKey; |
| | | await addOrUpdateSalesLedgerProduct(payload); |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | await getSalesLedgerWithProducts({ id: currentId.value, type: 1 }).then((res) => { |
| | | productData.value = res.productData; |
| | | }); |
| | | } else { |
| | | // 新增台账:仅在本地 productData 生效,最终随台账一起提交 |
| | | row.__isNew = false; |
| | | row.__editing = false; |
| | | delete row.__backup; |
| | | } |
| | | |
| | | stopOtherEditingRows(); |
| | | }; |
| | | |
| | | const cancelProductInline = (row, index) => { |
| | | if (!row) return; |
| | | if (row.__isNew) { |
| | | productData.value.splice(index, 1); |
| | | } else if (row.__backup) { |
| | | const restored = JSON.parse(JSON.stringify(row.__backup)); |
| | | // 保留 id 与状态字段 |
| | | const keepId = row.id; |
| | | Object.keys(row).forEach((k) => delete row[k]); |
| | | Object.assign(row, restored); |
| | | row.id = keepId; |
| | | row.__editing = false; |
| | | delete row.__backup; |
| | | } |
| | | stopOtherEditingRows(); |
| | | }; |
| | | |
| | | const openOtherAmountInline = async (row) => { |
| | | if (!row) return; |
| | | if (operationType.value === "view") return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | ensureProductRowDefaults(row); |
| | | productForm.value = row; |
| | | otherAmountAddTargetRow.value = row; |
| | | await fetchOtherAmountSelectOptions(true); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | // 只做数据准备与打开浮层(新增由浮层内按钮触发) |
| | | row.__otherAmountPopoverVisible = true; |
| | | }; |
| | | |
| | | const keepOtherAmountPopoverOpenKey = ref(null); |
| | | const keepOtherAmountPopoverOpenUntil = ref(0); |
| | | |
| | | const getOtherAmountRowKey = (row) => String(row?.__tempKey ?? row?.id ?? ""); |
| | | |
| | | const lockOtherAmountPopoverOpen = (row, durationMs = 1200) => { |
| | | const key = getOtherAmountRowKey(row); |
| | | if (!key) return; |
| | | keepOtherAmountPopoverOpenKey.value = key; |
| | | keepOtherAmountPopoverOpenUntil.value = Date.now() + durationMs; |
| | | }; |
| | | |
| | | const handleOtherAmountPopoverVisibleChange = (row, visible) => { |
| | | if (!row) return; |
| | | if (visible) { |
| | | row.__otherAmountPopoverVisible = true; |
| | | return; |
| | | } |
| | | if (row.__inlineOtherAmountAdding) { |
| | | row.__otherAmountPopoverVisible = true; |
| | | return; |
| | | } |
| | | const key = getOtherAmountRowKey(row); |
| | | const shouldKeepOpen = Boolean( |
| | | key && |
| | | keepOtherAmountPopoverOpenKey.value === key && |
| | | Date.now() < keepOtherAmountPopoverOpenUntil.value |
| | | ); |
| | | row.__otherAmountPopoverVisible = shouldKeepOpen; |
| | | }; |
| | | |
| | | const startAddOtherAmountForRow = async (row) => { |
| | | if (!row) return; |
| | | if (operationType.value === "view") return; |
| | | if (isProductShipped(row)) { |
| | | proxy.$modal.msgWarning("已发货或审核通过的产品不能编辑"); |
| | | return; |
| | | } |
| | | ensureProductRowDefaults(row); |
| | | productForm.value = row; |
| | | await fetchOtherAmountSelectOptions(true); |
| | | mergeOtherAmountOptionsBySelection(row.salesProductProcessList); |
| | | row.salesProductProcessList = fillOtherAmountProcessName(row.salesProductProcessList); |
| | | row.__inlineOtherAmountAddId = null; |
| | | row.__inlineOtherAmountAdding = true; |
| | | row.__otherAmountPopoverVisible = true; |
| | | }; |
| | | |
| | | const confirmAddOtherAmountForRow = (row) => { |
| | | if (!row) return; |
| | | ensureProductRowDefaults(row); |
| | | productForm.value = row; |
| | | const selectedId = row.__inlineOtherAmountAddId; |
| | | if (selectedId === null || selectedId === undefined || selectedId === "") return; |
| | | const opt = otherAmountSelectOptions.value.find((o) => String(o.id) === String(selectedId)); |
| | | if (!opt) return; |
| | | const exists = (row.salesProductProcessList ?? []).some( |
| | | (it) => String(it?.id) === String(opt.id) |
| | | ); |
| | | if (exists) { |
| | | proxy.$modal.msgWarning("该其他金额项目已添加"); |
| | | return; |
| | | } |
| | | row.salesProductProcessList.push({ |
| | | id: opt.id, |
| | | processName: opt.processName, |
| | | unitPrice: opt.unitPrice ?? 0, |
| | | quantity: 0, |
| | | }); |
| | | row.__inlineOtherAmountAddId = null; |
| | | row.__inlineOtherAmountAdding = false; |
| | | row.__otherAmountPopoverVisible = true; |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | |
| | | const removeOtherAmountAtForRow = (row, index) => { |
| | | if (!row) return; |
| | | if (operationType.value === "view") return; |
| | | if (isProductShipped(row)) return; |
| | | productForm.value = row; |
| | | removeOtherAmountAt(index); |
| | | }; |
| | | |
| | | const handleOtherAmountQuantityChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | |
| | | const handleInlineProductCategoryChange = async (row, val) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | // 复用原有逻辑:会写入 productCategory(名称)、重置规格/厚度并拉取型号 |
| | | await getModels(val); |
| | | // 行内编辑时把选中的 id 记录下来,便于回显 |
| | | row.__productCategoryId = val; |
| | | }; |
| | | |
| | | const handleInlineProductModelChange = (row, val) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | // 复用原有逻辑:会写入 specificationModel、厚度 |
| | | getProductModel(val); |
| | | }; |
| | | |
| | | const handleInlineSizeChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | recalcPerimeterFromWidthHeight(); |
| | | recalcAreaFromWidthHeight(); |
| | | recalcAreaTotals(); |
| | | }; |
| | | |
| | | const handleInlineUnitPriceChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromUnitPrice(); |
| | | recalcAreaTotals(); |
| | | }; |
| | | |
| | | const handleInlineQuantityChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromQuantity(); |
| | | recalcAreaTotals(); |
| | | }; |
| | | |
| | | const handleInlineTaxRateChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | calculateFromTaxRate(); |
| | | }; |
| | | |
| | | const handleInlineSettleAreaChange = (row) => { |
| | | if (!row) return; |
| | | productForm.value = row; |
| | | recalcAreaTotals(); |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | const upload = reactive({ |
| | | // 上传的地址 |
| | | url: import.meta.env.VITE_APP_BASE_API + "/file/upload", |
| | |
| | | type: "货车", // 货车, 快递 |
| | | }, |
| | | deliveryRules: { |
| | | type: [ |
| | | type: [ |
| | | { required: true, message: "请选择发货类型", trigger: "change" } |
| | | ] |
| | | }, |
| | |
| | | otherAmountSelectOptions.value = records.map((item) => ({ |
| | | id: item.id, |
| | | processName: item.processName ?? "", |
| | | unitPrice: item.unitPrice ?? 0, |
| | | })); |
| | | } finally { |
| | | otherAmountSelectOptionsLoading.value = false; |
| | |
| | | return { |
| | | id: s.id, |
| | | processName: opt?.processName ?? s.processName ?? "", |
| | | unitPrice: opt?.unitPrice ?? s.unitPrice ?? 0, |
| | | quantity: Number(s.quantity ?? 0) || 0, |
| | | }; |
| | | }); |
| | |
| | | // 其他金额:点击“新增”后在弹窗里选择一个项目 |
| | | const otherAmountAddDialogVisible = ref(false); |
| | | const otherAmountAddId = ref(null); |
| | | const otherAmountAddTargetRow = ref(null); |
| | | const otherAmountAddTargetRowKey = ref(null); |
| | | |
| | | const startAddOtherAmount = () => { |
| | | if (operationType.value === "view") return; |
| | |
| | | const cancelAddOtherAmount = () => { |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | otherAmountAddTargetRow.value = null; |
| | | otherAmountAddTargetRowKey.value = null; |
| | | keepOtherAmountPopoverOpenKey.value = null; |
| | | keepOtherAmountPopoverOpenUntil.value = 0; |
| | | }; |
| | | |
| | | const handleOtherAmountSelected = (id) => { |
| | |
| | | productForm.value.salesProductProcessList.push({ |
| | | id: opt.id, |
| | | processName: opt.processName, |
| | | unitPrice: opt.unitPrice ?? 0, |
| | | quantity: 0, |
| | | }); |
| | | calculateFromUnitPrice(true); |
| | | |
| | | // 选择完成后关闭弹窗,下一次可再次点击“新增”继续添加 |
| | | // 选择完成后关闭“新增其他金额”弹窗,并保持行内“其他金额”弹层开启,便于直接填写数量 |
| | | otherAmountAddDialogVisible.value = false; |
| | | otherAmountAddId.value = null; |
| | | const reopenOtherAmountPopover = () => { |
| | | let targetRow = otherAmountAddTargetRow.value; |
| | | const rowKey = otherAmountAddTargetRowKey.value; |
| | | if (rowKey) { |
| | | const matchedRow = (productData.value || []).find( |
| | | (it) => String(it?.__tempKey ?? it?.id ?? "") === rowKey |
| | | ); |
| | | if (matchedRow) targetRow = matchedRow; |
| | | } |
| | | if (targetRow && typeof targetRow === "object") { |
| | | lockOtherAmountPopoverOpen(targetRow, 1500); |
| | | targetRow.__otherAmountPopoverVisible = true; |
| | | } |
| | | }; |
| | | nextTick(() => { |
| | | reopenOtherAmountPopover(); |
| | | setTimeout(reopenOtherAmountPopover, 0); |
| | | setTimeout(reopenOtherAmountPopover, 80); |
| | | }); |
| | | otherAmountAddTargetRow.value = null; |
| | | otherAmountAddTargetRowKey.value = null; |
| | | }; |
| | | |
| | | const confirmAddOtherAmount = () => { |
| | |
| | | if (operationType.value === "view") return; |
| | | if (!Array.isArray(productForm.value?.salesProductProcessList)) return; |
| | | productForm.value.salesProductProcessList.splice(index, 1); |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | |
| | | // 发货审批人节点(仿协同审批 infoFormDia.vue) |
| | |
| | | const params = { ...rest, ...page }; |
| | | // 移除录入日期的默认值设置,只保留范围日期字段 |
| | | delete params.entryDate; |
| | | // 查询客户名称与新增保持一致:先选 customerId,再映射为 customerName 查询 |
| | | const selectedCustomer = (customerOption.value || []).find( |
| | | (item) => String(item?.id ?? "") === String(params.customerId ?? "") |
| | | ); |
| | | if (selectedCustomer?.customerName) { |
| | | params.customerName = String(selectedCustomer.customerName).trim(); |
| | | } else { |
| | | const cn = params.customerName != null ? String(params.customerName).trim() : ""; |
| | | if (cn) { |
| | | params.customerName = cn; |
| | | } else { |
| | | delete params.customerName; |
| | | } |
| | | } |
| | | delete params.customerId; |
| | | return ledgerListPage(params) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 入库(销售台账 -> 入库状态) |
| | | const handleSalesStock = async () => { |
| | | if (selectedRows.value.length !== 1) { |
| | | ElMessage.warning("请勾选一条台账数据进行入库"); |
| | | return; |
| | | } |
| | | const row = selectedRows.value[0] || {}; |
| | | const id = row?.id; |
| | | if (!id) { |
| | | ElMessage.warning("所选数据缺少id,无法入库"); |
| | | return; |
| | | } |
| | | if (Number(row.stockStatus) === 1) { |
| | | ElMessage.info("该台账已入库,无需重复操作"); |
| | | return; |
| | | } |
| | | try { |
| | | await ElMessageBox.confirm("确认对所选台账执行入库?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }); |
| | | } catch { |
| | | return; |
| | | } |
| | | proxy?.$modal?.loading?.("正在入库,请稍候..."); |
| | | try { |
| | | await salesStock({ id }); |
| | | proxy?.$modal?.msgSuccess?.("入库成功"); |
| | | await getList(); |
| | | } catch (e) { |
| | | proxy?.$modal?.msgError?.("入库失败,请稍后重试"); |
| | | } finally { |
| | | proxy?.$modal?.closeLoading?.(); |
| | | } |
| | | }; |
| | | |
| | | // 打开“工艺路线配置”选择弹窗(必须显式选择) |
| | |
| | | }; |
| | | // 获取tree子数据 |
| | | const getModels = (value) => { |
| | | // 产品大类变化时,重置规格型号与厚度,避免旧值残留 |
| | | productForm.value.productModelId = null; |
| | | productForm.value.specificationModel = ""; |
| | | productForm.value.thickness = null; |
| | | |
| | | if (!value) { |
| | | productForm.value.productCategory = ""; |
| | | modelOptions.value = []; |
| | | return; |
| | | } |
| | | |
| | | productForm.value.productCategory = findNodeById(productOptions.value, value); |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | modelOptions.value = res || []; |
| | | }); |
| | | }; |
| | | const getProductModel = (value) => { |
| | | const index = modelOptions.value.findIndex((item) => item.id === value); |
| | | if (index !== -1) { |
| | | productForm.value.specificationModel = modelOptions.value[index].model; |
| | | const selectedModel = modelOptions.value[index]; |
| | | const modelThickness = |
| | | selectedModel?.thickness ?? |
| | | selectedModel?.modelThickness ?? |
| | | selectedModel?.thick ?? |
| | | null; |
| | | productForm.value.thickness = |
| | | modelThickness === null || modelThickness === undefined || modelThickness === "" |
| | | ? null |
| | | : Number(modelThickness); |
| | | } else { |
| | | productForm.value.specificationModel = null; |
| | | productForm.value.thickness = null; |
| | | } |
| | | }; |
| | | const filterProductCategoryNode = (value, data) => { |
| | | if (!value) return true; |
| | | return String(data?.label || "").toLowerCase().includes(String(value).toLowerCase()); |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | |
| | | } |
| | | return null; // 没有找到节点,返回null |
| | | }; |
| | | function convertIdToValue(data) { |
| | | function convertIdToValue(data, level = 0) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item; |
| | | const hasChildren = Array.isArray(children) && children.length > 0; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // 将 id 改为 value |
| | | // 仅允许叶子节点被选择(有子节点的分类节点统一禁用) |
| | | disabled: Boolean(rest?.disabled) || hasChildren, |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | if (hasChildren) { |
| | | newItem.children = convertIdToValue(children, level + 1); |
| | | } |
| | | |
| | | return newItem; |
| | |
| | | // } |
| | | // }); |
| | | form.value.entryDate = getCurrentDate(); // 设置默认录入日期为当前日期 |
| | | if (type === "add") { |
| | | form.value.deliveryDate = dayjs(form.value.entryDate).add(7, "day").format("YYYY-MM-DD"); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | |
| | |
| | | productData.value = products.map((p) => { |
| | | const quantity = Number(p.quantity ?? 0) || 0; |
| | | const unitPrice = Number(p.unitPrice ?? 0) || 0; |
| | | const settlePieceArea = Number(p.settlePieceArea ?? 0) || 1; |
| | | const taxRate = "13"; // 默认 13%,便于直接提交(如需可在产品中自行修改) |
| | | const taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | const taxInclusiveTotalPrice = (unitPrice * settlePieceArea * quantity).toFixed(2); |
| | | const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice(taxInclusiveTotalPrice, taxRate); |
| | | return { |
| | | // 台账字段 |
| | |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | console.log('productData.value--', productData.value) |
| | | // 行内编辑未保存时不允许提交,避免脏数据/临时字段进入后端 |
| | | const hasEditingRow = (productData.value || []).some((r) => r && r.__editing); |
| | | if (hasEditingRow) { |
| | | proxy.$modal.msgWarning("产品信息存在未保存的编辑行,请先保存或取消"); |
| | | return; |
| | | } |
| | | if (productData.value !== null && productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | | const cleanedProducts = (productData.value || []).map((p) => { |
| | | if (!p || typeof p !== "object") return p; |
| | | const { __editing, __isNew, __backup, __productCategoryId, __tempKey, __otherAmountPopoverVisible, ...rest } = p; |
| | | rest.taxRate = Number(rest.taxRate ?? 0) || 0; |
| | | rest.quantity = Number(rest.quantity ?? 0) || 0; |
| | | return rest; |
| | | }); |
| | | form.value.productData = proxy.HaveJson(cleanedProducts); |
| | | } else { |
| | | proxy.$modal.msgWarning("请添加产品信息"); |
| | | return; |
| | |
| | | } |
| | | form.value.tempFileIds = tempFileIds; |
| | | form.value.type = 1; |
| | | addOrUpdateSalesLedger(form.value).then((res) => { |
| | | const submitPayload = { ...form.value }; |
| | | delete submitPayload.paymentMethod; |
| | | addOrUpdateSalesLedger(submitPayload).then((res) => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | getList(); |
| | |
| | | |
| | | // 面积/总计字段在提交前兜底计算一次 |
| | | recalcAreaTotals(); |
| | | // 提交兜底:税率/数量未填时按数字 0 传递 |
| | | productForm.value.taxRate = Number(productForm.value.taxRate ?? 0) || 0; |
| | | productForm.value.quantity = Number(productForm.value.quantity ?? 0) || 0; |
| | | // 其他金额只提交 {id, processName, quantity}(后端字段:salesProductProcessList) |
| | | productForm.value.salesProductProcessList = (Array.isArray(productForm.value.salesProductProcessList) |
| | | ? productForm.value.salesProductProcessList |
| | | : [] |
| | | ) |
| | | .map((it) => ({ |
| | | id: it?.id, |
| | | processName: it?.processName ?? "", |
| | | quantity: Number(it?.quantity ?? 0) || 0, |
| | | })) |
| | | .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); |
| | | ? productForm.value.salesProductProcessList |
| | | : [] |
| | | ) |
| | | .map((it) => ({ |
| | | id: it?.id, |
| | | processName: it?.processName ?? "", |
| | | unitPrice: Number(it?.unitPrice ?? 0) || 0, |
| | | quantity: Number(it?.quantity ?? 0) || 0, |
| | | })) |
| | | .filter((it) => it.id !== null && it.id !== undefined && it.id !== ""); |
| | | |
| | | if (operationType.value === "edit") { |
| | | submitProductEdit(); |
| | |
| | | |
| | | if (operationType.value === "add") { |
| | | productSelectedRows.value.forEach((selectedRow) => { |
| | | const index = productData.value.findIndex( |
| | | (product) => product.id === selectedRow.id |
| | | ); |
| | | const index = productData.value.findIndex((product) => { |
| | | if (!product || !selectedRow) return false; |
| | | // 新增行 id 为空时,用临时 key 定位 |
| | | if (product.id != null && selectedRow.id != null) { |
| | | return String(product.id) === String(selectedRow.id); |
| | | } |
| | | return ( |
| | | product.__tempKey && |
| | | selectedRow.__tempKey && |
| | | String(product.__tempKey) === String(selectedRow.__tempKey) |
| | | ); |
| | | }); |
| | | if (index !== -1) { |
| | | productData.value.splice(index, 1); |
| | | } |
| | |
| | | }; |
| | | |
| | | const handlePrintCommand = async (command) => { |
| | | if (command !== "finishedProcessCard") return; |
| | | if (selectedRows.value.length !== 1) { |
| | | if (command !== "finishedProcessCard" && command !== "salesOrder" && command !== "salesDeliveryNote") 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())) |
| | | ); |
| | | if (customerNames.length > 1) { |
| | | proxy.$modal.msgWarning("仅支持相同客户名称的销售台账合并发货打印"); |
| | | return; |
| | | } |
| | | } else if (selectedRows.value.length !== 1) { |
| | | proxy.$modal.msgWarning("请选择一条销售台账数据进行打印"); |
| | | return; |
| | | } |
| | | |
| | | const selectedRow = selectedRows.value[0]; |
| | | const selectedId = selectedRow?.id; |
| | | if (command === "salesDeliveryNote") { |
| | | const selectedIds = selectedRows.value |
| | | .map((item) => item?.id) |
| | | .filter((id) => id !== null && id !== undefined && id !== ""); |
| | | if (selectedIds.length !== selectedRows.value.length) { |
| | | proxy.$modal.msgWarning("当前选择数据存在缺少ID的记录,无法打印"); |
| | | return; |
| | | } |
| | | const loadingText = |
| | | command === "salesOrder" |
| | | ? "正在获取销售订单数据,请稍候..." |
| | | : command === "salesDeliveryNote" |
| | | ? "正在获取销售发货单数据,请稍候..." |
| | | : "正在获取生产流程卡数据,请稍候..."; |
| | | proxy.$modal.loading(loadingText); |
| | | try { |
| | | const res = await getSalesInvoices(selectedIds); |
| | | const salesInvoiceData = res?.data ?? {}; |
| | | printSalesDeliveryNote(salesInvoiceData, selectedRow); |
| | | } catch (error) { |
| | | console.error("打印销售发货单失败:", error); |
| | | proxy.$modal.msgError("打印失败,请稍后重试"); |
| | | } finally { |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | return; |
| | | } |
| | | if (!selectedId) { |
| | | proxy.$modal.msgWarning("当前选择数据缺少ID,无法打印"); |
| | | return; |
| | | } |
| | | |
| | | proxy.$modal.loading("正在获取生产流程卡数据,请稍候..."); |
| | | const loadingText = |
| | | command === "salesOrder" |
| | | ? "正在获取销售订单数据,请稍候..." |
| | | : command === "salesDeliveryNote" |
| | | ? "正在获取销售发货单数据,请稍候..." |
| | | : "正在获取生产流程卡数据,请稍候..."; |
| | | proxy.$modal.loading(loadingText); |
| | | try { |
| | | const res = await getProcessCard(selectedId); |
| | | const processCardData = res?.data ?? {}; |
| | | printFinishedProcessCard(processCardData); |
| | | if (command === "salesOrder") { |
| | | const res = await getSalesOrder(selectedId); |
| | | const salesOrderData = res?.data ?? {}; |
| | | printSalesOrder(salesOrderData); |
| | | } else { |
| | | const res = await getProcessCard(selectedId); |
| | | const processCardData = res?.data ?? {}; |
| | | printFinishedProcessCard(processCardData); |
| | | } |
| | | } catch (error) { |
| | | console.error("打印生产流程卡失败:", error); |
| | | console.error( |
| | | command === "salesOrder" |
| | | ? "打印销售订单失败:" |
| | | : command === "salesDeliveryNote" |
| | | ? "打印销售发货单失败:" |
| | | : "打印生产流程卡失败:", |
| | | error |
| | | ); |
| | | proxy.$modal.msgError("打印失败,请稍后重试"); |
| | | } finally { |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | }; |
| | | |
| | | const handlePrintLabel = async () => { |
| | | if (selectedRows.value.length !== 1) { |
| | | proxy.$modal.msgWarning("请选择一条销售台账数据进行标签打印"); |
| | | return; |
| | | } |
| | | |
| | | const selectedId = selectedRows.value[0]?.id; |
| | | if (!selectedId) { |
| | | proxy.$modal.msgWarning("当前选择数据缺少ID,无法打印标签"); |
| | | return; |
| | | } |
| | | |
| | | proxy.$modal.loading("正在获取标签数据,请稍候..."); |
| | | try { |
| | | const res = await getSalesLabel(selectedId); |
| | | const labelList = res?.data ?? []; |
| | | if (!Array.isArray(labelList) || labelList.length === 0) { |
| | | proxy.$modal.msgWarning("暂无可打印标签数据"); |
| | | return; |
| | | } |
| | | printSalesLabel(labelList); |
| | | } catch (error) { |
| | | console.error("打印标签失败:", error); |
| | | proxy.$modal.msgError("打印标签失败,请稍后重试"); |
| | | } finally { |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | |
| | | if (!productForm.value.quantity) { |
| | | return; |
| | | } |
| | | // 含税总价计算 |
| | | productForm.value.taxInclusiveTotalPrice = |
| | | proxy.calculateTaxIncludeTotalPrice( |
| | | productForm.value.taxInclusiveUnitPrice, |
| | | productForm.value.quantity |
| | | ); |
| | | const settlePieceArea = parseFloat(productForm.value.settlePieceArea) || 1; |
| | | // 含税总价计算 = 单价 * 结算面积 * 数量 + 其他金额总和 |
| | | const basePrice = proxy.calculateTaxIncludeTotalPrice( |
| | | productForm.value.taxInclusiveUnitPrice * settlePieceArea, |
| | | productForm.value.quantity |
| | | ); |
| | | const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { |
| | | return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); |
| | | }, 0); |
| | | productForm.value.taxInclusiveTotalPrice = (parseFloat(basePrice) + otherAmountTotal).toFixed(2); |
| | | if (productForm.value.taxRate) { |
| | | // 不含税总价计算 |
| | | productForm.value.taxExclusiveTotalPrice = |
| | |
| | | const computed = Number(computedPieceArea.toFixed(5)); |
| | | |
| | | productForm.value.actualPieceArea = computed; |
| | | |
| | | // settlePieceArea:若用户未填写/为0,则默认使用宽高计算值 |
| | | const settlePieceRaw = Number(productForm.value.settlePieceArea ?? 0) || 0; |
| | | if (!settlePieceRaw) { |
| | | productForm.value.settlePieceArea = computed; |
| | | } |
| | | productForm.value.settlePieceArea = computed; |
| | | |
| | | recalcPerimeterFromWidthHeight(); |
| | | recalcAreaTotals(); |
| | | // 面积更新后,重新计算含税总价 = 单价 * 结算面积 * 数量 |
| | | calculateFromUnitPrice(true); |
| | | }; |
| | | |
| | | // 根据含税总价计算含税单价和数量 |
| | |
| | | |
| | | isCalculating.value = true; |
| | | |
| | | // 计算含税单价 = 含税总价 / 数量 |
| | | productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2); |
| | | // 计算含税单价 = (含税总价 - 其他金额总和) / 数量 |
| | | const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { |
| | | return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); |
| | | }, 0); |
| | | const basePrice = totalPrice - otherAmountTotal; |
| | | productForm.value.taxInclusiveUnitPrice = (basePrice / quantity).toFixed(2); |
| | | |
| | | // 如果有税率,计算不含税总价 |
| | | if (productForm.value.taxRate) { |
| | |
| | | |
| | | // 根据不含税总价计算含税单价和数量 |
| | | const calculateFromExclusiveTotalPrice = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | // if (!productForm.value.taxRate) { |
| | | // proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | const exclusiveTotalPrice = parseFloat(productForm.value.taxExclusiveTotalPrice); |
| | |
| | | const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal); |
| | | productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2); |
| | | |
| | | // 计算含税单价 = 含税总价 / 数量 |
| | | productForm.value.taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2); |
| | | // 计算含税单价 = (含税总价 - 其他金额总和) / 数量 |
| | | const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { |
| | | return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); |
| | | }, 0); |
| | | const basePrice = inclusiveTotalPrice - otherAmountTotal; |
| | | productForm.value.taxInclusiveUnitPrice = (basePrice / quantity).toFixed(2); |
| | | |
| | | isCalculating.value = false; |
| | | }; |
| | | |
| | | // 根据数量变化计算总价 |
| | | const calculateFromQuantity = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | // if (!productForm.value.taxRate) { |
| | | // proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | |
| | | const quantity = parseFloat(productForm.value.quantity); |
| | | const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice); |
| | | |
| | | const settlePieceArea = parseFloat(productForm.value.settlePieceArea) || 1; |
| | | |
| | | if (!quantity || quantity <= 0 || !unitPrice) { |
| | | return; |
| | | } |
| | | |
| | | |
| | | isCalculating.value = true; |
| | | |
| | | // 计算含税总价 |
| | | productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | |
| | | |
| | | // 计算含税总价 = 单价 * 结算面积 * 数量 + 其他金额总和 |
| | | const basePrice = unitPrice * settlePieceArea * quantity; |
| | | const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { |
| | | return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); |
| | | }, 0); |
| | | productForm.value.taxInclusiveTotalPrice = (basePrice + otherAmountTotal).toFixed(2); |
| | | |
| | | // 如果有税率,计算不含税总价 |
| | | if (productForm.value.taxRate) { |
| | | productForm.value.taxExclusiveTotalPrice = |
| | |
| | | productForm.value.taxRate |
| | | ); |
| | | } |
| | | |
| | | |
| | | isCalculating.value = false; |
| | | }; |
| | | |
| | | // 根据含税单价变化计算总价 |
| | | const calculateFromUnitPrice = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | const calculateFromUnitPrice = (silent = false) => { |
| | | // if (!productForm.value.taxRate) { |
| | | // if (!silent) proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | |
| | | const quantity = parseFloat(productForm.value.quantity); |
| | | const unitPrice = parseFloat(productForm.value.taxInclusiveUnitPrice); |
| | | |
| | | const settlePieceArea = parseFloat(productForm.value.settlePieceArea) || 1; |
| | | |
| | | if (!quantity || quantity <= 0 || !unitPrice) { |
| | | return; |
| | | } |
| | | |
| | | |
| | | isCalculating.value = true; |
| | | |
| | | // 计算含税总价 |
| | | productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | |
| | | |
| | | // 计算含税总价 = 单价 * 结算面积 * 数量 + 其他金额总和 |
| | | const basePrice = unitPrice * settlePieceArea * quantity; |
| | | const otherAmountTotal = (productForm.value.salesProductProcessList || []).reduce((total, item) => { |
| | | return total + (Number(item.unitPrice) || 0) * (Number(item.quantity) || 0); |
| | | }, 0); |
| | | productForm.value.taxInclusiveTotalPrice = (basePrice + otherAmountTotal).toFixed(2); |
| | | |
| | | // 如果有税率,计算不含税总价 |
| | | if (productForm.value.taxRate) { |
| | | productForm.value.taxExclusiveTotalPrice = |
| | |
| | | productForm.value.taxRate |
| | | ); |
| | | } |
| | | |
| | | |
| | | isCalculating.value = false; |
| | | }; |
| | | |
| | | // 根据税率变化计算不含税总价 |
| | | const calculateFromTaxRate = () => { |
| | | if (!productForm.value.taxRate) { |
| | | proxy.$modal.msgWarning("请先选择税率"); |
| | | return; |
| | | } |
| | | // if (!productForm.value.taxRate) { |
| | | // proxy.$modal.msgWarning("请先选择税率"); |
| | | // return; |
| | | // } |
| | | if (isCalculating.value) return; |
| | | |
| | | const inclusiveTotalPrice = parseFloat(productForm.value.taxInclusiveTotalPrice); |
| | |
| | | * @param row 行数据 |
| | | */ |
| | | const canShip = (row) => { |
| | | |
| | | // 产品状态必须是充足(approveStatus === 1) |
| | | if (row.approveStatus !== 1) { |
| | | return false; |
| | | } |
| | | |
| | | // 如果后端返回了台账级发货状态(deliveryStatus) |
| | | // 1=已发货,则禁止再次发货 |
| | | const deliveryStatus = row.deliveryStatus; |
| | | if ( |
| | | deliveryStatus !== null && |
| | | deliveryStatus !== undefined && |
| | | String(deliveryStatus).trim() !== "" |
| | | ) { |
| | | if (Number(deliveryStatus) === 1) return false; |
| | | } |
| | | |
| | | // 获取发货状态 |
| | | const shippingStatus = row.shippingStatus; |
| | | |
| | |
| | | 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; |
| | | }); |
| | | if (canDeliveryLedgers.length === 0) { |
| | | proxy.$modal.msgWarning("仅未发货或审批失败的台账可以发货"); |
| | | return; |
| | | } |
| | | |
| | | // 已发货台账:弹窗提醒,不能再次发货(4 视为已发货) |
| | | const shippedLedgers = selectedRows.value.filter((r) => Number(r.deliveryStatus) === 4); |
| | | if (shippedLedgers.length === selectedRows.value.length) { |
| | | try { |
| | | await ElMessageBox.alert("所选销售台账均已发货,不能再次发货。", "提示", { |
| | | type: "warning", |
| | | confirmButtonText: "知道了", |
| | | }); |
| | | } catch { |
| | | /* 关闭弹窗 */ |
| | | } |
| | | return; |
| | | } |
| | | if (shippedLedgers.length > 0) { |
| | | try { |
| | | await ElMessageBox.alert( |
| | | "选中的销售台账中包含已发货记录,已发货的不能再次发货,系统将仅为未发货台账处理。", |
| | | "提示", |
| | | { |
| | | type: "warning", |
| | | confirmButtonText: "知道了", |
| | | } |
| | | ); |
| | | } catch { |
| | | return; |
| | | } |
| | | } |
| | | |
| | | const customerNames = selectedRows.value.map((r) => String(r.customerName || "").trim()); |
| | |
| | | try { |
| | | const targets = []; |
| | | for (const ledger of selectedRows.value) { |
| | | |
| | | //如果已经是“审批中(2)”或“已发货(4)”,则跳过,不允许重复操作 |
| | | const status = Number(ledger.deliveryStatus); |
| | | if (status === 2 || status === 4) { |
| | | console.warn(`台账编号 ${ledger.salesContractNo} 状态为 ${status},跳过发货`); |
| | | continue; |
| | | } |
| | | |
| | | let products = []; |
| | | try { |
| | | const res = await productList({ salesLedgerId: ledger.id, type: 1 }); |
| | | products = res?.data || []; |
| | | } catch { |
| | | } catch (error) { |
| | | products = []; |
| | | console.error('请求发生异常', error); |
| | | } |
| | | |
| | | for (const product of products) { |
| | | if (!canShip(product)) continue; |
| | | targets.push({ |
| | |
| | | }); |
| | | } |
| | | } |
| | | |
| | | if (targets.length === 0) { |
| | | proxy.$modal.msgWarning("没有可发货的数据"); |
| | | return; |
| | |
| | | }); |
| | | } |
| | | |
| | | // 打开发货弹框 |
| | | // 打开发货弹框(单条) |
| | | const openDeliveryForm = (row) => { |
| | | // 检查是否可以发货 |
| | | if (!canShip(row)) { |
| | | proxy.$modal.msgWarning("只有在产品状态是充足,发货状态是待发货或审核拒绝的时候才可以发货"); |
| | | // 只允许【未发货/审批失败】发货;已发货/审批中不允许 |
| | | const status = Number(row.deliveryStatus); |
| | | if (status !== 1 && status !== 3) { |
| | | proxy.$modal.msgWarning("只有发货状态为未发货或审批失败的记录才可以发货"); |
| | | return; |
| | | } |
| | | |
| | | |
| | | currentDeliveryRows.value = [row]; |
| | | deliveryForm.value = { |
| | | type: "货车", |
| | |
| | | return; |
| | | } |
| | | |
| | | // 依次发货(避免并发下库存扣减/状态更新互相影响) |
| | | const run = async () => { |
| | | for (const item of targets) { |
| | | const salesLedgerId = item.salesLedgerId; |
| | | if (!salesLedgerId) continue; |
| | | await addShippingInfo({ |
| | | salesLedgerId, |
| | | salesLedgerProductId: item.id, |
| | | type: deliveryForm.value.type, |
| | | approveUserIds, |
| | | }); |
| | | } |
| | | }; |
| | | // 按台账维度去重,每个 salesLedgerId 只调用一次发货接口 |
| | | const uniqueLedgerIds = [...new Set(targets.map((item) => item.salesLedgerId).filter(Boolean))]; |
| | | |
| | | const run = async () => { |
| | | for (const salesLedgerId of uniqueLedgerIds) { |
| | | await addShippingInfo({ |
| | | salesLedgerId, |
| | | type: deliveryForm.value.type, |
| | | approveUserIds, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | run() |
| | | .then(() => { |
| | |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | customerList().then((res) => { |
| | | customerOption.value = res; |
| | | }); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }) |