feat(mes): 添加生产领料出库单相关功能与接口

新增生产领料出库单的状态管理、行验证及明细查询功能,优化了相关数据模型和接口,提升了系统的可用性和用户体验。
pull/871/MERGE
YunaiV 2026-02-27 19:04:35 +08:00
parent a77ef94fd5
commit bd27ddfc71
16 changed files with 800 additions and 1098 deletions

View File

@ -0,0 +1,48 @@
import request from '@/config/axios'
// MES 领料出库明细 VO
export interface WmProductionIssueDetailVO {
id?: number
issueId: number
lineId: number
materialStockId: number
itemId: number
quantity: number
batchId?: number
batchCode?: string
warehouseId: number
warehouseName?: string
locationId?: number
locationName?: string
areaId?: number
areaName?: string
remark?: string
}
// MES 领料出库明细 API
export const WmProductionIssueDetailApi = {
// 查询领料出库明细分页
getDetailPage: async (params: any) => {
return await request.get({ url: '/mes/wm/production-issue-detail/page', params })
},
// 查询领料出库明细详情
getDetail: async (id: number) => {
return await request.get({ url: '/mes/wm/production-issue-detail/get?id=' + id })
},
// 新增领料出库明细
createDetail: async (data: WmProductionIssueDetailVO) => {
return await request.post({ url: '/mes/wm/production-issue-detail/create', data })
},
// 修改领料出库明细
updateDetail: async (data: WmProductionIssueDetailVO) => {
return await request.put({ url: '/mes/wm/production-issue-detail/update', data })
},
// 删除领料出库明细
deleteDetail: async (id: number) => {
return await request.delete({ url: '/mes/wm/production-issue-detail/delete?id=' + id })
}
}

View File

