| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder'; |
| | | import {getWorkOrderSchedulePage, type MesProWorkOrderApi} from '#/api/mes/pro/workorder'; |
| | | |
| | | import { onMounted, ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { |
| | | MesProWorkOrderStatusEnum, |
| | | MesProWorkOrderTypeEnum, |
| | | } from '../../../../packages/constants/src'; |
| | | } from '@vben/constants'; |
| | | |
| | | import { Button, Card } from 'ant-design-vue'; |
| | | import { Button, Card, Progress, Tabs, message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | 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 AutoSchedule from './modules/auto-schedule.vue'; |
| | | |
| | | const router = useRouter(); |
| | | const ganttTasks = ref<any[]>([]); // 甘特图预览数据 |
| | | const archiveVisible = ref(false); |
| | | const archiveProductCode = ref(''); |
| | | |
| | | const activeTab = ref('PENDING'); // 默认选中待排产 |
| | | |
| | | const [ScheduleModal, scheduleModalApi] = useVbenModal({ |
| | | connectedComponent: ScheduleForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [AutoScheduleModal, autoScheduleModalApi] = useVbenModal({ |
| | | connectedComponent: AutoSchedule, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function canAutoSchedule(row: MesProWorkOrderApi.WorkOrder) { |
| | | return row.status === MesProWorkOrderStatusEnum.CONFIRMED |
| | | && row.type === MesProWorkOrderTypeEnum.SELF |
| | | && !isCompletedWorkOrder(row) |
| | | && (row.quantity ?? 0) > 0 |
| | | && (row.quantityScheduled ?? 0) < (row.quantity ?? 0); |
| | | } |
| | | |
| | | function handleAutoSchedule(row?: MesProWorkOrderApi.WorkOrder) { |
| | | const rows: MesProWorkOrderApi.WorkOrder[] = row ? [row] : gridApi.grid.getCheckboxRecords(); |
| | | if (!rows.length) return message.warning('请先勾选待排产工单'); |
| | | if (rows.length > 30) return message.warning('一次最多选择30个工单,请分批排产'); |
| | | if (rows.some((order) => !canAutoSchedule(order))) return message.warning('请选择已确认且仍需排产的自行生产工单'); |
| | | autoScheduleModalApi.setData({ workOrderIds: rows.map((order) => order.id!) }).open(); |
| | | } |
| | | |
| | | |
| | | /** 刷新表格 */ |
| | | function handleRefresh() { |
| | | gridApi.grid.clearCheckboxRow(); |
| | | gridApi.query(); |
| | | loadGanttPreview(); |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | /** 查看工单详情 */ |
| | | /** 查看订单详情 */ |
| | | function handleDetail(row: MesProWorkOrderApi.WorkOrder) { |
| | | scheduleModalApi.setData({ formType: 'detail', id: row.id }).open(); |
| | | } |
| | |
| | | scheduleModalApi.setData({ formType: 'schedule', id: row.id }).open(); |
| | | } |
| | | |
| | | /** 一键排产预览 */ |
| | | /** 打开归档详情 */ |
| | | function handleArchive(row: MesProWorkOrderApi.WorkOrder) { |
| | | archiveProductCode.value = row.productCode || ''; |
| | | archiveVisible.value = true; |
| | | } |
| | | |
| | | /** 打开甘特图编辑页面 */ |
| | | function handleGanttEdit() { |
| | | router.push({ name: 'MesProTaskGanttEdit' }); |
| | | } |
| | | |
| | | /** 判断订单是否为“已完成类型”:订单状态=已完成 */ |
| | | function isCompletedWorkOrder(row: MesProWorkOrderApi.WorkOrder): boolean { |
| | | return row.status === MesProWorkOrderStatusEnum.FINISHED; |
| | | } |
| | | |
| | | /** |
| | | * 已完成类型行的“完成态”兜底展示: |
| | | * 已生产数量按订单数量展示(若低于订单数量),使完成进度显示为 100%。 |
| | | * 仅作用于本页列表展示,不修改后端数据,不影响库存/报工/导出等真实口径。 |
| | | */ |
| | | function normalizeCompletionDisplay(row: MesProWorkOrderApi.WorkOrder): void { |
| | | if (!isCompletedWorkOrder(row)) { |
| | | return; |
| | | } |
| | | const quantity = row.quantity ?? 0; |
| | | const produced = row.quantityProduced ?? 0; |
| | | if (produced < quantity) { |
| | | row.quantityProduced = quantity; |
| | | } |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | keepSource: true, |
| | | checkboxConfig: { |
| | | checkStrictly: true, |
| | | showHeader: true, |
| | | checkMethod: ({ row }: { row: MesProWorkOrderApi.WorkOrder }) => canAutoSchedule(row), |
| | | }, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getWorkOrderPage({ |
| | | let scheduleStatus: number | undefined = undefined; |
| | | |
| | | if (activeTab.value === 'PENDING') { |
| | | scheduleStatus = 0; // 未完成 |
| | | } else if (activeTab.value === 'FINISHED') { |
| | | scheduleStatus = 1; // 已完成 |
| | | } |
| | | // activeTab === 'ALL' 时不传 |
| | | |
| | | const res = await getWorkOrderSchedulePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | status: MesProWorkOrderStatusEnum.CONFIRMED, |
| | | scheduleStatus, |
| | | type: MesProWorkOrderTypeEnum.SELF, |
| | | ...formValues, |
| | | }); |
| | | // 已完成类型行:已生产数量/完成进度做“完成态”兜底展示(仅本页展示,不改库) |
| | | if (res?.list) { |
| | | res.list.forEach(normalizeCompletionDisplay); |
| | | } |
| | | return res; |
| | | }, |
| | | }, |
| | | }, |
| | |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height><ScheduleModal @success="handleRefresh" /> |
| | | |
| | | <Page auto-content-height> |
| | | <ScheduleModal @success="handleRefresh" /> |
| | | <AutoScheduleModal @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> |
| | | |
| | | <Grid table-title="待排产工单"> |
| | | <Grid> |
| | | <template #table-title> |
| | | <div class="flex flex-col gap-2"> |
| | | <span class="text-base font-medium">生产订单列表</span> |
| | | <Tabs v-model:activeKey="activeTab" @change="handleRefresh" :tabBarStyle="{ margin: 0, borderBottom: 0 }"> |
| | | <Tabs.TabPane key="ALL" tab="全部" /> |
| | | <Tabs.TabPane key="PENDING" tab="未完成" /> |
| | | <Tabs.TabPane key="FINISHED" tab="已完成" /> |
| | | </Tabs> |
| | | </div> |
| | | </template> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: '甘特图编辑', |
| | | label: '一键排产', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:pro-task:create'], |
| | | onClick: () => handleAutoSchedule(), |
| | | }, |
| | | { |
| | | label: '甘特图编辑', |
| | | icon: ACTION_ICON.EDIT, |
| | | onClick: handleGanttEdit, |
| | | }, |
| | |
| | | {{ 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, |
| | | ifShow: canAutoSchedule(row), |
| | | onClick: () => handleAutoSchedule(row), |
| | | }, |
| | | { |
| | | label: '排产维护', |
| | | type: 'link', |
| | | auth: ['mes:pro-task:create'], |
| | | ifShow: |
| | | row.status === MesProWorkOrderStatusEnum.CONFIRMED && |
| | | !isCompletedWorkOrder(row), |
| | | onClick: handleSchedule.bind(null, row), |
| | | }, |
| | | { |
| | | label: '归档详情', |
| | | type: 'link', |
| | | onClick: handleArchive.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | <ArchiveDetail :visible="archiveVisible" :product-code="archiveProductCode" @update:visible="archiveVisible = $event" /> |
| | | </Page> |
| | | </template> |