| | |
| | | <el-dialog |
| | | v-model="isShow" |
| | | title="新增采购退货" |
| | | width="1200" |
| | | width="1600" |
| | | @close="closeModal" |
| | | > |
| | | <el-form label-width="140px" :model="formState" label-position="top" ref="formRef" :inline="true"> |
| | | <div class="section-title"> |
| | | <span class="title-dot"></span> |
| | | <span class="title-text">基本信息</span> |
| | | </div> |
| | | <el-form-item |
| | | label="退料单号" |
| | | prop="no" |
| | |
| | | v-model="formState.no" |
| | | :placeholder="formState.isDefaultNo ? '使用系统编号' : '请输入退料单号'" |
| | | :disabled="formState.isDefaultNo" |
| | | style="width: 240px" |
| | | > |
| | | <template #append> |
| | | <el-checkbox v-model="formState.isDefaultNo" size="large" @change="handleChangeIsDefaultNo" /> |
| | |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="项目" |
| | | prop="projectId" |
| | | > |
| | | <el-select |
| | | v-model="formState.projectId" |
| | | placeholder="请选择项目" |
| | | style="width: 240px" |
| | | @focus="fetchProjectOptions" |
| | | > |
| | | <el-option |
| | | v-for="item in projectOptions" |
| | | :key="item.id" |
| | | :label="item.name" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | :reserve-keyword="false" |
| | | style="width: 240px" |
| | | @focus="fetchUserOptions" |
| | | @change="formState.preparedUserName = userOptions.find(item => item.userId === formState.preparedUserId)?.nickName || ''" |
| | | > |
| | | <el-option |
| | | v-for="item in userOptions" |
| | |
| | | style="width: 240px" |
| | | :reserve-keyword="false" |
| | | @focus="fetchUserOptions" |
| | | @change="formState.returnUserName = userOptions.find(item => item.userId === formState.returnUserId)?.nickName || ''" |
| | | > |
| | | <el-option |
| | | v-for="item in userOptions" |
| | |
| | | label="备注:" |
| | | prop="remark" |
| | | > |
| | | <el-input v-model="formState.remark" type="textarea" placeholder="请输入备注"/> |
| | | <el-input style="width: 240px" v-model="formState.remark" :rows="1" type="textarea" placeholder="请输入备注"/> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <el-button type="primary" size="small" @click="isShowProductsModal = true" :disabled="!formState.purchaseLedgerId">添加产品</el-button> |
| | | <el-table :data="products" border> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <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="unit" |
| | | width="70" /> |
| | | <el-table-column label="数量" |
| | | prop="quantity" |
| | | width="70" /> |
| | | <el-table-column label="库存预警数量" |
| | | prop="warnNum" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" |
| | | width="80" /> |
| | | <el-table-column label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="含税总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="不含税总价(元)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="是否质检" |
| | | prop="isChecked" |
| | | width="150"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.isChecked ? 'success' : 'info'"> |
| | | {{ scope.row.isChecked ? '是' : '否' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" |
| | | label="操作" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | > |
| | | 编辑 |
| | | </el-button> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="delProduct(scope.$index)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div style="margin: 20px 0;"> |
| | | <div class="section-title"> |
| | | <span class="title-dot"></span> |
| | | <span class="title-text">产品列表</span> |
| | | </div> |
| | | <el-button type="primary" size="small" style="margin-bottom: 20px" @click="isShowProductsModal = true" :disabled="!formState.purchaseLedgerId">添加产品</el-button> |
| | | <el-table :data="formState.purchaseReturnOrderProductsDtos" |
| | | border |
| | | max-height="400" |
| | | :scroll-y="true" |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <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="unit" |
| | | width="70" /> |
| | | <el-table-column label="数量" |
| | | prop="quantity" |
| | | width="100" /> |
| | | <el-table-column label="可退货数量" |
| | | prop="availableQuality" |
| | | width="130" /> |
| | | <el-table-column label="退货数量" |
| | | prop="returnQuantity" |
| | | width="180"> |
| | | <template #default="scope"> |
| | | <el-input-number v-model="scope.row.returnQuantity" |
| | | controls-position="right" |
| | | :step="1" |
| | | :min="0" |
| | | :max="getReturnQtyMax(scope.row)" |
| | | :disabled="getReturnQtyMax(scope.row) <= 0" |
| | | @change="syncReturnTotal(scope.row)" |
| | | required |
| | | placeholder="请输入退货数量" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="库存预警数量" |
| | | prop="warnNum" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" |
| | | width="80" /> |
| | | <el-table-column label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="退货总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="180"> |
| | | <template #default="scope"> |
| | | {{ formatAmount(getReturnTotal(scope.row)) || '--' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="是否质检" |
| | | prop="isChecked" |
| | | width="150"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.isChecked ? 'success' : 'info'"> |
| | | {{ scope.row.isChecked ? '是' : '否' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" |
| | | label="操作" |
| | | width="100" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | | type="danger" |
| | | size="small" |
| | | @click="delProduct(scope.$index)" |
| | | > |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div class="section-title"> |
| | | <span class="title-dot"></span> |
| | | <span class="title-text">费用信息</span> |
| | | </div> |
| | | |
| | | <el-form-item |
| | | label="整单折扣额:" |
| | | prop="totalDiscountAmount" |
| | | > |
| | | <el-input-number v-model="formState.totalDiscountAmount" |
| | | controls-position="right" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | @change="handleChangeTotalDiscountAmount" |
| | | placeholder="请输入整单折扣额"/> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="整单折扣率:" |
| | | prop="totalDiscountAmount" |
| | | > |
| | | <el-input-number v-model="formState.totalDiscountRate" |
| | | controls-position="right" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | placeholder="请输入整单折扣率"/> |
| | | </el-form-item> |
| | | |
| | | <el-form-item |
| | | label="成交金额:" |
| | | prop="totalAmount" |
| | | :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请输入成交金额', |
| | | trigger: 'change', |
| | | } |
| | | ]" |
| | | > |
| | | <el-input-number v-model="formState.totalAmount" |
| | | controls-position="right" |
| | | :step="0.01" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | placeholder="请输入成交金额"/> |
| | | </el-form-item> |
| | | <el-form-item label="收款方式" prop="incomeType" :rules="[ |
| | | { |
| | | required: true, |
| | | message: '请选择收款方式', |
| | | trigger: 'change', |
| | | } |
| | | ]"> |
| | | <el-select |
| | | style="width: 240px;" |
| | | v-model="formState.incomeType" |
| | | placeholder="请选择" |
| | | clearable |
| | | |
| | | > |
| | | <el-option :label="item.label" :value="item.value" v-for="(item,index) in payment_methods" :key="index" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="handleSubmit">确认</el-button> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, computed, getCurrentInstance} from "vue"; |
| | | import {ref, computed, getCurrentInstance, watch, defineAsyncComponent} from "vue"; |
| | | import {createPurchaseReturnOrder} from "@/api/procurementManagement/purchase_return_order.js"; |
| | | import {getOptions, purchaseList} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | const ProductList = defineAsyncComponent(() => import("@/views/procurementManagement/purchaseReturnOrder/ProductList.vue")); |
| | | import { |
| | | productList, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | |
| | | } |
| | | }); |
| | | let { proxy } = getCurrentInstance() |
| | | const { payment_methods } = proxy.useDict("payment_methods"); |
| | | const emit = defineEmits(['update:visible', 'completed']); |
| | | |
| | | // 响应式数据(替代选项式的 data) |
| | |
| | | no: '', |
| | | isDefaultNo: true, |
| | | returnType: 0, |
| | | incomeType: undefined, |
| | | remark: '', |
| | | supplierId: undefined, |
| | | projectId: undefined, |
| | |
| | | preparedUserId: undefined, |
| | | returnUserId: undefined, |
| | | purchaseLedgerId: undefined, |
| | | purchaseReturnOrderProductsDtos: [], |
| | | totalDiscountAmount: 0, |
| | | totalDiscountRate: undefined, |
| | | totalAmount: 0, |
| | | }); |
| | | // 供应商选项 |
| | | const supplierOptions = ref([]) |
| | | // 项目选项 |
| | | const projectOptions = ref([]) |
| | | // 项目阶段选项 |
| | | const projectStageOptions = ref([ |
| | | { |
| | |
| | | const userOptions = ref([]) |
| | | // 采购台账选项 |
| | | const purchaseLedgerOptions = ref([]) |
| | | // 产品列表数据 |
| | | const products = ref([]) |
| | | // 是否展示产品列表数据 |
| | | const isShowProductsModal = ref(false) |
| | | |
| | |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | |
| | | const formatAmount = (value) => { |
| | | if (value === null || value === undefined || value === '') { |
| | | return '--' |
| | | } |
| | | const num = Number(value) |
| | | if (Number.isNaN(num)) { |
| | | return '--' |
| | | } |
| | | return num.toFixed(2) |
| | | } |
| | | |
| | | const toNumber = (val) => { |
| | | const num = Number(val) |
| | | return Number.isNaN(num) ? 0 : num |
| | | } |
| | | |
| | | const getReturnTotal = (row) => { |
| | | const qty = toNumber(row?.returnQuantity) |
| | | const unitPrice = toNumber(row?.taxInclusiveUnitPrice) |
| | | const total = qty * unitPrice |
| | | return Number(total.toFixed(2)) |
| | | } |
| | | |
| | | const syncReturnTotal = (row) => { |
| | | if (!row) { |
| | | return |
| | | } |
| | | row.taxInclusiveTotalPrice = getReturnTotal(row) |
| | | } |
| | | |
| | | const getReturnQtyMax = (row) => { |
| | | const max = Number(row?.availableQuality) |
| | | if (Number.isNaN(max) || max < 0) { |
| | | return 0 |
| | | } |
| | | return max |
| | | } |
| | | |
| | | const closeModal = () => { |
| | | isShow.value = false; |
| | | }; |
| | | |
| | | const summarizeChildrenTable = (param) => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "quantity", |
| | | "availableQuality", |
| | | "returnQuantity", |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ], |
| | | { |
| | | quantity: { noDecimal: true }, // 不保留小数 |
| | | returnQuantity: { noDecimal: true }, // 不保留小数 |
| | | availableQuality: { noDecimal: true }, // 不保留小数 |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | const handleChangeTotalDiscountAmount= () => { |
| | | syncTotalAmount() |
| | | } |
| | | |
| | | const syncTotalAmount = () => { |
| | | const rows = formState.value.purchaseReturnOrderProductsDtos || [] |
| | | const baseAmount = rows.reduce((sum, item) => { |
| | | return sum + toNumber(item.taxInclusiveTotalPrice) |
| | | }, 0) |
| | | const discount = toNumber(formState.value.totalDiscountAmount) |
| | | // 成交金额 = 产品退货总价合计 - 折扣额 |
| | | formState.value.totalAmount = Number((baseAmount - discount).toFixed(2)) |
| | | } |
| | | |
| | | // 获取供应商选项 |
| | | const fetchSupplierOptions = () => { |
| | |
| | | }); |
| | | } |
| | | |
| | | // 获取项目选项 |
| | | const fetchProjectOptions = () => { |
| | | if (projectOptions.value.length > 0) { |
| | | return |
| | | } |
| | | // todo 项目选项 |
| | | } |
| | | |
| | | // 获取用户选项 |
| | | const fetchUserOptions = () => { |
| | |
| | | // 处理改变供应商数据 |
| | | const handleChangeSupplierId = () => { |
| | | formState.value.purchaseLedgerId = undefined |
| | | formState.value.supplierName = supplierOptions.value.find(item => item.id === formState.value.supplierId)?.supplierName || '' |
| | | fetchPurchaseLedgerOptions() |
| | | } |
| | | |
| | |
| | | const fetchPurchaseLedgerOptions = () => { |
| | | purchaseLedgerOptions.value = [] |
| | | if (formState.value.supplierId) { |
| | | purchaseList({supplierId: formState.value.supplierId}).then((res) => { |
| | | purchaseList({supplierId: formState.value.supplierId,approvalStatus:3}).then((res) => { |
| | | purchaseLedgerOptions.value = res.rows; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 处理改变采购台账数据 |
| | | const handleChangePurchaseLedgerId = () => { |
| | | products.value = [] |
| | | const handleChangePurchaseLedgerId = async () => { |
| | | const res = await productList({ salesLedgerId: formState.value.purchaseLedgerId, type: 2 }); |
| | | formState.value.purchaseReturnOrderProductsDtos = res.data.map(item => ({ |
| | | ...item, |
| | | returnQuantity: undefined, |
| | | taxInclusiveTotalPrice: 0, |
| | | salesLedgerProductId: item.id, |
| | | })) |
| | | syncTotalAmount() |
| | | } |
| | | |
| | | // 处理改变是否默认编号 |
| | |
| | | |
| | | // 增加产品 |
| | | const handleAddProduct = (selectedRows) => { |
| | | products.value.push(...selectedRows) |
| | | const existingIds = new Set(formState.value.purchaseReturnOrderProductsDtos.map(item => item.id)); |
| | | const newProducts = selectedRows.filter(item => !existingIds.has(item.id)).map(item => ({ |
| | | ...item, |
| | | returnQuantity: undefined, |
| | | taxInclusiveTotalPrice: 0, |
| | | salesLedgerProductId: item.id, |
| | | })); |
| | | formState.value.purchaseReturnOrderProductsDtos.push(...newProducts); |
| | | syncTotalAmount() |
| | | } |
| | | |
| | | // 删除单项产品 |
| | | const delProduct = (index) => { |
| | | products.value.splice(index, 1) |
| | | formState.value.purchaseReturnOrderProductsDtos.splice(index, 1) |
| | | syncTotalAmount() |
| | | } |
| | | |
| | | // 提交表单 |
| | | const handleSubmit = () => { |
| | | const productList = formState.value.purchaseReturnOrderProductsDtos || [] |
| | | |
| | | productList.forEach(syncReturnTotal) |
| | | |
| | | if (productList.length === 0) { |
| | | proxy.$modal.msgError("请先选择产品") |
| | | return |
| | | } |
| | | |
| | | // 逐行校验退货数量:任意一行未填/非法/超限都不允许提交 |
| | | const invalidRowIndex = productList.findIndex((item) => { |
| | | const qty = Number(item.returnQuantity) |
| | | const maxQty = Number(item.availableQuality) |
| | | |
| | | if (item.returnQuantity === null || item.returnQuantity === undefined || item.returnQuantity === "") { |
| | | return true |
| | | } |
| | | if (Number.isNaN(qty) || qty <= 0) { |
| | | return true |
| | | } |
| | | if (!Number.isNaN(maxQty) && maxQty > 0 && qty > maxQty) { |
| | | return true |
| | | } |
| | | return false |
| | | }) |
| | | |
| | | if (invalidRowIndex !== -1) { |
| | | proxy.$modal.msgError(`第${invalidRowIndex + 1}行退货数量未填写或不合法`) |
| | | return |
| | | } |
| | | |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | createPurchaseReturnOrder(formState.value).then(res => { |
| | |
| | | }) |
| | | }; |
| | | |
| | | watch( |
| | | () => formState.value.purchaseReturnOrderProductsDtos, |
| | | (rows) => { |
| | | (rows || []).forEach(syncReturnTotal) |
| | | syncTotalAmount() |
| | | }, |
| | | { deep: true } |
| | | ) |
| | | |
| | | defineExpose({ |
| | | closeModal, |
| | | handleSubmit, |
| | | isShow, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .section-title { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | width: 100%; |
| | | clear: both; |
| | | } |
| | | |
| | | .title-dot { |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | background-color: #409EFF; |
| | | border-radius: 50%; |
| | | margin-right: 8px; |
| | | } |
| | | </style> |