| | |
| | | </div> |
| | | <div class="table_list"> |
| | | <div style="display: flex;justify-content: flex-end;margin-bottom: 20px;"> |
| | | <el-button type="success" |
| | | plain |
| | | @click="handleBatchGenerate">批量生成数据</el-button> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">新增台账</el-button> |
| | | <el-button type="primary" |
| | |
| | | prop="specificationModel" /> |
| | | <el-table-column label="单位" |
| | | prop="unit" /> |
| | | <el-table-column label="入库审核状态" |
| | | <el-table-column label="入库审核状态" |
| | | prop="stockInApprovalStatus" |
| | | width="120"> |
| | | <template #default="scope"> |
| | |
| | | prop="availableQuality" /> |
| | | <el-table-column label="退货数量" |
| | | prop="returnQuality" /> |
| | | <el-table-column label="运费单价(元)" |
| | | prop="freightUnitPrice" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="总运费(元)" |
| | | prop="totalFreight" |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" /> |
| | | <el-table-column label="含税单价(元)" |
| | |
| | | width="100" |
| | | prop="paymentMethod" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="车牌号" |
| | | prop="carPlateNumber" |
| | | width="140" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="运输单位/个人" |
| | | prop="transportUnitOrPerson" |
| | | width="180" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="合同金额(元)" |
| | | prop="contractAmount" |
| | | width="200" |
| | |
| | | type="primary" |
| | | @click="openForm('edit', scope.row)" |
| | | :disabled="scope.row.stockInStatus === '完全入库'">编辑 |
| | | </el-button> |
| | | <el-button link |
| | | type="primary" |
| | | :disabled="scope.row.approvalStatus !== 3 || scope.row.stockInStatus === '完全入库'" |
| | | @click="handleStockIn(scope.row)">入库 |
| | | </el-button> |
| | | <el-button link |
| | | type="primary" |
| | |
| | | type="date" |
| | | placeholder="请选择" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="车牌号:" |
| | | prop="carPlateNumber"> |
| | | <el-input v-model="form.carPlateNumber" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="运输单位/个人:" |
| | | prop="transportUnitOrPerson"> |
| | | <el-input v-model="form.transportUnitOrPerson" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-table-column label="数量" |
| | | prop="quantity" |
| | | width="70" /> |
| | | <el-table-column label="运费单价(元)" |
| | | prop="freightUnitPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="总运费(元)" |
| | | prop="totalFreight" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="库存预警数量" |
| | | prop="warnNum" |
| | | width="120" |
| | |
| | | style="width: 100%" |
| | | v-model="productForm.quantity" |
| | | placeholder="请输入" |
| | | @change="mathNum" /> |
| | | @change="handleQuantityChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="运费单价(元):" |
| | | prop="freightUnitPrice"> |
| | | <el-input-number v-model="productForm.freightUnitPrice" |
| | | :precision="2" |
| | | :step="0.1" |
| | | :min="0" |
| | | clearable |
| | | style="width: 100%" |
| | | @change="handleFreightUnitPriceChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="总运费(元):" |
| | | prop="totalFreight"> |
| | | <el-input-number v-model="productForm.totalFreight" |
| | | :precision="2" |
| | | :step="0.1" |
| | | :min="0" |
| | | clearable |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | <!-- 入库弹窗 --> |
| | | <FormDialog v-model="stockInDialogVisible" |
| | | title="入库确认" |
| | | :width="'90%'" |
| | | @close="stockInDialogVisible = false" |
| | | @confirm="submitStockIn" |
| | | @cancel="stockInDialogVisible = false"> |
| | | <el-form :model="stockInForm" |
| | | label-width="120px" |
| | | label-position="top"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="采购合同号"> |
| | | <el-input v-model="stockInForm.purchaseContractNumber" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-table :data="stockInForm.details" |
| | | border> |
| | | <el-table-column align="center" |
| | | label="序号" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="产品大类" |
| | | prop="productCategory" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="规格型号" |
| | | prop="specificationModel" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="单位" |
| | | prop="unit" |
| | | width="70" /> |
| | | <el-table-column label="待入库数量" |
| | | prop="availableQuality" |
| | | width="100" /> |
| | | <el-table-column label="本次入库数量" |
| | | width="130"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.inboundQuantity" |
| | | :step="0.01" |
| | | :min="0" |
| | | :max="scope.row.availableQuality" |
| | | @change="handleInboundChange(scope.row)" |
| | | controls-position="right" |
| | | style="width: 100%" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="是否含水" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-switch v-model="scope.row.isContainsWater" |
| | | @change="handleInboundChange(scope.row)" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="含水量(%)" |
| | | width="130"> |
| | | <template #default="scope"> |
| | | <el-input-number v-if="scope.row.isContainsWater" |
| | | v-model="scope.row.waterContent" |
| | | :precision="2" |
| | | :step="0.1" |
| | | :min="0" |
| | | :max="100" |
| | | @change="handleInboundChange(scope.row)" |
| | | controls-position="right" |
| | | style="width: 100%" /> |
| | | <span v-else>--</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="实际入库数量" |
| | | width="130"> |
| | | <template #default="scope"> |
| | | <span style="font-weight: bold; color: #409EFF;">{{ scope.row.actualInboundQuantity }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-form> |
| | | </FormDialog> |
| | | <FileList v-if="fileListDialogVisible" |
| | | v-model:visible="fileListDialogVisible" |
| | | record-type="purchase_ledger" |
| | |
| | | getOptions, |
| | | getPurchaseTemplateList, |
| | | delPurchaseTemplate, |
| | | batchGeneratePurchaseInboundSteps, |
| | | manualStockIn, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | |
| | | const tableLoading = ref(false); |
| | | const recordId = ref(); |
| | | const fileListDialogVisible = ref(false); |
| | | // 入库弹窗相关 |
| | | const stockInDialogVisible = ref(false); |
| | | const stockInLoading = ref(false); |
| | | const stockInForm = reactive({ |
| | | purchaseLedgerId: null, |
| | | purchaseContractNumber: "", |
| | | details: [], |
| | | }); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const fileList = ref([]); |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import dayjs from "dayjs"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | | // 订单审批状态显示文本 |
| | | const approvalStatusText = { |
| | |
| | | // 获取入库状态标签类型 |
| | | const getStockInStatusType = status => { |
| | | const typeMap = { |
| | | "待入库": "info", // 待入库 - 灰色 |
| | | "入库中": "warning", // 入库中 - 橙色 |
| | | "完全入库": "success", // 完全入库 - 绿色 |
| | | 待入库: "info", // 待入库 - 灰色 |
| | | 入库中: "warning", // 入库中 - 橙色 |
| | | 完全入库: "success", // 完全入库 - 绿色 |
| | | }; |
| | | return typeMap[status] || ""; |
| | | }; |
| | |
| | | // 获取入库审核状态标签类型 |
| | | const getStockInApprovalStatusType = status => { |
| | | const typeMap = { |
| | | "待入库": "info", // 待入库 - 灰色 |
| | | "入库中": "warning", // 入库中 - 橙色 |
| | | "完全入库": "success", // 完全入库 - 绿色 |
| | | 待入库: "info", // 待入库 - 灰色 |
| | | 入库中: "warning", // 入库中 - 橙色 |
| | | 完全入库: "success", // 完全入库 - 绿色 |
| | | }; |
| | | return typeMap[status] || ""; |
| | | }; |
| | |
| | | supplierName: "", |
| | | supplierId: "", |
| | | paymentMethod: "", |
| | | carPlateNumber: "", |
| | | transportUnitOrPerson: "", |
| | | executionDate: "", |
| | | isChecked: false, |
| | | }, |
| | | rules: { |
| | | purchaseContractNumber: [ |
| | | { required: true, message: "请输入", trigger: "blur" }, |
| | | { required: false, message: "请输入", trigger: "blur" }, |
| | | ], |
| | | projectName: [ |
| | | { required: true, message: "请输入项目名称", trigger: "blur" }, |
| | |
| | | specificationModel: "", |
| | | unit: "", |
| | | quantity: "", |
| | | freightUnitPrice: "", |
| | | totalFreight: "", |
| | | taxInclusiveUnitPrice: "", |
| | | taxRate: "", |
| | | taxInclusiveTotalPrice: "", |
| | |
| | | }; |
| | | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | if (cellValue === undefined || cellValue === null || cellValue === "") { |
| | | return "0.00"; |
| | | } |
| | | const num = Number(cellValue); |
| | | if (Number.isNaN(num)) return "0.00"; |
| | | return num.toFixed(2); |
| | | }; |
| | | // 查询列表 |
| | | /** 搜索按钮操作 */ |
| | |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "freightUnitPrice", |
| | | "totalFreight", |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | |
| | | productSelectedRows.value = selectedRows; |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | // 入库处理 |
| | | const handleStockIn = async row => { |
| | | stockInForm.purchaseLedgerId = row.id; |
| | | stockInForm.purchaseContractNumber = row.purchaseContractNumber; |
| | | stockInForm.details = []; |
| | | |
| | | try { |
| | | proxy.$modal.loading("正在加载产品信息..."); |
| | | const res = await productList({ salesLedgerId: row.id, type: 2 }); |
| | | if (res.code === 200) { |
| | | // 过滤掉已经完全入库的产品(如果有这个状态的话,或者直接显示所有可用数量大于0的产品) |
| | | stockInForm.details = (res.data || []) |
| | | .filter(item => (item.availableQuality || 0) > 0) |
| | | .map(item => ({ |
| | | ...item, |
| | | inboundQuantity: item.availableQuality || 0, // 默认入库全部可用数量 |
| | | isContainsWater: false, |
| | | waterContent: 0, |
| | | actualInboundQuantity: item.availableQuality || 0, |
| | | })); |
| | | |
| | | if (stockInForm.details.length === 0) { |
| | | proxy.$modal.msgWarning("该合同下没有可入库的产品记录"); |
| | | return; |
| | | } |
| | | stockInDialogVisible.value = true; |
| | | } |
| | | } catch (error) { |
| | | console.error("加载产品列表失败:", error); |
| | | proxy.$modal.msgError("加载产品列表失败"); |
| | | } finally { |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | }; |
| | | |
| | | // 计算实际入库数量 |
| | | const handleInboundChange = row => { |
| | | if (row.isContainsWater) { |
| | | // 实际入库 = 本次入库数量 * (1 - 含水量/100) |
| | | row.actualInboundQuantity = Number( |
| | | (row.inboundQuantity * (1 - (row.waterContent || 0) / 100)).toFixed(2) |
| | | ); |
| | | } else { |
| | | row.actualInboundQuantity = row.inboundQuantity; |
| | | } |
| | | }; |
| | | |
| | | const submitStockIn = async () => { |
| | | if (stockInForm.details.length === 0) { |
| | | proxy.$modal.msgWarning("请选择入库产品"); |
| | | return; |
| | | } |
| | | |
| | | // 验证入库数量 |
| | | const invalid = stockInForm.details.some( |
| | | item => !item.inboundQuantity || item.inboundQuantity <= 0 |
| | | ); |
| | | if (invalid) { |
| | | proxy.$modal.msgWarning("请输入有效的入库数量"); |
| | | return; |
| | | } |
| | | |
| | | const overLimit = stockInForm.details.some( |
| | | item => item.inboundQuantity > item.availableQuality |
| | | ); |
| | | if (overLimit) { |
| | | proxy.$modal.msgWarning("入库数量不能超过可用数量"); |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | stockInLoading.value = true; |
| | | const params = { |
| | | purchaseLedgerId: stockInForm.purchaseLedgerId, |
| | | details: stockInForm.details.map(item => ({ |
| | | id: item.id, |
| | | inboundQuantity: Number(item.inboundQuantity), |
| | | isContainsWater: !!item.isContainsWater, |
| | | waterContent: item.isContainsWater ? Number(item.waterContent || 0) : 0, |
| | | actualInboundQuantity: Number(item.actualInboundQuantity), |
| | | })), |
| | | }; |
| | | |
| | | const res = await manualStockIn(params); |
| | | if (res.code === 200) { |
| | | proxy.$modal.msgSuccess("入库成功"); |
| | | stockInDialogVisible.value = false; |
| | | getList(); // 刷新列表 |
| | | } else { |
| | | proxy.$modal.msgError(res.msg || "入库失败"); |
| | | } |
| | | } catch (error) { |
| | | console.error("入库提交失败:", error); |
| | | proxy.$modal.msgError("入库提交失败"); |
| | | } finally { |
| | | stockInLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // 展开行 |
| | | const expandChange = async (row, expandedRows) => { |
| | | if (expandedRows.length > 0) { |
| | |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | "totalFreight", |
| | | ]); |
| | | }; |
| | | // 打开弹框 |
| | | const openForm = async (type, row) => { |
| | | // 编辑时检查入库状态,完全入库时不能编辑 |
| | | if (type === "edit" && row) { |
| | | if (row.stockInStatus === '完全入库') { |
| | | if (row.stockInStatus === "完全入库") { |
| | | proxy.$modal.msgWarning("完全入库状态的记录不能编辑"); |
| | | return; |
| | | } |
| | |
| | | |
| | | form.value.entryDate = getCurrentDate(); |
| | | |
| | | if (type === "add") { |
| | | // 新增时生成采购合同号 |
| | | try { |
| | | const purchaseNoRes = await createPurchaseNo(); |
| | | if (purchaseNoRes?.data) { |
| | | form.value.purchaseContractNumber = purchaseNoRes.data; |
| | | } |
| | | } catch (error) { |
| | | console.error("生成采购合同号失败:", error); |
| | | proxy.$modal.msgWarning("生成采购合同号失败"); |
| | | } |
| | | } else if (type === "edit" && row?.id) { |
| | | if (type === "edit" && row?.id) { |
| | | // 编辑时加载数据 |
| | | currentId.value = row.id; |
| | | try { |
| | |
| | | form.value = { ...purchaseRes, stockInStatus: row.stockInStatus }; |
| | | fileList.value = purchaseRes.storageBlobVOS || []; |
| | | // 使用 productList 接口获取产品列表,以获取入库审核状态 |
| | | const productRes = await productList({ salesLedgerId: row.id, type: 2 }); |
| | | const productRes = await productList({ |
| | | salesLedgerId: row.id, |
| | | type: 2, |
| | | }); |
| | | productData.value = productRes.data || []; |
| | | } catch (error) { |
| | | console.error("加载采购台账数据失败:", error); |
| | |
| | | |
| | | // 提交表单 |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | if (valid) { |
| | | if (productData.value.length > 0) { |
| | | // 新增时,需要从每个产品对象中删除 id 字段 |
| | |
| | | delete submitData.id; |
| | | } |
| | | |
| | | // 如果采购合同号为空,则根据录入日期自动生成 |
| | | if (!submitData.purchaseContractNumber) { |
| | | try { |
| | | const purchaseNoRes = await createPurchaseNo(submitData.entryDate); |
| | | if (purchaseNoRes?.data) { |
| | | submitData.purchaseContractNumber = purchaseNoRes.data; |
| | | } |
| | | } catch (error) { |
| | | console.error("生成采购合同号失败:", error); |
| | | proxy.$modal.msgWarning("生成采购合同号失败"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | addOrEditPurchase(submitData).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | |
| | | // 打开产品弹框 |
| | | const openProductForm = async (type, row, index) => { |
| | | // 编辑时检查产品入库审核状态,完全入库时不能编辑 |
| | | if (type === "edit" && row && row.stockInApprovalStatus === '完全入库') { |
| | | if (type === "edit" && row && row.stockInApprovalStatus === "完全入库") { |
| | | proxy.$modal.msgWarning("完全入库状态的产品不能编辑"); |
| | | return; |
| | | } |
| | |
| | | if (type === "edit") { |
| | | // 复制行数据 |
| | | productForm.value = { ...row }; |
| | | if (productForm.value.freightUnitPrice === undefined) { |
| | | productForm.value.freightUnitPrice = ""; |
| | | } |
| | | if (productForm.value.totalFreight === undefined) { |
| | | productForm.value.totalFreight = ""; |
| | | } |
| | | |
| | | // 如果是从模板加载的数据,可能没有 productId 和 productModelId |
| | | // 需要根据 productCategory 和 specificationModel 来查找对应的 ID |
| | |
| | | // 最后再等待一次 DOM 更新,确保所有数据都已设置 |
| | | await nextTick(); |
| | | } |
| | | computeTotalFreight(); |
| | | }; |
| | | |
| | | const computeTotalFreight = () => { |
| | | const hasQuantity = |
| | | productForm.value.quantity !== "" && |
| | | productForm.value.quantity !== null && |
| | | productForm.value.quantity !== undefined; |
| | | const hasFreightUnitPrice = |
| | | productForm.value.freightUnitPrice !== "" && |
| | | productForm.value.freightUnitPrice !== null && |
| | | productForm.value.freightUnitPrice !== undefined; |
| | | if (!hasQuantity || !hasFreightUnitPrice) return; |
| | | |
| | | const quantity = Number(productForm.value.quantity); |
| | | const freightUnitPrice = Number(productForm.value.freightUnitPrice); |
| | | if (!Number.isFinite(quantity) || !Number.isFinite(freightUnitPrice)) return; |
| | | |
| | | productForm.value.totalFreight = (quantity * freightUnitPrice).toFixed(2); |
| | | }; |
| | | |
| | | const handleQuantityChange = () => { |
| | | mathNum(); |
| | | computeTotalFreight(); |
| | | }; |
| | | |
| | | const handleFreightUnitPriceChange = () => { |
| | | computeTotalFreight(); |
| | | }; |
| | | const getProductOptions = () => { |
| | | return productTreeList().then(res => { |
| | |
| | | submitProductEdit(); |
| | | } else { |
| | | if (productOperationType.value === "add") { |
| | | productData.value.push({ ...productForm.value }); |
| | | console.log("productForm.value---", productForm.value); |
| | | console.log("productData.value---", productData.value); |
| | | productData.value.push({ ...productForm.value }); |
| | | } else { |
| | | productData.value[productOperationIndex.value] = { |
| | | ...productForm.value, |
| | |
| | | } |
| | | // 检查选中的产品中是否有完全入库的 |
| | | const hasFullyStocked = productSelectedRows.value.some( |
| | | row => row.stockInApprovalStatus === '完全入库' |
| | | row => row.stockInApprovalStatus === "完全入库" |
| | | ); |
| | | if (hasFullyStocked) { |
| | | proxy.$modal.msgWarning("选中的产品中包含完全入库的产品,无法删除"); |
| | |
| | | proxy.$modal.msgSuccess("删除成功"); |
| | | getList(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchGenerate = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("请选择数据"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | |
| | | ElMessageBox.confirm("确认批量生成数据?", "批量生成", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "info", |
| | | }) |
| | | .then(() => { |
| | | proxy.$modal.loading("正在批量生成数据,请稍候..."); |
| | | batchGeneratePurchaseInboundSteps({ ids }) |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("批量生成成功"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("批量生成失败"); |
| | | }) |
| | | .finally(() => { |
| | | proxy.$modal.closeLoading(); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | |
| | | contractNo: form.value.salesLedgerId, |
| | | }); |
| | | if (code == 200) { |
| | | productData.value = data; |
| | | productData.value = data || []; |
| | | } |
| | | }; |
| | | |