Files
mom-web/src/views/erp/purchase/order/PurchaseOrderForm.vue

367 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 移动端布局 -->
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
<div class="mobile-form" v-loading="formLoading">
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
<div class="mobile-form__section">
<div class="mobile-form__section-title">基本信息</div>
<el-form-item label="订单单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item>
<el-form-item label="订单时间" prop="orderTime"><el-date-picker v-model="formData.orderTime" type="date" value-format="x" placeholder="选择订单时间" style="width:100%" /></el-form-item>
<el-form-item label="关联请购单" prop="purchaseRequisitionNo"><el-input readonly><template #prefix><el-link v-if="formData.purchaseRequisitionId" type="primary" :underline="false" @click.stop="openRequisitionDetail">{{ formData.purchaseRequisitionNo }}</el-link></template><template #append><el-button @click="openRequisitionSelect"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
<el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" style="width:100%"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select><div class="mobile-form__tip">*如果没有供应商请忽略此项填写</div></el-form-item>
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注" /></el-form-item>
<el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item>
</div>
<div class="mobile-form__section">
<div class="mobile-form__section-title">订单产品清单</div>
<PurchaseOrderItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" />
</div>
<div class="mobile-form__section">
<div class="mobile-form__section-title">费用信息</div>
<el-form-item label="优惠率(%" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="2" placeholder="请输入优惠率" style="width:100%" /></el-form-item>
<el-form-item label="付款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
<el-form-item label="优惠后金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
<el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
<el-form-item label="支付订金" prop="depositPrice"><el-input-number v-model="formData.depositPrice" controls-position="right" :min="0" :precision="2" placeholder="请输入支付订金" style="width:100%" /></el-form-item>
</div>
</el-form>
<div class="mobile-form__footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled"> </el-button>
</div>
</div>
</el-drawer>
<!-- PC端布局 -->
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="1440">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading" :disabled="disabled">
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="订单单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="订单时间" prop="orderTime"><el-date-picker v-model="formData.orderTime" type="date" value-format="x" placeholder="选择订单时间" class="!w-1/1" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="关联请购单" prop="purchaseRequisitionNo"><el-input readonly><template #prefix><el-link v-if="formData.purchaseRequisitionNo && formData.purchaseRequisitionId" type="primary" :underline="false" @click.stop="openRequisitionDetail" class="requisition-link">{{ formData.purchaseRequisitionNo }}</el-link></template><template #append><el-button @click="openRequisitionSelect" :disabled="disabled"><Icon icon="ep:search" /> 选择</el-button></template></el-input></el-form-item></el-col>
<el-col :span="8"><el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" class="!w-1/1"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select><div style="margin-top:4px;font-size:12px;color:var(--el-color-danger);">*如果没有供应商请忽略此项填写</div></el-form-item></el-col>
<el-col :span="16"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注(如果有额外的内容填写,请在此文本框里面填写)" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item></el-col>
</el-row>
<ContentWrap>
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
<el-tab-pane label="订单产品清单" name="item"><PurchaseOrderItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" /></el-tab-pane>
</el-tabs>
</ContentWrap>
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="优惠率(%" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="2" placeholder="请输入优惠率" class="!w-1/1" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="付款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="优惠后金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" class="!w-1/1"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
<el-col :span="8"><el-form-item label="支付订金" prop="depositPrice"><el-input-number v-model="formData.depositPrice" controls-position="right" :min="0" :precision="2" placeholder="请输入支付订金" class="!w-1/1" /></el-form-item></el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
<!-- 请购单选择弹窗 -->
<PurchaseRequisitionTableSelect ref="requisitionSelectRef" @change="onRequisitionSelected" />
<!-- 请购单详情弹窗 -->
<PurchaseRequisitionForm ref="requisitionFormRef" />
</template>
<script setup lang="ts">
import { nextTick, computed } from 'vue'
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
import PurchaseOrderItemForm from './components/PurchaseOrderItemForm.vue'
import PurchaseRequisitionTableSelect from './components/PurchaseRequisitionTableSelect.vue'
import PurchaseRequisitionForm from '@/views/erp/purchaserequisition/PurchaseRequisitionForm.vue'
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
import { erpPriceInputFormatter, erpPriceMultiply } from '@/utils'
import * as UserApi from '@/api/system/user'
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
import { PurchaseRequisitionApi, PurchaseRequisition } from '@/api/erp/purchaserequisition'
import { useWindowSize } from '@vueuse/core'
/** ERP 采购订单表单 */
defineOptions({ name: 'PurchaseOrderForm' })
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 768)
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
// 类型声明,确保表单有稳定的类型
interface PurchaseOrderItem {
totalPrice?: number
// 其余字段不强制约束
[key: string]: any
}
interface PurchaseOrderFormModel {
id?: number
purchaseRequisitionId?: number
purchaseRequisitionNo?: string
supplierId?: number
accountId?: number
orderTime?: number
remark?: string
fileUrl: string
discountPercent: number
discountPrice: number
totalPrice: number
depositPrice: number
items: PurchaseOrderItem[]
no?: string
}
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formType = ref('') // 表单的类型create - 新增update - 修改detail - 详情
const formData = ref<PurchaseOrderFormModel>({
id: undefined,
purchaseRequisitionId: undefined,
purchaseRequisitionNo: undefined,
supplierId: undefined,
accountId: undefined,
orderTime: Date.now(),
remark: undefined,
fileUrl: '',
discountPercent: 0,
discountPrice: 0,
totalPrice: 0,
depositPrice: 0,
items: [],
no: undefined // 订单单号,后端返回
})
const formRules = reactive({
supplierId: [{ required: true, message: '供应商不能为空', trigger: 'blur' }],
orderTime: [{ required: true, message: '订单时间不能为空', trigger: 'blur' }]
})
const disabled = computed(() => formType.value === 'detail')
const formRef = ref() // 表单 Ref
const supplierList = ref<SupplierVO[]>([]) // 供应商列表
const accountList = ref<AccountVO[]>([]) // 账户列表
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
const requisitionSelectRef = ref() // 请购单选择弹窗Ref
const requisitionFormRef = ref() // 请购单详情弹窗Ref
const subTabsName = ref('item')
const itemFormRef = ref()
/** 计算 discountPrice、totalPrice 价格 */
watch(
() => formData.value,
(val) => {
if (!val) {
return
}
const totalPrice = (val.items || []).reduce(
(prev: number, curr: PurchaseOrderItem) => prev + (curr.totalPrice || 0),
0
)
const discountPrice =
val.discountPercent != null
? erpPriceMultiply(totalPrice, val.discountPercent / 100.0) ?? 0
: 0
formData.value.discountPrice = discountPrice
formData.value.totalPrice = totalPrice - discountPrice
},
{ deep: true }
)
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
await nextTick()
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = (await PurchaseOrderApi.getPurchaseOrder(id)) as unknown as PurchaseOrderFormModel
} finally {
formLoading.value = false
}
}
// 加载供应商列表
supplierList.value = await SupplierApi.getSupplierSimpleList()
// 如果是新增,默认选择第一个供应商
if (formType.value === 'create' && supplierList.value.length > 0 && !formData.value.supplierId) {
formData.value.supplierId = supplierList.value[0].id
}
// 加载用户列表
userList.value = await UserApi.getSimpleUserList()
// 加载账户列表
accountList.value = await AccountApi.getAccountSimpleList()
const defaultAccount = accountList.value.find((item) => item.defaultStatus)
if (formType.value === 'create' && defaultAccount) {
formData.value.accountId = defaultAccount.id
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
await itemFormRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as PurchaseOrderVO
if (formType.value === 'create') {
await PurchaseOrderApi.createPurchaseOrder(data)
message.success(t('common.createSuccess'))
} else {
await PurchaseOrderApi.updatePurchaseOrder(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 打开请购单选择弹窗 */
const openRequisitionSelect = () => {
requisitionSelectRef.value?.open(formData.value.purchaseRequisitionNo)
}
/** 打开请购单详情弹窗 */
const openRequisitionDetail = () => {
if (formData.value.purchaseRequisitionId) {
requisitionFormRef.value?.open('detail', formData.value.purchaseRequisitionId)
}
}
/** 请购单选择回调 */
const onRequisitionSelected = async (requisition: PurchaseRequisition) => {
debugger;
if (!requisition) {
return
}
formData.value.purchaseRequisitionId = requisition.id
formData.value.purchaseRequisitionNo = requisition.no
// 自动填充供应商信息(直接从主表数据获取)
if (requisition.supplierId) {
formData.value.supplierId = requisition.supplierId
}
try {
// 获取请购单详情(仅用于获取明细项)
const requisitionDetail = await PurchaseRequisitionApi.getPurchaseRequisition(requisition.id)
// 转换请购单明细为采购订单明细
const orderItems = (requisitionDetail.items || []).map(requisitionItem => ({
id: undefined,
productId: requisitionItem.productId,
productName: requisitionItem.productName,
productUnitName: requisitionItem.productUnitName,
productBarCode: undefined,
productPrice: requisitionItem.productPrice,
stockCount: undefined,
count: requisitionItem.count,
totalProductPrice: requisitionItem.totalPrice,
taxPercent: 0,
taxPrice: 0,
totalPrice: requisitionItem.totalPrice,
remark: requisitionItem.remark
}))
formData.value.items = orderItems
// 如果请购单有备注,也带过来(优先使用主表数据)
if (requisition.remark) {
formData.value.remark = `基于请购单 ${requisition.no} 生成\n${requisition.remark}`
} else if (requisitionDetail.remark) {
formData.value.remark = `基于请购单 ${requisitionDetail.no} 生成\n${requisitionDetail.remark}`
}
} catch (error) {
console.error('获取请购单详情失败', error)
message.error('获取请购单详情失败')
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
purchaseRequisitionId: undefined,
purchaseRequisitionNo: undefined,
supplierId: undefined,
accountId: undefined,
orderTime: Date.now(),
remark: undefined,
fileUrl: '',
discountPercent: 0,
discountPrice: 0,
totalPrice: 0,
depositPrice: 0,
items: []
}
formRef.value?.resetFields()
}
</script>
<style lang="scss" scoped>
.mobile-form {
padding: 12px;
}
.mobile-form__section {
background: #fff;
border-radius: 10px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.mobile-form__section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.mobile-form__tip {
margin-top: 4px;
font-size: 12px;
color: var(--el-color-danger);
}
.mobile-form__requisition {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.mobile-form__requisition-empty {
color: #909399;
font-size: 13px;
}
.mobile-form__footer {
position: sticky;
bottom: 0;
background: #fff;
padding: 12px 16px;
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
padding-bottom: calc(12px + env(safe-area-inset-bottom));
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
z-index: 10;
margin: 0 -12px -12px;
.el-button {
flex: 1;
height: 40px;
font-size: 15px;
}
}
</style>