feat(wms):新增移库、盘库管理
parent
b3b35e147b
commit
70aff05ef5
|
|
@ -0,0 +1,29 @@
|
||||||
|
// WMS 盘库单明细 VO
|
||||||
|
export interface CheckOrderDetailVO {
|
||||||
|
id?: number
|
||||||
|
orderId?: number
|
||||||
|
itemId?: number
|
||||||
|
itemCode?: string
|
||||||
|
itemName?: string
|
||||||
|
unit?: string
|
||||||
|
skuId?: number
|
||||||
|
skuCode?: string
|
||||||
|
skuName?: string
|
||||||
|
inventoryId?: number
|
||||||
|
inventoryDetailId?: number
|
||||||
|
warehouseId?: number
|
||||||
|
warehouseName?: string
|
||||||
|
areaId?: number
|
||||||
|
areaName?: string
|
||||||
|
batchNo?: string
|
||||||
|
productionDate?: Date
|
||||||
|
expirationDate?: Date
|
||||||
|
receiptTime?: Date
|
||||||
|
quantity?: number
|
||||||
|
checkQuantity?: number
|
||||||
|
differenceQuantity?: number
|
||||||
|
availableQuantity?: number
|
||||||
|
amount?: number
|
||||||
|
remark?: string
|
||||||
|
createTime?: Date
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import { CheckOrderDetailVO } from './detail'
|
||||||
|
|
||||||
|
// WMS 盘库单 VO
|
||||||
|
export interface CheckOrderVO {
|
||||||
|
id?: number
|
||||||
|
no?: string
|
||||||
|
status?: number
|
||||||
|
remark?: string
|
||||||
|
warehouseId?: number
|
||||||
|
warehouseName?: string
|
||||||
|
areaId?: number
|
||||||
|
areaName?: string
|
||||||
|
totalQuantity?: number
|
||||||
|
totalAmount?: number
|
||||||
|
details?: CheckOrderDetailVO[]
|
||||||
|
createTime?: Date
|
||||||
|
creator?: string
|
||||||
|
creatorName?: string
|
||||||
|
updateTime?: Date
|
||||||
|
updater?: string
|
||||||
|
updaterName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WMS 盘库单 API
|
||||||
|
export const CheckOrderApi = {
|
||||||
|
// 查询盘库单分页
|
||||||
|
getCheckOrderPage: async (params: any) => {
|
||||||
|
return await request.get({ url: '/wms/check-order/page', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询盘库单详情
|
||||||
|
getCheckOrder: async (id: number) => {
|
||||||
|
return await request.get({ url: '/wms/check-order/get?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询盘库单明细
|
||||||
|
getCheckOrderDetailListByOrderId: async (orderId: number) => {
|
||||||
|
return await request.get({
|
||||||
|
url: '/wms/check-order-detail/list-by-order-id?orderId=' + orderId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增盘库单
|
||||||
|
createCheckOrder: async (data: CheckOrderVO) => {
|
||||||
|
return await request.post({ url: '/wms/check-order/create', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改盘库单
|
||||||
|
updateCheckOrder: async (data: CheckOrderVO) => {
|
||||||
|
return await request.put({ url: '/wms/check-order/update', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 完成盘库
|
||||||
|
completeCheckOrder: async (id: number) => {
|
||||||
|
return await request.put({ url: '/wms/check-order/complete?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 作废盘库单
|
||||||
|
cancelCheckOrder: async (id: number) => {
|
||||||
|
return await request.put({ url: '/wms/check-order/cancel?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除盘库单
|
||||||
|
deleteCheckOrder: async (id: number) => {
|
||||||
|
return await request.delete({ url: '/wms/check-order/delete?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出盘库单
|
||||||
|
exportCheckOrder: async (params: any) => {
|
||||||
|
return await request.download({ url: '/wms/check-order/export-excel', params })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// WMS 移库单明细 VO
|
||||||
|
export interface MovementOrderDetailVO {
|
||||||
|
id?: number
|
||||||
|
orderId?: number
|
||||||
|
itemId?: number
|
||||||
|
itemCode?: string
|
||||||
|
itemName?: string
|
||||||
|
unit?: string
|
||||||
|
skuId?: number
|
||||||
|
skuCode?: string
|
||||||
|
skuName?: string
|
||||||
|
inventoryDetailId?: number
|
||||||
|
sourceWarehouseId?: number
|
||||||
|
sourceWarehouseName?: string
|
||||||
|
sourceAreaId?: number
|
||||||
|
sourceAreaName?: string
|
||||||
|
targetWarehouseId?: number
|
||||||
|
targetWarehouseName?: string
|
||||||
|
targetAreaId?: number
|
||||||
|
targetAreaName?: string
|
||||||
|
batchNo?: string
|
||||||
|
productionDate?: Date
|
||||||
|
expirationDate?: Date
|
||||||
|
quantity?: number
|
||||||
|
availableQuantity?: number
|
||||||
|
amount?: number
|
||||||
|
remark?: string
|
||||||
|
createTime?: Date
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import { MovementOrderDetailVO } from './detail'
|
||||||
|
|
||||||
|
// WMS 移库单 VO
|
||||||
|
export interface MovementOrderVO {
|
||||||
|
id?: number
|
||||||
|
no?: string
|
||||||
|
status?: number
|
||||||
|
remark?: string
|
||||||
|
sourceWarehouseId?: number
|
||||||
|
sourceWarehouseName?: string
|
||||||
|
sourceAreaId?: number
|
||||||
|
sourceAreaName?: string
|
||||||
|
targetWarehouseId?: number
|
||||||
|
targetWarehouseName?: string
|
||||||
|
targetAreaId?: number
|
||||||
|
targetAreaName?: string
|
||||||
|
totalQuantity?: number
|
||||||
|
totalAmount?: number
|
||||||
|
details?: MovementOrderDetailVO[]
|
||||||
|
createTime?: Date
|
||||||
|
creator?: string
|
||||||
|
creatorName?: string
|
||||||
|
updateTime?: Date
|
||||||
|
updater?: string
|
||||||
|
updaterName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WMS 移库单 API
|
||||||
|
export const MovementOrderApi = {
|
||||||
|
// 查询移库单分页
|
||||||
|
getMovementOrderPage: async (params: any) => {
|
||||||
|
return await request.get({ url: '/wms/movement-order/page', params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询移库单详情
|
||||||
|
getMovementOrder: async (id: number) => {
|
||||||
|
return await request.get({ url: '/wms/movement-order/get?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询移库单明细
|
||||||
|
getMovementOrderDetailListByOrderId: async (orderId: number) => {
|
||||||
|
return await request.get({
|
||||||
|
url: '/wms/movement-order-detail/list-by-order-id?orderId=' + orderId
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增移库单
|
||||||
|
createMovementOrder: async (data: MovementOrderVO) => {
|
||||||
|
return await request.post({ url: '/wms/movement-order/create', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改移库单
|
||||||
|
updateMovementOrder: async (data: MovementOrderVO) => {
|
||||||
|
return await request.put({ url: '/wms/movement-order/update', data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 完成移库
|
||||||
|
completeMovementOrder: async (id: number) => {
|
||||||
|
return await request.put({ url: '/wms/movement-order/complete?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 作废移库单
|
||||||
|
cancelMovementOrder: async (id: number) => {
|
||||||
|
return await request.put({ url: '/wms/movement-order/cancel?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除移库单
|
||||||
|
deleteMovementOrder: async (id: number) => {
|
||||||
|
return await request.delete({ url: '/wms/movement-order/delete?id=' + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出移库单
|
||||||
|
exportMovementOrder: async (params: any) => {
|
||||||
|
return await request.download({ url: '/wms/movement-order/export-excel', params })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
<!-- WMS 盘库单详情 -->
|
||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="盘库单详情" width="1200px">
|
||||||
|
<div v-loading="loading">
|
||||||
|
<div class="mb-16px text-18px font-bold">单据信息</div>
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="盘库单号">{{ detailData.no || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="单据状态">
|
||||||
|
<dict-tag
|
||||||
|
v-if="detailData.status !== undefined"
|
||||||
|
:type="DICT_TYPE.WMS_ORDER_STATUS"
|
||||||
|
:value="detailData.status"
|
||||||
|
/>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="仓库">{{ detailData.warehouseName || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item v-if="AREA_ENABLE" label="库区">{{ detailData.areaName || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="盈亏数量">
|
||||||
|
{{ formatQuantity(detailData.totalQuantity) || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="总金额">
|
||||||
|
{{ formatPrice(detailData.totalAmount) || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间">
|
||||||
|
{{ formatNullableDate(detailData.createTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建人">
|
||||||
|
{{ detailData.creatorName || detailData.creator || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="更新时间">
|
||||||
|
{{ formatNullableDate(detailData.updateTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="更新人">
|
||||||
|
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="备注">{{ detailData.remark || '-' }}</el-descriptions-item>
|
||||||
|
</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-column label="商品信息" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.itemName || '-' }}</div>
|
||||||
|
<div v-if="row.itemCode" class="text-12px text-gray-500">商品编号:{{ row.itemCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="规格信息" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.skuName || '-' }}</div>
|
||||||
|
<div v-if="row.skuCode" class="text-12px text-gray-500">规格编号:{{ row.skuCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="140" prop="batchNo" />
|
||||||
|
<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="checkQuantity" width="120">
|
||||||
|
<template #default="{ row }">{{ formatQuantity(row.checkQuantity) || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="right" label="盈亏数量" prop="differenceQuantity" width="120">
|
||||||
|
<template #default="{ row }">{{ formatQuantity(row.differenceQuantity) || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="right" label="金额(元)" prop="amount" width="140">
|
||||||
|
<template #default="{ row }">{{ formatPrice(row.amount) || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { formatNullableDate } from '@/utils/formatTime'
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { CheckOrderApi, CheckOrderVO } from '@/api/wms/order/check'
|
||||||
|
import { CheckOrderDetailVO } from '@/api/wms/order/check/detail'
|
||||||
|
import { AREA_ENABLE, BATCH_ENABLE } from '@/views/wms/utils/config'
|
||||||
|
import { formatPrice, formatQuantity, formatSumPrice, formatSumQuantity } from '@/views/wms/utils/format'
|
||||||
|
|
||||||
|
/** WMS 盘库单详情 */
|
||||||
|
defineOptions({ name: 'WmsCheckOrderDetail' })
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const detailData = ref<CheckOrderVO>({})
|
||||||
|
|
||||||
|
const getSummaries = ({ columns, data }: { columns: any[]; data: CheckOrderDetailVO[] }) =>
|
||||||
|
columns.map((column, index) => {
|
||||||
|
if (index === 0) return '合计'
|
||||||
|
if (column.property === 'quantity') return formatSumQuantity(data, (detail) => detail.quantity)
|
||||||
|
if (column.property === 'checkQuantity') return formatSumQuantity(data, (detail) => detail.checkQuantity)
|
||||||
|
if (column.property === 'differenceQuantity') return formatSumQuantity(data, (detail) => detail.differenceQuantity)
|
||||||
|
if (column.property === 'amount') return formatSumPrice(data, (detail) => detail.amount)
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (id: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
detailData.value = await CheckOrderApi.getCheckOrder(id)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,388 @@
|
||||||
|
<!-- 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="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="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-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="210">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.itemName || '-' }}</div>
|
||||||
|
<div v-if="row.itemCode" class="text-12px text-gray-500">商品编号:{{ row.itemCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="规格信息" min-width="210">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.skuName || '-' }}</div>
|
||||||
|
<div v-if="row.skuCode" class="text-12px text-gray-500">规格编号:{{ row.skuCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="140" prop="batchNo" />
|
||||||
|
<el-table-column align="right" label="账面数量" width="120">
|
||||||
|
<template #default="{ row }">{{ formatQuantity(row.quantity) || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="实盘数量" width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.checkQuantity"
|
||||||
|
:controls="false"
|
||||||
|
:min="0"
|
||||||
|
:precision="QUANTITY_PRECISION"
|
||||||
|
class="!w-1/1"
|
||||||
|
placeholder="数量"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="right" label="盈亏数量" width="120">
|
||||||
|
<template #default="{ row }">{{ formatQuantity(getDifferenceQuantity(row)) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="金额(元)" width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.amount"
|
||||||
|
:controls="false"
|
||||||
|
:min="0"
|
||||||
|
:precision="PRICE_PRECISION"
|
||||||
|
class="!w-1/1"
|
||||||
|
placeholder="金额"
|
||||||
|
@change="refreshTotalAmount"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input v-model="row.remark" maxlength="255" placeholder="请输入备注" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="操作" width="80">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-button link type="danger" @click="handleDeleteDetail($index)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<InventorySelect
|
||||||
|
ref="inventorySelectRef"
|
||||||
|
:area-id="formData.areaId"
|
||||||
|
:warehouse-id="formData.warehouseId"
|
||||||
|
@change="handleSelectInventory"
|
||||||
|
/>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
v-if="isSavedPrepareOrder"
|
||||||
|
v-hasPermi="['wms:check-order:complete']"
|
||||||
|
:disabled="formLoading"
|
||||||
|
type="success"
|
||||||
|
@click="handleComplete"
|
||||||
|
>
|
||||||
|
完成盘库
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="isSavedPrepareOrder"
|
||||||
|
v-hasPermi="['wms:check-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 { CheckOrderApi, CheckOrderVO } from '@/api/wms/order/check'
|
||||||
|
import { CheckOrderDetailVO } from '@/api/wms/order/check/detail'
|
||||||
|
import InventorySelect, { InventorySelectRow } from '@/views/wms/inventory/components/InventorySelect.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, OrderUpdateStatusList } from '@/views/wms/utils/constants'
|
||||||
|
import { formatQuantity, PRICE_PRECISION, QUANTITY_PRECISION, sumPrice } from '@/views/wms/utils/format'
|
||||||
|
import { generateOrderNo } from '@/views/wms/utils/order'
|
||||||
|
|
||||||
|
/** WMS 盘库单表单 */
|
||||||
|
defineOptions({ name: 'WmsCheckOrderForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formLoading = ref(false)
|
||||||
|
const formType = ref('')
|
||||||
|
const originalFormData = ref('')
|
||||||
|
const formData = ref<CheckOrderVO>({
|
||||||
|
id: undefined,
|
||||||
|
no: undefined,
|
||||||
|
status: OrderStatusEnum.PREPARE,
|
||||||
|
warehouseId: undefined,
|
||||||
|
areaId: undefined,
|
||||||
|
totalQuantity: 0,
|
||||||
|
totalAmount: 0,
|
||||||
|
remark: undefined,
|
||||||
|
details: []
|
||||||
|
})
|
||||||
|
const formRules = reactive<FormRules>({
|
||||||
|
no: [{ required: true, message: '盘库单号不能为空', trigger: 'blur' }],
|
||||||
|
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'change' }]
|
||||||
|
})
|
||||||
|
const formRef = ref()
|
||||||
|
const inventorySelectRef = ref()
|
||||||
|
|
||||||
|
const getDifferenceQuantity = (detail: CheckOrderDetailVO) => Number(detail.checkQuantity || 0) - Number(detail.quantity || 0)
|
||||||
|
const totalQuantity = computed(() =>
|
||||||
|
(formData.value.details || []).reduce((sum, detail) => sum + getDifferenceQuantity(detail), 0)
|
||||||
|
)
|
||||||
|
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 CheckOrderApi.getCheckOrder(id)
|
||||||
|
formData.value = { ...order, details: order.details || [] }
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
originalFormData.value = JSON.stringify(buildSubmitData())
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
|
||||||
|
/** 构建盘库明细 */
|
||||||
|
const buildDetail = (inventory: InventorySelectRow): CheckOrderDetailVO => ({
|
||||||
|
id: undefined,
|
||||||
|
itemId: inventory.itemId,
|
||||||
|
itemCode: inventory.itemCode,
|
||||||
|
itemName: inventory.itemName,
|
||||||
|
unit: inventory.unit,
|
||||||
|
skuId: inventory.skuId,
|
||||||
|
skuCode: inventory.skuCode,
|
||||||
|
skuName: inventory.skuName,
|
||||||
|
inventoryId: inventory.id,
|
||||||
|
inventoryDetailId: inventory.inventoryDetailId,
|
||||||
|
warehouseId: inventory.warehouseId,
|
||||||
|
warehouseName: inventory.warehouseName,
|
||||||
|
areaId: inventory.areaId,
|
||||||
|
areaName: inventory.areaName,
|
||||||
|
batchNo: inventory.batchNo,
|
||||||
|
productionDate: inventory.productionDate,
|
||||||
|
expirationDate: inventory.expirationDate,
|
||||||
|
quantity: inventory.availableQuantity,
|
||||||
|
checkQuantity: inventory.availableQuantity,
|
||||||
|
availableQuantity: inventory.availableQuantity,
|
||||||
|
amount: inventory.amount,
|
||||||
|
remark: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleAddDetail = () => inventorySelectRef.value?.open()
|
||||||
|
const handleSelectInventory = (inventories: InventorySelectRow[]) => {
|
||||||
|
if (!inventories.length) return
|
||||||
|
formData.value.details = formData.value.details || []
|
||||||
|
inventories.forEach((inventory) => {
|
||||||
|
if (isInventorySelected(inventory)) return
|
||||||
|
formData.value.details!.push(buildDetail(inventory))
|
||||||
|
})
|
||||||
|
refreshTotalAmount()
|
||||||
|
}
|
||||||
|
const isInventorySelected = (inventory: InventorySelectRow) =>
|
||||||
|
(formData.value.details || []).some((detail) => {
|
||||||
|
if (BATCH_ENABLE) return detail.inventoryDetailId === inventory.inventoryDetailId
|
||||||
|
return detail.inventoryId === inventory.id
|
||||||
|
})
|
||||||
|
const handleDeleteDetail = (index: number) => {
|
||||||
|
formData.value.details?.splice(index, 1)
|
||||||
|
refreshTotalAmount()
|
||||||
|
}
|
||||||
|
const handleWarehouseChange = () => {
|
||||||
|
formData.value.areaId = undefined
|
||||||
|
formData.value.details = []
|
||||||
|
refreshTotalAmount()
|
||||||
|
}
|
||||||
|
const handleAreaChange = () => {
|
||||||
|
formData.value.details = []
|
||||||
|
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 (AREA_ENABLE && !detail.areaId) {
|
||||||
|
message.error(`第 ${i + 1} 行明细请选择库区`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (detail.checkQuantity === undefined || detail.checkQuantity < 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 CheckOrderVO
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
const submitForm = async () => {
|
||||||
|
await formRef.value.validate()
|
||||||
|
if (!validateDetails(false)) return
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = buildSubmitData()
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await CheckOrderApi.createCheckOrder(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await CheckOrderApi.updateCheckOrder(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 CheckOrderApi.updateCheckOrder(data)
|
||||||
|
}
|
||||||
|
await CheckOrderApi.completeCheckOrder(formData.value.id!)
|
||||||
|
message.success('盘库成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 作废盘库单 */
|
||||||
|
const handleCancel = async () => {
|
||||||
|
try {
|
||||||
|
await message.confirm('确认作废该盘库单?作废后不可恢复。')
|
||||||
|
formLoading.value = true
|
||||||
|
await CheckOrderApi.cancelCheckOrder(formData.value.id!)
|
||||||
|
message.success('作废成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
no: generateOrderNo('PK'),
|
||||||
|
status: OrderStatusEnum.PREPARE,
|
||||||
|
warehouseId: undefined,
|
||||||
|
areaId: undefined,
|
||||||
|
totalQuantity: 0,
|
||||||
|
totalAmount: 0,
|
||||||
|
remark: undefined,
|
||||||
|
details: []
|
||||||
|
}
|
||||||
|
originalFormData.value = ''
|
||||||
|
nextTick(() => formRef.value?.clearValidate())
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
<!-- WMS 盘库单 -->
|
||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="90px">
|
||||||
|
<el-form-item label="盘库单号" prop="no">
|
||||||
|
<el-input v-model="queryParams.no" class="!w-240px" clearable placeholder="请输入盘库单号" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="单据状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" class="!w-240px" clearable placeholder="请选择单据状态">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_ORDER_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="仓库" prop="warehouseId">
|
||||||
|
<WarehouseSelect v-model="queryParams.warehouseId" class="!w-240px" @change="handleWarehouseChange" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="AREA_ENABLE" label="库区" prop="areaId">
|
||||||
|
<WarehouseAreaSelect v-model="queryParams.areaId" :warehouse-id="queryParams.warehouseId" class="!w-240px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="单据日期" prop="orderDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.orderDate"
|
||||||
|
:shortcuts="defaultShortcuts"
|
||||||
|
class="!w-240px"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
type="datetimerange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建用户" prop="creator">
|
||||||
|
<UserSelectV2 v-model="queryParams.creator" class="!w-240px" placeholder="请选择创建用户" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="更新用户" prop="updater">
|
||||||
|
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||||
|
</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-button v-hasPermi="['wms:check-order:create']" plain type="primary" @click="openForm('create')">
|
||||||
|
<Icon class="mr-5px" icon="ep:plus" />
|
||||||
|
新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['wms:check-order:export']"
|
||||||
|
:loading="exportLoading"
|
||||||
|
plain
|
||||||
|
type="success"
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<Icon class="mr-5px" icon="ep:download" />
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
:show-overflow-tooltip="true"
|
||||||
|
border
|
||||||
|
@expand-change="handleExpandChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="expand" width="48">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-table :data="detailMap[row.id] || []" border>
|
||||||
|
<el-table-column label="商品信息" min-width="220">
|
||||||
|
<template #default="{ row: detail }">
|
||||||
|
<div>{{ detail.itemName || '-' }}</div>
|
||||||
|
<div v-if="detail.itemCode" class="text-12px text-gray-500">商品编号:{{ detail.itemCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="规格信息" min-width="220">
|
||||||
|
<template #default="{ row: detail }">
|
||||||
|
<div>{{ detail.skuName || '-' }}</div>
|
||||||
|
<div v-if="detail.skuCode" class="text-12px text-gray-500">规格编号:{{ detail.skuCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="140" prop="batchNo" />
|
||||||
|
<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 }">{{ formatQuantity(detail.checkQuantity) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="right" label="盈亏数量" width="120">
|
||||||
|
<template #default="{ row: detail }">{{ formatQuantity(detail.differenceQuantity) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column fixed="left" label="单号" width="210">
|
||||||
|
<template #default="{ row }">
|
||||||
|
单号:
|
||||||
|
<el-button link type="primary" @click="openDetail(row.id)">{{ row.no }}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" fixed="left" label="盘库状态" width="110">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<dict-tag :type="DICT_TYPE.WMS_ORDER_STATUS" :value="row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="AREA_ENABLE ? '仓库/库区' : '仓库'" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="AREA_ENABLE">
|
||||||
|
<div>仓库:{{ row.warehouseName || '-' }}</div>
|
||||||
|
<div>库区:{{ row.areaName || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ row.warehouseName || '-' }}</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="盈亏数量/总金额(元)" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>数量:</span>
|
||||||
|
<span>{{ formatQuantity(row.totalQuantity) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>金额:</span>
|
||||||
|
<span>{{ formatPrice(row.totalAmount) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作信息" min-width="280">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>创建:{{ formatNullableDate(row.createTime) }} / {{ row.creatorName || row.creator || '-' }}</div>
|
||||||
|
<div>更新:{{ formatNullableDate(row.updateTime) }} / {{ row.updaterName || row.updater || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="160" prop="remark" />
|
||||||
|
<el-table-column align="center" fixed="right" label="操作" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip :content="getUpdateTip(row.status)" :disabled="canUpdate(row.status)" placement="top">
|
||||||
|
<span>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['wms:check-order:update']"
|
||||||
|
:disabled="!canUpdate(row.status)"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', row.id)"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip :content="getDeleteTip(row.status)" :disabled="canDelete(row.status)" placement="top">
|
||||||
|
<span>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['wms:check-order:delete']"
|
||||||
|
:disabled="!canDelete(row.status)"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(row.id)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<Pagination v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNo" :total="total" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<CheckOrderForm ref="formRef" @success="getList" />
|
||||||
|
<CheckOrderDetail ref="detailRef" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defaultShortcuts, formatNullableDate } from '@/utils/formatTime'
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { CheckOrderApi, CheckOrderVO } from '@/api/wms/order/check'
|
||||||
|
import { CheckOrderDetailVO } from '@/api/wms/order/check/detail'
|
||||||
|
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 { OrderDeleteStatusList, OrderStatusEnum, OrderUpdateStatusList } from '@/views/wms/utils/constants'
|
||||||
|
import { formatPrice, formatQuantity } from '@/views/wms/utils/format'
|
||||||
|
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||||
|
import CheckOrderDetail from './CheckOrderDetail.vue'
|
||||||
|
import CheckOrderForm from './CheckOrderForm.vue'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
|
||||||
|
/** WMS 盘库单 */
|
||||||
|
defineOptions({ name: 'WmsCheckOrder' })
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const list = ref<CheckOrderVO[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const getDefaultQueryParams = () => ({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: undefined as string | undefined,
|
||||||
|
status: undefined as number | undefined,
|
||||||
|
warehouseId: undefined as number | undefined,
|
||||||
|
areaId: undefined as number | undefined,
|
||||||
|
orderDate: undefined as string[] | undefined,
|
||||||
|
creator: undefined as number | undefined,
|
||||||
|
updater: undefined as number | undefined
|
||||||
|
})
|
||||||
|
const queryParams = reactive(getDefaultQueryParams())
|
||||||
|
const queryFormRef = ref()
|
||||||
|
const exportLoading = ref(false)
|
||||||
|
const detailMap = reactive<Record<number, CheckOrderDetailVO[]>>({})
|
||||||
|
|
||||||
|
const canUpdate = (status?: number) => status !== undefined && OrderUpdateStatusList.includes(status)
|
||||||
|
const canDelete = (status?: number) => status !== undefined && OrderDeleteStatusList.includes(status)
|
||||||
|
const getUpdateTip = (status?: number) => {
|
||||||
|
if (status === OrderStatusEnum.FINISHED) return '已盘库,无法修改'
|
||||||
|
if (status === OrderStatusEnum.CANCELED) return '已作废,无法修改'
|
||||||
|
return '当前状态无法修改'
|
||||||
|
}
|
||||||
|
const getDeleteTip = (status?: number) => {
|
||||||
|
if (status === OrderStatusEnum.FINISHED) return '已盘库,无法删除'
|
||||||
|
return '当前状态无法删除'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await CheckOrderApi.getCheckOrderPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
const resetQuery = () => {
|
||||||
|
Object.assign(queryParams, getDefaultQueryParams())
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
const handleWarehouseChange = () => {
|
||||||
|
queryParams.areaId = undefined
|
||||||
|
}
|
||||||
|
const handleExpandChange = async (row: CheckOrderVO) => {
|
||||||
|
if (!row.id || detailMap[row.id]) return
|
||||||
|
detailMap[row.id] = await CheckOrderApi.getCheckOrderDetailListByOrderId(row.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => formRef.value.open(type, id)
|
||||||
|
const detailRef = ref()
|
||||||
|
const openDetail = (id: number) => detailRef.value.open(id)
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await message.delConfirm()
|
||||||
|
await CheckOrderApi.deleteCheckOrder(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
await message.exportConfirm()
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await CheckOrderApi.exportCheckOrder(queryParams)
|
||||||
|
download.excel(data, '盘库单.xls')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => getList())
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
<!-- WMS 移库单详情 -->
|
||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="移库单详情" width="1200px">
|
||||||
|
<div v-loading="loading">
|
||||||
|
<div class="mb-16px text-18px font-bold">单据信息</div>
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="移库单号">{{ detailData.no || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="单据状态">
|
||||||
|
<dict-tag
|
||||||
|
v-if="detailData.status !== undefined"
|
||||||
|
:type="DICT_TYPE.WMS_ORDER_STATUS"
|
||||||
|
:value="detailData.status"
|
||||||
|
/>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="AREA_ENABLE ? '来源仓库/库区' : '来源仓库'">
|
||||||
|
<template v-if="AREA_ENABLE">
|
||||||
|
{{ detailData.sourceWarehouseName || '-' }} / {{ detailData.sourceAreaName || '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ detailData.sourceWarehouseName || '-' }}</template>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="AREA_ENABLE ? '目标仓库/库区' : '目标仓库'">
|
||||||
|
<template v-if="AREA_ENABLE">
|
||||||
|
{{ detailData.targetWarehouseName || '-' }} / {{ detailData.targetAreaName || '-' }}
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ detailData.targetWarehouseName || '-' }}</template>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="总数量">
|
||||||
|
{{ formatQuantity(detailData.totalQuantity) || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="总金额">
|
||||||
|
{{ formatPrice(detailData.totalAmount) || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间">
|
||||||
|
{{ formatNullableDate(detailData.createTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建人">
|
||||||
|
{{ detailData.creatorName || detailData.creator || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="更新时间">
|
||||||
|
{{ formatNullableDate(detailData.updateTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="更新人">
|
||||||
|
{{ detailData.updaterName || detailData.updater || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="2" label="备注">
|
||||||
|
{{ detailData.remark || '-' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</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-column label="商品信息" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.itemName || '-' }}</div>
|
||||||
|
<div v-if="row.itemCode" class="text-12px text-gray-500">商品编号:{{ row.itemCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="规格信息" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.skuName || '-' }}</div>
|
||||||
|
<div v-if="row.skuCode" class="text-12px text-gray-500">规格编号:{{ row.skuCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="140" prop="batchNo" />
|
||||||
|
<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>
|
||||||
|
<el-table-column label="备注" min-width="160" prop="remark" />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { formatNullableDate } from '@/utils/formatTime'
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { MovementOrderApi, MovementOrderVO } from '@/api/wms/order/movement'
|
||||||
|
import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
|
||||||
|
import { AREA_ENABLE, BATCH_ENABLE } from '@/views/wms/utils/config'
|
||||||
|
import { formatPrice, formatQuantity, formatSumPrice, formatSumQuantity } from '@/views/wms/utils/format'
|
||||||
|
|
||||||
|
/** WMS 移库单详情 */
|
||||||
|
defineOptions({ name: 'WmsMovementOrderDetail' })
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const detailData = ref<MovementOrderVO>({})
|
||||||
|
|
||||||
|
const getSummaries = ({ 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 === 'amount') return formatSumPrice(data, (detail) => detail.amount)
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (id: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
detailData.value = await MovementOrderApi.getMovementOrder(id)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,439 @@
|
||||||
|
<!-- WMS 移库单表单 -->
|
||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" :title="dialogTitle" width="1280px">
|
||||||
|
<el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" label-width="98px">
|
||||||
|
<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="sourceWarehouseId">
|
||||||
|
<WarehouseSelect v-model="formData.sourceWarehouseId" @change="handleSourceWarehouseChange" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="AREA_ENABLE" :span="8">
|
||||||
|
<el-form-item label="来源库区" prop="sourceAreaId">
|
||||||
|
<WarehouseAreaSelect
|
||||||
|
v-model="formData.sourceAreaId"
|
||||||
|
:warehouse-id="formData.sourceWarehouseId"
|
||||||
|
@change="handleSourceAreaChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="目标仓库" prop="targetWarehouseId">
|
||||||
|
<WarehouseSelect v-model="formData.targetWarehouseId" @change="handleTargetWarehouseChange" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col v-if="AREA_ENABLE" :span="8">
|
||||||
|
<el-form-item label="目标库区" prop="targetAreaId">
|
||||||
|
<WarehouseAreaSelect
|
||||||
|
v-model="formData.targetAreaId"
|
||||||
|
:warehouse-id="formData.targetWarehouseId"
|
||||||
|
@change="handleTargetAreaChange"
|
||||||
|
/>
|
||||||
|
</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="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-row>
|
||||||
|
|
||||||
|
<div class="mb-12px flex items-center justify-between">
|
||||||
|
<span class="text-14px font-bold">移库明细</span>
|
||||||
|
<el-tooltip content="请先选择来源仓库" :disabled="!!formData.sourceWarehouseId" placement="top">
|
||||||
|
<span>
|
||||||
|
<el-button :disabled="!formData.sourceWarehouseId" 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="210">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.itemName || '-' }}</div>
|
||||||
|
<div v-if="row.itemCode" class="text-12px text-gray-500">商品编号:{{ row.itemCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="规格信息" min-width="210">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>{{ row.skuName || '-' }}</div>
|
||||||
|
<div v-if="row.skuCode" class="text-12px text-gray-500">规格编号:{{ row.skuCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="AREA_ENABLE" label="来源库区" min-width="140" prop="sourceAreaName" />
|
||||||
|
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="140" prop="batchNo" />
|
||||||
|
<el-table-column align="right" label="可用库存" width="120">
|
||||||
|
<template #default="{ row }">{{ formatQuantity(row.availableQuantity) || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="移库数量" width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="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="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.amount"
|
||||||
|
:controls="false"
|
||||||
|
:min="0"
|
||||||
|
:precision="PRICE_PRECISION"
|
||||||
|
class="!w-1/1"
|
||||||
|
placeholder="金额"
|
||||||
|
@change="refreshTotalAmount"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input v-model="row.remark" maxlength="255" placeholder="请输入备注" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" label="操作" width="80">
|
||||||
|
<template #default="{ $index }">
|
||||||
|
<el-button link type="danger" @click="handleDeleteDetail($index)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<InventorySelect
|
||||||
|
ref="inventorySelectRef"
|
||||||
|
:area-id="formData.sourceAreaId"
|
||||||
|
:warehouse-id="formData.sourceWarehouseId"
|
||||||
|
@change="handleSelectInventory"
|
||||||
|
/>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<el-button
|
||||||
|
v-if="isSavedPrepareOrder"
|
||||||
|
v-hasPermi="['wms:movement-order:complete']"
|
||||||
|
:disabled="formLoading"
|
||||||
|
type="success"
|
||||||
|
@click="handleComplete"
|
||||||
|
>
|
||||||
|
完成移库
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="isSavedPrepareOrder"
|
||||||
|
v-hasPermi="['wms:movement-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 { MovementOrderApi, MovementOrderVO } from '@/api/wms/order/movement'
|
||||||
|
import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
|
||||||
|
import InventorySelect, { InventorySelectRow } from '@/views/wms/inventory/components/InventorySelect.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, 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: 'WmsMovementOrderForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const dialogTitle = ref('')
|
||||||
|
const formLoading = ref(false)
|
||||||
|
const formType = ref('')
|
||||||
|
const originalFormData = ref('')
|
||||||
|
const formData = ref<MovementOrderVO>({
|
||||||
|
id: undefined,
|
||||||
|
no: undefined,
|
||||||
|
status: OrderStatusEnum.PREPARE,
|
||||||
|
sourceWarehouseId: undefined,
|
||||||
|
sourceAreaId: undefined,
|
||||||
|
targetWarehouseId: undefined,
|
||||||
|
targetAreaId: undefined,
|
||||||
|
totalQuantity: 0,
|
||||||
|
totalAmount: 0,
|
||||||
|
remark: undefined,
|
||||||
|
details: []
|
||||||
|
})
|
||||||
|
const formRules = reactive<FormRules>({
|
||||||
|
no: [{ required: true, message: '移库单号不能为空', trigger: 'blur' }],
|
||||||
|
sourceWarehouseId: [{ required: true, message: '来源仓库不能为空', trigger: 'change' }],
|
||||||
|
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 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 MovementOrderApi.getMovementOrder(id)
|
||||||
|
formData.value = { ...order, details: order.details || [] }
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
originalFormData.value = JSON.stringify(buildSubmitData())
|
||||||
|
}
|
||||||
|
defineExpose({ open })
|
||||||
|
|
||||||
|
/** 构建移库明细 */
|
||||||
|
const buildDetail = (inventory: InventorySelectRow): MovementOrderDetailVO => ({
|
||||||
|
id: undefined,
|
||||||
|
itemId: inventory.itemId,
|
||||||
|
itemCode: inventory.itemCode,
|
||||||
|
itemName: inventory.itemName,
|
||||||
|
unit: inventory.unit,
|
||||||
|
skuId: inventory.skuId,
|
||||||
|
skuCode: inventory.skuCode,
|
||||||
|
skuName: inventory.skuName,
|
||||||
|
inventoryDetailId: inventory.inventoryDetailId,
|
||||||
|
sourceWarehouseId: inventory.warehouseId,
|
||||||
|
sourceWarehouseName: inventory.warehouseName,
|
||||||
|
sourceAreaId: inventory.areaId,
|
||||||
|
sourceAreaName: inventory.areaName,
|
||||||
|
targetWarehouseId: formData.value.targetWarehouseId,
|
||||||
|
targetAreaId: formData.value.targetAreaId,
|
||||||
|
batchNo: inventory.batchNo,
|
||||||
|
productionDate: inventory.productionDate,
|
||||||
|
expirationDate: inventory.expirationDate,
|
||||||
|
quantity: undefined,
|
||||||
|
availableQuantity: inventory.availableQuantity,
|
||||||
|
amount: undefined,
|
||||||
|
remark: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleAddDetail = () => inventorySelectRef.value?.open()
|
||||||
|
|
||||||
|
/** 选择库存 */
|
||||||
|
const handleSelectInventory = (inventories: InventorySelectRow[]) => {
|
||||||
|
if (!inventories.length) return
|
||||||
|
formData.value.details = formData.value.details || []
|
||||||
|
inventories.forEach((inventory) => {
|
||||||
|
if (isInventorySelected(inventory)) return
|
||||||
|
formData.value.details!.push(buildDetail(inventory))
|
||||||
|
})
|
||||||
|
refreshTotalAmount()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判断库存是否已选择 */
|
||||||
|
const isInventorySelected = (inventory: InventorySelectRow) =>
|
||||||
|
(formData.value.details || []).some((detail) => {
|
||||||
|
if (BATCH_ENABLE) return detail.inventoryDetailId === inventory.inventoryDetailId
|
||||||
|
return (
|
||||||
|
detail.skuId === inventory.skuId &&
|
||||||
|
detail.sourceWarehouseId === inventory.warehouseId &&
|
||||||
|
detail.sourceAreaId === inventory.areaId
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleDeleteDetail = (index: number) => {
|
||||||
|
formData.value.details?.splice(index, 1)
|
||||||
|
refreshTotalAmount()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSourceWarehouseChange = () => {
|
||||||
|
formData.value.sourceAreaId = undefined
|
||||||
|
formData.value.details = []
|
||||||
|
refreshTotalAmount()
|
||||||
|
}
|
||||||
|
const handleSourceAreaChange = () => {
|
||||||
|
formData.value.details = []
|
||||||
|
refreshTotalAmount()
|
||||||
|
}
|
||||||
|
const handleTargetWarehouseChange = () => {
|
||||||
|
formData.value.targetAreaId = undefined
|
||||||
|
refreshTargetToDetails()
|
||||||
|
}
|
||||||
|
const handleTargetAreaChange = () => refreshTargetToDetails()
|
||||||
|
const refreshTargetToDetails = () => {
|
||||||
|
const details = formData.value.details || []
|
||||||
|
details.forEach((detail) => {
|
||||||
|
detail.targetWarehouseId = formData.value.targetWarehouseId
|
||||||
|
detail.targetAreaId = formData.value.targetAreaId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const refreshTotalAmount = () => {
|
||||||
|
formData.value.totalAmount = detailTotalAmount.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 校验明细 */
|
||||||
|
const validateDetails = (required: boolean) => {
|
||||||
|
if (!formData.value.details?.length) {
|
||||||
|
if (required) {
|
||||||
|
message.error('至少包含一条移库明细')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
formData.value.sourceWarehouseId === formData.value.targetWarehouseId &&
|
||||||
|
formData.value.sourceAreaId === formData.value.targetAreaId
|
||||||
|
) {
|
||||||
|
message.error('来源仓库库区和目标仓库库区不能相同')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (let i = 0; i < formData.value.details.length; i++) {
|
||||||
|
const detail = formData.value.details[i]
|
||||||
|
if (AREA_ENABLE && (!detail.sourceAreaId || !detail.targetAreaId)) {
|
||||||
|
message.error(`第 ${i + 1} 行明细请选择来源和目标库区`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!detail.quantity || detail.quantity <= 0) {
|
||||||
|
message.error(`第 ${i + 1} 行明细移库数量必须大于 0`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (detail.availableQuantity !== undefined && detail.quantity > detail.availableQuantity) {
|
||||||
|
message.error(`第 ${i + 1} 行明细移库数量不能大于可用库存`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构建提交数据 */
|
||||||
|
const buildSubmitData = () =>
|
||||||
|
({
|
||||||
|
...formData.value,
|
||||||
|
totalQuantity: totalQuantity.value,
|
||||||
|
totalAmount: formData.value.totalAmount,
|
||||||
|
details: formData.value.details || []
|
||||||
|
}) as MovementOrderVO
|
||||||
|
|
||||||
|
const emit = defineEmits(['success'])
|
||||||
|
const submitForm = async () => {
|
||||||
|
await formRef.value.validate()
|
||||||
|
if (!validateDetails(false)) return
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = buildSubmitData()
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await MovementOrderApi.createMovementOrder(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await MovementOrderApi.updateMovementOrder(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 MovementOrderApi.updateMovementOrder(data)
|
||||||
|
}
|
||||||
|
await MovementOrderApi.completeMovementOrder(formData.value.id!)
|
||||||
|
message.success('移库成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 作废移库单 */
|
||||||
|
const handleCancel = async () => {
|
||||||
|
try {
|
||||||
|
await message.confirm('确认作废该移库单?作废后不可恢复。')
|
||||||
|
formLoading.value = true
|
||||||
|
await MovementOrderApi.cancelMovementOrder(formData.value.id!)
|
||||||
|
message.success('作废成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
no: generateOrderNo('YK'),
|
||||||
|
status: OrderStatusEnum.PREPARE,
|
||||||
|
sourceWarehouseId: undefined,
|
||||||
|
sourceAreaId: undefined,
|
||||||
|
targetWarehouseId: undefined,
|
||||||
|
targetAreaId: undefined,
|
||||||
|
totalQuantity: 0,
|
||||||
|
totalAmount: 0,
|
||||||
|
remark: undefined,
|
||||||
|
details: []
|
||||||
|
}
|
||||||
|
originalFormData.value = ''
|
||||||
|
nextTick(() => formRef.value?.clearValidate())
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
<!-- WMS 移库单 -->
|
||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="90px">
|
||||||
|
<el-form-item label="移库单号" prop="no">
|
||||||
|
<el-input v-model="queryParams.no" class="!w-240px" clearable placeholder="请输入移库单号" @keyup.enter="handleQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="单据状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" class="!w-240px" clearable placeholder="请选择单据状态">
|
||||||
|
<el-option
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.WMS_ORDER_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="来源仓库" prop="sourceWarehouseId">
|
||||||
|
<WarehouseSelect v-model="queryParams.sourceWarehouseId" class="!w-240px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="目标仓库" prop="targetWarehouseId">
|
||||||
|
<WarehouseSelect v-model="queryParams.targetWarehouseId" class="!w-240px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="单据日期" prop="orderDate">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.orderDate"
|
||||||
|
:shortcuts="defaultShortcuts"
|
||||||
|
class="!w-240px"
|
||||||
|
end-placeholder="结束时间"
|
||||||
|
start-placeholder="开始时间"
|
||||||
|
type="datetimerange"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建用户" prop="creator">
|
||||||
|
<UserSelectV2 v-model="queryParams.creator" class="!w-240px" placeholder="请选择创建用户" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="更新用户" prop="updater">
|
||||||
|
<UserSelectV2 v-model="queryParams.updater" class="!w-240px" placeholder="请选择更新用户" />
|
||||||
|
</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-button v-hasPermi="['wms:movement-order:create']" plain type="primary" @click="openForm('create')">
|
||||||
|
<Icon class="mr-5px" icon="ep:plus" />
|
||||||
|
新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['wms:movement-order:export']"
|
||||||
|
:loading="exportLoading"
|
||||||
|
plain
|
||||||
|
type="success"
|
||||||
|
@click="handleExport"
|
||||||
|
>
|
||||||
|
<Icon class="mr-5px" icon="ep:download" />
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
:show-overflow-tooltip="true"
|
||||||
|
border
|
||||||
|
@expand-change="handleExpandChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="expand" width="48">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-table :data="detailMap[row.id] || []" border>
|
||||||
|
<el-table-column label="商品信息" min-width="220">
|
||||||
|
<template #default="{ row: detail }">
|
||||||
|
<div>{{ detail.itemName || '-' }}</div>
|
||||||
|
<div v-if="detail.itemCode" class="text-12px text-gray-500">商品编号:{{ detail.itemCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="规格信息" min-width="220">
|
||||||
|
<template #default="{ row: detail }">
|
||||||
|
<div>{{ detail.skuName || '-' }}</div>
|
||||||
|
<div v-if="detail.skuCode" class="text-12px text-gray-500">规格编号:{{ detail.skuCode }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="BATCH_ENABLE" label="批号" min-width="140" prop="batchNo" />
|
||||||
|
<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.amount) || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="160" prop="remark" />
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column fixed="left" label="单号" width="210">
|
||||||
|
<template #default="{ row }">
|
||||||
|
单号:
|
||||||
|
<el-button link type="primary" @click="openDetail(row.id)">{{ row.no }}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column align="center" fixed="left" label="移库状态" width="110">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<dict-tag :type="DICT_TYPE.WMS_ORDER_STATUS" :value="row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="AREA_ENABLE ? '来源仓库/库区' : '来源仓库'" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="AREA_ENABLE">
|
||||||
|
<div>仓库:{{ row.sourceWarehouseName || '-' }}</div>
|
||||||
|
<div>库区:{{ row.sourceAreaName || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ row.sourceWarehouseName || '-' }}</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column :label="AREA_ENABLE ? '目标仓库/库区' : '目标仓库'" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="AREA_ENABLE">
|
||||||
|
<div>仓库:{{ row.targetWarehouseName || '-' }}</div>
|
||||||
|
<div>库区:{{ row.targetAreaName || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>{{ row.targetWarehouseName || '-' }}</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="总数量/总金额(元)" min-width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>数量:</span>
|
||||||
|
<span>{{ formatQuantity(row.totalQuantity) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>金额:</span>
|
||||||
|
<span>{{ formatPrice(row.totalAmount) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作信息" min-width="280">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>创建:{{ formatNullableDate(row.createTime) }} / {{ row.creatorName || row.creator || '-' }}</div>
|
||||||
|
<div>更新:{{ formatNullableDate(row.updateTime) }} / {{ row.updaterName || row.updater || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="160" prop="remark" />
|
||||||
|
<el-table-column align="center" fixed="right" label="操作" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tooltip :content="getUpdateTip(row.status)" :disabled="canUpdate(row.status)" placement="top">
|
||||||
|
<span>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['wms:movement-order:update']"
|
||||||
|
:disabled="!canUpdate(row.status)"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', row.id)"
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip :content="getDeleteTip(row.status)" :disabled="canDelete(row.status)" placement="top">
|
||||||
|
<span>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['wms:movement-order:delete']"
|
||||||
|
:disabled="!canDelete(row.status)"
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(row.id)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<Pagination v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNo" :total="total" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<MovementOrderForm ref="formRef" @success="getList" />
|
||||||
|
<MovementOrderDetail ref="detailRef" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { defaultShortcuts, formatNullableDate } from '@/utils/formatTime'
|
||||||
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { MovementOrderApi, MovementOrderVO } from '@/api/wms/order/movement'
|
||||||
|
import { MovementOrderDetailVO } from '@/api/wms/order/movement/detail'
|
||||||
|
import WarehouseSelect from '@/views/wms/md/warehouse/components/WarehouseSelect.vue'
|
||||||
|
import { AREA_ENABLE, BATCH_ENABLE } from '@/views/wms/utils/config'
|
||||||
|
import { OrderDeleteStatusList, OrderStatusEnum, OrderUpdateStatusList } from '@/views/wms/utils/constants'
|
||||||
|
import { formatPrice, formatQuantity } from '@/views/wms/utils/format'
|
||||||
|
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||||
|
import MovementOrderDetail from './MovementOrderDetail.vue'
|
||||||
|
import MovementOrderForm from './MovementOrderForm.vue'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
|
||||||
|
/** WMS 移库单 */
|
||||||
|
defineOptions({ name: 'WmsMovementOrder' })
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const list = ref<MovementOrderVO[]>([])
|
||||||
|
const total = ref(0)
|
||||||
|
const getDefaultQueryParams = () => ({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: undefined as string | undefined,
|
||||||
|
status: undefined as number | undefined,
|
||||||
|
sourceWarehouseId: undefined as number | undefined,
|
||||||
|
targetWarehouseId: undefined as number | undefined,
|
||||||
|
orderDate: undefined as string[] | undefined,
|
||||||
|
creator: undefined as number | undefined,
|
||||||
|
updater: undefined as number | undefined
|
||||||
|
})
|
||||||
|
const queryParams = reactive(getDefaultQueryParams())
|
||||||
|
const queryFormRef = ref()
|
||||||
|
const exportLoading = ref(false)
|
||||||
|
const detailMap = reactive<Record<number, MovementOrderDetailVO[]>>({})
|
||||||
|
|
||||||
|
const canUpdate = (status?: number) => status !== undefined && OrderUpdateStatusList.includes(status)
|
||||||
|
const canDelete = (status?: number) => status !== undefined && OrderDeleteStatusList.includes(status)
|
||||||
|
const getUpdateTip = (status?: number) => {
|
||||||
|
if (status === OrderStatusEnum.FINISHED) return '已移库,无法修改'
|
||||||
|
if (status === OrderStatusEnum.CANCELED) return '已作废,无法修改'
|
||||||
|
return '当前状态无法修改'
|
||||||
|
}
|
||||||
|
const getDeleteTip = (status?: number) => {
|
||||||
|
if (status === OrderStatusEnum.FINISHED) return '已移库,无法删除'
|
||||||
|
return '当前状态无法删除'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await MovementOrderApi.getMovementOrderPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
const resetQuery = () => {
|
||||||
|
Object.assign(queryParams, getDefaultQueryParams())
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
const handleExpandChange = async (row: MovementOrderVO) => {
|
||||||
|
if (!row.id || detailMap[row.id]) return
|
||||||
|
detailMap[row.id] = await MovementOrderApi.getMovementOrderDetailListByOrderId(row.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => formRef.value.open(type, id)
|
||||||
|
const detailRef = ref()
|
||||||
|
const openDetail = (id: number) => detailRef.value.open(id)
|
||||||
|
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
await message.delConfirm()
|
||||||
|
await MovementOrderApi.deleteMovementOrder(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
await message.exportConfirm()
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await MovementOrderApi.exportMovementOrder(queryParams)
|
||||||
|
download.excel(data, '移库单.xls')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => getList())
|
||||||
|
</script>
|
||||||
Loading…
Reference in New Issue