feat(wms):修复只能删除作废的入库单的问题
parent
ac49ba5c6d
commit
765c8ea94f
|
|
@ -63,7 +63,7 @@ export const defaultShortcuts = [
|
|||
* @description format 季度 + 星期 + 几周:"YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ"
|
||||
* @returns 返回拼接后的时间字符串
|
||||
*/
|
||||
export function formatDate(date: Date, format?: string): string {
|
||||
export function formatDate(date: Date | string, format?: string): string {
|
||||
// 日期不存在,则返回空
|
||||
if (!date) {
|
||||
return ''
|
||||
|
|
@ -72,6 +72,25 @@ export function formatDate(date: Date, format?: string): string {
|
|||
return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化可为空的时间日期
|
||||
*
|
||||
* @param date 当前时间,new Date() 格式或者字符串时间格式
|
||||
* @param format 需要转换的时间格式字符串
|
||||
* @param emptyText 空值展示文案
|
||||
* @returns 返回格式化后的时间字符串
|
||||
*/
|
||||
export function formatNullableDate(
|
||||
date?: Date | string | null,
|
||||
format = 'YYYY-MM-DD HH:mm:ss',
|
||||
emptyText = '-'
|
||||
): string {
|
||||
if (!date) {
|
||||
return emptyText
|
||||
}
|
||||
return formatDate(date, format) || emptyText
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前的日期+时间
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,295 @@
|
|||
<!-- WMS 商品 SKU 选择器 -->
|
||||
<template>
|
||||
<Dialog
|
||||
v-model="dialogVisible"
|
||||
:append-to-body="true"
|
||||
:scroll="true"
|
||||
max-height="calc(100vh - 220px)"
|
||||
title="商品选择"
|
||||
width="80%"
|
||||
>
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="商品名称" prop="itemName">
|
||||
<el-input
|
||||
v-model="queryParams.itemName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品编号" prop="itemCode">
|
||||
<el-input
|
||||
v-model="queryParams.itemCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入商品编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格名称" prop="skuName">
|
||||
<el-input
|
||||
v-model="queryParams.skuName"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="规格编号" prop="skuCode">
|
||||
<el-input
|
||||
v-model="queryParams.skuCode"
|
||||
class="!w-240px"
|
||||
clearable
|
||||
placeholder="请输入规格编号"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<ContentWrap class="!mb-0">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
:show-overflow-tooltip="true"
|
||||
:stripe="true"
|
||||
border
|
||||
max-height="calc(100vh - 430px)"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleRowDblClick"
|
||||
>
|
||||
<el-table-column :reserve-selection="true" align="center" type="selection" width="50" />
|
||||
<el-table-column label="商品信息" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.itemName || '-' }}</div>
|
||||
<div v-if="row.itemCode" class="text-12px text-gray-500">
|
||||
商品编号:{{ row.itemCode }}
|
||||
</div>
|
||||
<div v-if="row.brandName" class="text-12px text-gray-500">品牌:{{ row.brandName }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格信息" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<div>{{ row.name || '-' }}</div>
|
||||
<div v-if="row.code" class="text-12px text-gray-500">编号:{{ row.code }}</div>
|
||||
<div v-if="row.barCode" class="text-12px text-gray-500">条码:{{ row.barCode }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额(元)" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.costPrice !== undefined">成本价:{{ formatPrice(row.costPrice) }}</div>
|
||||
<div v-if="row.sellingPrice !== undefined">销售价:{{ formatPrice(row.sellingPrice) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="重量(kg)" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.netWeight !== undefined">净重:{{ formatWeight(row.netWeight) }}</div>
|
||||
<div v-if="row.grossWeight !== undefined">毛重:{{ formatWeight(row.grossWeight) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="长宽高(cm)" min-width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDimensionText(row.length, row.width, row.height) || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="overflow-hidden">
|
||||
<Pagination
|
||||
v-model:limit="queryParams.pageSize"
|
||||
v-model:page="queryParams.pageNo"
|
||||
:total="total"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="handleConfirm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElTable } from 'element-plus'
|
||||
import { ItemApi, ItemVO } from '@/api/wms/md/item'
|
||||
import { ItemSkuVO } from '@/api/wms/md/item/sku'
|
||||
import { formatDimensionText, formatPrice, formatWeight } from '@/views/wms/utils/format'
|
||||
|
||||
/** WMS 商品 SKU 选择器 */
|
||||
defineOptions({ name: 'WmsItemSkuSelect' })
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const allList = ref<ItemSkuVO[]>([]) // 全部 SKU 列表
|
||||
const filteredList = ref<ItemSkuVO[]>([]) // 过滤后的 SKU 列表
|
||||
const list = ref<ItemSkuVO[]>([]) // 当前页 SKU 列表
|
||||
const total = ref(0) // 列表的总条数
|
||||
const selectedList = ref<ItemSkuVO[]>([]) // 已选择 SKU 列表
|
||||
const selectedMap = ref<Map<number, ItemSkuVO>>(new Map()) // 跨页已选择 SKU
|
||||
const preSelectedIds = ref<number[]>([]) // 打开弹窗时传入的已选 SKU 编号
|
||||
const tableRef = ref<InstanceType<typeof ElTable>>() // 表格 Ref
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const getDefaultQueryParams = () => ({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
itemName: undefined as string | undefined,
|
||||
itemCode: undefined as string | undefined,
|
||||
skuName: undefined as string | undefined,
|
||||
skuCode: undefined as string | undefined
|
||||
})
|
||||
const queryParams = reactive(getDefaultQueryParams())
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [list: ItemSkuVO[]]
|
||||
}>()
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (selectedIds?: number[]) => {
|
||||
dialogVisible.value = true
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
selectedList.value = []
|
||||
selectedMap.value = new Map()
|
||||
preSelectedIds.value = selectedIds || []
|
||||
await nextTick()
|
||||
tableRef.value?.clearSelection()
|
||||
await loadSkuList()
|
||||
}
|
||||
defineExpose({ open })
|
||||
|
||||
/** 获得 SKU 列表 */
|
||||
const loadSkuList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const items = await ItemApi.getItemSimpleList()
|
||||
allList.value = items.flatMap((item: ItemVO) =>
|
||||
(item.skus || []).map((sku) => ({
|
||||
...sku,
|
||||
itemId: item.id,
|
||||
itemCode: item.code,
|
||||
itemName: item.name,
|
||||
categoryId: item.categoryId,
|
||||
categoryName: item.categoryName,
|
||||
unit: item.unit,
|
||||
brandId: item.brandId,
|
||||
brandName: item.brandName
|
||||
}))
|
||||
)
|
||||
initSelectedList()
|
||||
getList()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化已选 SKU */
|
||||
const initSelectedList = () => {
|
||||
if (preSelectedIds.value.length === 0) {
|
||||
return
|
||||
}
|
||||
allList.value.forEach((sku) => {
|
||||
if (sku.id && preSelectedIds.value.includes(sku.id)) {
|
||||
selectedMap.value.set(sku.id, sku)
|
||||
}
|
||||
})
|
||||
selectedList.value = Array.from(selectedMap.value.values())
|
||||
}
|
||||
|
||||
/** 查询 SKU 列表 */
|
||||
const getList = async () => {
|
||||
filteredList.value = allList.value.filter((sku) => {
|
||||
return (
|
||||
includes(sku.itemName, queryParams.itemName) &&
|
||||
includes(sku.itemCode, queryParams.itemCode) &&
|
||||
includes(sku.name, queryParams.skuName) &&
|
||||
includes(sku.code, queryParams.skuCode)
|
||||
)
|
||||
})
|
||||
total.value = filteredList.value.length
|
||||
const start = (queryParams.pageNo - 1) * queryParams.pageSize
|
||||
list.value = filteredList.value.slice(start, start + queryParams.pageSize)
|
||||
await nextTick()
|
||||
applyPreSelection()
|
||||
}
|
||||
|
||||
const includes = (value?: string, keyword?: string) => {
|
||||
if (!keyword) {
|
||||
return true
|
||||
}
|
||||
return (value || '').toLowerCase().includes(keyword.toLowerCase())
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, getDefaultQueryParams())
|
||||
queryFormRef.value?.resetFields()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 恢复预选状态(当前页可见范围内) */
|
||||
const applyPreSelection = () => {
|
||||
const table = tableRef.value
|
||||
if (!table || selectedMap.value.size === 0) {
|
||||
return
|
||||
}
|
||||
list.value.forEach((row) => {
|
||||
if (row.id && selectedMap.value.has(row.id)) {
|
||||
table.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 选择变化 */
|
||||
const handleSelectionChange = (rows: ItemSkuVO[]) => {
|
||||
const currentPageIds = list.value.map((row) => row.id).filter((id): id is number => !!id)
|
||||
currentPageIds.forEach((id) => selectedMap.value.delete(id))
|
||||
rows.forEach((row) => {
|
||||
if (row.id) {
|
||||
selectedMap.value.set(row.id, row)
|
||||
}
|
||||
})
|
||||
selectedList.value = Array.from(selectedMap.value.values())
|
||||
}
|
||||
|
||||
/** 双击行:切换勾选 */
|
||||
const handleRowDblClick = (row: ItemSkuVO) => {
|
||||
tableRef.value?.toggleRowSelection(row)
|
||||
}
|
||||
|
||||
/** 确认选择 */
|
||||
const handleConfirm = () => {
|
||||
if (selectedList.value.length === 0) {
|
||||
message.warning('请至少选择一条数据')
|
||||
return
|
||||
}
|
||||
emit('change', selectedList.value)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
|
@ -40,13 +40,13 @@
|
|||
{{ formatPrice(detailData.totalAmount) || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatNullableDate(detailData.createTime) || '-' }}
|
||||
{{ formatNullableDate(detailData.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">
|
||||
{{ detailData.creatorName || detailData.creator || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatNullableDate(detailData.updateTime) || '-' }}
|
||||
{{ formatNullableDate(detailData.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新人">
|
||||
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||
|
|
@ -78,12 +78,12 @@
|
|||
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="140" prop="batchNo" />
|
||||
<el-table-column v-if="BATCH_ENABLE" label="生产日期" width="140">
|
||||
<template #default="scope">
|
||||
{{ formatNullableDate(scope.row.productionDate, 'YYYY-MM-DD') || '-' }}
|
||||
{{ formatNullableDate(scope.row.productionDate, 'YYYY-MM-DD') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="BATCH_ENABLE" label="过期日期" width="140">
|
||||
<template #default="scope">
|
||||
{{ formatNullableDate(scope.row.expirationDate, 'YYYY-MM-DD') || '-' }}
|
||||
{{ formatNullableDate(scope.row.expirationDate, 'YYYY-MM-DD') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="right" label="数量" prop="quantity" width="120">
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
|
||||
import { ReceiptOrderDetailVO } from '@/api/wms/order/receipt/detail'
|
||||
|
|
@ -140,11 +140,6 @@ const detailRows = computed<DetailRow[]>(() =>
|
|||
}))
|
||||
)
|
||||
|
||||
// TODO @AI:抽到 date 或者 time ts 全局的工具里。
|
||||
const formatNullableDate = (value?: Date | string, format?: string) => {
|
||||
return value ? formatDate(value as Date, format) : ''
|
||||
}
|
||||
|
||||
const getSummaries = ({ columns, data }: { columns: any[]; data: DetailRow[] }) =>
|
||||
columns.map((column, index) => {
|
||||
if (index === 0) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,480 @@
|
|||
<!-- 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="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="warehouseId">
|
||||
<WarehouseSelect v-model="formData.warehouseId" @change="handleWarehouseChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="AREA_ENABLE" :span="8">
|
||||
<el-form-item label="库区" prop="areaId">
|
||||
<WarehouseAreaSelect
|
||||
v-model="formData.areaId"
|
||||
:warehouse-id="formData.warehouseId"
|
||||
@change="handleAreaChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="总数量">
|
||||
<el-input :model-value="formatQuantity(totalQuantity)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="总金额">
|
||||
<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="AREA_ENABLE ? 8 : 16">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" maxlength="255" 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-button :disabled="!formData.warehouseId" plain type="primary" @click="handleAddDetail">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
添加商品
|
||||
</el-button>
|
||||
</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 v-if="AREA_ENABLE" label="库区" min-width="180">
|
||||
<template #default="scope">
|
||||
<WarehouseAreaSelect
|
||||
v-model="scope.row.areaId"
|
||||
:disabled="!formData.warehouseId || !!formData.areaId"
|
||||
:warehouse-id="formData.warehouseId"
|
||||
placeholder="请选择库区"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="160">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.batchNo" maxlength="64" placeholder="请输入批号" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="BATCH_ENABLE" label="生产日期" width="180">
|
||||
<template #default="scope">
|
||||
<el-date-picker
|
||||
v-model="scope.row.productionDate"
|
||||
class="!w-1/1"
|
||||
placeholder="请选择生产日期"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="BATCH_ENABLE" label="过期日期" width="180">
|
||||
<template #default="scope">
|
||||
<el-date-picker
|
||||
v-model="scope.row.expirationDate"
|
||||
class="!w-1/1"
|
||||
placeholder="请选择过期日期"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</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 label="备注" min-width="180">
|
||||
<template #default="scope">
|
||||
<el-input v-model="scope.row.remark" maxlength="255" placeholder="请输入备注" />
|
||||
</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 WarehouseAreaSelect from '@/views/wms/md/warehouse/components/WarehouseAreaSelect.vue'
|
||||
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||
import { AREA_ENABLE, BATCH_ENABLE } from '@/views/wms/utils/config'
|
||||
import { OrderStatusEnum } 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,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
bizOrderNo: undefined,
|
||||
merchantId: undefined,
|
||||
warehouseId: undefined,
|
||||
areaId: undefined,
|
||||
totalQuantity: 0,
|
||||
totalAmount: 0,
|
||||
remark: undefined,
|
||||
details: []
|
||||
})
|
||||
const formRules = reactive<FormRules>({
|
||||
no: [{ required: true, message: '入库单号不能为空', trigger: 'blur' }],
|
||||
type: [{ required: true, message: '入库类型不能为空', trigger: 'change' }],
|
||||
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'change' }]
|
||||
})
|
||||
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 === OrderStatusEnum.PREPARE
|
||||
)
|
||||
const isSavedPrepareOrder = computed(
|
||||
() => !!formData.value.id && formData.value.status === OrderStatusEnum.PREPARE
|
||||
)
|
||||
|
||||
/** 打开弹窗 */
|
||||
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,
|
||||
areaId: formData.value.areaId,
|
||||
batchNo: undefined,
|
||||
productionDate: undefined,
|
||||
expirationDate: undefined,
|
||||
quantity: undefined,
|
||||
amount: undefined,
|
||||
remark: undefined
|
||||
})
|
||||
|
||||
/** 添加商品 */
|
||||
const handleAddDetail = () => {
|
||||
skuSelectRef.value?.open()
|
||||
}
|
||||
|
||||
/** 选择商品 SKU */
|
||||
const handleSelectSku = (skus: ItemSkuVO[]) => {
|
||||
if (!skus.length) {
|
||||
return
|
||||
}
|
||||
formData.value.details = formData.value.details || []
|
||||
skus.forEach((sku) => formData.value.details!.push(buildDetail(sku)))
|
||||
refreshTotalAmount()
|
||||
}
|
||||
|
||||
/** 删除明细 */
|
||||
const handleDeleteDetail = (index: number) => {
|
||||
formData.value.details?.splice(index, 1)
|
||||
refreshTotalAmount()
|
||||
}
|
||||
|
||||
/** 仓库变化 */
|
||||
const handleWarehouseChange = () => {
|
||||
formData.value.areaId = undefined
|
||||
formData.value.details?.forEach((detail) => (detail.areaId = undefined))
|
||||
}
|
||||
|
||||
/** 库区变化 */
|
||||
const handleAreaChange = () => {
|
||||
formData.value.details?.forEach((detail) => (detail.areaId = formData.value.areaId))
|
||||
}
|
||||
|
||||
/** 明细金额变化 */
|
||||
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 (AREA_ENABLE && !detail.areaId) {
|
||||
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,
|
||||
status: OrderStatusEnum.PREPARE,
|
||||
bizOrderNo: undefined,
|
||||
merchantId: undefined,
|
||||
warehouseId: undefined,
|
||||
areaId: undefined,
|
||||
totalQuantity: 0,
|
||||
totalAmount: 0,
|
||||
remark: undefined,
|
||||
details: []
|
||||
}
|
||||
originalFormData.value = ''
|
||||
nextTick(() => formRef.value?.clearValidate())
|
||||
}
|
||||
</script>
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
<div v-if="AREA_ENABLE">库区:{{ printData.areaName || '-' }}</div>
|
||||
<div>总数量:{{ formatQuantity(printData.totalQuantity) || '-' }}</div>
|
||||
<div>总金额:{{ formatPrice(printData.totalAmount) || '-' }}</div>
|
||||
<div>创建时间:{{ formatNullableDate(printData.createTime) || '-' }}</div>
|
||||
<div>创建时间:{{ formatNullableDate(printData.createTime) }}</div>
|
||||
<div class="col-span-3">备注:{{ printData.remark || '-' }}</div>
|
||||
</div>
|
||||
<table class="w-full border-collapse text-13px">
|
||||
|
|
@ -70,10 +70,10 @@
|
|||
{{ detail.batchNo || '-' }}
|
||||
</td>
|
||||
<td v-if="BATCH_ENABLE" class="border border-solid border-#dcdfe6 p-8px">
|
||||
{{ formatNullableDate(detail.productionDate, 'YYYY-MM-DD') || '-' }}
|
||||
{{ formatNullableDate(detail.productionDate, 'YYYY-MM-DD') }}
|
||||
</td>
|
||||
<td v-if="BATCH_ENABLE" class="border border-solid border-#dcdfe6 p-8px">
|
||||
{{ formatNullableDate(detail.expirationDate, 'YYYY-MM-DD') || '-' }}
|
||||
{{ formatNullableDate(detail.expirationDate, 'YYYY-MM-DD') }}
|
||||
</td>
|
||||
<td class="border border-solid border-#dcdfe6 p-8px text-right">
|
||||
{{ formatQuantity(detail.quantity) || '-' }}
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
|
||||
import { AREA_ENABLE, BATCH_ENABLE } from '@/views/wms/utils/config'
|
||||
|
|
@ -118,11 +118,6 @@ const printObj = ref({
|
|||
zIndex: 20003
|
||||
})
|
||||
|
||||
// TODO @AI:抽到 date 或者 time ts 全局的工具里。
|
||||
const formatNullableDate = (value?: Date | string, format?: string) => {
|
||||
return value ? formatDate(value as Date, format) : ''
|
||||
}
|
||||
|
||||
/** 打印入库单 */
|
||||
const print = async (id: number) => {
|
||||
printData.value = await ReceiptOrderApi.getReceiptOrder(id)
|
||||
|
|
|
|||
|
|
@ -254,10 +254,10 @@
|
|||
<el-table-column v-if="BATCH_ENABLE" label="生产日期/过期日期" min-width="180">
|
||||
<template #default="detailScope">
|
||||
<div v-if="detailScope.row.productionDate">
|
||||
生产日期:{{ formatDate(detailScope.row.productionDate, 'YYYY-MM-DD') }}
|
||||
生产日期:{{ formatNullableDate(detailScope.row.productionDate, 'YYYY-MM-DD') }}
|
||||
</div>
|
||||
<div v-if="detailScope.row.expirationDate">
|
||||
过期日期:{{ formatDate(detailScope.row.expirationDate, 'YYYY-MM-DD') }}
|
||||
过期日期:{{ formatNullableDate(detailScope.row.expirationDate, 'YYYY-MM-DD') }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
|
@ -352,8 +352,8 @@
|
|||
min-width="160"
|
||||
>
|
||||
<template #default="scope">
|
||||
<div>创建:{{ formatNullableTime(scope.row.createTime) }}</div>
|
||||
<div>更新:{{ formatNullableTime(scope.row.updateTime) }}</div>
|
||||
<div>创建:{{ formatNullableDate(scope.row.createTime, 'MM-DD HH:mm') }}</div>
|
||||
<div>更新:{{ formatNullableDate(scope.row.updateTime, 'MM-DD HH:mm') }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="isTableColumnVisible('operator')" label="操作人" min-width="120">
|
||||
|
|
@ -375,7 +375,7 @@
|
|||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['wms:receipt-order:print']"
|
||||
v-hasPermi="['wms:receipt-order:query']"
|
||||
link
|
||||
type="primary"
|
||||
@click="handlePrint(scope.row.id)"
|
||||
|
|
@ -383,7 +383,7 @@
|
|||
打印
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.status === OrderStatusEnum.PREPARE"
|
||||
v-if="canDeleteReceiptOrder(scope.row.status)"
|
||||
v-hasPermi="['wms:receipt-order:delete']"
|
||||
link
|
||||
type="danger"
|
||||
|
|
@ -410,7 +410,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defaultShortcuts, formatDate } from '@/utils/formatTime'
|
||||
import { defaultShortcuts, formatNullableDate } from '@/utils/formatTime'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { ReceiptOrderApi, ReceiptOrderVO } from '@/api/wms/order/receipt'
|
||||
import { ReceiptOrderDetailVO } from '@/api/wms/order/receipt/detail'
|
||||
|
|
@ -493,10 +493,9 @@ const queryFormRef = ref() // 搜索的表单
|
|||
const exportLoading = ref(false) // 导出的加载中
|
||||
const detailMap = reactive<Record<number, ReceiptOrderDetailVO[]>>({}) // 入库单明细缓存
|
||||
|
||||
/** 格式化列表时间 */
|
||||
// TODO @AI:抽到 date 或者 time ts 全局的工具里。
|
||||
const formatNullableTime = (value?: Date | string) => {
|
||||
return value ? formatDate(value as Date, 'MM-DD HH:mm') : '-'
|
||||
/** 是否允许删除入库单 */
|
||||
const canDeleteReceiptOrder = (status?: number) => {
|
||||
return status !== undefined && [OrderStatusEnum.PREPARE, OrderStatusEnum.CANCELED].includes(status)
|
||||
}
|
||||
|
||||
/** 查询入库单列表 */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* WMS 订单工具
|
||||
*/
|
||||
|
||||
/** 生成业务单号:前缀 + 月日 + 4 位随机数 */
|
||||
export const generateOrderNo = (prefix: string) => {
|
||||
const now = new Date()
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
const randomNo = String(Math.floor(Math.random() * 10000)).padStart(4, '0')
|
||||
return `${prefix}${month}${day}${randomNo}`
|
||||
}
|
||||
Loading…
Reference in New Issue