admin-vue3/src/views/wms/order/receipt/ReceiptOrderForm.vue

455 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- WMS 入库单表单 -->
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1280px">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="92px"
>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="入库单号" prop="no">
<el-input v-model="formData.no" maxlength="64" placeholder="请输入入库单号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="入库类型" prop="type">
<el-select v-model="formData.type" class="w-1/1" placeholder="请选择入库类型">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_RECEIPT_ORDER_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="仓库" prop="warehouseId">
<WarehouseSelect v-model="formData.warehouseId" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="单据日期" prop="orderTime">
<el-date-picker
v-model="formData.orderTime"
class="!w-1/1"
placeholder="请选择单据日期"
type="date"
value-format="x"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="供应商" prop="merchantId">
<MerchantSelect
v-model="formData.merchantId"
placeholder="请选择供应商"
supplier
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="业务单号" prop="bizOrderNo">
<el-input v-model="formData.bizOrderNo" maxlength="64" placeholder="请输入业务单号" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
maxlength="255"
placeholder="请输入备注"
:rows="3"
type="textarea"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="总数量" prop="totalQuantity">
<el-input :model-value="formatQuantity(totalQuantity)" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="总金额" prop="totalAmount">
<el-input-number
v-model="formData.totalAmount"
:controls="false"
:min="0"
:precision="PRICE_PRECISION"
class="!w-1/1"
placeholder="请输入总金额"
/>
</el-form-item>
</el-col>
</el-row>
<div class="mb-12px flex items-center justify-between">
<span class="text-14px font-bold">入库明细</span>
<el-tooltip
content="请先选择仓库"
:disabled="!!formData.warehouseId"
placement="top"
>
<span>
<el-button :disabled="!formData.warehouseId" plain type="primary" @click="handleAddDetail">
<Icon class="mr-5px" icon="ep:plus" />
添加商品
</el-button>
</span>
</el-tooltip>
</div>
<el-table :data="formData.details" border empty-text="暂无商品明细">
<el-table-column label="商品信息" min-width="220">
<template #default="scope">
<div>{{ scope.row.itemName || '-' }}</div>
<div v-if="scope.row.itemCode" class="text-12px text-gray-500">
商品编号:{{ scope.row.itemCode }}
</div>
</template>
</el-table-column>
<el-table-column label="规格信息" min-width="220">
<template #default="scope">
<div>{{ scope.row.skuName || '-' }}</div>
<div v-if="scope.row.skuCode" class="text-12px text-gray-500">
规格编号:{{ scope.row.skuCode }}
</div>
</template>
</el-table-column>
<el-table-column label="入库数量" width="160">
<template #default="scope">
<el-input-number
v-model="scope.row.quantity"
:controls="false"
:min="0"
:precision="QUANTITY_PRECISION"
class="!w-1/1"
placeholder="数量"
/>
</template>
</el-table-column>
<el-table-column label="金额(元)" width="160">
<template #default="scope">
<el-input-number
v-model="scope.row.amount"
:controls="false"
:min="0"
:precision="PRICE_PRECISION"
class="!w-1/1"
placeholder="金额"
@change="handleDetailAmountChange"
/>
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="80">
<template #default="scope">
<el-button link type="danger" @click="handleDeleteDetail(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
<ItemSkuSelect ref="skuSelectRef" @change="handleSelectSku" />
</el-form>
<template #footer>
<div class="flex items-center justify-between">
<div>
<el-button
v-if="isSavedPrepareOrder"
v-hasPermi="['wms:receipt-order:complete']"
:disabled="formLoading"
type="success"
@click="handleComplete"
>
完成入库
</el-button>
<el-button
v-if="isSavedPrepareOrder"
v-hasPermi="['wms:receipt-order:cancel']"
:disabled="formLoading"
type="danger"
@click="handleCancel"
>
作废
</el-button>
</div>
<div>
<el-button v-if="isPrepareOrder" :disabled="formLoading" type="primary" @click="submitForm">
保存
</el-button>
<el-button @click="dialogVisible = false"> </el-button>
</div>
</div>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { FormRules } from 'element-plus'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
import { ReceiptOrderDetailVO } from '@/api/wms/order/receipt/detail'
import { ItemSkuVO } from '@/api/wms/md/item/sku'
import ItemSkuSelect from '@/views/wms/md/item/sku/components/ItemSkuSelect.vue'
import MerchantSelect from '@/views/wms/md/merchant/components/MerchantSelect.vue'
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
import { OrderStatusEnum, OrderUpdateStatusList } from '@/views/wms/utils/constants'
import {
formatQuantity,
PRICE_PRECISION,
QUANTITY_PRECISION,
sumPrice,
sumQuantity
} from '@/views/wms/utils/format'
import { generateOrderNo } from '@/views/wms/utils/order'
/** WMS 入库单表单 */
defineOptions({ name: 'WmsReceiptOrderForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中
const formType = ref('') // 表单的类型create - 新增update - 修改
const originalFormData = ref('') // 表单原始数据快照
const formData = ref<ReceiptOrderVO>({
id: undefined,
no: undefined,
type: undefined,
orderTime: undefined,
status: OrderStatusEnum.PREPARE,
bizOrderNo: undefined,
merchantId: undefined,
warehouseId: undefined,
totalQuantity: 0,
totalAmount: 0,
remark: undefined,
details: []
})
const formRules = reactive<FormRules>({
no: [{ required: true, message: '入库单号不能为空', trigger: 'blur' }],
type: [{ required: true, message: '入库类型不能为空', trigger: 'change' }],
orderTime: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'change' }],
totalQuantity: [{ required: true, message: '入库数量不能为空', trigger: 'change' }],
totalAmount: [{ required: true, message: '总金额不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const skuSelectRef = ref() // 商品 SKU 选择弹窗 Ref
const totalQuantity = computed(() => sumQuantity(formData.value.details || [], (detail) => detail.quantity))
const detailTotalAmount = computed(() => sumPrice(formData.value.details || [], (detail) => detail.amount))
const isPrepareOrder = computed(
() =>
!formData.value.id ||
(formData.value.status !== undefined && OrderUpdateStatusList.includes(formData.value.status))
)
const isSavedPrepareOrder = computed(
() =>
!!formData.value.id &&
formData.value.status !== undefined &&
OrderUpdateStatusList.includes(formData.value.status)
)
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
const order = await ReceiptOrderApi.getReceiptOrder(id)
formData.value = {
...order,
details: order.details || []
}
} finally {
formLoading.value = false
}
}
originalFormData.value = JSON.stringify(buildSubmitData())
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 构建入库明细 */
const buildDetail = (sku: ItemSkuVO): ReceiptOrderDetailVO => ({
id: undefined,
itemId: sku.itemId,
itemCode: sku.itemCode,
itemName: sku.itemName,
unit: sku.unit,
skuId: sku.id,
skuCode: sku.code,
skuName: sku.name,
quantity: undefined,
amount: undefined
})
/** 添加商品 */
const handleAddDetail = () => {
skuSelectRef.value?.open(getSelectedSkuIds())
}
/** 选择商品 SKU */
const handleSelectSku = (skus: ItemSkuVO[]) => {
if (!skus.length) {
return
}
formData.value.details = formData.value.details || []
const selectedSkuIds = new Set(getSelectedSkuIds())
skus.forEach((sku) => {
if (!sku.id || selectedSkuIds.has(sku.id)) {
return
}
formData.value.details!.push(buildDetail(sku))
selectedSkuIds.add(sku.id)
})
refreshTotalAmount()
}
/** 获得已选 SKU 编号 */
const getSelectedSkuIds = () => {
return (formData.value.details || [])
.map((detail) => detail.skuId)
.filter((id): id is number => !!id)
}
/** 删除明细 */
const handleDeleteDetail = (index: number) => {
formData.value.details?.splice(index, 1)
refreshTotalAmount()
}
/** 明细金额变化 */
const handleDetailAmountChange = () => {
refreshTotalAmount()
}
/** 汇总明细金额 */
const refreshTotalAmount = () => {
formData.value.totalAmount = detailTotalAmount.value
}
/** 校验明细 */
const validateDetails = (required: boolean) => {
if (!formData.value.details?.length) {
if (required) {
message.error('至少包含一条入库明细')
return false
}
return true
}
for (let i = 0; i < formData.value.details.length; i++) {
const detail = formData.value.details[i]
if (!detail.skuId) {
message.error(`${i + 1} 行明细请选择商品规格`)
return false
}
if (!detail.quantity || detail.quantity <= 0) {
message.error(`${i + 1} 行明细入库数量必须大于 0`)
return false
}
}
return true
}
/** 构建提交数据 */
const buildSubmitData = () =>
({
...formData.value,
totalQuantity: totalQuantity.value,
totalAmount: formData.value.totalAmount,
details: formData.value.details || []
}) as ReceiptOrderVO
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
if (!validateDetails(false)) {
return
}
// 提交请求
formLoading.value = true
try {
const data = buildSubmitData()
if (formType.value === 'create') {
await ReceiptOrderApi.createReceiptOrder(data)
message.success(t('common.createSuccess'))
} else {
await ReceiptOrderApi.updateReceiptOrder(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 完成入库:表单修改过则先保存,再完成 */
const handleComplete = async () => {
await formRef.value.validate()
if (!validateDetails(true)) {
return
}
try {
await message.confirm('确认完成入库?完成后将更新库存。')
formLoading.value = true
const data = buildSubmitData()
if (JSON.stringify(data) !== originalFormData.value) {
await ReceiptOrderApi.updateReceiptOrder(data)
}
await ReceiptOrderApi.completeReceiptOrder(formData.value.id!)
message.success('入库成功')
dialogVisible.value = false
emit('success')
} catch {
} finally {
formLoading.value = false
}
}
/** 作废入库单 */
const handleCancel = async () => {
try {
await message.confirm('确认作废该入库单?作废后不可恢复。')
formLoading.value = true
await ReceiptOrderApi.cancelReceiptOrder(formData.value.id!)
message.success('作废成功')
dialogVisible.value = false
emit('success')
} catch {
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
no: generateOrderNo('RK'),
type: undefined,
orderTime: undefined,
status: OrderStatusEnum.PREPARE,
bizOrderNo: undefined,
merchantId: undefined,
warehouseId: undefined,
totalQuantity: 0,
totalAmount: 0,
remark: undefined,
details: []
}
originalFormData.value = ''
nextTick(() => formRef.value?.clearValidate())
}
</script>