feat(wms):移库管理,调整合计金额、数量的字段与交互。(前端负责展示,后端负责计算)

pull/878/head
YunaiV 2026-05-14 09:46:19 +08:00
parent df013ac69c
commit eaedb1e0ca
5 changed files with 130 additions and 62 deletions

View File

@ -15,6 +15,7 @@ export interface MovementOrderDetailVO {
targetWarehouseName?: string
quantity?: number
availableQuantity?: number
amount?: number
price?: number
totalPrice?: number
createTime?: Date
}

View File

@ -13,7 +13,7 @@ export interface MovementOrderVO {
targetWarehouseId?: number
targetWarehouseName?: string
totalQuantity?: number
totalAmount?: number
totalPrice?: number
details?: MovementOrderDetailVO[]
createTime?: Date
creator?: string

View File

@ -26,7 +26,7 @@
{{ formatQuantity(detailData.totalQuantity) || '-' }}
</el-descriptions-item>
<el-descriptions-item label="总金额">
{{ formatPrice(detailData.totalAmount) || '-' }}
{{ formatPrice(detailData.totalPrice) || '-' }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ formatNullableDate(detailData.createTime) }}
@ -46,7 +46,7 @@
</el-descriptions>
<div class="mb-16px mt-24px text-18px font-bold">商品明细</div>
<el-table :data="detailData.details || []" :summary-method="getSummaries" border show-summary>
<el-table :data="detailRows" :summary-method="getSummaries" border show-summary>
<el-table-column label="商品信息" min-width="200">
<template #default="{ row }">
<div>{{ row.itemName || '-' }}</div>
@ -62,8 +62,11 @@
<el-table-column align="right" label="数量" prop="quantity" width="120">
<template #default="{ row }">{{ formatQuantity(row.quantity) || '-' }}</template>
</el-table-column>
<el-table-column align="right" label="金额(元)" prop="amount" width="140">
<template #default="{ row }">{{ formatPrice(row.amount) || '-' }}</template>
<el-table-column align="right" label="单价(元)" prop="price" width="140">
<template #default="{ row }">{{ formatPrice(row.price) || '-' }}</template>
</el-table-column>
<el-table-column align="right" label="金额(元)" prop="totalPrice" width="140">
<template #default="{ row }">{{ formatPrice(row.totalPrice) || '-' }}</template>
</el-table-column>
</el-table>
</div>
@ -84,11 +87,26 @@ const loading = ref(false)
const dialogVisible = ref(false)
const detailData = ref<MovementOrderVO>({})
const getSummaries = ({ columns, data }: { columns: any[]; data: MovementOrderDetailVO[] }) =>
interface DetailRow extends MovementOrderDetailVO {
totalPrice?: number
}
const detailRows = computed<DetailRow[]>(() =>
(detailData.value.details || []).map((detail) => ({
...detail,
totalPrice:
detail.price != null && detail.quantity
? Number(detail.price) * Number(detail.quantity)
: undefined
}))
)
const getSummaries = ({ columns, data }: { columns: any[]; data: DetailRow[] }) =>
columns.map((column, index) => {
if (index === 0) return '合计'
if (column.property === 'quantity') return formatSumQuantity(data, (detail) => detail.quantity)
if (column.property === 'amount') return formatSumPrice(data, (detail) => detail.amount)
if (column.property === 'price') return formatSumPrice(data, (detail) => detail.price)
if (column.property === 'totalPrice') return formatSumPrice(data, (detail) => detail.totalPrice)
return ''
})

View File

