feat:物料档案/生产计划/订单排产-添加工艺路线逻辑

main
黄伟杰 8 hours ago
parent 6c30ba975a
commit f05c908a7d

@ -9,8 +9,13 @@ export interface ProductCategoryVO {
sort: number // 分类排序
status: number // 开启状态
type: number // 类型
processRouteId?: string | number // 工艺路线 ID
processRouteName?: string // 工艺路线名称
}
const getProductCategoryRequestParams = (data: ProductCategoryVO) => ({
processRouteId: data.processRouteId || undefined
})
// ERP 产品分类 API
export const ProductCategoryApi = {
// 查询产品分类分组树
@ -35,12 +40,12 @@ export const ProductCategoryApi = {
// 新增产品分类
createProductCategory: async (data: ProductCategoryVO) => {
return await request.post({ url: `/erp/product-category/create`, data })
return await request.post({ url: `/erp/product-category/create`, data, params: getProductCategoryRequestParams(data) })
},
// 修改产品分类
updateProductCategory: async (data: ProductCategoryVO) => {
return await request.put({ url: `/erp/product-category/update`, data })
return await request.put({ url: `/erp/product-category/update`, data, params: getProductCategoryRequestParams(data) })
},
// 删除产品分类

@ -29,6 +29,26 @@ export interface ProductVO {
moldList?: any[]
}
export interface ErpProductCategoryProcessRouteItemRespVO {
id?: number
categoryId?: number
processRouteId?: number
processRouteItemId?: number
sort?: number
processParameterId?: number
processParameterCode?: string
processParameterName?: string
remark?: string
deviceId?: number | string
deviceName?: string
}
export interface ErpProductProcessRouteRespVO {
productId?: number
processRouteId?: number
processRouteName?: string
processRouteItems?: ErpProductCategoryProcessRouteItemRespVO[]
}
// ERP 产品 API
export const ProductApi = {
// 查询产品分页
@ -57,6 +77,11 @@ export const ProductApi = {
return await request.get({ url: `/erp/product/simple-list-component` })
},
// 查询产品工艺路线快照
getProductProcessRoute: async (productId: number | string) => {
return await request.get<ErpProductProcessRouteRespVO>({ url: '/erp/product/get-process-route', params: { productId } })
},
// 查询产品详情
getProduct: async (id: number) => {
return await request.get({ url: `/erp/product/get?id=` + id })

@ -101,6 +101,7 @@ export interface StockVO {
totalBaseCount?: number
// 库存数量
count: number
lockedStockCount?: number
latestInTime?: number | string
lastInTime?: number | string
recentInTime?: number | string

@ -31,6 +31,55 @@ export interface PlanRecordVO {
createTime: number // 创建时间戳
}
export interface PlanProcessRouteItemVO {
id?: number
categoryId?: number
processRouteId?: number
processRouteItemId?: number
sort?: number
processParameterId?: number
processParameterCode?: string
processParameterName?: string
remark?: string
deviceId?: number | string
deviceName?: string
}
export interface LineChangeRecordVO {
id: number
planId: number
changeBatchNo?: string
planProcessRouteItemId: number
processParameterId?: number
processParameterName?: string
beforeDeviceId?: number
beforeDeviceName?: string
afterDeviceId?: number
afterDeviceName?: string
changeTime?: string
operatorUserId?: number
passNumber?: number
remark?: string
}
export interface LineChangeRecordBatchVO {
changeBatchNo: string
planId: number
changeTime?: string
operatorUserId?: number
passNumber?: number
remark?: string
changeCount?: number
items?: LineChangeRecordVO[]
}
export interface LineChangeRecordPageReqVO {
planId: number | string
pageNo: number
pageSize: number
}
export interface ChangeLineBatchItemVO {
planProcessRouteItemId: number | string
deviceId: number | string
remark?: string
}
// 生产计划 VO
export interface PlanVO {
id: number // ID
@ -59,6 +108,9 @@ export interface PlanVO {
passNumber?: number // 合格数量
noPassNumber?: number // 不合格数量
deviceName?: string // 设备名称
processRouteId?: number | string // 工艺路线 ID
processRouteName?: string // 工艺路线名称
processRouteItems?: PlanProcessRouteItemVO[] // 工艺路线明细
planRecordList?: PlanRecordVO[] // 计划记录列表
}
@ -179,6 +231,15 @@ export const PlanApi = {
return await request.get({ url: `/mes/plan/page-by-task`, params })
},
changeLine: async (params: { planId: number | string; planProcessRouteItemId: number | string; deviceId: number | string }) => {
return await request.put({ url: `/mes/plan/change-line`, params })
},
changeLineBatch: async (params: { planId: number | string; items: ChangeLineBatchItemVO[] }) => {
return await request.put({ url: `/mes/plan/change-line-batch`, data: params })
},
getLineChangeRecordPage: async (params: LineChangeRecordPageReqVO) => {
return await request.get<{ list: LineChangeRecordBatchVO[]; total: number }>({ url: `/mes/plan/line-change-record/page`, params })
},
getProductCapacityPage: async (params: any) => {
return await request.get({ url: `/mes/plan/product-capacity-page`, params })
}

@ -775,6 +775,7 @@ export default {
materialLabel: 'Material',
sparePartLabel: 'Spare Part',
stock: 'Stock',
lockedStock: 'Locked Stock',
barcode: 'Barcode',
unit: 'Unit',
inventoryUnit: 'Inventory Unit',
@ -2567,6 +2568,7 @@ export default {
tableSortColumn: 'Sort',
tableStatusColumn: 'Status',
tableCreateTimeColumn: 'Create Time',
tableRemarkColumn: 'Remark',
tableOperateColumn: 'Operation',
tableEditAction: 'Edit',
tableDeleteAction: 'Delete',
@ -2576,6 +2578,8 @@ export default {
dialogCodePlaceholder: 'Please enter code',
dialogNameLabel: 'Name',
dialogNamePlaceholder: 'Please enter name',
dialogProcessRouteLabel: 'Process Route',
dialogProcessRoutePlaceholder: 'Please select process route',
dialogTypeLabel: 'Type',
dialogSortLabel: 'Sort',
dialogSortPlaceholder: 'Please enter sort',
@ -2588,7 +2592,14 @@ export default {
validatorCodeRequired: 'Code can not be empty',
validatorSortRequired: 'Sort can not be empty',
validatorStatusRequired: 'Status can not be empty',
validatorTypeRequired: 'Type can not be empty'
validatorTypeRequired: 'Type can not be empty',
processRouteDialogTitle: 'Select Process Route',
processRouteCodeLabel: 'Process Route Code',
processRouteCodePlaceholder: 'Please enter process route code',
processRouteNameLabel: 'Process Route Name',
processRouteNamePlaceholder: 'Please enter process route name',
processRouteSelectButton: 'Select',
processRouteSelectRequired: 'Please select process route'
},
ProductInformation: {
moduleName: 'Product Material Information',
@ -4779,11 +4790,30 @@ export default {
dialogTaskPlaceholder: 'Please select task order',
dialogTaskDetailLabel: 'Detail Item',
dialogTaskDetailPlaceholder: 'Please select detail item',
dialogTaskDetailUnplannedPrefix: 'Unplanned ',
dialogCodeLabel: 'Plan Code',
dialogCodeTooltip: 'Production plan code',
dialogCodePlaceholder: 'Code will be generated automatically after saving',
dialogFeedingPipelineLabel: 'Production Line',
dialogFeedingPipelinePlaceholder: 'Please select production line',
dialogDeliveryDateLabel: 'Delivery Date',
dialogLatestStartLabel: 'Latest Start Time',
dialogLatestStartPlaceholder: 'Please select latest start time',
dialogProcessRouteLabel: 'Process Route',
dialogProcessRouteItemsLabel: 'Process Route Items',
dialogProcessRouteEmptyText: 'No process route',
routeItemSortColumn: 'Sort',
routeItemParameterCodeColumn: 'Process Parameter Code',
routeItemParameterNameColumn: 'Process Parameter Name',
routeItemRemarkColumn: 'Remark',
routeItemDeviceColumn: 'Device',
routeItemDevicePlaceholder: 'Please select device',
routeItemDeviceSelectButton: 'Select',
selectDeviceDialogTitle: 'Select Device',
deviceColumnCode: 'Device Code',
deviceColumnName: 'Device Name',
deviceColumnModel: 'Device Model',
deviceColumnWorkshop: 'Workshop',
dialogWorkerLabel: 'Feeding Worker',
dialogWorkerPlaceholder: 'Please select worker',
dialogPlanNumberLabel: 'Quantity',
@ -4802,6 +4832,12 @@ export default {
validatorPlanNumberRequired: 'Quantity cannot be empty',
validatorPlanStartRequired: 'Plan start time cannot be empty',
validatorPlanEndRequired: 'Plan end time cannot be empty',
validatorLatestStartRequired: 'Please select latest start time',
validatorLatestStartAfterDelivery: 'Latest start time cannot be later than delivery date',
validatorPlanStartBeforeEnd: 'Plan start time cannot be later than plan end time',
validatorPlanEndAfterStart: 'Plan end time cannot be earlier than plan start time',
validatorPlanNumberNumeric: 'Plan quantity must be a number',
validatorPlanNumberLimit: 'Plan quantity cannot be greater than unplanned quantity {limit}',
validatorFeedingPipelineRequired: 'Production line cannot be empty',
validatorWorkerRequired: 'Feeding worker cannot be empty',
validatorPreProductionRequired: 'Trial production cannot be empty',

@ -775,6 +775,7 @@ export default {
materialLabel: '物料',
sparePartLabel: '备件',
stock: '库存',
lockedStock: '锁定库存',
barcode: '编码',
unit: '单位',
inventoryUnit: '库存单位',
@ -3797,6 +3798,7 @@ export default {
tableSortColumn: '排序',
tableStatusColumn: '状态',
tableCreateTimeColumn: '创建时间',
tableRemarkColumn: '备注',
tableOperateColumn: '操作',
tableEditAction: '编辑',
tableDeleteAction: '删除',
@ -3806,6 +3808,8 @@ export default {
dialogCodePlaceholder: '请输入编码',
dialogNameLabel: '名称',
dialogNamePlaceholder: '请输入名称',
dialogProcessRouteLabel: '工艺路线',
dialogProcessRoutePlaceholder: '请选择工艺路线',
dialogTypeLabel: '类型',
dialogSortLabel: '排序',
dialogSortPlaceholder: '请输入排序',
@ -3818,7 +3822,14 @@ export default {
validatorCodeRequired: '编码不能为空',
validatorSortRequired: '排序不能为空',
validatorStatusRequired: '状态不能为空',
validatorTypeRequired: '类型不能为空'
validatorTypeRequired: '类型不能为空',
processRouteDialogTitle: '选择工艺路线',
processRouteCodeLabel: '工艺路线编码',
processRouteCodePlaceholder: '请输入工艺路线编码',
processRouteNameLabel: '工艺路线名称',
processRouteNamePlaceholder: '请输入工艺路线名称',
processRouteSelectButton: '选择',
processRouteSelectRequired: '请选择工艺路线'
},
ProductInformation: {
moduleName: '产品物料信息',
@ -4627,11 +4638,30 @@ export default {
dialogTaskPlaceholder: '请选择任务单',
dialogTaskDetailLabel: '明细项',
dialogTaskDetailPlaceholder: '请选择明细项',
dialogTaskDetailUnplannedPrefix: '未计划',
dialogCodeLabel: '计划编码',
dialogCodeTooltip: '生产计划编码',
dialogCodePlaceholder: '编码保存后自动生成',
dialogFeedingPipelineLabel: '生产线',
dialogFeedingPipelinePlaceholder: '请选择生产线',
dialogDeliveryDateLabel: '交货日期',
dialogLatestStartLabel: '最晚开工时间',
dialogLatestStartPlaceholder: '请选择最晚开工时间',
dialogProcessRouteLabel: '工艺路线',
dialogProcessRouteItemsLabel: '工艺路线明细',
dialogProcessRouteEmptyText: '暂无工艺路线',
routeItemSortColumn: '排序',
routeItemParameterCodeColumn: '工艺参数编码',
routeItemParameterNameColumn: '工艺参数名称',
routeItemRemarkColumn: '备注',
routeItemDeviceColumn: '设备',
routeItemDevicePlaceholder: '请选择设备',
routeItemDeviceSelectButton: '选择',
selectDeviceDialogTitle: '选择设备',
deviceColumnCode: '设备编号',
deviceColumnName: '设备名称',
deviceColumnModel: '设备型号',
deviceColumnWorkshop: '所属车间',
dialogWorkerLabel: '领料人',
dialogWorkerPlaceholder: '请选择领料人',
dialogPlanNumberLabel: '数量',
@ -4650,6 +4680,12 @@ export default {
validatorPlanNumberRequired: '数量不能为空',
validatorPlanStartRequired: '计划开始时间不能为空',
validatorPlanEndRequired: '计划结束时间不能为空',
validatorLatestStartRequired: '请选择最晚开工时间',
validatorLatestStartAfterDelivery: '最晚开工时间不能晚于交货日期',
validatorPlanStartBeforeEnd: '计划开始时间不能晚于计划结束时间',
validatorPlanEndAfterStart: '计划结束时间不能早于计划开始时间',
validatorPlanNumberNumeric: '计划数量必须为数字',
validatorPlanNumberLimit: '计划数量不能大于未计划数量{limit}',
validatorFeedingPipelineRequired: '生产线不能为空',
validatorWorkerRequired: '下料工人不能为空',
validatorPreProductionRequired: '试生产不能为空',

@ -29,13 +29,38 @@
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductCategory.dialogCodeLabel')" prop="code">
<el-input v-model="formData.code" :placeholder="t('FactoryModeling.ProductCategory.dialogCodePlaceholder')" :disabled = "formType === 'update'"/>
<el-input
v-model="formData.code"
:placeholder="t('FactoryModeling.ProductCategory.dialogCodePlaceholder')"
:disabled="formType === 'update'"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductCategory.dialogNameLabel')" prop="name">
<el-input v-model="formData.name" :placeholder="t('FactoryModeling.ProductCategory.dialogNamePlaceholder')" />
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductCategory.dialogProcessRouteLabel')" prop="processRouteId">
<el-input
:model-value="processRouteDisplayName"
:placeholder="t('FactoryModeling.ProductCategory.dialogProcessRoutePlaceholder')"
readonly
clearable
@click="openProcessRouteDialog"
@clear="clearProcessRoute"
>
<template #append>
<el-button @click.stop="openProcessRouteDialog">
{{ t('FactoryModeling.ProductCategory.processRouteSelectButton') }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductCategory.dialogSortLabel')" prop="sort">
<el-input-number v-model="formData.sort" :placeholder="t('FactoryModeling.ProductCategory.dialogSortPlaceholder')" class="!w-full" :min="0"/>
<el-input-number
v-model="formData.sort"
:placeholder="t('FactoryModeling.ProductCategory.dialogSortPlaceholder')"
class="!w-full"
:min="0"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductCategory.dialogStatusLabel')" prop="status">
<el-radio-group v-model="formData.status">
@ -50,35 +75,157 @@
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('FactoryModeling.ProductCategory.dialogSubmitButton') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('FactoryModeling.ProductCategory.dialogCancelButton') }}</el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading">
{{ t('FactoryModeling.ProductCategory.dialogSubmitButton') }}
</el-button>
<el-button @click="dialogVisible = false">
{{ t('FactoryModeling.ProductCategory.dialogCancelButton') }}
</el-button>
</template>
</Dialog>
<Dialog
:title="t('FactoryModeling.ProductCategory.processRouteDialogTitle')"
v-model="processRouteDialogVisible"
width="900px"
:fullscreen="false"
:scroll="false"
>
<div class="process-route-select-dialog">
<el-form
ref="processRouteQueryFormRef"
:model="processRouteQueryParams"
:inline="true"
label-width="auto"
class="process-route-search-form"
@submit.prevent
>
<el-form-item :label="t('FactoryModeling.ProductCategory.processRouteCodeLabel')" prop="code">
<el-input
v-model="processRouteQueryParams.code"
:placeholder="t('FactoryModeling.ProductCategory.processRouteCodePlaceholder')"
clearable
class="!w-200px"
@keyup.enter="handleProcessRouteQuery"
/>
</el-form-item>
<el-form-item :label="t('FactoryModeling.ProductCategory.processRouteNameLabel')" prop="name">
<el-input
v-model="processRouteQueryParams.name"
:placeholder="t('FactoryModeling.ProductCategory.processRouteNamePlaceholder')"
clearable
class="!w-200px"
@keyup.enter="handleProcessRouteQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleProcessRouteQuery">
<Icon icon="ep:search" class="mr-5px" /> {{ t('FactoryModeling.ProductCategory.searchButtonText') }}
</el-button>
<el-button @click="resetProcessRouteQuery">
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('FactoryModeling.ProductCategory.resetButtonText') }}
</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="processRouteLoading"
:data="processRouteList"
:stripe="true"
:show-overflow-tooltip="true"
highlight-current-row
row-key="id"
max-height="360px"
@row-click="handleProcessRouteRowClick"
>
<el-table-column width="52" align="center" fixed="left">
<template #default="scope">
<el-radio
class="process-route-radio"
:model-value="selectedProcessRouteId"
:label="scope.row.id"
@change="handleProcessRouteRowClick(scope.row)"
/>
</template>
</el-table-column>
<el-table-column
:label="t('FactoryModeling.ProductCategory.processRouteCodeLabel')"
align="center"
prop="code"
min-width="160"
sortable
/>
<el-table-column
:label="t('FactoryModeling.ProductCategory.processRouteNameLabel')"
align="center"
prop="name"
min-width="180"
sortable
/>
<el-table-column
:label="t('FactoryModeling.ProductCategory.tableCreateTimeColumn')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
sortable
/>
<el-table-column
:label="t('FactoryModeling.ProductCategory.tableRemarkColumn')"
align="center"
prop="remark"
min-width="180"
/>
</el-table>
<div class="process-route-pagination">
<Pagination
:total="processRouteTotal"
v-model:page="processRouteQueryParams.pageNo"
v-model:limit="processRouteQueryParams.pageSize"
@pagination="getProcessRouteList"
/>
</div>
</div>
<template #footer>
<el-button type="primary" @click="confirmProcessRoute">
{{ t('FactoryModeling.ProductCategory.dialogSubmitButton') }}
</el-button>
<el-button @click="processRouteDialogVisible = false">
{{ t('FactoryModeling.ProductCategory.dialogCancelButton') }}
</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
import { ProcessRouteApi, ProcessRouteVO } from '@/api/mes/processroute'
import { defaultProps, handleTree } from '@/utils/tree'
import { CommonStatusEnum } from '@/utils/constants'
/** ERP 产品分类 表单 */
defineOptions({ name: 'ProductCategoryForm' })
const { t } = useI18n() //
const message = useMessage() //
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formData = ref<any>({
id: undefined,
parentId: undefined,
name: undefined,
code: undefined,
sort: undefined,
status: CommonStatusEnum.ENABLE,
type: undefined
type: undefined,
processRouteId: undefined,
processRouteName: undefined
})
const formRules = reactive({
parentId: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorParentRequired'), trigger: 'blur' }],
@ -88,8 +235,27 @@ const formRules = reactive({
status: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorStatusRequired'), trigger: 'blur' }],
type: [{ required: true, message: t('FactoryModeling.ProductCategory.validatorTypeRequired'), trigger: 'change' }]
})
const formRef = ref() // Ref
const productCategoryTree = ref() //
const formRef = ref()
const productCategoryTree = ref()
const processRouteDialogVisible = ref(false)
const processRouteLoading = ref(false)
const processRouteList = ref<ProcessRouteVO[]>([])
const processRouteTotal = ref(0)
const processRouteQueryFormRef = ref()
const processRouteQueryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
name: undefined
})
const selectedProcessRouteId = ref<string | number | undefined>()
const selectedProcessRouteRow = ref<ProcessRouteVO | undefined>()
const processRouteDisplayName = computed(() => {
if (!formData.value.processRouteId) return ''
return formData.value.processRouteName || String(formData.value.processRouteId)
})
/** 打开弹窗 */
const open = async (type: string, id?: number, defaultData?: any) => {
@ -97,7 +263,6 @@ const open = async (type: string, id?: number, defaultData?: any) => {
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (defaultData) {
if (defaultData.type !== undefined) formData.value.type = defaultData.type
if (defaultData.parentId !== undefined) formData.value.parentId = defaultData.parentId
@ -108,28 +273,25 @@ const open = async (type: string, id?: number, defaultData?: any) => {
formData.value.type = typeOptions[0].value
}
}
//
if (id) {
formLoading.value = true
try {
formData.value = await ProductCategoryApi.getProductCategory(id)
await syncProcessRouteName()
} finally {
formLoading.value = false
}
}
await getProductCategoryTree()
}
defineExpose({ open }) // open
defineExpose({ open })
/** 提交表单 */
const emit = defineEmits(['success']) // success
const emit = defineEmits(['success'])
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as ProductCategoryVO
const data = { ...formData.value } as ProductCategoryVO
if (formType.value === 'create') {
await ProductCategoryApi.createProductCategory(data)
message.success(t('common.createSuccess'))
@ -138,14 +300,12 @@ const submitForm = async () => {
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
@ -154,12 +314,15 @@ const resetForm = () => {
code: undefined,
sort: undefined,
status: CommonStatusEnum.ENABLE,
type: undefined
type: undefined,
processRouteId: undefined,
processRouteName: undefined
}
selectedProcessRouteId.value = undefined
selectedProcessRouteRow.value = undefined
formRef.value?.resetFields()
}
/** 获得产品分类树 */
const getProductCategoryTree = async () => {
productCategoryTree.value = []
const data = await ProductCategoryApi.getProductCategoryList(formData.value.type)
@ -168,9 +331,107 @@ const getProductCategoryTree = async () => {
productCategoryTree.value.push(root)
}
/** 类型变更时刷新上级编号树 */
const handleTypeChange = async () => {
formData.value.parentId = undefined
await getProductCategoryTree()
}
const syncProcessRouteName = async () => {
if (!formData.value.processRouteId || formData.value.processRouteName) return
try {
const data = await ProcessRouteApi.getProcessRoute(Number(formData.value.processRouteId))
formData.value.processRouteName = data?.name
} catch {}
}
const getProcessRouteList = async () => {
processRouteLoading.value = true
try {
const data = await ProcessRouteApi.getProcessRoutePage(processRouteQueryParams)
processRouteList.value = data.list || []
processRouteTotal.value = data.total || 0
if (selectedProcessRouteId.value) {
selectedProcessRouteRow.value =
processRouteList.value.find((item) => String(item.id) === String(selectedProcessRouteId.value)) ||
selectedProcessRouteRow.value
}
} finally {
processRouteLoading.value = false
}
}
const openProcessRouteDialog = async () => {
selectedProcessRouteId.value = formData.value.processRouteId
selectedProcessRouteRow.value = formData.value.processRouteId
? {
id: formData.value.processRouteId,
name: formData.value.processRouteName
}
: undefined
processRouteQueryParams.pageNo = 1
processRouteDialogVisible.value = true
await getProcessRouteList()
}
const handleProcessRouteRowClick = (row: ProcessRouteVO) => {
selectedProcessRouteId.value = row.id
selectedProcessRouteRow.value = row
}
const confirmProcessRoute = () => {
if (!selectedProcessRouteId.value) {
message.warning(t('FactoryModeling.ProductCategory.processRouteSelectRequired'))
return
}
const row =
selectedProcessRouteRow.value ||
processRouteList.value.find((item) => String(item.id) === String(selectedProcessRouteId.value))
formData.value.processRouteId = selectedProcessRouteId.value
formData.value.processRouteName = row?.name || String(selectedProcessRouteId.value)
processRouteDialogVisible.value = false
}
const clearProcessRoute = () => {
formData.value.processRouteId = undefined
formData.value.processRouteName = undefined
selectedProcessRouteId.value = undefined
selectedProcessRouteRow.value = undefined
}
const handleProcessRouteQuery = () => {
processRouteQueryParams.pageNo = 1
getProcessRouteList()
}
const resetProcessRouteQuery = () => {
processRouteQueryFormRef.value?.resetFields()
handleProcessRouteQuery()
}
</script>
<style scoped>
.process-route-select-dialog {
display: flex;
flex-direction: column;
gap: 12px;
}
.process-route-search-form {
margin-bottom: -12px;
}
.process-route-radio :deep(.el-radio__label) {
display: none;
}
.process-route-pagination {
display: flex;
justify-content: flex-end;
width: 100%;
}
.process-route-pagination :deep(.el-pagination) {
float: none !important;
margin: 12px 0 0 !important;
}
</style>

@ -1,14 +1,13 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1000px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
label-width="120px"
v-loading="formLoading"
>
<div class="section-title">计划</div>
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskLabel')" prop="taskId">
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskLabel')" prop="taskId">
<el-select
disabled
v-model="formData.taskId"
@ -17,15 +16,10 @@
:placeholder="t('ProductionPlan.Plan.dialogTaskPlaceholder')"
@change="handleTaskChange"
>
<el-option
v-for="item in taskList"
:key="item.id"
:label="item.code"
:value="item.id"
/>
<el-option v-for="item in taskList" :key="item.id" :label="item.code" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskDetailLabel')" prop="taskId">
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskDetailLabel')" prop="taskDetailId">
<el-select
disabled
v-model="formData.taskDetailId"
@ -37,72 +31,89 @@
<el-option
v-for="item in taskDetailList"
:key="item.id"
:label="item.productName+'-未计划:'+(item.number-item.planNumber)"
:label="`${item.productName}-${t('ProductionPlan.Plan.dialogTaskDetailUnplannedPrefix')}${Number(item.number || 0) - Number(item.planNumber || 0)}`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item prop="code">
<template #label>
<span>
{{ t('ProductionPlan.Plan.dialogCodeLabel') }}
<el-tooltip :content="t('ProductionPlan.Plan.dialogCodeTooltip')" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-row :gutter="10" class="!w-full">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
:disabled="formData.isCode == true || formType === 'update'"
v-model="formData.code"
:placeholder="t('ProductionPlan.Plan.dialogCodePlaceholder')"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
/>
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="交货日期">
<template #label>
<span>
{{ t('ProductionPlan.Plan.dialogCodeLabel') }}
<el-tooltip :content="t('ProductionPlan.Plan.dialogCodeTooltip')" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-row :gutter="10" class="!w-full">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
:disabled="formData.isCode === true || formType === 'update'"
v-model="formData.code"
:placeholder="t('ProductionPlan.Plan.dialogCodePlaceholder')"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<el-switch v-model="formData.isCode" :disabled="formType === 'update'" />
</el-col>
</el-row>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogDeliveryDateLabel')">
<el-input :model-value="deliveryDateText" disabled />
</el-form-item>
<!-- <el-form-item label="产品" prop="productId">-->
<!-- <el-select-->
<!-- :disabled='editDisable'-->
<!-- v-model="formData.productId"-->
<!-- clearable-->
<!-- filterable-->
<!-- placeholder="请选择产品"-->
<!-- class="!w-1/1"-->
<!-- >-->
<!-- <el-option-->
<!-- v-for="item in productList"-->
<!-- :key="item.id"-->
<!-- :label="item.name"-->
<!-- :value="item.id"-->
<!-- />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item label="设备" prop="deviceId">
<el-form-item :label="t('ProductionPlan.Plan.dialogProcessRouteLabel')" prop="processRouteId">
<el-input
:model-value="deviceDisplayText"
placeholder="点击选择设备"
readonly
@click="openDeviceSelectDialog"
:model-value="processRouteDisplayName"
disabled
:placeholder="t('ProductionPlan.Plan.dialogProcessRouteEmptyText')"
/>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogProcessRouteItemsLabel')">
<el-table
:data="formData.processRouteItems"
border
size="small"
row-key="processRouteItemId"
class="process-route-items-table"
:empty-text="t('ProductionPlan.Plan.dialogProcessRouteEmptyText')"
>
<el-table-column :label="t('ProductionPlan.Plan.routeItemSortColumn')" prop="sort" width="70" align="center" />
<el-table-column
:label="t('ProductionPlan.Plan.routeItemParameterCodeColumn')"
prop="processParameterCode"
min-width="140"
align="center"
/>
<el-table-column
:label="t('ProductionPlan.Plan.routeItemParameterNameColumn')"
prop="processParameterName"
min-width="160"
align="center"
/>
<el-table-column :label="t('ProductionPlan.Plan.routeItemRemarkColumn')" prop="remark" min-width="140" align="center" />
<el-table-column :label="t('ProductionPlan.Plan.routeItemDeviceColumn')" min-width="240" align="center">
<template #default="scope">
<el-input
:model-value="getRouteItemDeviceDisplay(scope.row)"
:placeholder="t('ProductionPlan.Plan.routeItemDevicePlaceholder')"
readonly
clearable
@click="openDeviceSelectDialog(scope.row)"
@clear="clearRouteItemDevice(scope.row)"
>
<template #append>
<el-button @click.stop="openDeviceSelectDialog(scope.row)">
{{ t('ProductionPlan.Plan.routeItemDeviceSelectButton') }}
</el-button>
</template>
</el-input>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanNumberLabel')" prop="planNumber">
<el-input v-model="formData.planNumber" />
<el-input v-model="formData.planNumber" />
</el-form-item>
<!-- <el-form-item label="热压数量" prop="reyaNumber">
<el-input v-model="formData.reyaNumber" />
</el-form-item> -->
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanStartLabel')" prop="planStartTime">
<el-date-picker
v-model="formData.planStartTime"
@ -123,48 +134,36 @@
@change="handlePlanEndTimeChange"
/>
</el-form-item>
<el-form-item label="最晚开工时间" prop="latestStartTime">
<el-form-item :label="t('ProductionPlan.Plan.dialogLatestStartLabel')" prop="latestStartTime">
<el-date-picker
v-model="formData.latestStartTime"
type="datetime"
value-format="x"
:disabled-date="latestStartDisabledDate"
placeholder="请选择最晚开工时间"
:placeholder="t('ProductionPlan.Plan.dialogLatestStartPlaceholder')"
class="!w-full"
/>
</el-form-item>
<!-- <el-form-item label="班别" prop="groupType">
<el-radio-group v-model="formData.groupType">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.MES_GROUP_TYPE)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item> -->
<el-form-item :label="t('ProductionPlan.Plan.dialogRemarkLabel')" prop="remark">
<el-input type="textarea" v-model="formData.remark" :placeholder="t('ProductionPlan.Plan.dialogRemarkPlaceholder')" />
</el-form-item>
<div class="section-title">领料</div>
<el-form-item label="领料人" prop="workerId">
<el-select v-model="formData.workerId" clearable filterable placeholder="请选择领料人">
<el-option v-for="item in workerList" :key="item.id" :label="item.nickname" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="是否计算损耗" prop="isCalculateLoss">
<el-switch v-model="formData.isCalculateLoss" />
<el-input
type="textarea"
v-model="formData.remark"
:placeholder="t('ProductionPlan.Plan.dialogRemarkPlaceholder')"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">{{ t('ProductionPlan.Plan.dialogSubmitButtonText') }}</el-button>
<el-button @click="dialogVisible = false">{{ t('ProductionPlan.Plan.dialogCancelButtonText') }}</el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading">
{{ t('ProductionPlan.Plan.dialogSubmitButtonText') }}
</el-button>
<el-button @click="dialogVisible = false">
{{ t('ProductionPlan.Plan.dialogCancelButtonText') }}
</el-button>
</template>
</Dialog>
<TableSelectDialog
ref="deviceSelectDialogRef"
title="选择设备"
:title="t('ProductionPlan.Plan.selectDeviceDialogTitle')"
:columns="deviceColumns"
:fetch-api="fetchDeviceLedgerPage"
selection-type="single"
@ -172,58 +171,40 @@
@confirm="handleDeviceSelectConfirm"
/>
</template>
<script setup lang="ts">
<script setup lang="ts">
import dayjs from 'dayjs'
import { PlanApi, PlanVO } from '@/api/mes/plan'
import { PlanApi, PlanProcessRouteItemVO, PlanVO } from '@/api/mes/plan'
import { TaskApi, TaskDetailVO, TaskVO } from '@/api/mes/task'
import { DeviceLedgerApi, DeviceLedgerVO } from '@/api/mes/deviceledger'
import { DeviceLedgerApi } from '@/api/mes/deviceledger'
import { ErpProductCategoryProcessRouteItemRespVO, ProductApi } from '@/api/erp/product/product'
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
import * as UserApi from '@/api/system/user'
/** 生产计划 表单 */
defineOptions({ name: 'PlanForm' })
const taskList = ref<TaskVO[]>([]) //
const taskDetailList = ref<TaskDetailVO[]>([]) //
const workerList = ref<UserApi.UserVO[]>([])
const deviceSelectDialogRef = ref()
const selectedDeviceRows = ref<DeviceLedgerVO[]>([])
type PlanFormData = Partial<PlanVO> & {
isCode?: boolean
finishDate?: string | number
deliveryDate?: string | number
processRouteItems?: PlanProcessRouteItemVO[]
}
const taskList = ref<TaskVO[]>([])
const taskDetailList = ref<TaskDetailVO[]>([])
const maxUnplannedNumber = ref<number | undefined>(undefined)
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
code: undefined,
productId: undefined,
taskDetailId: undefined,
taskId: undefined,
planNumber: undefined,
reyaNumber: undefined,
finishNumber: undefined,
status: undefined,
planStartTime: undefined,
planEndTime: undefined,
startTime: undefined,
endTime: undefined,
productionManagerId: undefined,
remark: undefined,
groupType: undefined,
isEnable: undefined,
deviceId: undefined,
deviceName: undefined,
workerId: undefined,
isCode: undefined,
latestStartTime: undefined,
finishDate: undefined,
deliveryDate: undefined,
isCalculateLoss: true
})
const { t } = useI18n()
const message = useMessage()
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formData = ref<PlanFormData>({})
const formRef = ref()
const deviceSelectDialogRef = ref()
const activeRouteItemRow = ref<PlanProcessRouteItemVO>()
const deliveryDateText = computed(() => {
const sourceValue = formData.value.finishDate || formData.value.deliveryDate
if (!sourceValue) return ''
@ -231,33 +212,21 @@ const deliveryDateText = computed(() => {
if (!value.isValid()) return String(sourceValue)
return value.format('YYYY-MM-DD')
})
const deviceDisplayText = computed(() => {
if (selectedDeviceRows.value.length) {
return selectedDeviceRows.value
.map((item: any) => item.deviceName || item.name || item.code || `ID:${item.id}`)
.join('、')
}
if (formData.value.deviceName) return String(formData.value.deviceName)
return ''
const processRouteDisplayName = computed(() => {
if (!formData.value.processRouteId) return ''
return formData.value.processRouteName || String(formData.value.processRouteId)
})
const deviceColumns = [
{ label: '设备编号', prop: 'deviceCode', minWidth: 140 },
{ label: '设备名称', prop: 'deviceName', minWidth: 160 },
{ label: '设备型号', prop: 'deviceModel', minWidth: 140 },
{ label: '所属车间', prop: 'workshopName', minWidth: 140 }
]
const latestStartDisabledDate = (date: Date) => {
if (!formData.value.finishDate) return false
const deliveryDate = dayjs(formData.value.finishDate)
if (!deliveryDate.isValid()) return false
return dayjs(date).startOf('day').valueOf() > deliveryDate.startOf('day').valueOf()
}
const validateLatestStartTime = (_rule: any, value: any, callback: (error?: Error) => void) => {
if (!value) {
callback()
return
}
if (!formData.value.finishDate) {
if (!value || !formData.value.finishDate) {
callback()
return
}
@ -268,11 +237,12 @@ const validateLatestStartTime = (_rule: any, value: any, callback: (error?: Erro
return
}
if (latestStart.startOf('day').valueOf() > deliveryDate.startOf('day').valueOf()) {
callback(new Error('最晚开工时间不能晚于交货日期'))
callback(new Error(t('ProductionPlan.Plan.validatorLatestStartAfterDelivery')))
return
}
callback()
}
const validatePlanStartTime = (_rule: any, value: any, callback: (error?: Error) => void) => {
if (!value || !formData.value.planEndTime) {
callback()
@ -285,11 +255,12 @@ const validatePlanStartTime = (_rule: any, value: any, callback: (error?: Error)
return
}
if (startTime > endTime) {
callback(new Error('计划开始时间不能晚于计划结束时间'))
callback(new Error(t('ProductionPlan.Plan.validatorPlanStartBeforeEnd')))
return
}
callback()
}
const validatePlanEndTime = (_rule: any, value: any, callback: (error?: Error) => void) => {
if (!value || !formData.value.planStartTime) {
callback()
@ -302,11 +273,12 @@ const validatePlanEndTime = (_rule: any, value: any, callback: (error?: Error) =
return
}
if (endTime < startTime) {
callback(new Error('计划结束时间不能早于计划开始时间'))
callback(new Error(t('ProductionPlan.Plan.validatorPlanEndAfterStart')))
return
}
callback()
}
const getSelectedTaskDetailUnplanned = () => {
const detail = taskDetailList.value.find(
(item: any) => Number(item.id) === Number(formData.value.taskDetailId)
@ -317,11 +289,13 @@ const getSelectedTaskDetailUnplanned = () => {
if (!Number.isFinite(total) || !Number.isFinite(planned)) return undefined
return Math.max(total - planned, 0)
}
const getPlanNumberLimit = () => {
const detailUnplanned = getSelectedTaskDetailUnplanned()
if (detailUnplanned !== undefined) return detailUnplanned
return maxUnplannedNumber.value
}
const validatePlanNumber = (_rule: any, value: any, callback: (error?: Error) => void) => {
if (value === undefined || value === null || value === '') {
callback()
@ -329,16 +303,17 @@ const validatePlanNumber = (_rule: any, value: any, callback: (error?: Error) =>
}
const currentPlanNumber = Number(value)
if (!Number.isFinite(currentPlanNumber)) {
callback(new Error('计划数量必须为数字'))
callback(new Error(t('ProductionPlan.Plan.validatorPlanNumberNumeric')))
return
}
const planNumberLimit = getPlanNumberLimit()
if (planNumberLimit !== undefined && currentPlanNumber > planNumberLimit) {
callback(new Error(`计划数量不能大于未计划数量(${planNumberLimit})`))
callback(new Error(t('ProductionPlan.Plan.validatorPlanNumberLimit', { limit: planNumberLimit })))
return
}
callback()
}
const formRules = reactive({
taskDetailId: [{ required: true, message: t('ProductionPlan.Plan.validatorTaskDetailRequired'), trigger: 'blur' }],
taskId: [{ required: true, message: t('ProductionPlan.Plan.validatorTaskRequired'), trigger: 'blur' }],
@ -346,7 +321,6 @@ const formRules = reactive({
{ required: true, message: t('ProductionPlan.Plan.validatorPlanNumberRequired'), trigger: 'blur' },
{ validator: validatePlanNumber, trigger: ['blur', 'change'] }
],
// reyaNumber: [{ required: true, message: t('ProductionPlan.Plan.validatorReyaNumberRequired'), trigger: 'blur' }],
planStartTime: [
{ required: true, message: t('ProductionPlan.Plan.validatorPlanStartRequired'), trigger: 'blur' },
{ validator: validatePlanStartTime, trigger: 'change' }
@ -355,13 +329,18 @@ const formRules = reactive({
{ required: true, message: t('ProductionPlan.Plan.validatorPlanEndRequired'), trigger: 'blur' },
{ validator: validatePlanEndTime, trigger: 'change' }
],
deviceId: [{ required: true, message: '请选择设备', trigger: 'change' }],
latestStartTime: [
{ required: true, message: '请选择最晚开工时间', trigger: 'blur' },
{ required: true, message: t('ProductionPlan.Plan.validatorLatestStartRequired'), trigger: 'blur' },
{ validator: validateLatestStartTime, trigger: 'change' }
]
})
const formRef = ref() // Ref
const deviceColumns = [
{ label: t('ProductionPlan.Plan.deviceColumnCode'), prop: 'deviceCode', minWidth: 140 },
{ label: t('ProductionPlan.Plan.deviceColumnName'), prop: 'deviceName', minWidth: 160 },
{ label: t('ProductionPlan.Plan.deviceColumnModel'), prop: 'deviceModel', minWidth: 140 },
{ label: t('ProductionPlan.Plan.deviceColumnWorkshop'), prop: 'workshopName', minWidth: 140 }
]
const fetchDeviceLedgerPage = (params: Record<string, any>) => {
return DeviceLedgerApi.getDeviceLedgerPage({
@ -370,14 +349,18 @@ const fetchDeviceLedgerPage = (params: Record<string, any>) => {
})
}
const openDeviceSelectDialog = () => {
const rows = selectedDeviceRows.value.length
? selectedDeviceRows.value.map((item: any) => ({ ...item, id: Number(item.id) }))
: formData.value.deviceId
const getRouteItemDeviceDisplay = (row: PlanProcessRouteItemVO) => {
if (!row?.deviceId) return ''
return row.deviceName || String(row.deviceId)
}
const openDeviceSelectDialog = (row: PlanProcessRouteItemVO) => {
activeRouteItemRow.value = row
const rows = row.deviceId
? [
{
id: Number(formData.value.deviceId),
deviceName: formData.value.deviceName
id: Number(row.deviceId),
deviceName: row.deviceName
}
]
: []
@ -386,78 +369,117 @@ const openDeviceSelectDialog = () => {
const handleDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
const row = payload.rows?.[0]
if (!row) return
if (!row || !activeRouteItemRow.value) return
const deviceId = Number(row.id)
if (!Number.isFinite(deviceId)) return
formData.value.deviceId = deviceId
formData.value.deviceName = row.deviceName || row.name || row.code || `设备ID:${deviceId}`
selectedDeviceRows.value = [row]
activeRouteItemRow.value.deviceId = deviceId
activeRouteItemRow.value.deviceName = row.deviceName || row.name || row.code || `ID:${deviceId}`
}
const clearRouteItemDevice = (row: PlanProcessRouteItemVO) => {
row.deviceId = undefined
row.deviceName = undefined
}
const syncFinishDateByTaskDetail = () => {
const detail = taskDetailList.value.find((item: any) => Number(item.id) === Number(formData.value.taskDetailId))
const detail = taskDetailList.value.find((item: any) => Number(item.id) === Number(formData.value.taskDetailId)) as any
formData.value.finishDate = detail?.finishDate || detail?.deliveryDate || undefined
formData.value.deliveryDate = detail?.deliveryDate || formData.value.deliveryDate
}
/** 打开弹窗 */
const open = async (type: string, id?: number,
taskId?: number, productId?: number, number?: number, taskDetailId?:string,
taskDeliveryDate?: string | number) => {
const normalizeProcessRouteItems = (items?: Array<ErpProductCategoryProcessRouteItemRespVO | PlanProcessRouteItemVO>) => {
if (!Array.isArray(items)) return []
return items.map((item, index) => ({
id: (item as any).id,
categoryId: (item as any).categoryId,
processRouteId: (item as any).processRouteId,
processRouteItemId: (item as any).processRouteItemId || (item as any).id,
sort: item.sort || index + 1,
processParameterId: item.processParameterId,
processParameterCode: item.processParameterCode,
processParameterName: item.processParameterName,
remark: item.remark,
deviceId: (item as any).deviceId,
deviceName: (item as any).deviceName
}))
}
const loadProductProcessRoute = async (productId?: number | string) => {
formData.value.processRouteId = undefined
formData.value.processRouteName = undefined
formData.value.processRouteItems = []
if (!productId) return
const data = await ProductApi.getProductProcessRoute(productId)
formData.value.processRouteId = data?.processRouteId
formData.value.processRouteName = data?.processRouteName
formData.value.processRouteItems = normalizeProcessRouteItems(data?.processRouteItems)
}
const open = async (
type: string,
id?: number,
taskId?: number,
productId?: number,
number?: number,
taskDetailId?: string,
taskDeliveryDate?: string | number
) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
maxUnplannedNumber.value = number !== undefined ? Math.max(Number(number), 0) : undefined
formData.value.deliveryDate = taskDeliveryDate || undefined
if(taskId) {
formData.value.taskId = taskId
taskDetailList.value =await TaskApi.getTaskDetailListByTaskId(formData.value.taskId)
if(taskDetailId){
const taskDetails = taskDetailList.value.filter(item => item.id === taskDetailId)
if(taskDetails && taskDetails.length > 0 && taskDetails[0].id) {
formData.value.taskDetailId = taskDetails[0].id
formData.value.finishDate = (taskDetails[0] as any).finishDate || undefined
formData.value.deliveryDate = (taskDetails[0] as any).deliveryDate || taskDeliveryDate || undefined
}
}
}
if(productId)
formData.value.productId = productId
if(number){
formData.value.planNumber = number
formData.value.reyaNumber = number
}
//
if (id) {
formLoading.value = true
try {
formData.value = await PlanApi.getPlan(id)
if (formData.value.deviceId) {
selectedDeviceRows.value = [
{
id: Number(formData.value.deviceId),
deviceName: formData.value.deviceName
} as DeviceLedgerVO
]
formLoading.value = true
try {
maxUnplannedNumber.value = number !== undefined ? Math.max(Number(number), 0) : undefined
formData.value.deliveryDate = taskDeliveryDate || undefined
if (productId) {
formData.value.productId = productId
}
if (!id) {
await loadProductProcessRoute(productId)
}
if (taskId) {
formData.value.taskId = taskId
taskDetailList.value = await TaskApi.getTaskDetailListByTaskId(formData.value.taskId)
if (taskDetailId) {
const taskDetails = taskDetailList.value.filter((item) => String(item.id) === String(taskDetailId))
if (taskDetails?.[0]?.id) {
formData.value.taskDetailId = taskDetails[0].id
formData.value.finishDate = (taskDetails[0] as any).finishDate || undefined
formData.value.deliveryDate = (taskDetails[0] as any).deliveryDate || taskDeliveryDate || undefined
}
}
}
if (number) {
formData.value.planNumber = number
formData.value.reyaNumber = number
}
if (!taskList.value.length) {
taskList.value = await TaskApi.getPlanTaskList()
}
if (id) {
const data = await PlanApi.getPlan(id)
formData.value = {
...data,
isCode: data.isCode ?? false,
processRouteItems: normalizeProcessRouteItems((data as any).processRouteItems)
}
} finally {
formLoading.value = false
}
} finally {
formLoading.value = false
}
taskList.value = await TaskApi.getPlanTaskList()
workerList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // open
defineExpose({ open })
/** 提交表单 */
const emit = defineEmits(['success']) // success
const emit = defineEmits(['success'])
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as PlanVO
const data = {
...formData.value,
processRouteItems: normalizeProcessRouteItems(formData.value.processRouteItems)
} as unknown as PlanVO
if (formType.value === 'create') {
await PlanApi.createPlan(data)
message.success(t('common.createSuccess'))
@ -466,14 +488,12 @@ const submitForm = async () => {
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
@ -482,6 +502,7 @@ const resetForm = () => {
taskDetailId: undefined,
taskId: undefined,
planNumber: 0,
reyaNumber: undefined,
finishNumber: 0,
status: undefined,
planStartTime: undefined,
@ -492,52 +513,42 @@ const resetForm = () => {
remark: undefined,
groupType: undefined,
isEnable: undefined,
deviceId: undefined,
deviceName: undefined,
isPreProduction: 0,
isCode: true,
latestStartTime: undefined,
finishDate: undefined,
deliveryDate: undefined,
isCalculateLoss: true
isCode: true,
latestStartTime: undefined,
finishDate: undefined,
deliveryDate: undefined,
processRouteId: undefined,
processRouteName: undefined,
processRouteItems: []
}
maxUnplannedNumber.value = undefined
selectedDeviceRows.value = []
formRef.value?.resetFields()
}
/** 任务单变化 */
const handleTaskChange = async() => {
taskDetailList.value =await TaskApi.getTaskDetailListByTaskId(formData.value.taskId)
const handleTaskChange = async () => {
taskDetailList.value = await TaskApi.getTaskDetailListByTaskId(formData.value.taskId)
formData.value.taskDetailId = undefined
formData.value.finishDate = undefined
formRef.value?.validateField('planNumber')
}
/** 明细变化 */
const handleTaskDetailChange = async() => {
const handleTaskDetailChange = async () => {
syncFinishDateByTaskDetail()
formRef.value?.validateField('planNumber')
}
const handlePlanStartTimeChange = () => {
formRef.value?.validateField(['planStartTime', 'planEndTime'])
}
const handlePlanEndTimeChange = () => {
formRef.value?.validateField(['planStartTime', 'planEndTime'])
}
</script>
<style scoped>
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 16px 0 12px;
padding-left: 10px;
border-left: 3px solid #409eff;
}
.section-title:first-child {
margin-top: 0;
.process-route-items-table {
width: 100%;
}
</style>

@ -1,129 +1,230 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskLabel')" prop="taskCode">
<el-input disabled v-model="formData.taskCode" :placeholder="t('ProductionPlan.Plan.dialogTaskPlaceholder')" />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskDetailLabel')" prop="taskDetailId">
<el-select
disabled
v-model="formData.taskDetailId"
clearable
filterable
:placeholder="t('ProductionPlan.Plan.dialogTaskDetailPlaceholder')"
<el-drawer
v-model="drawerVisible"
:title="drawerTitle"
direction="rtl"
size="50%"
destroy-on-close
class="plan-detail-drawer"
>
<el-tabs v-model="activeTab" class="plan-detail-tabs">
<el-tab-pane label="基础信息" name="base">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-option
v-for="item in taskDetailList"
:key="item.id"
:label="item.productName + '-未计划:' + (item.number - item.planNumber)"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item prop="code">
<template #label>
<span>
{{ t('ProductionPlan.Plan.dialogCodeLabel') }}
<el-tooltip :content="t('ProductionPlan.Plan.dialogCodeTooltip')" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-row :gutter="10" class="!w-full">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input disabled v-model="formData.code" :placeholder="t('ProductionPlan.Plan.dialogCodePlaceholder')" />
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch disabled v-model="formData.isCode" />
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="交货日期">
<el-input disabled :model-value="deliveryDateText" />
</el-form-item>
<el-form-item label="设备" prop="feedingPipeline">
<el-input disabled :model-value="deviceDisplayText" placeholder="点击选择设备" readonly />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanNumberLabel')" prop="planNumber">
<el-input disabled v-model="formData.planNumber" />
</el-form-item>
<!-- <el-form-item :label="t('ProductionPlan.Plan.dialogPreProductionLabel')" prop="isPreProduction">
<el-radio-group disabled v-model="formData.isPreProduction">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.MES_PRE_PRODUCTION)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item> -->
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanStartLabel')" prop="planStartTime">
<el-date-picker
disabled
v-model="formData.planStartTime"
type="date"
value-format="x"
:placeholder="t('ProductionPlan.Plan.dialogPlanStartPlaceholder')"
class="!w-full"
/>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanEndLabel')" prop="planEndTime">
<el-date-picker
disabled
v-model="formData.planEndTime"
type="date"
value-format="x"
:placeholder="t('ProductionPlan.Plan.dialogPlanEndPlaceholder')"
class="!w-full"
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskLabel')" prop="taskCode">
<el-input disabled v-model="formData.taskCode" :placeholder="t('ProductionPlan.Plan.dialogTaskPlaceholder')" />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogTaskDetailLabel')" prop="taskDetailId">
<el-select
disabled
v-model="formData.taskDetailId"
clearable
filterable
:placeholder="t('ProductionPlan.Plan.dialogTaskDetailPlaceholder')"
>
<el-option
v-for="item in taskDetailList"
:key="item.id"
:label="`${item.productName}-${t('ProductionPlan.Plan.dialogTaskDetailUnplannedPrefix')}${Number(item.number || 0) - Number(item.planNumber || 0)}`"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item prop="code">
<template #label>
<span>
{{ t('ProductionPlan.Plan.dialogCodeLabel') }}
<el-tooltip :content="t('ProductionPlan.Plan.dialogCodeTooltip')" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-row :gutter="10" class="!w-full">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input disabled v-model="formData.code" :placeholder="t('ProductionPlan.Plan.dialogCodePlaceholder')" />
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<el-switch disabled v-model="formData.isCode" />
</el-col>
</el-row>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogDeliveryDateLabel')">
<el-input disabled :model-value="deliveryDateText" />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogProcessRouteLabel')" prop="processRouteId">
<el-input
disabled
:model-value="processRouteDisplayName"
:placeholder="t('ProductionPlan.Plan.dialogProcessRouteEmptyText')"
/>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogProcessRouteItemsLabel')">
<el-table
:data="formData.processRouteItems"
border
size="small"
row-key="id"
class="process-route-items-table"
:empty-text="t('ProductionPlan.Plan.dialogProcessRouteEmptyText')"
>
<el-table-column :label="t('ProductionPlan.Plan.routeItemSortColumn')" prop="sort" width="70" align="center" />
<el-table-column
:label="t('ProductionPlan.Plan.routeItemParameterCodeColumn')"
prop="processParameterCode"
min-width="140"
align="center"
/>
<el-table-column
:label="t('ProductionPlan.Plan.routeItemParameterNameColumn')"
prop="processParameterName"
min-width="160"
align="center"
/>
<el-table-column :label="t('ProductionPlan.Plan.routeItemRemarkColumn')" prop="remark" min-width="140" align="center" />
<el-table-column :label="t('ProductionPlan.Plan.routeItemDeviceColumn')" min-width="180" align="center">
<template #default="scope">
{{ getRouteItemDeviceDisplay(scope.row) }}
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanNumberLabel')" prop="planNumber">
<el-input disabled v-model="formData.planNumber" />
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanStartLabel')" prop="planStartTime">
<el-date-picker
disabled
v-model="formData.planStartTime"
type="date"
value-format="x"
:placeholder="t('ProductionPlan.Plan.dialogPlanStartPlaceholder')"
class="!w-full"
/>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogPlanEndLabel')" prop="planEndTime">
<el-date-picker
disabled
v-model="formData.planEndTime"
type="date"
value-format="x"
:placeholder="t('ProductionPlan.Plan.dialogPlanEndPlaceholder')"
class="!w-full"
/>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogLatestStartLabel')" prop="latestStartTime">
<el-date-picker
disabled
v-model="formData.latestStartTime"
type="date"
value-format="x"
:placeholder="t('ProductionPlan.Plan.dialogLatestStartPlaceholder')"
class="!w-full"
/>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogRemarkLabel')" prop="remark">
<el-input disabled type="textarea" v-model="formData.remark" :placeholder="t('ProductionPlan.Plan.dialogRemarkPlaceholder')" />
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="报工记录" name="baogong" lazy>
<BaogongRecordList
v-if="currentPlanId"
:key="`${currentPlanId}-${recordRefreshKey}`"
:plan-id="currentPlanId"
/>
</el-form-item>
<el-form-item label="最晚开工时间" prop="latestStartTime">
<el-date-picker
disabled
v-model="formData.latestStartTime"
type="date"
value-format="x"
placeholder="请选择最晚开工时间"
class="!w-full"
</el-tab-pane>
<el-tab-pane label="操作记录" name="operation" lazy>
<el-table
v-loading="lineChangeRecordLoading"
:data="lineChangeRecordList"
border
:stripe="true"
:show-overflow-tooltip="true"
:row-key="getLineChangeBatchRowKey"
empty-text="暂无换线记录"
>
<el-table-column type="expand" width="48">
<template #default="scope">
<el-table
:data="scope.row.items || []"
border
size="small"
class="line-change-items-table"
row-key="id"
empty-text="暂无换线明细"
>
<el-table-column label="编号" prop="id" width="80" align="center" />
<el-table-column label="流程明细ID" prop="planProcessRouteItemId" width="110" align="center" />
<el-table-column label="工艺参数名称" prop="processParameterName" min-width="150" align="center" />
<el-table-column label="换线前设备" prop="beforeDeviceName" min-width="150" align="center" />
<el-table-column label="换线后设备" prop="afterDeviceName" min-width="150" align="center" />
<el-table-column label="备注" prop="remark" min-width="160" align="center" />
</el-table>
</template>
</el-table-column>
<el-table-column label="换线批次号" prop="changeBatchNo" min-width="170" align="center" />
<el-table-column label="换线时间" prop="changeTime" :formatter="dateFormatter2" width="170" align="center" />
<el-table-column label="变更工序数量" prop="changeCount" width="120" align="center" />
<el-table-column label="操作人ID" prop="operatorUserId" width="100" align="center" />
<el-table-column label="当前合格数量" prop="passNumber" width="120" align="center" />
<el-table-column label="备注" prop="remark" min-width="160" align="center" />
</el-table>
<Pagination
:total="lineChangeRecordTotal"
v-model:page="lineChangeRecordQueryParams.pageNo"
v-model:limit="lineChangeRecordQueryParams.pageSize"
@pagination="loadLineChangeRecords"
/>
</el-form-item>
<el-form-item :label="t('ProductionPlan.Plan.dialogRemarkLabel')" prop="remark">
<el-input disabled type="textarea" v-model="formData.remark" :placeholder="t('ProductionPlan.Plan.dialogRemarkPlaceholder')" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">{{ t('ProductionPlan.Plan.detailDialogCloseButtonText') }}</el-button>
</template>
</Dialog>
</el-tab-pane>
</el-tabs>
</el-drawer>
</template>
<script setup lang="ts">
import { PlanApi } from '@/api/mes/plan'
import { LineChangeRecordBatchVO, PlanApi, PlanProcessRouteItemVO, PlanVO } from '@/api/mes/plan'
import { TaskApi, TaskDetailVO } from '@/api/mes/task'
import { dateFormatter2 } from '@/utils/formatTime'
import BaogongRecordList from '@/views/mes/baogongrecord/components/BaogongRecordList.vue'
import dayjs from 'dayjs'
import {DICT_TYPE, getIntDictOptions} from "@/utils/dict";
/** 生产计划 表单 */
defineOptions({ name: 'PlanDetail' })
const { t } = useI18n() //
const { t } = useI18n()
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const drawerVisible = ref(false)
const drawerTitle = ref('')
const activeTab = ref('base')
const formLoading = ref(false)
const formRules = reactive({})
const taskDetailList = ref<TaskDetailVO[]>([])
const formData = ref({
const currentPlanId = ref<number>()
const recordRefreshKey = ref(0)
const lineChangeRecordLoading = ref(false)
const lineChangeRecordList = ref<LineChangeRecordBatchVO[]>([])
const lineChangeRecordTotal = ref(0)
const lineChangeRecordQueryParams = reactive({
pageNo: 1,
pageSize: 10
})
type PlanDetailFormData = Partial<PlanVO> & {
workerId?: number
groupType?: number | string
isPreProduction?: number
isCode?: boolean
latestStartTime?: string | number
deliveryDate?: string | number
finishDate?: string | number
processRouteItems: PlanProcessRouteItemVO[]
}
const formData = ref<PlanDetailFormData>({
id: undefined,
code: undefined,
productId: undefined,
@ -150,8 +251,12 @@ const formData = ref({
isCode: undefined,
latestStartTime: undefined,
deliveryDate: undefined,
finishDate: undefined
finishDate: undefined,
processRouteId: undefined,
processRouteName: undefined,
processRouteItems: []
})
const deliveryDateText = computed(() => {
const sourceValue = formData.value.deliveryDate ?? formData.value.finishDate
if (!sourceValue) return ''
@ -159,28 +264,66 @@ const deliveryDateText = computed(() => {
if (!value.isValid()) return String(sourceValue)
return value.format('YYYY-MM-DD')
})
const deviceDisplayText = computed(() => {
if (formData.value.deviceName) return String(formData.value.deviceName)
return ''
const processRouteDisplayName = computed(() => {
if (!formData.value.processRouteId) return ''
return formData.value.processRouteName || String(formData.value.processRouteId)
})
const formRef = ref() // Ref
const getRouteItemDeviceDisplay = (row: PlanProcessRouteItemVO) => {
if (!row?.deviceId) return ''
return row.deviceName || String(row.deviceId)
}
const getLineChangeBatchRowKey = (row: LineChangeRecordBatchVO) => {
return row.changeBatchNo || [row.planId, row.changeTime, row.operatorUserId].filter(Boolean).join('-')
}
const formRef = ref()
const loadLineChangeRecords = async () => {
if (!currentPlanId.value) return
lineChangeRecordLoading.value = true
try {
const data = await PlanApi.getLineChangeRecordPage({
planId: currentPlanId.value,
pageNo: lineChangeRecordQueryParams.pageNo,
pageSize: lineChangeRecordQueryParams.pageSize
})
lineChangeRecordList.value = data?.list ?? []
lineChangeRecordTotal.value = data?.total ?? 0
} finally {
lineChangeRecordLoading.value = false
}
}
/** 打开弹窗 */
const open = async (id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('ProductionPlan.Plan.detailDialogTitle')
drawerVisible.value = true
drawerTitle.value = t('ProductionPlan.Plan.detailDialogTitle')
activeTab.value = 'base'
resetForm()
formData.value = await PlanApi.getPlan(id)
if (formData.value.taskId) {
taskDetailList.value = await TaskApi.getTaskDetailListByTaskId(formData.value.taskId)
if (!id) return
currentPlanId.value = id
recordRefreshKey.value += 1
formLoading.value = true
try {
const data = await PlanApi.getPlan(id)
formData.value = {
...data,
processRouteItems: Array.isArray(data?.processRouteItems) ? data.processRouteItems : []
}
if (formData.value.taskId) {
taskDetailList.value = await TaskApi.getTaskDetailListByTaskId(formData.value.taskId)
}
} finally {
formLoading.value = false
}
lineChangeRecordQueryParams.pageNo = 1
await loadLineChangeRecords()
}
defineExpose({ open }) // open
defineExpose({ open })
/** 重置表单 */
const resetForm = () => {
currentPlanId.value = undefined
lineChangeRecordList.value = []
formData.value = {
id: undefined,
code: undefined,
@ -206,10 +349,27 @@ const resetForm = () => {
isCode: true,
latestStartTime: undefined,
deliveryDate: undefined,
finishDate: undefined
finishDate: undefined,
processRouteId: undefined,
processRouteName: undefined,
processRouteItems: []
}
taskDetailList.value = []
formRef.value?.resetFields()
}
</script>
<style scoped>
.plan-detail-tabs {
height: 100%;
}
.process-route-items-table,
.line-change-items-table {
width: 100%;
}
.plan-detail-drawer :deep(.el-drawer__body) {
padding-top: 0;
}
</style>

@ -72,21 +72,6 @@
</el-tabs>
<el-table ref="tableRef" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"
:max-height="planTableMaxHeight" row-key="id">
<!-- 投料进度子表的列表 -->
<el-table-column type="expand">
<template #default="scope">
<!-- <el-tabs v-if="scope.row.status == 0" model-value="paigongRecord">
<el-tab-pane label="派工记录" name="paigongRecord">
<PaigongRecordList :plan-id="scope.row.id"/>
</el-tab-pane>
</el-tabs> -->
<el-tabs model-value="baogongRecord">
<el-tab-pane :label="t('ProductionPlan.Plan.detailBaogongRecordTabLabel')" name="baogongRecord">
<BaogongRecordList :key="`${scope.row.id}-${baogongRecordRefreshKey}`" :plan-id="scope.row.id" />
</el-tab-pane>
</el-tabs>
</template>
</el-table-column>
<el-table-column :label="t('ProductionPlan.Plan.tableCodeColumn')" align="center" prop="code" min-width="180px"
sortable />
<el-table-column :label="t('ProductionPlan.Plan.tableTaskCodeColumn')" align="center" prop="taskCode"
@ -110,7 +95,7 @@
</template>
</el-table-column>
<!-- <el-table-column label="热压数量" align="center" prop="reyaNumber" /> -->
<el-table-column :label="t('ProductionPlan.Plan.tableStatusColumn')" align="center" prop="status" sortable>
<el-table-column :label="t('ProductionPlan.Plan.tableStatusColumn')" align="center" prop="status" sortable min-width="90px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_PLAN_STATUS" :value="scope.row.status" />
</template>
@ -132,19 +117,22 @@
</template>
</el-table-column>
<!-- <el-table-column :label="t('ProductionPlan.Plan.tableRemarkColumn')" align="center" prop="remark" /> -->
<el-table-column :label="t('ProductionPlan.Plan.tableOperateColumn')" align="center" fixed="right" width="350">
<el-table-column :label="t('ProductionPlan.Plan.tableOperateColumn')" align="center" fixed="right" min-width="260">
<template #default="scope">
<el-button link type="info" @click="openDetail(scope.row.id)" v-hasPermi="['mes:plan:query']">
{{ t('ProductionPlan.Plan.actionDetailLabel') }}
</el-button>
<el-button link type="primary" @click="openChangeLineDialog(scope.row)" v-hasPermi="['mes:plan:update']">
换线
</el-button>
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['mes:plan:update']"
v-if="scope.row.status === 0">
{{ t('ProductionPlan.Plan.actionEditLabel') }}
</el-button>
<el-button link type="info" @click="openItemNeed(scope.row.code, scope.row.id)"
<!-- <el-button link type="info" @click="openItemNeed(scope.row.code, scope.row.id)"
v-hasPermi="['mes:task:query']">
{{ t('ProductionPlan.Plan.actionMaterialLabel') }}
</el-button>
</el-button> -->
<el-button link type="primary" @click="openPaiForm(scope.row.code, scope.row.id)"
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 0">
{{ t('ProductionPlan.Plan.actionDispatchFeedingLabel') }}
@ -158,10 +146,10 @@
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 1 && scope.row.isPreProduction === 1">
{{ t('ProductionPlan.Plan.actionTrialLabel') }}
</el-button>
<el-button link type="primary" @click.stop="openZjTaskForm(scope.row)"
<!-- <el-button link type="primary" @click.stop="openZjTaskForm(scope.row)"
v-if="scope.row.status === 6 && inspectableMap[scope.row.id] !== false || scope.row.status === 8">
{{ t('ProductionPlan.Plan.actionInspectLabel') }}
</el-button>
</el-button> -->
<!-- <el-button
link
type="primary"
@ -188,15 +176,15 @@
{{ t('ProductionPlan.Plan.actionPauseLabel') }}
</el-button>
<el-button link type="primary"
@click="handleStatus(scope.row.code, scope.row.id, 'end', 4, t('ProductionPlan.Plan.actionFinishLabel'), scope.row.isZj)"
@click="handleStatus(scope.row.code, scope.row.id, 'end', 9, t('ProductionPlan.Plan.actionFinishLabel'), scope.row.isZj)"
v-hasPermi="['mes:plan:update']"
v-if="scope.row.status === 2 || scope.row.status === 3 || scope.row.status === 8">
{{ t('ProductionPlan.Plan.actionFinishLabel') }}
</el-button>
<el-button link type="primary" @click="openStoreDialog(scope.row.code, scope.row.id, scope.row.isZj)"
<!-- <el-button link type="primary" @click="openStoreDialog(scope.row.code, scope.row.id, scope.row.isZj)"
v-hasPermi="['mes:plan:update']" v-if="scope.row.status === 4">
{{ t('ProductionPlan.Plan.actionStoreLabel') }}
</el-button>
</el-button> -->
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['mes:plan:delete']"
v-if="scope.row.status === 0">
{{ t('ProductionPlan.Plan.actionDeleteLabel') }}
@ -223,7 +211,63 @@
<!-- 报工弹出 -->
<Baogong ref="baogongFormRef" @success="handleBaogongSuccess" />
<!-- 详情弹窗 -->
<!-- 换线弹窗 -->
<Dialog v-model="lineChangeDialogVisible" :title="lineChangeDialogTitle" width="1250px">
<el-table
v-loading="lineChangeLoading"
:data="lineChangeItems"
border
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
empty-text="暂无工艺路线明细"
>
<el-table-column label="排序" prop="sort" width="70" align="center" />
<el-table-column label="工艺参数编码" prop="processParameterCode" min-width="140" align="center" />
<el-table-column label="工艺参数名称" prop="processParameterName" min-width="160" align="center" />
<el-table-column label="备注" prop="remark" min-width="140" align="center" />
<el-table-column label="当前设备" min-width="170" align="center">
<template #default="scope">
{{ scope.row.originalDeviceName || scope.row.originalDeviceId || '' }}
</template>
</el-table-column>
<el-table-column label="换线后设备" min-width="260" align="center">
<template #default="scope">
<el-input
:model-value="getLineChangeDeviceDisplay(scope.row)"
placeholder="请选择设备"
readonly
@click="openLineDeviceSelectDialog(scope.row)"
>
<template #append>
<el-button @click.stop="openLineDeviceSelectDialog(scope.row)">选择</el-button>
</template>
</el-input>
</template>
</el-table-column>
<el-table-column label="换线备注" min-width="180" align="center">
<template #default="scope">
<el-input v-model="scope.row.lineChangeRemark" placeholder="请输入备注" clearable />
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="lineChangeDialogVisible = false">关闭</el-button>
<el-button type="primary" :loading="lineChangeSaving" :disabled="!hasLineChangeChanges || lineChangeLoading" @click="submitLineChangeBatch"></el-button>
</template>
</Dialog>
<TableSelectDialog
ref="lineDeviceSelectDialogRef"
title="选择设备"
:columns="lineDeviceColumns"
:fetch-api="fetchLineDeviceLedgerPage"
selection-type="single"
row-key="id"
@confirm="handleLineDeviceSelectConfirm"
/>
<!-- 详情侧边栏 -->
<PlanDetail ref="detailRef" />
<!-- 入库仓库选择弹窗 -->
@ -250,7 +294,7 @@
import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download'
import { PlanApi, PlanVO } from '@/api/mes/plan'
import { PlanApi, PlanProcessRouteItemVO, PlanVO } from '@/api/mes/plan'
import PlanForm from './PlanForm.vue'
import { ProductApi, ProductVO } from "@/api/erp/product/product";
import { TaskApi, TaskVO } from "@/api/mes/task";
@ -260,7 +304,8 @@ import TypePaigong from "./components/TypePaigong.vue";
import PlanDetail from "./components/PlanDetail.vue";
import Baogong from "./components/Baogong.vue";
import ZjTaskForm from "@/views/mes/zjTask/ZjTaskForm.vue";
import BaogongRecordList from "@/views/mes/baogongrecord/components/BaogongRecordList.vue";
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
import { DeviceLedgerApi } from '@/api/mes/deviceledger'
import { ZjTaskApi } from '@/api/mes/zjtask'
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
@ -295,7 +340,28 @@ const updatePlanTableMaxHeight = async () => {
planTableMaxHeight.value = Math.min(window.innerHeight, Math.max(240, Math.floor(availableHeight)))
}
const inspectableMap = reactive<Record<number, boolean | undefined>>({})
const baogongRecordRefreshKey = ref(0)
type LineChangeRouteItem = PlanProcessRouteItemVO & {
planProcessRouteItemId?: number | string
originalDeviceId?: number | string
originalDeviceName?: string
selectedDeviceId?: number | string
selectedDeviceName?: string
lineChangeRemark?: string
}
const lineChangeDialogVisible = ref(false)
const lineChangeDialogTitle = ref('')
const lineChangeLoading = ref(false)
const lineChangeSaving = ref(false)
const lineChangePlanId = ref<number>()
const lineChangeItems = ref<LineChangeRouteItem[]>([])
const activeLineChangeItem = ref<LineChangeRouteItem>()
const lineDeviceSelectDialogRef = ref()
const lineDeviceColumns = [
{ label: t('ProductionPlan.Plan.deviceColumnCode'), prop: 'deviceCode', minWidth: 140 },
{ label: t('ProductionPlan.Plan.deviceColumnName'), prop: 'deviceName', minWidth: 160 },
{ label: t('ProductionPlan.Plan.deviceColumnModel'), prop: 'deviceModel', minWidth: 140 },
{ label: t('ProductionPlan.Plan.deviceColumnWorkshop'), prop: 'workshopName', minWidth: 140 }
]
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -441,8 +507,119 @@ const baogongFormRef = ref()
const openBaogongForm = (planCode: string, planId: number) => {
baogongFormRef.value.open(planCode, planId)
}
const fetchLineDeviceLedgerPage = (params: Record<string, any>) => {
return DeviceLedgerApi.getDeviceLedgerPage({
...params,
isScheduled: 1
})
}
const getPlanProcessRouteItemId = (row: LineChangeRouteItem) => {
return row.planProcessRouteItemId ?? row.id ?? row.processRouteItemId
}
const getLineChangeDeviceDisplay = (row: LineChangeRouteItem) => {
if (row.selectedDeviceName) return row.selectedDeviceName
if (row.selectedDeviceId) return String(row.selectedDeviceId)
return ''
}
const isLineChangeRowChanged = (row: LineChangeRouteItem) => {
if (!row.selectedDeviceId) return false
return String(row.selectedDeviceId) !== String(row.originalDeviceId ?? '')
}
const changedLineChangeItems = computed(() => lineChangeItems.value.filter(isLineChangeRowChanged))
const hasLineChangeChanges = computed(() => changedLineChangeItems.value.length > 0)
const openChangeLineDialog = async (row: PlanVO) => {
if (!row?.id) return
lineChangePlanId.value = row.id
lineChangeDialogTitle.value = row.code ? `换线 - ${row.code}` : '换线'
lineChangeDialogVisible.value = true
lineChangeLoading.value = true
lineChangeSaving.value = false
lineChangeItems.value = []
try {
const data = await PlanApi.getPlan(row.id)
const items = Array.isArray(data?.processRouteItems) ? data.processRouteItems : []
lineChangeItems.value = items.map((item: PlanProcessRouteItemVO, index: number) => ({
...item,
sort: item.sort || index + 1,
originalDeviceId: item.deviceId,
originalDeviceName: item.deviceName,
selectedDeviceId: item.deviceId,
selectedDeviceName: item.deviceName,
lineChangeRemark: ''
}))
} finally {
lineChangeLoading.value = false
}
}
const openLineDeviceSelectDialog = (row: LineChangeRouteItem) => {
activeLineChangeItem.value = row
const rows = row.selectedDeviceId
? [
{
id: Number(row.selectedDeviceId),
deviceName: row.selectedDeviceName
}
]
: []
lineDeviceSelectDialogRef.value?.open(rows)
}
const handleLineDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
const row = payload.rows?.[0]
if (!row || !activeLineChangeItem.value) return
const deviceId = Number(row.id)
if (!Number.isFinite(deviceId)) return
activeLineChangeItem.value.selectedDeviceId = deviceId
activeLineChangeItem.value.selectedDeviceName = row.deviceName || row.name || row.code || `ID:${deviceId}`
}
const submitLineChangeBatch = async () => {
const planId = lineChangePlanId.value
if (!planId) {
message.warning('缺少生产计划 ID')
return
}
const items = changedLineChangeItems.value.reduce<
Array<{ planProcessRouteItemId: number | string; deviceId: number | string; remark: string }>
>((result, row) => {
const planProcessRouteItemId = getPlanProcessRouteItemId(row)
const deviceId = row.selectedDeviceId
if (!planProcessRouteItemId || !deviceId) return result
result.push({
planProcessRouteItemId,
deviceId,
remark: row.lineChangeRemark || ''
})
return result
}, [])
if (!items.length) return
lineChangeSaving.value = true
try {
await PlanApi.changeLineBatch({ planId, items })
const changedItemMap = new Map(items.map((item) => [String(item.planProcessRouteItemId), item]))
lineChangeItems.value.forEach((row) => {
const item = changedItemMap.get(String(getPlanProcessRouteItemId(row)))
if (!item) return
row.deviceId = item.deviceId
row.deviceName = row.selectedDeviceName
row.originalDeviceId = item.deviceId
row.originalDeviceName = row.selectedDeviceName || String(item.deviceId)
row.lineChangeRemark = ''
})
message.success('换线成功')
await getList()
} finally {
lineChangeSaving.value = false
}
}
const handleBaogongSuccess = async () => {
baogongRecordRefreshKey.value += 1
await getList()
}

Loading…
Cancel
Save