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