feat(mes): 添加 MES 领料出库单相关数据模型及接口

新增 MES 领料出库单及其行的响应和请求 VO,包含必要的字段和注释。
同时实现了相关的控制器和服务接口,支持创建、更新、删除和查询领料出库单的功能。

此功能将提升领料出库单的管理效率,便于后续的业务逻辑实现。
pull/871/MERGE
YunaiV 2026-02-26 19:18:10 +08:00
parent fe04f0485e
commit 84f417823d
3 changed files with 618 additions and 0 deletions

View File

@ -0,0 +1,89 @@
import request from '@/config/axios'
// TODO @AI对齐 /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/api/mes/wm/itemreceipt/index.ts
// MES 领料出库单行 VO
export interface WmProductionIssueLineVO {
id?: number
issueId?: number
itemId: number
itemCode?: string
itemName?: string
specification?: string
unitOfMeasure?: string
quantityIssued: number
batchId?: number
batchCode?: string
remark?: string
}
// MES 领料出库单 VO
export interface WmProductionIssueVO {
id?: number
code?: string
name: string
workstationId?: number
workstationCode?: string
workstationName?: string
workorderId?: number
workorderCode?: string
taskId?: number
taskCode?: string
clientId?: number
clientCode?: string
clientName?: string
clientNickname?: string
issueDate?: string
requiredTime?: string
status?: number
remark?: string
createTime?: string
lines?: WmProductionIssueLineVO[]
}
// MES 领料出库单 API
export const WmProductionIssueApi = {
// 查询领料出库单分页
getIssuePage: async (params: any) => {
return await request.get({ url: '/mes/wm/production-issue/page', params })
},
// 查询领料出库单详情
getIssue: async (id: number) => {
return await request.get({ url: '/mes/wm/production-issue/get?id=' + id })
},
// 新增领料出库单
createIssue: async (data: WmProductionIssueVO) => {
return await request.post({ url: '/mes/wm/production-issue/create', data })
},
// 修改领料出库单
updateIssue: async (data: WmProductionIssueVO) => {
return await request.put({ url: '/mes/wm/production-issue/update', data })
},
// 删除领料出库单
deleteIssue: async (id: number) => {
return await request.delete({ url: '/mes/wm/production-issue/delete?id=' + id })
},
// 审批领料出库单
approveIssue: async (id: number) => {
return await request.put({ url: '/mes/wm/production-issue/approve?id=' + id })
},
// 反审批领料出库单
unapproveIssue: async (id: number) => {
return await request.put({ url: '/mes/wm/production-issue/unapprove?id=' + id })
},
// 完成领料出库单
finishIssue: async (id: number) => {
return await request.put({ url: '/mes/wm/production-issue/finish?id=' + id })
},
// 导出领料出库单 Excel
exportIssue: async (params: any) => {
return await request.download({ url: '/mes/wm/production-issue/export-excel', params })
}
}

View File

