admin-vue3/src/views/mes/pro/feedback/FeedbackForm.vue

609 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!-- MES 生产报工表单 -->
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="960px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
:disabled="isDetail"
>
<!-- 基本信息 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="报工单号" prop="code">
<el-input
v-model="formData.code"
placeholder="请输入报工单号"
:disabled="isHeaderReadonly"
>
<template #append>
<el-button @click="generateCode"> 生成 </el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报工类型" prop="type">
<el-select
v-model="formData.type"
placeholder="请选择报工类型"
:disabled="isHeaderReadonly"
class="!w-1/1"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.MES_PRO_FEEDBACK_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 工单 / 任务 / 工作站 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="生产工单" prop="workOrderId">
<ProWorkOrderSelect
v-model="formData.workOrderId"
:status="MesProWorkOrderStatusEnum.CONFIRMED"
:disabled="isHeaderReadonly"
placeholder="请选择工单"
@change="handleWorkOrderChange"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="生产任务" prop="taskId">
<ProTaskSelect
v-model="formData.taskId"
:workOrderId="formData.workOrderId"
:statuses="[MesProTaskStatusEnum.NORMAL, MesProTaskStatusEnum.START, MesProTaskStatusEnum.PAUSE]"
:disabled="isHeaderReadonly || !formData.workOrderId"
placeholder="请选择任务"
@change="handleTaskChange"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="工作站" prop="workstationId">
<MdWorkstationSelect
v-model="formData.workstationId"
:disabled="isHeaderReadonly"
placeholder="请选择工作站"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 产品信息(只读展示) -->
<el-row :gutter="20" v-if="productInfo.itemCode">
<el-col :span="8">
<el-form-item label="产品编码">
<el-input :model-value="productInfo.itemCode" disabled />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="产品名称">
<el-input :model-value="productInfo.itemName" disabled />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="单位" label-width="60px">
<el-input :model-value="productInfo.unitMeasureName" disabled />
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="规格" label-width="60px">
<el-input :model-value="productInfo.itemSpecification" disabled />
</el-form-item>
</el-col>
</el-row>
<!-- 数量区域 -->
<el-divider content-position="left">报工数量</el-divider>
<!-- 非质检工序:报工数量(只读自动计算) + 合格品 + 不良品 -->
<el-row :gutter="20" v-if="!checkFlag">
<el-col :span="8">
<el-form-item label="报工数量" prop="feedbackQuantity">
<el-input-number
v-model="formData.feedbackQuantity"
:min="0"
:precision="2"
disabled
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="合格品数量" prop="qualifiedQuantity">
<el-input-number
v-model="formData.qualifiedQuantity"
:min="0"
:precision="2"
class="!w-1/1"
@change="handleQuantityChanged"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="不良品数量" prop="unqualifiedQuantity">
<el-input-number
v-model="formData.unqualifiedQuantity"
:min="0"
:precision="2"
class="!w-1/1"
@change="handleQuantityChanged"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 质检工序:只填报工数量 -->
<el-row :gutter="20" v-else>
<el-col :span="8">
<el-form-item label="报工数量" prop="feedbackQuantity">
<el-input-number
v-model="formData.feedbackQuantity"
:min="0"
:precision="2"
class="!w-1/1"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 废品分类(非质检工序 且 不良品>0 时展开) -->
<el-row :gutter="20" v-if="!checkFlag && formData.unqualifiedQuantity > 0">
<el-col :span="8">
<el-form-item label="工废数量">
<el-input-number
v-model="formData.laborScrapQuantity"
:min="0"
:precision="2"
class="!w-1/1"
@change="handleScrapChanged"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="料废数量">
<el-input-number
v-model="formData.materialScrapQuantity"
:min="0"
:precision="2"
class="!w-1/1"
@change="handleScrapChanged"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="其他废品">
<el-input-number
v-model="formData.otherScrapQuantity"
:min="0"
:precision="2"
class="!w-1/1"
@change="handleScrapChanged"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 报工人 / 报工时间 / 审核人 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="报工人" prop="feedbackUserId">
<UserSelectV2
v-model="formData.feedbackUserId"
:disabled="isHeaderReadonly"
placeholder="请选择报工人"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报工时间" prop="feedbackTime">
<el-date-picker
v-model="formData.feedbackTime"
type="datetime"
value-format="x"
placeholder="请选择报工时间"
:disabled="isHeaderReadonly"
class="!w-1/1"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="审核人" prop="approveUserId">
<UserSelectV2
v-model="formData.approveUserId"
:disabled="isHeaderReadonly"
placeholder="请选择审核人"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 备注 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- BOM 物资消耗 / 产品产出 Tab仅非草稿、非审批中时显示 -->
<el-tabs
v-if="
formData.id &&
formData.status !== MesProFeedbackStatusEnum.PREPARE &&
formData.status !== MesProFeedbackStatusEnum.APPROVING
"
type="border-card"
class="mt-10px"
>
<el-tab-pane label="BOM 物资消耗">
<ItemConsumeList :feedbackId="formData.id" />
</el-tab-pane>
<el-tab-pane label="产品产出">
<ProductProduceList :feedbackId="formData.id" />
</el-tab-pane>
</el-tabs>
<template #footer>
<el-button v-if="isEditable" @click="submitForm" type="primary" :disabled="formLoading">
</el-button>
<el-button
v-if="isEditable && formData.status === MesProFeedbackStatusEnum.PREPARE"
@click="handleSubmit"
type="warning"
:disabled="formLoading"
>
提 交
</el-button>
<el-button
v-if="formType === 'approve'"
type="success"
@click="handleApprove"
:disabled="formLoading"
>
通 过
</el-button>
<el-button
v-if="formType === 'approve'"
type="danger"
@click="handleReject"
:disabled="formLoading"
>
不通过
</el-button>
<el-button @click="dialogVisible = false">关 闭</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { ProFeedbackApi, ProFeedbackVO } from '@/api/mes/pro/feedback'
import { AutoCodeRecordApi } from '@/api/mes/md/autocode/record'
import { ProRouteProcessApi } from '@/api/mes/pro/route/process'
import ProWorkOrderSelect from '@/views/mes/pro/workorder/components/ProWorkOrderSelect.vue'
import ProTaskSelect from '@/views/mes/pro/task/components/ProTaskSelect.vue'
import MdWorkstationSelect from '@/views/mes/md/workstation/components/MdWorkstationSelect.vue'
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
import ItemConsumeList from './ItemConsumeList.vue'
import ProductProduceList from './ProductProduceList.vue'
import { useUserStore } from '@/store/modules/user'
import {
MesAutoCodeRuleCode,
MesProFeedbackStatusEnum,
MesProTaskStatusEnum,
MesProWorkOrderStatusEnum
} from '@/views/mes/utils/constants'
defineOptions({ name: 'FeedbackForm' })
const emit = defineEmits(['success'])
const message = useMessage() //
const { t } = useI18n() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formType = ref<string>('create') // 表单的类型create / update / submit / approve / detail
const isEditable = computed(() => ['create', 'update', 'submit'].includes(formType.value))
const isDetail = computed(() => ['detail', 'approve'].includes(formType.value))
const isHeaderReadonly = computed(() => ['submit', 'detail', 'approve'].includes(formType.value))
const dialogTitle = computed(() => {
const titles: Record<string, string> = {
create: '新增生产报工',
update: '编辑生产报工',
submit: '提交生产报工',
approve: '审批生产报工',
detail: '生产报工详情'
}
return titles[formType.value] || formType.value
})
const formData = ref<Record<string, any>>({
id: undefined,
code: undefined,
type: undefined,
workstationId: undefined,
routeId: undefined,
processId: undefined,
workOrderId: undefined,
taskId: undefined,
itemId: undefined,
expireDate: undefined,
feedbackQuantity: 0,
qualifiedQuantity: 0,
unqualifiedQuantity: 0,
uncheckQuantity: 0,
laborScrapQuantity: 0,
materialScrapQuantity: 0,
otherScrapQuantity: 0,
feedbackUserId: undefined,
feedbackTime: undefined,
approveUserId: undefined,
status: undefined,
remark: undefined
})
const formRules = reactive({
code: [{ required: true, message: '报工单号不能为空', trigger: 'blur' }],
type: [{ required: true, message: '报工类型不能为空', trigger: 'change' }],
workOrderId: [{ required: true, message: '生产工单不能为空', trigger: 'change' }],
taskId: [{ required: true, message: '生产任务不能为空', trigger: 'change' }],
workstationId: [{ required: true, message: '工作站不能为空', trigger: 'change' }],
feedbackQuantity: [{ required: true, message: '报工数量不能为空', trigger: 'blur' }],
feedbackUserId: [{ required: true, message: '报工人不能为空', trigger: 'change' }],
feedbackTime: [{ required: true, message: '报工时间不能为空', trigger: 'change' }],
approveUserId: [{ required: true, message: '审核人不能为空', trigger: 'change' }]
})
const formRef = ref() // 表单 Ref
const originalFormData = ref<string>('') // 原始表单数据快照,用于脏检查
const checkFlag = ref(true) // 是否需要检验(默认 true未选任务时只展示报工数量
// 产品信息展示(只读)
const productInfo = ref({
itemCode: '',
itemName: '',
unitMeasureName: '',
itemSpecification: ''
})
/** 生成报工单编号 */
const generateCode = async () => {
formData.value.code = await AutoCodeRecordApi.generateAutoCode(
MesAutoCodeRuleCode.PRO_FEEDBACK_CODE
)
}
/** 加载工序的 checkFlag */
const loadCheckFlag = async (routeId?: number, processId?: number) => {
if (!routeId || !processId) {
checkFlag.value = true
return
}
try {
const routeProcess = await ProRouteProcessApi.getRouteProcessByRouteAndProcess(
routeId,
processId
)
checkFlag.value = routeProcess?.checkFlag ?? false
} catch {
checkFlag.value = true
}
}
/** 工单变更:清空任务相关字段 */
const handleWorkOrderChange = () => {
formData.value.taskId = undefined
formData.value.routeId = undefined
formData.value.processId = undefined
formData.value.workstationId = undefined
formData.value.itemId = undefined
checkFlag.value = true
productInfo.value = { itemCode: '', itemName: '', unitMeasureName: '', itemSpecification: '' }
}
/** 任务变更:自动填充关联字段 + 产品信息 */
const handleTaskChange = async (task: any) => {
if (!task) {
return
}
formData.value.routeId = task.routeId
formData.value.processId = task.processId
formData.value.workstationId = task.workstationId
formData.value.itemId = task.itemId
// 填充产品信息展示
productInfo.value = {
itemCode: task.itemCode || '',
itemName: task.itemName || '',
unitMeasureName: '',
itemSpecification: task.itemSpec || ''
}
await loadCheckFlag(task.routeId, task.processId)
}
/** 合格品/不良品变更:自动计算报工数量 = 合格 + 不良 */
const handleQuantityChanged = () => {
formData.value.feedbackQuantity =
(formData.value.qualifiedQuantity || 0) + (formData.value.unqualifiedQuantity || 0)
}
/** 废品明细变更:自动计算不良品数量 = 工废 + 料废 + 其他 */
const handleScrapChanged = () => {
formData.value.unqualifiedQuantity =
(formData.value.laborScrapQuantity || 0) +
(formData.value.materialScrapQuantity || 0) +
(formData.value.otherScrapQuantity || 0)
handleQuantityChanged()
}
/** 对齐数量:提交前根据 checkFlag 设置待检/合格/不良数量 */
const alignQuantity = (data: ProFeedbackVO) => {
if (checkFlag.value) {
// 质检工序:报工数量即待检数量,合格/不良/废品归零
data.uncheckQuantity = data.feedbackQuantity
data.qualifiedQuantity = 0
data.unqualifiedQuantity = 0
data.laborScrapQuantity = 0
data.materialScrapQuantity = 0
data.otherScrapQuantity = 0
} else {
// 非质检工序:报工数量 = 合格 + 不良,待检归零
data.feedbackQuantity = (data.qualifiedQuantity || 0) + (data.unqualifiedQuantity || 0)
data.uncheckQuantity = 0
}
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
formType.value = type
resetForm()
// 修改/提交/审批/详情时,加载数据
if (id) {
formLoading.value = true
try {
const data = await ProFeedbackApi.getFeedback(id)
formData.value = data as any
await loadCheckFlag(data.routeId, data.processId)
// 填充产品信息展示
productInfo.value = {
itemCode: data.itemCode || '',
itemName: data.itemName || '',
unitMeasureName: data.unitMeasureName || '',
itemSpecification: data.itemSpecification || ''
}
} finally {
formLoading.value = false
}
} else {
// 创建模式:默认报工人为当前用户
formData.value.feedbackUserId = useUserStore().getUser.id
// 自动生成报工单号
await generateCode()
}
// 保存原始数据快照
originalFormData.value = JSON.stringify(formData.value)
}
/** 提交表单(保存 create/update */
const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = formData.value as unknown as ProFeedbackVO
alignQuantity(data)
if (formType.value === 'create') {
const res = await ProFeedbackApi.createFeedback(data)
message.success(t('common.createSuccess'))
// 创建成功后,切换为编辑模式
formData.value.id = res
formData.value.status = MesProFeedbackStatusEnum.PREPARE
formType.value = 'update'
} else {
await ProFeedbackApi.updateFeedback(data)
message.success(t('common.updateSuccess'))
}
// 更新快照
originalFormData.value = JSON.stringify(formData.value)
emit('success')
} finally {
formLoading.value = false
}
}
/** 提交操作:表单修改过则先保存,再提交 */
const handleSubmit = async () => {
await formRef.value.validate()
try {
await message.confirm('确认提交该报工单?【提交后将不能修改】')
formLoading.value = true
const data = formData.value as unknown as ProFeedbackVO
alignQuantity(data)
// 1. 表单有修改时,先保存
if (JSON.stringify(formData.value) !== originalFormData.value) {
if (formType.value === 'create') {
const res = await ProFeedbackApi.createFeedback(data)
formData.value.id = res
} else {
await ProFeedbackApi.updateFeedback(data)
}
}
// 2. 提交报工单
await ProFeedbackApi.submitFeedback(formData.value.id!)
message.success('报工单已提交')
dialogVisible.value = false
emit('success')
} catch {
} finally {
formLoading.value = false
}
}
/** 审批通过 */
const handleApprove = async () => {
formLoading.value = true
try {
const finished = await ProFeedbackApi.approveFeedback(formData.value.id!)
if (finished) {
message.success('报工单已审批完成')
} else {
message.success('报工成功,请等待质量检验完成!')
}
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
/** 审批不通过(驳回) */
const handleReject = async () => {
formLoading.value = true
try {
await ProFeedbackApi.rejectFeedback(formData.value.id!)
message.success('报工单已驳回')
dialogVisible.value = false
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
code: undefined,
type: undefined,
workstationId: undefined,
routeId: undefined,
processId: undefined,
workOrderId: undefined,
taskId: undefined,
itemId: undefined,
expireDate: undefined,
feedbackQuantity: 0,
qualifiedQuantity: 0,
unqualifiedQuantity: 0,
uncheckQuantity: 0,
laborScrapQuantity: 0,
materialScrapQuantity: 0,
otherScrapQuantity: 0,
feedbackUserId: undefined,
feedbackTime: undefined,
approveUserId: undefined,
status: undefined,
remark: undefined
}
checkFlag.value = true
productInfo.value = { itemCode: '', itemName: '', unitMeasureName: '', itemSpecification: '' }
formRef.value?.resetFields()
}
defineExpose({ open })
</script>