@ -1,6 +1,5 @@
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
@ -9,8 +8,8 @@ export interface WmProductionIssueLineVO {
itemCode?: string
itemName?: string
specification?: string
unitOfMeasure?: string
quantityIssued: number
unitMeasureName?: string
quantity: number
batchId?: number
batchCode?: string
remark?: string
@ -24,15 +23,8 @@ export interface WmProductionIssueVO {
workstationId?: number
workstationCode?: string
workstationName?: string
workorderId?: number
workorderCode?: string
taskId?: number
taskCode?: string
clientId?: number
clientCode?: string
clientName?: string
clientNickname?: string
issueDate?: string
workOrderId?: number
workOrderCode?: string
requiredTime?: string
status?: number
remark?: string
@ -43,37 +35,47 @@ export interface WmProductionIssueVO {
// MES 领料出库单 API
export const WmProductionIssueApi = {
// 查询领料出库单分页
getIssuePage: async (params: any) => {
getProductionIssuePage: async (params: any) => {
return await request.get({ url: '/mes/wm/production-issue/page', params })
},
// 查询领料出库单详情
getIssue: async (id: number) => {
getProductionIssue: async (id: number) => {
return await request.get({ url: '/mes/wm/production-issue/get?id=' + id })
},
// 新增领料出库单
createIssue: async (data: WmProductionIssueVO) => {
createProductionIssue: async (data: WmProductionIssueVO) => {
return await request.post({ url: '/mes/wm/production-issue/create', data })
},
// 修改领料出库单
updateIssue: async (data: WmProductionIssueVO) => {
updateProductionIssue: async (data: WmProductionIssueVO) => {
return await request.put({ url: '/mes/wm/production-issue/update', data })
},
// 删除领料出库单
deleteIssue: async (id: number) => {
deleteProductionIssue: async (id: number) => {
return await request.delete({ url: '/mes/wm/production-issue/delete?id=' + id })
},
// 提交领料出库单(进入审批流程)
submitProductionIssue: async (id: number) => {
return await request.put({ url: '/mes/wm/production-issue/submit?id=' + id })
},
// 执行拣货
stockProductionIssue: async (id: number) => {
return await request.put({ url: '/mes/wm/production-issue/stock?id=' + id })
},
// 完成领料出库单(执行出库)
finishIssue: async (id: number) => {
finishProductionIssue: async (id: number) => {
return await request.put({ url: '/mes/wm/production-issue/finish?id=' + id })
},
// 导出领料出库单 Excel
exportIssue: async (params: any) => {
exportProductionIssue: async (params: any) => {
return await request.download({ url: '/mes/wm/production-issue/export-excel', params })
}
}

View File

@ -0,0 +1,44 @@
import request from '@/config/axios'
// MES 领料出库单行 VO
export interface WmProductionIssueLineVO {
id?: number
issueId: number
itemId: number
itemCode?: string
itemName?: string
specification?: string
unitMeasureName?: string
quantity: number
batchId?: number
batchCode?: string
remark?: string
}
// MES 领料出库单行 API
export const WmProductionIssueLineApi = {
// 查询领料出库单行分页
getLinePage: async (params: any) => {
return await request.get({ url: '/mes/wm/production-issue-line/page', params })
},
// 查询领料出库单行详情
getLine: async (id: number) => {
return await request.get({ url: '/mes/wm/production-issue-line/get?id=' + id })
},
// 新增领料出库单行
createLine: async (data: WmProductionIssueLineVO) => {
return await request.post({ url: '/mes/wm/production-issue-line/create', data })
},
// 修改领料出库单行
updateLine: async (data: WmProductionIssueLineVO) => {
return await request.put({ url: '/mes/wm/production-issue-line/update', data })
},
// 删除领料出库单行
deleteLine: async (id: number) => {
return await request.delete({ url: '/mes/wm/production-issue-line/delete?id=' + id })
}
}

View File

@ -1,138 +0,0 @@
<!-- TODO @AI这些组件已经有了你找下 -->
<template>
<el-dialog
v-model="dialogVisible"
title="选择物料"
width="1000px"
@close="handleClose"
>
<el-form :inline="true" :model="queryParams" class="mb-10px">
<el-form-item label="物料编号">
<el-input
v-model="queryParams.code"
placeholder="请输入物料编号"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item label="物料名称">
<el-input
v-model="queryParams.name"
placeholder="请输入物料名称"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @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-form-item>
</el-form>
<el-table
v-loading="loading"
:data="list"
@row-click="handleRowClick"
highlight-current-row
max-height="400px"
>
<el-table-column label="物料编号" prop="code" width="150" />
<el-table-column label="物料名称" prop="name" width="200" />
<el-table-column label="规格型号" prop="specification" width="150" />
<el-table-column label="单位" prop="unitName" width="100" />
<el-table-column label="物料分类" prop="typeName" width="150" />
</el-table>
<Pagination
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
<template #footer>
<el-button @click="handleClose"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ItemApi } from '@/api/mes/md/item'
defineOptions({ name: 'ItemSelect' })
const emit = defineEmits(['select'])
const dialogVisible = ref(false)
const loading = ref(false)
const list = ref([])
const total = ref(0)
const selectedRow = ref(null)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
name: undefined
})
const open = () => {
dialogVisible.value = true
getList()
}
const handleClose = () => {
dialogVisible.value = false
selectedRow.value = null
}
const getList = async () => {
loading.value = true
try {
const data = await ItemApi.getItemPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryParams.code = undefined
queryParams.name = undefined
handleQuery()
}
const handleRowClick = (row: any) => {
selectedRow.value = row
}
const handleConfirm = () => {
if (!selectedRow.value) {
return
}
emit('select', {
itemId: selectedRow.value.id,
itemCode: selectedRow.value.code,
itemName: selectedRow.value.name,
specification: selectedRow.value.specification,
unitName: selectedRow.value.unitName
})
handleClose()
}
defineExpose({ open })
</script>

View File

@ -1,135 +0,0 @@
<!-- TODO @AI这些组件已经有了你找下 -->
<template>
<el-dialog v-model="dialogVisible" title="选择生产工单" width="1200px" @close="handleClose">
<el-form :inline="true" :model="queryParams" class="mb-10px">
<el-form-item label="工单编号">
<el-input
v-model="queryParams.code"
placeholder="请输入工单编号"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item label="产品名称">
<el-input
v-model="queryParams.productName"
placeholder="请输入产品名称"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @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-form-item>
</el-form>
<el-table
v-loading="loading"
:data="list"
@row-click="handleRowClick"
highlight-current-row
max-height="400px"
>
<el-table-column label="工单编号" prop="code" width="150" />
<el-table-column label="产品编号" prop="productCode" width="150" />
<el-table-column label="产品名称" prop="productName" width="200" />
<el-table-column label="计划数量" prop="planQuantity" width="120" />
<el-table-column label="状态" prop="status" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_WORK_ORDER_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
</el-table>
<Pagination
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
<template #footer>
<el-button @click="handleClose"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { WorkOrderApi } from '@/api/mes/pro/workorder'
defineOptions({ name: 'WorkorderSelect' })
const emit = defineEmits(['select'])
const dialogVisible = ref(false)
const loading = ref(false)
const list = ref([])
const total = ref(0)
const selectedRow = ref(null)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
productName: undefined
})
const open = () => {
dialogVisible.value = true
getList()
}
const handleClose = () => {
dialogVisible.value = false
selectedRow.value = null
}
const getList = async () => {
loading.value = true
try {
const data = await WorkOrderApi.getWorkOrderPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryParams.code = undefined
queryParams.productName = undefined
handleQuery()
}
const handleRowClick = (row: any) => {
selectedRow.value = row
}
const handleConfirm = () => {
if (!selectedRow.value) {
return
}
emit('select', {
workorderId: selectedRow.value.id,
workorderCode: selectedRow.value.code,
productCode: selectedRow.value.productCode,
productName: selectedRow.value.productName
})
handleClose()
}
defineExpose({ open })
</script>

View File

@ -1,134 +0,0 @@
<!-- TODO @AI这些组件已经有了你找下 -->
<template>
<el-dialog v-model="dialogVisible" title="选择工作站" width="1000px" @close="handleClose">
<el-form :inline="true" :model="queryParams" class="mb-10px">
<el-form-item label="工作站编号">
<el-input
v-model="queryParams.code"
placeholder="请输入工作站编号"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item label="工作站名称">
<el-input
v-model="queryParams.name"
placeholder="请输入工作站名称"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @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-form-item>
</el-form>
<el-table
v-loading="loading"
:data="list"
@row-click="handleRowClick"
highlight-current-row
max-height="400px"
>
<el-table-column label="工作站编号" prop="code" width="150" />
<el-table-column label="工作站名称" prop="name" width="200" />
<el-table-column label="车间" prop="workshopName" width="150" />
<el-table-column label="状态" prop="status" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<Pagination
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
<template #footer>
<el-button @click="handleClose"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { WorkstationApi } from '@/api/mes/md/workstation'
defineOptions({ name: 'WorkstationSelect' })
const emit = defineEmits(['select'])
const dialogVisible = ref(false)
const loading = ref(false)
const list = ref([])
const total = ref(0)
const selectedRow = ref(null)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
name: undefined
})
const open = () => {
dialogVisible.value = true
getList()
}
const handleClose = () => {
dialogVisible.value = false
selectedRow.value = null
}
const getList = async () => {
loading.value = true
try {
const data = await WorkstationApi.getWorkstationPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryParams.code = undefined
queryParams.name = undefined
handleQuery()
}
const handleRowClick = (row: any) => {
selectedRow.value = row
}
const handleConfirm = () => {
if (!selectedRow.value) {
return
}
emit('select', {
workstationId: selectedRow.value.id,
workstationCode: selectedRow.value.code,
workstationName: selectedRow.value.name
})
handleClose()
}
defineExpose({ open })
</script>

View File

@ -294,4 +294,5 @@ export enum DICT_TYPE {
MES_RQC_TYPE = 'mes_rqc_type', // MES 退货检验类型
MES_WM_ARRIVAL_NOTICE_STATUS = 'mes_wm_arrival_notice_status', // MES 到货通知单状态
MES_WM_ITEM_RECEIPT_STATUS = 'mes_wm_item_receipt_status', // MES 物料接收单状态
MES_WM_PRODUCTION_ISSUE_STATUS = 'mes_wm_production_issue_status', // MES 领料出库单状态
}

View File

@ -209,6 +209,15 @@ export const MesWmItemReceiptStatusEnum = {
CANCELED: MesOrderStatusConstants.CANCELLED
}
/** MES 生产领料出库单状态枚举 */
export const MesWmProductionIssueStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
APPROVING: MesOrderStatusConstants.APPROVING,
APPROVED: MesOrderStatusConstants.APPROVED,
FINISHED: MesOrderStatusConstants.FINISHED,
CANCELED: MesOrderStatusConstants.CANCELLED
}
/** 获取物料/产品标识的标签 */
export const getItemOrProductLabel = (value: string): string => {
for (const item of Object.values(MesItemOrProductEnum)) {

View File

@ -155,7 +155,6 @@
>
取消
</el-button>
<el-button link type="info" @click="openForm('detail', scope.row.id)"> 详情 </el-button>
</template>
</el-table-column>
</el-table>
@ -171,8 +170,8 @@
</template>
<script setup lang="ts">
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter2 } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import download from '@/utils/download'
import { WmItemReceiptApi, WmItemReceiptVO } from '@/api/mes/wm/itemreceipt'
import { MdVendorApi } from '@/api/mes/md/vendor'
@ -198,6 +197,7 @@ const queryParams = reactive({
receiptDate: undefined
})
const queryFormRef = ref() //
const formRef = ref() //
/** 查询列表 */
const getList = async () => {
@ -211,25 +211,24 @@ const getList = async () => {
}
}
/** 搜索 */
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置 */
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 新增/修改/上架/详情 */
const formRef = ref() //
/** 添加/修改操作 */
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 提交 */
/** 提交按钮操作 */
const handleSubmit = async (id: number) => {
try {
await message.confirm('确认提交该采购入库单?')

View File

@ -1,4 +1,3 @@
<!-- TODO @AI这些组件已经有了你找下 -->
<template>
<el-dialog v-model="dialogVisible" title="选择库存" width="1200px" @close="handleClose">
<el-form :inline="true" :model="queryParams" class="mb-10px">
@ -59,9 +58,9 @@
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { MaterialStockApi } from '@/api/mes/wm/materialstock'
import { WmMaterialStockApi } from '@/api/mes/wm/materialstock'
defineOptions({ name: 'StockSelect' })
defineOptions({ name: 'WmMaterialStockSelect' })
const emit = defineEmits(['select'])
@ -96,7 +95,7 @@ const handleClose = () => {
const getList = async () => {
loading.value = true
try {
const data = await MaterialStockApi.getMaterialStockPage(queryParams)
const data = await WmMaterialStockApi.getMaterialStockPage(queryParams)
list.value = data.list
total.value = data.total
} finally {

View File

@ -0,0 +1,244 @@
<!-- MES 领料出库明细列表展开行内嵌子组件 -->
<template>
<div class="pl-60px pr-20px py-10px">
<el-button
v-if="canPick"
type="primary"
size="small"
@click="handlePick"
class="mb-10px"
>
<Icon icon="ep:plus" class="mr-5px" /> 添加拣货
</el-button>
<el-table v-loading="loading" :data="list" border size="small">
<el-table-column label="批次号" align="center" prop="batchCode" min-width="120" />
<el-table-column label="仓库" align="center" prop="warehouseName" min-width="100" />
<el-table-column label="库区" align="center" prop="locationName" min-width="100" />
<el-table-column label="库位" align="center" prop="areaName" min-width="100" />
<el-table-column label="拣货数量" align="center" prop="quantity" width="100" />
<el-table-column
v-if="canPick"
label="操作"
align="center"
width="120"
fixed="right"
>
<template #default="scope">
<el-button link type="danger" @click="handleDelete(scope.row.id)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 拣货对话框 -->
<el-dialog v-model="dialogVisible" title="添加拣货" width="600px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="库存位置" prop="materialStockId">
<el-input
v-model="formData.warehouseName"
placeholder="请选择库存位置"
readonly
@click="handleSelectStock"
class="cursor-pointer"
>
<template #append>
<el-button @click="handleSelectStock">
<Icon icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="批次号">
<el-input v-model="formData.batchCode" disabled />
</el-form-item>
<el-form-item label="库区">
<el-input v-model="formData.locationName" disabled />
</el-form-item>
<el-form-item label="库位">
<el-input v-model="formData.areaName" disabled />
</el-form-item>
<el-form-item label="可用数量">
<el-input v-model="formData.availableQuantity" disabled />
</el-form-item>
<el-form-item label="拣货数量" prop="quantity">
<el-input-number
v-model="formData.quantity"
:min="0"
:max="formData.availableQuantity"
:precision="2"
class="!w-full"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
<!-- 库存选择器 -->
<WmMaterialStockSelect ref="stockSelectRef" @select="handleStockSelected" />
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { WmProductionIssueDetailApi, WmProductionIssueDetailVO } from '@/api/mes/wm/productionissue/detail'
import WmMaterialStockSelect from '@/views/mes/wm/materialstock/components/WmMaterialStockSelect.vue'
defineOptions({ name: 'ProductionIssueDetailList' })
const props = defineProps<{
issueId: number
lineId: number
itemId: number
formType: string
}>()
const emit = defineEmits(['edit-detail'])
const { t } = useI18n()
const message = useMessage()
const loading = ref(false)
const list = ref<WmProductionIssueDetailVO[]>([])
const dialogVisible = ref(false)
const formRef = ref()
const stockSelectRef = ref()
const canPick = computed(() => props.formType === 'stock') //
const formData = reactive<WmProductionIssueDetailVO & { availableQuantity?: number }>({
issueId: props.issueId,
lineId: props.lineId,
itemId: props.itemId,
materialStockId: 0,
quantity: 0,
batchId: undefined,
batchCode: '',
warehouseId: 0,
warehouseName: '',
locationId: undefined,
locationName: '',
areaId: undefined,
areaName: '',
remark: '',
availableQuantity: 0
})
const formRules = {
materialStockId: [{ required: true, message: '请选择库存位置', trigger: 'change' }],
quantity: [{ required: true, message: '请输入拣货数量', trigger: 'blur' }]
}
/** 查询明细列表 */
const getList = async () => {
loading.value = true
try {
const data = await WmProductionIssueDetailApi.getDetailPage({
pageNo: 1,
pageSize: 100,
lineId: props.lineId
})
list.value = data.list
} finally {
loading.value = false
}
}
defineExpose({ getList })
/** 添加拣货 */
const handlePick = () => {
resetForm()
dialogVisible.value = true
}
/** 选择库存 */
const handleSelectStock = () => {
stockSelectRef.value.open(props.itemId)
}
/** 库存选择回调 */
const handleStockSelected = (stock: any) => {
formData.materialStockId = stock.materialStockId
formData.batchId = stock.batchId
formData.batchCode = stock.batchCode
formData.warehouseId = stock.warehouseId
formData.warehouseName = stock.warehouseName
formData.locationId = stock.locationId
formData.locationName = stock.locationName
formData.areaId = stock.areaId
formData.areaName = stock.areaName
formData.availableQuantity = stock.availableQuantity
}
/** 确认添加 */
const handleConfirm = async () => {
await formRef.value.validate()
try {
const data: WmProductionIssueDetailVO = {
issueId: props.issueId,
lineId: props.lineId,
itemId: props.itemId,
materialStockId: formData.materialStockId,
quantity: formData.quantity,
batchId: formData.batchId,
batchCode: formData.batchCode,
warehouseId: formData.warehouseId,
warehouseName: formData.warehouseName,
locationId: formData.locationId,
locationName: formData.locationName,
areaId: formData.areaId,
areaName: formData.areaName,
remark: formData.remark
}
await WmProductionIssueDetailApi.createDetail(data)
message.success('添加成功')
dialogVisible.value = false
await getList()
} catch {}
}
/** 删除明细 */
const handleDelete = async (detailId: number) => {
try {
await message.delConfirm()
await WmProductionIssueDetailApi.deleteDetail(detailId)
message.success(t('common.delSuccess'))
await getList()
} catch {}
}
/** 重置表单 */
const resetForm = () => {
Object.assign(formData, {
issueId: props.issueId,
lineId: props.lineId,
itemId: props.itemId,
materialStockId: 0,
quantity: 0,
batchId: undefined,
batchCode: '',
warehouseId: 0,
warehouseName: '',
locationId: undefined,
locationName: '',
areaId: undefined,
areaName: '',
remark: '',
availableQuantity: 0
})
formRef.value?.clearValidate()
}
/** 初始化:延迟加载,展开时才触发 */
onMounted(() => {
getList()
})
</script>
<style scoped>
.cursor-pointer {
cursor: pointer;
}
</style>

View File

@ -1,23 +1,35 @@
<!-- 表单布局一行 3 个字段 -->
<!-- 必填字段领料单编号必填领料单名称必填需求时间必填生产工单必填工作站 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1200px">
<Dialog :title="dialogTitle" v-model="dialogVisible" width="960px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
label-width="110px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-row>
<el-col :span="8">
<el-form-item label="领料单编号" prop="code">
<el-input v-model="formData.code" placeholder="自动生成" disabled />
<el-input
v-model="formData.code"
placeholder="请输入领料单编号"
:disabled="isHeaderReadonly"
>
<template #append>
<el-button @click="generateCode" :disabled="formType !== 'create'">
生成
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="领料单名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入领料单名称" :disabled="isDetail" />
<el-input
v-model="formData.name"
placeholder="请输入领料单名称"
:disabled="isHeaderReadonly"
/>
</el-form-item>
</el-col>
<el-col :span="8">
@ -27,163 +39,108 @@
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择需求时间"
:disabled="isDetail"
:disabled="isHeaderReadonly"
class="!w-full"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="生产工单" prop="workorderId">
<el-input
v-model="formData.workorderCode"
placeholder="请选择工单"
:disabled="isDetail"
/>
<el-form-item label="生产工单" prop="workOrderId">
<ProWorkOrderSelect v-model="formData.workOrderId" :disabled="isHeaderReadonly" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="工作站" prop="workstationId">
<el-input
v-model="formData.workstationCode"
placeholder="请选择工作站"
:disabled="isDetail"
/>
<MdWorkstationSelect v-model="formData.workstationId" :disabled="isHeaderReadonly" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
placeholder="请输入备注"
:disabled="isDetail"
:disabled="isHeaderReadonly"
/>
</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 v-if="formData.id">
<el-divider content-position="center">物料信息</el-divider>
<ProductionIssueLineList :issue-id="formData.id" :form-type="formType" />
</template>
<template #footer>
<el-button v-if="isUpdate" @click="submitForm" type="primary" :disabled="formLoading">
</el-button>
<el-button v-if="isStock" @click="handleStock" type="primary" :disabled="formLoading">
执行拣货
</el-button>
<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'
import { generateRandomStr } from '@/utils'
import { WmProductionIssueApi, WmProductionIssueVO } from '@/api/mes/wm/productionissue'
import ProWorkOrderSelect from '@/views/mes/pro/workorder/components/ProWorkOrderSelect.vue'
import MdWorkstationSelect from '@/views/mes/md/workstation/components/MdWorkstationSelect.vue'
import ProductionIssueLineList from './ProductionIssueLineList.vue'
defineOptions({ name: 'IssueForm' })
defineOptions({ name: 'ProductionIssueForm' })
const { t } = useI18n()
const message = useMessage()
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: '',
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formType = ref<string>('create') // 表单的类型create / update / stock / detail
const formData = ref({
id: undefined as number | undefined,
code: undefined,
name: undefined,
workOrderId: undefined,
workstationId: undefined,
workstationCode: '',
requiredTime: undefined,
remark: '',
lines: []
remark: undefined
})
const formRules = reactive({
// TODO @AI workorderid
code: [{ required: true, message: '领料单编号不能为空', trigger: 'blur' }],
name: [{ required: true, message: '领料单名称不能为空', trigger: 'blur' }]
})
const formRef = ref()
const formRef = ref() // Ref
const isUpdate = computed(() => ['create', 'update'].includes(formType.value)) //
const isStock = computed(() => formType.value === 'stock') //
const isHeaderReadonly = computed(() => ['stock', 'detail'].includes(formType.value)) //
const dialogTitle = computed(() => {
const titles = {
create: '新增领料出库单',
update: '编辑领料出库单',
stock: '执行拣货',
detail: '领料出库单详情'
}
return titles[formType.value] || formType.value
})
/** 生成领料单编号 */
const generateCode = () => {
formData.value.code = 'PI' + generateRandomStr(10)
}
/** 打开弹窗 */
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 = []
}
formData.value = await WmProductionIssueApi.getProductionIssue(id)
} finally {
formLoading.value = false
}
@ -191,24 +148,41 @@ const open = async (type: string, id?: number) => {
}
defineExpose({ open })
/** 提交表单 */
/** 提交表单create/update 模式) */
const emit = defineEmits(['success'])
const submitForm = async () => {
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value
const data = formData.value as unknown as WmProductionIssueVO
if (formType.value === 'create') {
await WmProductionIssueApi.createIssue(data)
message.success(t('common.createSuccess'))
const res = await WmProductionIssueApi.createProductionIssue(data)
message.success('新增成功')
formData.value.id = res
formType.value = 'update'
} else {
await WmProductionIssueApi.updateIssue(data)
message.success(t('common.updateSuccess'))
await WmProductionIssueApi.updateProductionIssue(data)
message.success('修改成功')
}
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 执行拣货 */
const handleStock = async () => {
try {
await message.confirm('确认执行拣货?')
formLoading.value = true
await WmProductionIssueApi.stockProductionIssue(formData.value.id!)
message.success('拣货成功')
dialogVisible.value = false
emit('success')
} catch {
} finally {
formLoading.value = false
}
@ -218,37 +192,13 @@ const submitForm = async () => {
const resetForm = () => {
formData.value = {
id: undefined,
name: '',
workorderId: undefined,
workorderCode: '',
code: undefined,
name: undefined,
workOrderId: undefined,
workstationId: undefined,
workstationCode: '',
requiredTime: undefined,
remark: '',
lines: []
remark: undefined
}
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,256 @@
<!-- MES 领料出库单行列表子组件 -->
<template>
<div>
<el-button v-if="isUpdate" type="primary" plain @click="openForm('create')" class="mb-10px">
<Icon icon="ep:plus" class="mr-5px" /> 添加物料
</el-button>
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
border
:row-key="(row: any) => row.id"
>
<el-table-column type="expand">
<template #default="scope">
<ProductionIssueDetailList
:ref="(el: any) => setDetailListRef(scope.row.id, el)"
:issue-id="props.issueId"
:line-id="scope.row.id"
:item-id="scope.row.itemId"
:form-type="props.formType"
@edit-detail="
(detailId: number) =>
openDetailForm('update', scope.row.id, scope.row.itemId, detailId)
"
/>
</template>
</el-table-column>
<el-table-column label="物料编码" align="center" prop="itemCode" min-width="120" />
<el-table-column label="物料名称" align="center" prop="itemName" min-width="140" />
<el-table-column label="规格型号" align="center" prop="specification" min-width="120" />
<el-table-column label="单位" align="center" prop="unitMeasureName" width="80" />
<el-table-column label="领料数量" align="center" prop="quantity" width="100" />
<el-table-column label="批次号" align="center" prop="batchCode" min-width="120" />
<el-table-column
v-if="isUpdate"
label="操作"
align="center"
width="160"
fixed="right"
>
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row.id)">
编辑
</el-button>
<el-button link type="danger" @click="handleDelete(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"
/>
</div>
<!-- 添加/编辑行弹窗 -->
<Dialog :title="dialogTitle" v-model="dialogVisible" width="960px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-row>
<el-col :span="8">
<el-form-item label="物料" prop="itemId">
<MdItemSelect
v-model="formData.itemId"
placeholder="请选择物料"
class="!w-1/1"
@change="handleItemChange"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="领料数量" prop="quantity">
<el-input-number
v-model="formData.quantity"
:precision="2"
:min="0"
controls-position="right"
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="批次号" prop="batchCode">
<el-input v-model="formData.batchCode" placeholder="请输入批次号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { WmProductionIssueLineApi, WmProductionIssueLineVO } from '@/api/mes/wm/productionissue/line'
import MdItemSelect from '@/views/mes/md/item/components/MdItemSelect.vue'
import ProductionIssueDetailList from './ProductionIssueDetailList.vue'
defineOptions({ name: 'ProductionIssueLineList' })
const message = useMessage()
const props = defineProps({
issueId: { type: Number, required: true },
formType: { type: String, required: true }
})
const loading = ref(false)
const list = ref<WmProductionIssueLineVO[]>([])
const total = ref(0)
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const lineFormType = ref<string>('create')
const formRef = ref()
const detailListRefs = ref<Map<number, any>>(new Map())
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
issueId: props.issueId
})
const formData = ref<WmProductionIssueLineVO>({
id: undefined,
issueId: props.issueId,
itemId: 0,
quantity: 0,
batchCode: undefined,
remark: undefined
})
const formRules = reactive({
itemId: [{ required: true, message: '请选择物料', trigger: 'change' }],
quantity: [{ required: true, message: '请输入领料数量', trigger: 'blur' }]
})
const isUpdate = computed(() => ['create', 'update'].includes(props.formType))
const setDetailListRef = (lineId: number, el: any) => {
if (el) {
detailListRefs.value.set(lineId, el)
}
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await WmProductionIssueLineApi.getLinePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 打开表单弹窗 */
const openForm = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = type === 'create' ? '添加物料' : '编辑物料'
lineFormType.value = type
resetForm()
// //
if (id) {
formLoading.value = true
try {
formData.value = await WmProductionIssueLineApi.getLine(id)
} finally {
formLoading.value = false
}
}
}
/** 提交表单 */
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = formData.value
if (lineFormType.value === 'create') {
await WmProductionIssueLineApi.createLine(data)
message.success('新增成功')
} else {
await WmProductionIssueLineApi.updateLine(data)
message.success('修改成功')
}
dialogVisible.value = false
await getList()
} finally {
formLoading.value = false
}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await WmProductionIssueLineApi.deleteLine(id)
message.success('删除成功')
await getList()
} catch {}
}
/** 物料变化时,自动填充信息 */
const handleItemChange = (item: any) => {
if (item) {
formData.value.itemCode = item.code
formData.value.itemName = item.name
formData.value.specification = item.specification
formData.value.unitMeasureName = item.unitName
}
}
/** 打开明细表单弹窗 */
const openDetailForm = (type: string, lineId: number, itemId: number, detailId?: number) => {
// ProductionIssueDetailList
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
issueId: props.issueId,
itemId: 0,
quantity: 0,
batchCode: undefined,
remark: undefined
}
formRef.value?.resetFields()
}
onMounted(() => {
getList()
})
</script>

View File

@ -1,274 +0,0 @@
<!-- 生产领料单明细组件 - 用于 APPROVING/APPROVED 状态下显示拣货明细 -->
<!-- TODO @AI参考 /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/wm/arrivalnotice -->
<template>
<div>
<el-table :data="lines" border>
<el-table-column type="expand">
<template #default="{ row }">
<div class="p-10px">
<div class="mb-10px" v-if="canPick">
<el-button type="primary" size="small" @click="handlePick(row)">
<Icon icon="ep:plus" class="mr-5px" /> 拣货
</el-button>
</div>
<el-table :data="row.details" border size="small">
<el-table-column label="批次号" prop="batchCode" width="150" />
<el-table-column label="仓库" prop="warehouseName" width="150" />
<el-table-column label="库区" prop="locationName" width="150" />
<el-table-column label="库位" prop="areaName" width="150" />
<el-table-column label="拣货数量" prop="quantity" width="120" />
<el-table-column label="操作" width="100" v-if="canPick">
<template #default="scope">
<el-button
link
type="danger"
size="small"
@click="handleDeleteDetail(row, scope.$index)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column label="物料编号" prop="itemCode" width="150" />
<el-table-column label="物料名称" prop="itemName" width="200" />
<el-table-column label="规格型号" prop="specification" width="150" />
<el-table-column label="单位" prop="unitOfMeasure" width="100" />
<el-table-column label="需求数量" prop="quantityIssued" width="120" />
<el-table-column label="已拣数量" prop="pickedQuantity" width="120">
<template #default="{ row }">
<span :class="{ 'text-red-500': row.pickedQuantity < row.quantityIssued }">
{{ row.pickedQuantity || 0 }}
</span>
</template>
</el-table-column>
</el-table>
<!-- 拣货对话框 -->
<el-dialog v-model="dialogVisible" title="拣货" width="600px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="物料">
<el-input :value="currentLine?.itemName" disabled />
</el-form-item>
<el-form-item label="需求数量">
<el-input :value="currentLine?.quantityIssued" disabled />
</el-form-item>
<el-form-item label="已拣数量">
<el-input :value="currentLine?.pickedQuantity || 0" disabled />
</el-form-item>
<el-form-item label="库存位置" prop="materialStockId">
<el-input
v-model="formData.warehouseName"
placeholder="请选择库存位置"
readonly
@click="handleSelectStock"
class="cursor-pointer"
>
<template #append>
<el-button @click="handleSelectStock">
<Icon icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="批次号">
<el-input v-model="formData.batchCode" disabled />
</el-form-item>
<el-form-item label="库区">
<el-input v-model="formData.locationName" disabled />
</el-form-item>
<el-form-item label="库位">
<el-input v-model="formData.areaName" disabled />
</el-form-item>
<el-form-item label="可用数量">
<el-input v-model="formData.availableQuantity" disabled />
</el-form-item>
<el-form-item label="拣货数量" prop="quantity">
<el-input-number
v-model="formData.quantity"
:min="0"
:max="formData.availableQuantity"
:precision="2"
class="!w-full"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
<!-- 库存选择器 -->
<StockSelect ref="stockSelectRef" @select="handleStockSelected" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import StockSelect from '@/components/MES/StockSelect.vue'
defineOptions({ name: 'ProductionIssueDetail' })
interface DetailItem {
id?: number
lineId?: number
materialStockId: number
batchId?: number
batchCode?: string
warehouseId: number
warehouseName?: string
locationId?: number
locationName?: string
areaId?: number
areaName?: string
quantity: number
}
interface LineItem {
id?: number
itemId: number
itemCode?: string
itemName?: string
specification?: string
unitOfMeasure?: string
quantityIssued: number
pickedQuantity?: number
details?: DetailItem[]
}
const props = defineProps({
modelValue: {
type: Array as () => LineItem[],
default: () => []
},
status: {
type: Number,
default: 0
}
})
const emit = defineEmits(['update:modelValue'])
const lines = ref<LineItem[]>(props.modelValue || [])
const dialogVisible = ref(false)
const currentLine = ref<LineItem | null>(null)
const formRef = ref()
const stockSelectRef = ref()
const canPick = computed(() => props.status === 2) // APPROVING status
const formData = reactive<DetailItem & { availableQuantity?: number }>({
materialStockId: 0,
batchCode: '',
warehouseId: 0,
warehouseName: '',
locationId: 0,
locationName: '',
areaId: 0,
areaName: '',
quantity: 0,
availableQuantity: 0
})
const formRules = {
materialStockId: [{ required: true, message: '请选择库存位置', trigger: 'change' }],
quantity: [{ required: true, message: '请输入拣货数量', trigger: 'blur' }]
}
const handlePick = (row: LineItem) => {
currentLine.value = row
resetForm()
dialogVisible.value = true
}
const handleSelectStock = () => {
if (!currentLine.value) return
stockSelectRef.value.open(currentLine.value.itemId)
}
const handleStockSelected = (stock: any) => {
formData.materialStockId = stock.materialStockId
formData.batchId = stock.batchId
formData.batchCode = stock.batchCode
formData.warehouseId = stock.warehouseId
formData.warehouseName = stock.warehouseName
formData.locationId = stock.locationId
formData.locationName = stock.locationName
formData.areaId = stock.areaId
formData.areaName = stock.areaName
formData.availableQuantity = stock.availableQuantity
}
const handleConfirm = async () => {
await formRef.value.validate()
if (!currentLine.value) return
if (!currentLine.value.details) {
currentLine.value.details = []
}
const detailData: DetailItem = {
lineId: currentLine.value.id,
materialStockId: formData.materialStockId,
batchId: formData.batchId,
batchCode: formData.batchCode,
warehouseId: formData.warehouseId,
warehouseName: formData.warehouseName,
locationId: formData.locationId,
locationName: formData.locationName,
areaId: formData.areaId,
areaName: formData.areaName,
quantity: formData.quantity
}
currentLine.value.details.push(detailData)
//
currentLine.value.pickedQuantity = currentLine.value.details.reduce(
(sum, detail) => sum + detail.quantity,
0
)
emit('update:modelValue', lines.value)
dialogVisible.value = false
}
const handleDeleteDetail = (row: LineItem, index: number) => {
if (!row.details) return
row.details.splice(index, 1)
//
row.pickedQuantity = row.details.reduce((sum, detail) => sum + detail.quantity, 0)
emit('update:modelValue', lines.value)
}
const resetForm = () => {
Object.assign(formData, {
materialStockId: 0,
batchCode: '',
warehouseId: 0,
warehouseName: '',
locationId: 0,
locationName: '',
areaId: 0,
areaName: '',
quantity: 0,
availableQuantity: 0
})
formRef.value?.clearValidate()
}
</script>
<style scoped>
.cursor-pointer {
cursor: pointer;
}
</style>

View File

@ -1,202 +0,0 @@
<!-- 生产领料单行项目组件 - 用于 PREPARE 状态下编辑行项目 -->
<!-- TODO @AI参考 /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/wm/arrivalnotice -->
<template>
<div>
<el-button type="primary" @click="handleAdd" class="mb-10px" v-if="!readonly">
<Icon icon="ep:plus" class="mr-5px" /> 添加物料
</el-button>
<el-table :data="lines" border>
<el-table-column label="物料编号" prop="itemCode" width="150" />
<el-table-column label="物料名称" prop="itemName" width="200" />
<el-table-column label="规格型号" prop="specification" width="150" />
<el-table-column label="单位" prop="unitOfMeasure" width="100" />
<el-table-column label="领料数量" prop="quantityIssued" width="120" />
<el-table-column label="批次号" prop="batchCode" width="150" />
<el-table-column label="备注" prop="remark" min-width="150" />
<el-table-column label="操作" width="150" fixed="right" v-if="!readonly">
<template #default="scope">
<el-button link type="primary" @click="handleEdit(scope.row, scope.$index)">
编辑
</el-button>
<el-button link type="danger" @click="handleDelete(scope.$index)"> </el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加/编辑对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<el-form-item label="物料" prop="itemId">
<el-input
v-model="formData.itemCode"
placeholder="请选择物料"
readonly
@click="handleSelectItem"
class="cursor-pointer"
>
<template #append>
<el-button @click="handleSelectItem">
<Icon icon="ep:search" />
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item label="物料名称">
<el-input v-model="formData.itemName" disabled />
</el-form-item>
<el-form-item label="规格型号">
<el-input v-model="formData.specification" disabled />
</el-form-item>
<el-form-item label="单位">
<el-input v-model="formData.unitOfMeasure" disabled />
</el-form-item>
<el-form-item label="领料数量" prop="quantityIssued">
<el-input-number
v-model="formData.quantityIssued"
:min="0"
:precision="2"
class="!w-full"
/>
</el-form-item>
<el-form-item label="批次号">
<el-input v-model="formData.batchCode" placeholder="请输入批次号" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</template>
</el-dialog>
<!-- 物料选择器 -->
<ItemSelect ref="itemSelectRef" @select="handleItemSelected" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import ItemSelect from '@/components/MES/ItemSelect.vue'
defineOptions({ name: 'ProductionIssueLine' })
interface LineItem {
id?: number
issueId?: number
itemId: number
itemCode?: string
itemName?: string
specification?: string
unitOfMeasure?: string
quantityIssued: number
batchId?: number
batchCode?: string
remark?: string
}
const props = defineProps({
modelValue: {
type: Array as () => LineItem[],
default: () => []
},
readonly: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue'])
const lines = ref<LineItem[]>(props.modelValue || [])
const dialogVisible = ref(false)
const dialogTitle = ref('')
const editIndex = ref(-1)
const formRef = ref()
const itemSelectRef = ref()
const formData = reactive<LineItem>({
itemId: 0,
itemCode: '',
itemName: '',
specification: '',
unitOfMeasure: '',
quantityIssued: 0,
batchCode: '',
remark: ''
})
const formRules = {
itemId: [{ required: true, message: '请选择物料', trigger: 'change' }],
quantityIssued: [{ required: true, message: '请输入领料数量', trigger: 'blur' }]
}
const handleAdd = () => {
dialogTitle.value = '添加物料'
editIndex.value = -1
resetForm()
dialogVisible.value = true
}
const handleEdit = (row: LineItem, index: number) => {
dialogTitle.value = '编辑物料'
editIndex.value = index
Object.assign(formData, row)
dialogVisible.value = true
}
const handleDelete = (index: number) => {
lines.value.splice(index, 1)
emit('update:modelValue', lines.value)
}
const handleSelectItem = () => {
itemSelectRef.value.open()
}
const handleItemSelected = (item: any) => {
formData.itemId = item.itemId
formData.itemCode = item.itemCode
formData.itemName = item.itemName
formData.specification = item.specification
formData.unitOfMeasure = item.unitName
}
const handleConfirm = async () => {
await formRef.value.validate()
const lineData = { ...formData }
if (editIndex.value >= 0) {
lines.value[editIndex.value] = lineData
} else {
lines.value.push(lineData)
}
emit('update:modelValue', lines.value)
dialogVisible.value = false
}
const resetForm = () => {
Object.assign(formData, {
itemId: 0,
itemCode: '',
itemName: '',
specification: '',
unitOfMeasure: '',
quantityIssued: 0,
batchCode: '',
remark: ''
})
formRef.value?.clearValidate()
}
</script>
<style scoped>
.cursor-pointer {
cursor: pointer;
}
</style>

View File

@ -43,8 +43,12 @@
clearable
class="!w-240px"
>
<el-option label="草稿" :value="0" />
<el-option label="已完成" :value="4" />
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.MES_WM_PRODUCTION_ISSUE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
@ -94,41 +98,59 @@
/>
<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" />
<dict-tag :type="DICT_TYPE.MES_WM_PRODUCTION_ISSUE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="240" fixed="right">
<template #default="scope">
<!-- 草稿(0)编辑完成删除 -->
<!-- 草稿编辑提交删除 -->
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update']"
v-if="scope.row.status === 0"
v-if="scope.row.status === MesWmProductionIssueStatusEnum.PREPARE"
>
编辑
</el-button>
<el-button
link
type="success"
@click="handleFinish(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update-status']"
v-if="scope.row.status === 0"
type="warning"
@click="handleSubmit(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update']"
v-if="scope.row.status === MesWmProductionIssueStatusEnum.PREPARE"
>
完成
提交
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:delete']"
v-if="scope.row.status === 0"
v-if="scope.row.status === MesWmProductionIssueStatusEnum.PREPARE"
>
删除
</el-button>
<!-- 所有状态详情 -->
<el-button link type="info" @click="openForm('detail', scope.row.id)"> 详情 </el-button>
<!-- 待拣货执行拣货 -->
<el-button
link
type="success"
@click="openForm('stock', scope.row.id)"
v-hasPermi="['mes:wm-production-issue:update']"
v-if="scope.row.status === MesWmProductionIssueStatusEnum.APPROVING"
>
执行拣货
</el-button>
<!-- 待执行领出完成 -->
<el-button
link
type="success"
@click="handleFinish(scope.row.id)"
v-hasPermi="['mes:wm-production-issue:execute']"
v-if="scope.row.status === MesWmProductionIssueStatusEnum.APPROVED"
>
完成
</el-button>
</template>
</el-table-column>
</el-table>
@ -140,25 +162,26 @@
/>
</ContentWrap>
<IssueForm ref="formRef" @success="getList" />
<ProductionIssueForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter2 } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import download from '@/utils/download'
import { WmProductionIssueApi, WmProductionIssueVO } from '@/api/mes/wm/production-issue'
import IssueForm from './IssueForm.vue'
import { WmProductionIssueApi, WmProductionIssueVO } from '@/api/mes/wm/productionissue'
import ProductionIssueForm from './ProductionIssueForm.vue'
import { MesWmProductionIssueStatusEnum } from '@/views/mes/utils/constants'
defineOptions({ name: 'MesWmIssue' })
const message = useMessage()
const { t } = useI18n()
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true)
const list = ref<WmProductionIssueVO[]>([])
const total = ref(0)
const exportLoading = ref(false)
const loading = ref(true) //
const list = ref<WmProductionIssueVO[]>([]) //
const total = ref(0) //
const exportLoading = ref(false) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -167,14 +190,14 @@ const queryParams = reactive({
status: undefined,
issueDate: undefined
})
const queryFormRef = ref()
const formRef = ref()
const queryFormRef = ref() //
const formRef = ref() //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await WmProductionIssueApi.getIssuePage(queryParams)
const data = await WmProductionIssueApi.getProductionIssuePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
@ -199,11 +222,21 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 提交按钮操作 */
const handleSubmit = async (id: number) => {
try {
await message.confirm('确认提交该领料单进入审批流程吗?')
await WmProductionIssueApi.submitProductionIssue(id)
message.success('提交成功')
await getList()
} catch {}
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await WmProductionIssueApi.deleteIssue(id)
await WmProductionIssueApi.deleteProductionIssue(id)
message.success(t('common.delSuccess'))
await getList()
} catch {}
@ -213,7 +246,7 @@ const handleDelete = async (id: number) => {
const handleFinish = async (id: number) => {
try {
await message.confirm('确认完成该领料单并执行出库吗?')
await WmProductionIssueApi.finishIssue(id)
await WmProductionIssueApi.finishProductionIssue(id)
message.success('完成成功')
await getList()
} catch {}
@ -224,7 +257,7 @@ const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
const data = await WmProductionIssueApi.exportIssue(queryParams)
const data = await WmProductionIssueApi.exportProductionIssue(queryParams)
download.excel(data, '领料出库单.xls')
} catch {
} finally {