@ -0,0 +1,266 @@
<!-- TODO @AI一行 3 -->
<!-- TODO @AI领料单编号必填领料单名称必填需求时间必填生产工单必填工作站 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1200px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="12">
<!-- TODO @AI领料单编号字段生成 -->
<el-form-item label="领料单名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入领料单名称" :disabled="isDetail" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工单编号" prop="workorderId">
<el-input
v-model="formData.workorderCode"
placeholder="请选择工单"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工作站" prop="workstationId">
<el-input
v-model="formData.workstationCode"
placeholder="请选择工作站"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
<!-- TODO @AI不用这个字段 -->
<el-col :span="12">
<el-form-item label="领料日期" prop="issueDate">
<el-date-picker
v-model="formData.issueDate"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择领料日期"
:disabled="isDetail"
class="!w-full"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="需求时间" prop="requiredTime">
<el-date-picker
v-model="formData.requiredTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择需求时间"
:disabled="isDetail"
class="!w-full"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
placeholder="请输入备注"
:disabled="isDetail"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 领料明细 -->
<!-- TODO @AI参考别的模块独立文件 -->
<el-divider content-position="left">领料明细</el-divider>
<el-row>
<el-col :span="24">
<el-button type="primary" @click="handleAddLine" v-if="!isDetail" class="mb-10px">
<Icon icon="ep:plus" class="mr-5px" /> 添加物料
</el-button>
<el-table :data="formData.lines" border>
<el-table-column label="物料编码" prop="itemCode" width="150">
<template #default="{ row, $index }">
<el-input
v-model="row.itemCode"
placeholder="请输入物料编码"
:disabled="isDetail"
/>
</template>
</el-table-column>
<el-table-column label="物料名称" prop="itemName" width="150">
<template #default="{ row, $index }">
<el-input
v-model="row.itemName"
placeholder="请输入物料名称"
:disabled="isDetail"
/>
</template>
</el-table-column>
<el-table-column label="规格型号" prop="specification" width="150">
<template #default="{ row, $index }">
<el-input
v-model="row.specification"
placeholder="请输入规格型号"
:disabled="isDetail"
/>
</template>
</el-table-column>
<el-table-column label="单位" prop="unitOfMeasure" width="100">
<template #default="{ row, $index }">
<el-input v-model="row.unitOfMeasure" placeholder="单位" :disabled="isDetail" />
</template>
</el-table-column>
<el-table-column label="领料数量" prop="quantityIssued" width="120">
<template #default="{ row, $index }">
<el-input-number
v-model="row.quantityIssued"
:min="0"
:precision="2"
:disabled="isDetail"
class="!w-full"
/>
</template>
</el-table-column>
<el-table-column label="批次号" prop="batchCode" width="120">
<template #default="{ row, $index }">
<el-input v-model="row.batchCode" placeholder="批次号" :disabled="isDetail" />
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="150">
<template #default="{ row, $index }">
<el-input v-model="row.remark" placeholder="备注" :disabled="isDetail" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80" v-if="!isDetail">
<template #default="{ row, $index }">
<el-button link type="danger" @click="handleDeleteLine($index)"></el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitForm" v-if="!isDetail"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { WmProductionIssueApi, WmProductionIssueVO } from '@/api/mes/wm/production-issue'
defineOptions({ name: 'IssueForm' })
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const isDetail = ref(false)
const formData = ref<WmProductionIssueVO>({
id: undefined,
name: '',
workorderId: undefined,
workorderCode: '',
workstationId: undefined,
workstationCode: '',
issueDate: undefined,
requiredTime: undefined,
remark: '',
lines: []
})
const formRules = reactive({
name: [{ required: true, message: '领料单名称不能为空', trigger: 'blur' }],
issueDate: [{ required: true, message: '领料日期不能为空', trigger: 'change' }]
})
const formRef = ref()
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value =
type === 'create' ? '新增领料出库单' : type === 'update' ? '修改领料出库单' : '领料出库单详情'
formType.value = type
isDetail.value = type === 'detail'
resetForm()
if (id) {
formLoading.value = true
try {
formData.value = await WmProductionIssueApi.getIssue(id)
if (!formData.value.lines) {
formData.value.lines = []
}
} finally {
formLoading.value = false
}
}
}
defineExpose({ open })
/** 提交表单 */
const emit = defineEmits(['success'])
const submitForm = async () => {
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
formLoading.value = true
try {
const data = formData.value
if (formType.value === 'create') {
await WmProductionIssueApi.createIssue(data)
message.success(t('common.createSuccess'))
} else {
await WmProductionIssueApi.updateIssue(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
workorderId: undefined,
workorderCode: '',
workstationId: undefined,
workstationCode: '',
issueDate: undefined,
requiredTime: undefined,
remark: '',
lines: []
}
formRef.value?.resetFields()
}
/** 添加行 */
const handleAddLine = () => {
if (!formData.value.lines) {
formData.value.lines = []
}
formData.value.lines.push({
itemId: 0,
itemCode: '',
itemName: '',
specification: '',
unitOfMeasure: '',
quantityIssued: 1,
batchCode: '',
remark: ''
})
}
/** 删除行 */
const handleDeleteLine = (index: number) => {
formData.value.lines?.splice(index, 1)
}
</script>

View File

@ -0,0 +1,263 @@
<template>
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="领料单编号" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入领料单编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="领料单名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入领料单名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="领料日期" prop="issueDate">
<el-date-picker
v-model="queryParams.issueDate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<!-- TODO @AI单据状态 -->
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['mes:wm-production-issue:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['mes:wm-production-issue:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<!-- TODO @AI点击后跳转详情 -->
<el-table-column label="领料单编号" align="center" prop="code" min-width="160" />
<el-table-column label="领料单名称" align="center" prop="name" min-width="150" />
<el-table-column label="生产工单" align="center" prop="workorderCode" min-width="140" />
<!-- TODO @AI工作站workstationName -->
<!-- TODO @AI客户编号客户名称 -->
<!-- TODO @AI需求日期 -->
<!-- TODO @AI移除领料日期 -->
<el-table-column
label="领料日期"
align="center"
prop="issueDate"
:formatter="dateFormatter2"
width="180px"
/>
<el-table-column label="单据状态" align="center" prop="status" min-width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_WM_ISSUE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="240" fixed="right">
<template #default="scope">
<!-- TODO @AI操作和 /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/wm/itemreceipt/index.vue 对齐下 -->
<!-- TODO @AI应该是执行领出修改删除 -->
<!-- 准备中编辑审批删除 -->
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update']"
v-if="scope.row.status === 10"
>
编辑
</el-button>
<el-button
link
type="success"
@click="handleApprove(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update-status']"
v-if="scope.row.status === 10"
>
审批
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:delete']"
v-if="scope.row.status === 10"
>
删除
</el-button>
<!-- 已审批反审批完成 -->
<el-button
link
type="warning"
@click="handleUnapprove(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update-status']"
v-if="scope.row.status === 20"
>
反审批
</el-button>
<el-button
link
type="primary"
@click="handleFinish(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update-status']"
v-if="scope.row.status === 20"
>
完成
</el-button>
<el-button link type="info" @click="openForm('detail', scope.row.id)"> 详情 </el-button>
</template>
</el-table-column>
</el-table>
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<IssueForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter2 } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import download from '@/utils/download'
import { WmProductionIssueApi, WmProductionIssueVO } from '@/api/mes/wm/production-issue'
import IssueForm from './IssueForm.vue'
defineOptions({ name: 'MesWmIssue' })
const message = useMessage()
const { t } = useI18n()
const loading = ref(true)
const list = ref<WmProductionIssueVO[]>([])
const total = ref(0)
const exportLoading = ref(false)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
name: undefined,
issueDate: undefined
})
const queryFormRef = ref()
const formRef = ref()
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await WmProductionIssueApi.getIssuePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await WmProductionIssueApi.deleteIssue(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
/** 审批按钮操作 */
const handleApprove = async (id: number) => {
try {
await message.confirm('确认审批该领料单吗?')
await WmProductionIssueApi.approveIssue(id)
message.success('审批成功')
await getList()
} catch {}
}
/** 反审批按钮操作 */
const handleUnapprove = async (id: number) => {
try {
await message.confirm('确认反审批该领料单吗?')
await WmProductionIssueApi.unapproveIssue(id)
message.success('反审批成功')
await getList()
} catch {}
}
/** 完成按钮操作 */
const handleFinish = async (id: number) => {
try {
await message.confirm('确认完成该领料单吗?')
await WmProductionIssueApi.finishIssue(id)
message.success('完成成功')
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const data = await WmProductionIssueApi.exportIssue(queryParams)
download.excel(data, '领料出库单.xls')
} catch {
} finally {
exportLoading.value = false
}
}
onMounted(() => {
getList()
})
</script>