@ -29,24 +29,7 @@
/>
</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-col :span="8">
<el-col :span="16">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" maxlength="255" placeholder="请输入备注" :rows="3" type="textarea" />
</el-form-item>
@ -64,7 +47,13 @@
</span>
</el-tooltip>
</div>
<el-table :data="formData.details" border empty-text="">
<el-table
:data="formData.details"
:summary-method="getDetailSummaries"
border
empty-text="暂无商品明细"
show-summary
>
<el-table-column label="商品信息" min-width="210">
<template #default="{ row }">
<div>{{ row.itemName || '-' }}</div>
@ -80,7 +69,7 @@
<el-table-column align="right" label="可用库存" width="120">
<template #default="{ row }">{{ formatQuantity(row.availableQuantity) || '-' }}</template>
</el-table-column>
<el-table-column label="移库数量" width="160">
<el-table-column label="移库数量" prop="quantity" width="160">
<template #default="{ row }">
<el-input-number
v-model="row.quantity"
@ -89,19 +78,33 @@
:precision="QUANTITY_PRECISION"
class="!w-1/1"
placeholder="数量"
@change="handleDetailQuantityChange(row)"
/>
</template>
</el-table-column>
<el-table-column label="金额(元)" width="160">
<el-table-column label="单价(元)" prop="price" width="160">
<template #default="{ row }">
<el-input-number
v-model="row.amount"
v-model="row.price"
:controls="false"
:min="0"
:precision="PRICE_PRECISION"
class="!w-1/1"
placeholder="单价"
@change="handleDetailPriceChange(row)"
/>
</template>
</el-table-column>
<el-table-column label="金额(元)" prop="totalPrice" width="160">
<template #default="{ row }">
<el-input-number
v-model="row.totalPrice"
:controls="false"
:min="0"
:precision="PRICE_PRECISION"
class="!w-1/1"
placeholder="金额"
@change="refreshTotalAmount"
@change="handleDetailTotalPriceChange(row)"
/>
</template>
</el-table-column>
@ -155,7 +158,17 @@ import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
import InventorySelect, { InventorySelectRow } from '@/views/wms/inventory/components/InventorySelect.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 {
dividePrice,
formatPrice,
formatQuantity,
formatSumPrice,
formatSumQuantity,
multiplyPrice,
PRICE_PRECISION,
QUANTITY_PRECISION,
sumPrice
} from '@/views/wms/utils/format'
import { generateOrderNo } from '@/views/wms/utils/order'
/** WMS 移库单表单 */
@ -176,8 +189,6 @@ const formData = ref<MovementOrderVO>({
status: OrderStatusEnum.PREPARE,
sourceWarehouseId: undefined,
targetWarehouseId: undefined,
totalQuantity: 0,
totalAmount: 0,
remark: undefined,
details: []
})
@ -185,15 +196,12 @@ const formRules = reactive<FormRules>({
no: [{ required: true, message: '移库单号不能为空', trigger: 'blur' }],
orderTime: [{ required: true, message: '单据日期不能为空', trigger: 'change' }],
sourceWarehouseId: [{ required: true, message: '来源仓库不能为空', trigger: 'change' }],
targetWarehouseId: [{ required: true, message: '目标仓库不能为空', trigger: 'change' }],
totalQuantity: [{ required: true, message: '移库数量不能为空', trigger: 'change' }],
totalAmount: [{ required: true, message: '总金额不能为空', trigger: 'blur' }]
targetWarehouseId: [{ required: true, message: '目标仓库不能为空', trigger: 'change' }]
})
const formRef = ref()
const inventorySelectRef = ref()
const totalQuantity = computed(() => sumQuantity(formData.value.details || [], (detail) => detail.quantity))
const detailTotalAmount = computed(() => sumPrice(formData.value.details || [], (detail) => detail.amount))
const detailPriceSum = computed(() => sumPrice(formData.value.details || [], (detail) => detail.price))
const isPrepareOrder = computed(
() =>
!formData.value.id ||
@ -216,7 +224,7 @@ const open = async (type: string, id?: number) => {
formLoading.value = true
try {
const order = await MovementOrderApi.getMovementOrder(id)
formData.value = { ...order, details: order.details || [] }
formData.value = { ...order, details: normalizeDetails(order.details || []) }
} finally {
formLoading.value = false
}
@ -240,9 +248,16 @@ const buildDetail = (inventory: InventorySelectRow): MovementOrderDetailVO => ({
targetWarehouseId: formData.value.targetWarehouseId,
quantity: undefined,
availableQuantity: inventory.availableQuantity,
amount: undefined
price: undefined,
totalPrice: undefined
})
const normalizeDetails = (details: MovementOrderDetailVO[]) =>
details.map((detail) => ({
...detail,
totalPrice: detail.totalPrice ?? multiplyPrice(detail.quantity, detail.price)
}))
const handleAddDetail = () => inventorySelectRef.value?.open()
/** 选择库存 */
@ -253,7 +268,6 @@ const handleSelectInventory = (inventories: InventorySelectRow[]) => {
if (isInventorySelected(inventory)) return
formData.value.details!.push(buildDetail(inventory))
})
refreshTotalAmount()
}
/** 判断库存是否已选择 */
@ -267,12 +281,10 @@ const isInventorySelected = (inventory: InventorySelectRow) =>
const handleDeleteDetail = (index: number) => {
formData.value.details?.splice(index, 1)
refreshTotalAmount()
}
const handleSourceWarehouseChange = () => {
formData.value.details = []
refreshTotalAmount()
}
const handleTargetWarehouseChange = () => {
refreshTargetToDetails()
@ -283,10 +295,40 @@ const refreshTargetToDetails = () => {
detail.targetWarehouseId = formData.value.targetWarehouseId
})
}
const refreshTotalAmount = () => {
formData.value.totalAmount = detailTotalAmount.value
const handleDetailQuantityChange = (detail: MovementOrderDetailVO) => {
if (detail.price !== undefined && detail.price !== null) {
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
return
}
detail.price = dividePrice(detail.totalPrice, detail.quantity)
}
const handleDetailPriceChange = (detail: MovementOrderDetailVO) => {
detail.totalPrice = multiplyPrice(detail.quantity, detail.price)
}
const handleDetailTotalPriceChange = (detail: MovementOrderDetailVO) => {
detail.price = dividePrice(detail.totalPrice, detail.quantity)
}
const getDetailSummaries = ({ columns, data }: { columns: any[]; data: MovementOrderDetailVO[] }) =>
columns.map((column, index) => {
if (index === 0) {
return '合计'
}
if (column.property === 'quantity') {
return formatSumQuantity(data, (detail) => detail.quantity)
}
if (column.property === 'price') {
return formatPrice(detailPriceSum.value)
}
if (column.property === 'totalPrice') {
return formatSumPrice(data, (detail) => detail.totalPrice)
}
return ''
})
/** 校验明细 */
const validateDetails = (required: boolean) => {
if (formData.value.sourceWarehouseId === formData.value.targetWarehouseId) {
@ -315,13 +357,13 @@ const validateDetails = (required: boolean) => {
}
/** 构建提交数据 */
const buildSubmitData = () =>
({
...formData.value,
totalQuantity: totalQuantity.value,
totalAmount: formData.value.totalAmount,
details: formData.value.details || []
}) as MovementOrderVO
const buildSubmitData = () => {
const { totalQuantity: _totalQuantity, totalPrice: _totalPrice, details, ...order } = formData.value
return {
...order,
details: (details || []).map(({ totalPrice: _rowTotalPrice, ...detail }) => detail)
} as MovementOrderVO
}
const emit = defineEmits(['success'])
const submitForm = async () => {
@ -387,8 +429,6 @@ const resetForm = () => {
status: OrderStatusEnum.PREPARE,
sourceWarehouseId: undefined,
targetWarehouseId: undefined,
totalQuantity: 0,
totalAmount: 0,
remark: undefined,
details: []
}

View File

@ -53,10 +53,10 @@
/>
</div>
</el-form-item>
<el-form-item label="总金额" prop="totalAmountMin">
<el-form-item label="总金额" prop="totalPriceMin">
<div class="flex w-240px items-center gap-8px">
<el-input-number
v-model="queryParams.totalAmountMin"
v-model="queryParams.totalPriceMin"
:controls="false"
:min="0"
:precision="PRICE_PRECISION"
@ -65,7 +65,7 @@
/>
<span></span>
<el-input-number
v-model="queryParams.totalAmountMax"
v-model="queryParams.totalPriceMax"
:controls="false"
:min="0"
:precision="PRICE_PRECISION"
@ -172,8 +172,11 @@
<el-table-column align="right" label="移库数量" width="120">
<template #default="{ row: detail }">{{ formatQuantity(detail.quantity) }}</template>
</el-table-column>
<el-table-column align="right" label="单价(元)" width="120">
<template #default="{ row: detail }">{{ formatPrice(detail.price) || '-' }}</template>
</el-table-column>
<el-table-column align="right" label="金额(元)" width="120">
<template #default="{ row: detail }">{{ formatPrice(detail.amount) || '-' }}</template>
<template #default="{ row: detail }">{{ formatPrice(getDetailTotalPrice(detail)) || '-' }}</template>
</el-table-column>
</el-table>
</template>
@ -221,7 +224,7 @@
</div>
<div class="flex items-center justify-between">
<span>金额</span>
<span>{{ formatPrice(row.totalAmount) }}</span>
<span>{{ formatPrice(row.totalPrice) }}</span>
</div>
</template>
</el-table-column>
@ -331,8 +334,8 @@ const getDefaultQueryParams = () => ({
orderTime: undefined as string[] | undefined,
totalQuantityMin: undefined as number | undefined,
totalQuantityMax: undefined as number | undefined,
totalAmountMin: undefined as number | undefined,
totalAmountMax: undefined as number | undefined,
totalPriceMin: undefined as number | undefined,
totalPriceMax: undefined as number | undefined,
creator: undefined as number | undefined,
updater: undefined as number | undefined,
createTime: undefined as string[] | undefined,
@ -373,6 +376,12 @@ const resetQuery = () => {
Object.assign(queryParams, getDefaultQueryParams())
handleQuery()
}
const getDetailTotalPrice = (detail: MovementOrderDetailVO) => {
if (!detail.quantity || detail.price === undefined || detail.price === null) {
return undefined
}
return Number(detail.quantity) * Number(detail.price)
}
const handleExpandChange = async (row: MovementOrderVO) => {
if (!row.id || detailMap[row.id]) return
detailMap[row.id] = await MovementOrderApi.getMovementOrderDetailListByOrderId(row.id)