Merge remote-tracking branch 'origin/dev_pro2.0' into dev_pro2.0
# Conflicts:
# src/views/mes/pro/task/data.ts
| | |
| | | createTime?: number; // 创建时间 |
| | | processInstanceId?: string; // BPM 流程实例编号 |
| | | processDefinitionKey?: string; // BPM 流程定义 Key |
| | | paramValues?: ParamValue[]; |
| | | } |
| | | |
| | | export interface ParamValue { |
| | | detailId: number; |
| | | value?: string; |
| | | } |
| | | |
| | | /** MES 生产报工分页查询参数 */ |
| | |
| | | /** 快速报工请求 */ |
| | | export interface QuickCreateReq { |
| | | taskId: number; |
| | | paramValues?: MesProFeedbackApi.ParamValue[]; |
| | | feedbackQuantity: number; |
| | | qualifiedQuantity?: number; |
| | | unqualifiedQuantity?: number; |
| | |
| | | id?: number; |
| | | routeId?: number; |
| | | processId?: number; |
| | | paramTemplateId?: number; |
| | | processCode?: string; |
| | | processName?: string; |
| | | sort?: number; |
| | |
| | | workstationName?: string; // 工作站名称 |
| | | routeId?: number; // 工艺路线编号 |
| | | processId?: number; // 工序编号 |
| | | paramTemplateId?: number; // 工艺参数模板编号 |
| | | processName?: string; // 工序名称 |
| | | itemId?: number; // 产品物料编号 |
| | | itemCode?: string; // 产品编码 |
| | |
| | | processId?: number; // 工序编号 |
| | | processCode?: string; // 工序编码 |
| | | processName?: string; // 工序名称 |
| | | paramTemplateId?: number; // 工艺参数模板编号 |
| | | sort?: number; // 序号 |
| | | nextProcessId?: number; // 下一道工序编号 |
| | | nextProcessName?: string; // 下一道工序名称 |
| | |
| | | if (to.path === LOGIN_PATH && accessStore.accessToken) { |
| | | return decodeURIComponent( |
| | | (to.query?.redirect as string) || |
| | | userStore.userInfo?.homePath || |
| | | preferences.app.defaultHomePath, |
| | | '/analytics', |
| | | ); |
| | | } |
| | | return true; |
| | |
| | | userStore.setUserRoles(userRoles); |
| | | const redirectPath = (from.query.redirect ?? |
| | | (to.path === preferences.app.defaultHomePath |
| | | ? userInfo?.homePath || preferences.app.defaultHomePath |
| | | ? '/analytics' |
| | | : to.fullPath)) as string; |
| | | |
| | | return { |
| | |
| | | // oxlint-disable-next-line no-unused-expressions |
| | | onSuccess |
| | | ? await onSuccess?.() |
| | | : await router.push( |
| | | userInfo.homePath || preferences.app.defaultHomePath, |
| | | ); |
| | | : await router.push('/analytics'); |
| | | } |
| | | |
| | | if (userInfo?.nickname) { |
| | |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process'; |
| | | import { getWorkOrderParamRecordPage } from '#/api/mes/pro/workorderparamrecord'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getRangePickerDefaultProps } from '#/utils'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | |
| | | import { MesAutoCodeRuleCode, MesProFeedbackStatusEnum } from '@vben/constants'; |
| | | import { useUserStore } from '@vben/stores'; |
| | | |
| | | import { Button, message, Popconfirm, Tabs } from 'ant-design-vue'; |
| | | import { Button, Input, message, Popconfirm, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | |
| | | } from '#/api/mes/pro/feedback'; |
| | | import { getRouteProcessByRouteAndProcess } from '#/api/mes/pro/route/process'; |
| | | import { getWorkOrder } from '#/api/mes/pro/workorder'; |
| | | import { getWorkOrderParamRecordPage } from '#/api/mes/pro/workorderparamrecord'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | |
| | | const userStore = useUserStore(); |
| | | const subTabsName = ref('itemConsume'); |
| | | const originalSnapshot = ref(''); // 表单原始数据快照,用于 submit 时跳过未变更的保存请求 |
| | | const paramDetails = ref<Array<{ id: number; paramName?: string; paramCode?: string; unitMeasureName?: string; paramValue?: string }>>([]); |
| | | |
| | | const isEditable = computed(() => |
| | | ['create', 'submit', 'update'].includes(formType.value), |
| | |
| | | /** 读取并按提交规则对齐后的表单数据,用于快照和保存 */ |
| | | async function getAlignedData(): Promise<MesProFeedbackApi.Feedback> { |
| | | const data = (await formApi.getValues()) as MesProFeedbackApi.Feedback; |
| | | syncParamValues(data); |
| | | alignQuantity(data); |
| | | return data; |
| | | } |
| | |
| | | |
| | | const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>(); |
| | | const batchActionType = ref<'submit' | 'approve'>(''); |
| | | |
| | | async function loadParamDetails(taskId?: number) { |
| | | paramDetails.value = []; |
| | | if (!taskId) return; |
| | | const page = await getWorkOrderParamRecordPage({ taskId, pageNo: 1, pageSize: 1 }); |
| | | const record = page.list?.[0]; |
| | | paramDetails.value = (record?.details ?? []).map((detail) => ({ |
| | | id: detail.id!, |
| | | paramCode: detail.paramCode, |
| | | paramName: detail.paramName, |
| | | unitMeasureName: detail.unitMeasureName, |
| | | paramValue: detail.paramValue, |
| | | })); |
| | | } |
| | | |
| | | function syncParamValues(data: MesProFeedbackApi.Feedback) { |
| | | data.paramValues = paramDetails.value.map((detail) => ({ |
| | | detailId: detail.id, |
| | | value: detail.paramValue, |
| | | })); |
| | | } |
| | | |
| | | /** 执行提交(含批次属性) */ |
| | | async function doSubmit(batchFields?: Record<string, unknown>) { |
| | |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getFeedback(data.id); |
| | | await loadParamDetails(formData.value.taskId); |
| | | const checkFlag = await resolveCheckFlag( |
| | | formData.value.routeId, |
| | | formData.value.processId, |
| | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | <div v-if="paramDetails.length" class="mx-4 mt-4 rounded border border-gray-200 p-4"> |
| | | <div class="mb-3 font-medium">工艺参数</div> |
| | | <div class="grid grid-cols-2 gap-3"> |
| | | <div v-for="detail in paramDetails" :key="detail.id" class="flex items-center gap-2"> |
| | | <span class="w-40 text-right text-sm text-gray-600"> |
| | | {{ detail.paramName || detail.paramCode }}{{ detail.unitMeasureName ? `(${detail.unitMeasureName})` : '' }} |
| | | </span> |
| | | <Input v-model:value="detail.paramValue" :disabled="!isEditable" placeholder="请输入参数值" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <Tabs |
| | | v-if="showSubTabs" |
| | | v-model:active-key="subTabsName" |
| | |
| | | |
| | | import { z } from '#/adapter/form'; |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getTemplatePage } from '#/api/mes/pro/processdesign/template'; |
| | | import { |
| | | MdItemSelect, |
| | | MdProductBomSelect, |
| | |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'paramTemplateId', |
| | | label: '工艺参数模板', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: () => getTemplatePage({ pageNo: 1, pageSize: 100 }), |
| | | labelField: 'templateName', |
| | | valueField: 'id', |
| | | resultField: 'list', |
| | | allowClear: true, |
| | | placeholder: '请选择工艺参数模板', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'linkType', |
| | | label: '与下道工序关系', |
| | | component: 'Select', |
| | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import { z } from '#/adapter/form'; |
| | | import { getSimpleUserListByRoleCode } from '#/api/system/user'; |
| | | import { getTemplatePage } from '#/api/mes/pro/processdesign/template'; |
| | | import { getUserListByWorkstationId } from '#/api/mes/md/workstation/worker'; |
| | | import type { MesProTaskApi } from '#/api/mes/pro/task'; |
| | | import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder'; |
| | |
| | | field: 'quantityProduced', |
| | | title: '已生产数量', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'progressPercent', |
| | | title: '完成进度', |
| | | width: 150, |
| | | slots: { default: 'progressPercent' }, |
| | | }, |
| | | { |
| | | field: 'clientCode', |
| | |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'paramTemplateId', |
| | | title: '工艺参数模板', |
| | | minWidth: 140, |
| | | formatter: ({ cellValue }) => cellValue ? `模板 #${cellValue}` : '未配置', |
| | | }, |
| | | { |
| | | field: 'quantity', |
| | | title: '排产数量', |
| | | width: 100, |
| | |
| | | export function useTaskFormSchema(formApi?: VbenFormApi, workOrderQuantity?: Ref<number | undefined>): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'paramTemplateId', |
| | | label: '工艺参数模板', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | api: () => getTemplatePage({ pageNo: 1, pageSize: 100 }), |
| | | labelField: 'templateName', |
| | | valueField: 'id', |
| | | resultField: 'list', |
| | | allowClear: true, |
| | | placeholder: '请选择工艺参数模板', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'workstationId', |
| | | label: '工作站', |
| | | component: markRaw(MdWorkstationSelect), |
| | |
| | | MesProWorkOrderTypeEnum, |
| | | } from '@vben/constants'; |
| | | |
| | | import { Button, Card, message, Tabs } from 'ant-design-vue'; |
| | | import { Button, Card, Progress, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getGanttTaskList, previewAutoGenerate, autoGenerateTasks } from '#/api/mes/pro/task'; |
| | | import { getGanttTaskList } from '#/api/mes/pro/task'; |
| | | import { getWorkOrderPage } from '#/api/mes/pro/workorder'; |
| | | |
| | | import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue'; |
| | | import { GanttChart } from './components'; |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import ScheduleForm from './modules/schedule-form.vue'; |
| | | import SchedulePreview from '../workorder-center/modules/schedule-preview.vue'; |
| | | |
| | | const router = useRouter(); |
| | | const ganttTasks = ref<any[]>([]); // 甘特图预览数据 |
| | |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [SchedulePreviewModal, schedulePreviewModalApi] = useVbenModal({ |
| | | connectedComponent: SchedulePreview, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** 刷新表格 */ |
| | | function handleRefresh() { |
| | |
| | | } |
| | | |
| | | /** 一键排产预览 */ |
| | | async function handleAutoSchedulePreview(row: MesProWorkOrderApi.WorkOrder) { |
| | | try { |
| | | const preview = await previewAutoGenerate({ workOrderId: row.id! }); |
| | | schedulePreviewModalApi |
| | | .setData({ workOrder: row, preview }) |
| | | .open(); |
| | | } catch { |
| | | message.error('获取排产预览失败'); |
| | | } |
| | | } |
| | | |
| | | /** 一键排产执行 */ |
| | | async function handleAutoSchedule(row: MesProWorkOrderApi.WorkOrder) { |
| | | try { |
| | | const result = await autoGenerateTasks({ workOrderId: row.id! }); |
| | | message.success(`排产成功,生成 ${result.taskIds?.length || 0} 个任务`); |
| | | handleRefresh(); |
| | | } catch { |
| | | message.error('排产失败'); |
| | | } |
| | | } |
| | | |
| | | /** 打开归档详情 */ |
| | | function handleArchive(row: MesProWorkOrderApi.WorkOrder) { |
| | | archiveProductCode.value = row.productCode || ''; |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <ScheduleModal @success="handleRefresh" /> |
| | | <SchedulePreviewModal @success="handleRefresh" /> |
| | | |
| | | <!-- 排产甘特图预览 --> |
| | | <Card title="排产甘特图" class="mb-4"> |
| | | <template #extra> |
| | | <span class="text-xs text-gray-400">展示已确认自制订单的任务计划</span> |
| | | </template> |
| | | <GanttChart :height="350" :readonly="true" :tasks="ganttTasks" /> |
| | | </Card> |
| | | |
| | |
| | | {{ row.code }} |
| | | </Button> |
| | | </template> |
| | | <template #progressPercent="{ row }"> |
| | | <Progress |
| | | :percent="Math.min(100, Math.round((row.quantity ? ((row.quantityProduced || 0) / row.quantity) * 100 : 0)))" |
| | | size="small" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: '排产', |
| | | label: '排产维护', |
| | | type: 'link', |
| | | auth: ['mes:pro-task:create'], |
| | | ifShow: row.status === MesProWorkOrderStatusEnum.CONFIRMED, |
| | |
| | | v-for="rp in routeProcessList" |
| | | :key="rp.processId" |
| | | :title="rp.processName" |
| | | :description="rp.paramTemplateId ? `参数模板 #${rp.paramTemplateId}` : '未配置参数模板'" |
| | | /> |
| | | </Steps> |
| | | <template v-for="(rp, index) in routeProcessList" :key="rp.processId"> |
| | |
| | | |
| | | import { useTaskGridColumns } from '../data'; |
| | | import TaskForm from './task-form.vue'; |
| | | import QuickFeedbackForm from '../../workorder-center/modules/quick-feedback.vue'; |
| | | |
| | | const props = defineProps<{ |
| | | colorCode?: string; |
| | |
| | | |
| | | const [TaskFormModal, taskFormModalApi] = useVbenModal({ |
| | | connectedComponent: TaskForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [QuickFeedbackModal, quickFeedbackModalApi] = useVbenModal({ |
| | | connectedComponent: QuickFeedbackForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | /** 编辑任务 */ |
| | | function handleEdit(row: MesProTaskApi.Task) { |
| | | taskFormModalApi.setData({ id: row.id }).open(); |
| | | } |
| | | |
| | | /** 快速报工 */ |
| | | function handleQuickFeedback(row: MesProTaskApi.Task) { |
| | | quickFeedbackModalApi |
| | | .setData({ |
| | | taskId: row.id, |
| | | taskCode: row.code, |
| | | taskQuantity: row.quantity, |
| | | taskProducedQuantity: row.producedQuantity, |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | /** 删除任务 */ |
| | |
| | | |
| | | // 工序步骤切换时重新加载当前工序的任务 |
| | | watch( |
| | | () => props.processId, |
| | | () => [props.workOrderId, props.routeId, props.processId], |
| | | () => handleRefresh(), |
| | | ); |
| | | |
| | |
| | | <template> |
| | | <div> |
| | | <TaskFormModal @success="handleRefresh" /> |
| | | <QuickFeedbackModal @success="handleRefresh" /> |
| | | <Grid table-title="生产任务"> |
| | | <template v-if="editable" #toolbar-tools> |
| | | <TableAction |
| | |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: '报工', |
| | | type: 'link', |
| | | auth: ['mes:pro-feedback:create'], |
| | | ifShow: (row.quantity ?? 0) > (row.producedQuantity ?? 0), |
| | | onClick: handleQuickFeedback.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const archiveDetailRef = ref<InstanceType<typeof ArchiveDetail>>(); |
| | | const archiveVisible = ref(false); |
| | | const archiveProductCode = ref(''); |
| | | // const barcodeDetailRef = ref<InstanceType<typeof BarcodeDetail>>(); // 条码详情弹窗 |
| | | |
| | | /** 刷新表格 */ |
| | |
| | | await cancelWorkOrder(row.id!); |
| | | message.success("订单已取消"); |
| | | handleRefresh(); |
| | | } |
| | | |
| | | /** 打开归档详情 */ |
| | | function handleArchive(row: MesProWorkOrderApi.WorkOrder) { |
| | | archiveProductCode.value = row.productCode || ''; |
| | | archiveVisible.value = true; |
| | | } |
| | | |
| | | /** 查看订单条码 */ |
| | |
| | | }, |
| | | }, |
| | | { |
| | | label: $t('common.create'), |
| | | label: '新增子订单', |
| | | type: 'link', |
| | | auth: ['mes:pro-work-order:create'], |
| | | ifShow: |
| | |
| | | { |
| | | label: '归档', |
| | | type: 'link', |
| | | onClick: () => archiveDetailRef.value?.open(row.productCode), |
| | | onClick: handleArchive.bind(null, row), |
| | | }, |
| | | ]" /> |
| | | </template> |
| | | </Grid> |
| | | <!-- <BarcodeDetail ref="barcodeDetailRef" /> --> |
| | | <ArchiveDetail ref="archiveDetailRef" /> |
| | | <ArchiveDetail :visible="archiveVisible" :product-code="archiveProductCode" @update:visible="archiveVisible = $event" /> |
| | | </Page> |
| | | </template> |
| | | |