You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
783 lines
30 KiB
Vue
783 lines
30 KiB
Vue
<template>
|
|
<Dialog v-model="dialogVisible" :title="t('ProductionPlan.TaskSummary.scheduleDialogTitle')" :initialFullscreen="true" destroy-on-close>
|
|
<div class="flex items-center gap-20px mb-16px">
|
|
<div class="flex items-center gap-8px">
|
|
<span class="text-red-500">*</span>
|
|
<span>{{ t('ProductionPlan.TaskSummary.scheduleRuleLabel') }}</span>
|
|
<el-select v-model="searchForm.sortRule" class="!w-220px" clearable>
|
|
<el-option v-for="item in scheduleRuleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
</el-select>
|
|
</div>
|
|
<div class="flex items-center gap-8px">
|
|
<span class="text-red-500">*</span>
|
|
<span>{{ t('ProductionPlan.TaskSummary.capacitySourceLabel') }}</span>
|
|
<el-select v-model="searchForm.capacityType" class="!w-220px">
|
|
<el-option v-for="item in capacityTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
</el-select>
|
|
</div>
|
|
<div class="flex items-center gap-8px">
|
|
<span class="text-red-500">*</span>
|
|
<span>{{ t('ProductionPlan.TaskSummary.workTimeLabel') }}</span>
|
|
<el-time-picker
|
|
v-model="searchForm.workTimeRange"
|
|
is-range
|
|
value-format="HH:mm"
|
|
format="HH:mm"
|
|
range-separator="To"
|
|
start-placeholder="Start time"
|
|
end-placeholder="End time"
|
|
class="!w-240px"
|
|
/>
|
|
</div>
|
|
<div class="flex items-center gap-8px">
|
|
<span>{{ t('ProductionPlan.TaskSummary.skipHolidayLabel') }}</span>
|
|
<el-switch v-model="searchForm.skipHoliday" />
|
|
</div>
|
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit" class="ml-auto">{{ t('ProductionPlan.TaskSummary.confirmScheduleButton') }}</el-button>
|
|
</div>
|
|
|
|
<div class="border border-solid border-gray-200 rounded p-16px mt-16px">
|
|
<el-form :inline="true" :model="searchForm" label-width="90px" class="-mb-8px">
|
|
<el-form-item :label="t('ProductionPlan.TaskSummary.scheduleFormCodeLabel')">
|
|
<el-input v-model="searchForm.code" :placeholder="t('ProductionPlan.TaskSummary.scheduleFormCodePlaceholder')" clearable class="!w-200px" />
|
|
</el-form-item>
|
|
<el-form-item :label="t('ProductionPlan.TaskSummary.scheduleFormOrderDateLabel')">
|
|
<el-date-picker
|
|
v-model="searchForm.orderDate"
|
|
type="daterange"
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
:start-placeholder="t('ProductionPlan.TaskSummary.startDatePlaceholder')"
|
|
:end-placeholder="t('ProductionPlan.TaskSummary.endDatePlaceholder')"
|
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
class="!w-260px"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item :label="t('ProductionPlan.TaskSummary.scheduleFormDeliveryDateLabel')">
|
|
<el-date-picker
|
|
v-model="searchForm.deliveryDate"
|
|
type="daterange"
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
:start-placeholder="t('ProductionPlan.TaskSummary.startDatePlaceholder')"
|
|
:end-placeholder="t('ProductionPlan.TaskSummary.endDatePlaceholder')"
|
|
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
class="!w-260px"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item :label="t('ProductionPlan.TaskSummary.scheduleFormRemarkLabel')">
|
|
<el-input v-model="searchForm.remark" :placeholder="t('ProductionPlan.TaskSummary.scheduleFormRemarkPlaceholder')" clearable class="!w-220px" />
|
|
</el-form-item>
|
|
<el-form-item :label="t('ProductionPlan.TaskSummary.scheduleFormInventoryTaskLabel')">
|
|
<div class="flex items-center gap-8px">
|
|
<el-switch v-model="searchForm.inventoryTaskSchedule" />
|
|
</div>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="handleSearch">
|
|
<Icon icon="ep:search" class="mr-5px" /> {{ t('ProductionPlan.TaskSummary.scheduleFormSearchButton') }}
|
|
</el-button>
|
|
<el-button @click="handleReset">
|
|
<Icon icon="ep:refresh" class="mr-5px" /> {{ t('ProductionPlan.TaskSummary.scheduleFormResetButton') }}
|
|
</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<el-table
|
|
ref="taskTableRef"
|
|
v-loading="taskTableBusy"
|
|
:data="taskList"
|
|
border
|
|
:stripe="true"
|
|
:show-overflow-tooltip="true"
|
|
highlight-current-row
|
|
class="mt-12px"
|
|
row-key="id"
|
|
@current-change="handleCurrentTaskChange"
|
|
@select="handleTaskSelect"
|
|
@select-all="handleTaskSelectAll"
|
|
>
|
|
<el-table-column type="selection" width="55" :reserve-selection="true" align="center" :selectable="isTaskRowSelectable" />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableTaskCodeColumn')" align="center" prop="code" width="200px" sortable />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableOrderDateColumn')" align="center" prop="orderDate" :formatter="dateFormatter2" sortable />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableDeliveryDateColumn')" align="center" prop="deliveryDate" :formatter="dateFormatter2" sortable />
|
|
<el-table-column :label="t('ProductionPlan.Task.tableTaskTypeColumn')" align="center" prop="taskType" sortable>
|
|
<template #default="scope">
|
|
<dict-tag :type="DICT_TYPE.MES_TASK_TYPE" :value="scope.row.taskType" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableStatusColumn')" align="center" prop="status" sortable>
|
|
<template #default="scope">
|
|
<dict-tag :type="DICT_TYPE.MES_TASK_STATUS" :value="scope.row.status" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableScheduleCompletedColumn')" align="center">
|
|
<template #default="scope">
|
|
<el-tag :type="scope.row.isScheduled ? 'success' : 'info'">{{ scope.row.isScheduled ? t('ProductionPlan.TaskSummary.scheduleYesLabel') : t('ProductionPlan.TaskSummary.scheduleNoLabel') }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('ProductionPlan.Task.tableIsUrgentColumn')" align="center" prop="isUrgent" sortable>
|
|
<template #default="scope">
|
|
<el-tag :type="String(scope.row.isUrgent) === '1' ? 'danger' : 'info'">
|
|
{{ String(scope.row.isUrgent) === '1' ? t('ProductionPlan.Task.urgentYesLabel') : t('ProductionPlan.Task.urgentNoLabel') }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableRemarkColumn')" align="center" prop="remark" />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.tableOperateColumn')" align="center" min-width="100px">
|
|
<template #default="scope">
|
|
<el-button link type="info" @click="openTaskItemNeed(scope.row)">
|
|
{{ t('ProductionPlan.TaskSummary.actionMaterialLabel') }}
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<!-- 分页 -->
|
|
<Pagination
|
|
:total="total"
|
|
v-model:page="queryParams.pageNo"
|
|
v-model:limit="queryParams.pageSize"
|
|
@pagination="loadTaskList"
|
|
/>
|
|
|
|
<div class="text-18px font-bold mt-20px mb-12px">{{ t('ProductionPlan.TaskSummary.detailTabSummaryLabel') }}</div>
|
|
<el-table
|
|
ref="detailTableRef"
|
|
v-loading="detailLoading"
|
|
:data="detailList"
|
|
border
|
|
:stripe="true"
|
|
:show-overflow-tooltip="true"
|
|
row-key="id"
|
|
@select="handleDetailSelect"
|
|
@select-all="handleDetailSelectAll"
|
|
>
|
|
<el-table-column type="selection" width="55" :reserve-selection="true" align="center" :selectable="isDetailRowSelectable" />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTableTaskCodeColumn')" align="center" prop="taskCode" sortable width="200px"/>
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTableProductCodeColumn')" align="center" prop="barCode" sortable />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTableProductNameColumn')" align="center" prop="productName" sortable />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTableDeviceNameColumn')" align="center" prop="deviceDisplayName" />
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTableTotalNumberColumn')" align="center" prop="number" width="120px"/>
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTablePlanNumberColumn')" align="center" prop="planNumber" width="120px"/>
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTableUnplanNumberColumn')" align="center" width="120px">
|
|
<template #default="scope">
|
|
<span class="el-alert--warning" style="color: #e66126">
|
|
{{ scope.row.number - scope.row.planNumber > 0 ? scope.row.number - scope.row.planNumber : 0 }}
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('ProductionPlan.TaskSummary.detailTableOperateColumn')" align="center" width="180px">
|
|
<template #default="scope">
|
|
<el-button link type="info" @click="openProductItemNeed(scope.row)">
|
|
{{ t('ProductionPlan.TaskSummary.detailActionMaterialLabel') }}
|
|
</el-button>
|
|
<el-button link type="primary" @click="openDeviceRelationDialog(scope.row)">
|
|
{{ t('ProductionPlan.TaskSummary.relateDeviceButton') }}
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
|
|
</Dialog>
|
|
|
|
<TaskSchedulePreviewDialog
|
|
v-model="previewVisible"
|
|
:schedule-list="previewScheduleList"
|
|
@saved="handlePreviewSaved"
|
|
/>
|
|
|
|
<Dialog v-model="deviceRelationDialogVisible" :title="t('ProductionPlan.TaskSummary.deviceRelationDialogTitle')" width="520px">
|
|
<el-form :model="deviceRelationForm" label-width="90px" v-loading="deviceRelationLoading">
|
|
<el-form-item :label="t('ProductionPlan.TaskSummary.deviceRelationLabel')">
|
|
<el-input
|
|
:model-value="deviceRelationDisplayText"
|
|
:placeholder="t('ProductionPlan.TaskSummary.deviceRelationPlaceholder')"
|
|
readonly
|
|
@click="openDeviceSelectDialog"
|
|
/>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button type="primary" :loading="deviceRelationSaving" @click="submitDeviceRelation">{{ t('ProductionPlan.TaskSummary.deviceRelationSaveButton') }}</el-button>
|
|
<el-button @click="deviceRelationDialogVisible = false">{{ t('ProductionPlan.TaskSummary.deviceRelationCancelButton') }}</el-button>
|
|
</template>
|
|
</Dialog>
|
|
|
|
<TableSelectDialog
|
|
ref="deviceSelectDialogRef"
|
|
:title="t('ProductionPlan.TaskSummary.selectDeviceDialogTitle')"
|
|
:columns="deviceColumns"
|
|
:fetch-api="fetchDeviceLedgerPage"
|
|
row-key="id"
|
|
@confirm="handleDeviceSelectConfirm"
|
|
/>
|
|
|
|
<ItemNeedIndex ref="itemNeedRef" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { DICT_TYPE } from '@/utils/dict'
|
|
import { TaskApi } from '@/api/mes/task'
|
|
import { ProductApi } from '@/api/erp/product/product'
|
|
import { DeviceLedgerApi } from '@/api/mes/deviceledger'
|
|
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
|
|
import ItemNeedIndex from '@/views/mes/bom/ItemNeedIndex.vue'
|
|
import { dateFormatter2 } from '@/utils/formatTime'
|
|
import TaskSchedulePreviewDialog from './TaskSchedulePreviewDialog.vue'
|
|
|
|
defineOptions({ name: 'TaskScheduleDialog' })
|
|
|
|
const message = useMessage()
|
|
const { t } = useI18n() // 国际化
|
|
const emit = defineEmits(['success'])
|
|
|
|
const dialogVisible = ref(false)
|
|
const taskLoading = ref(false)
|
|
const detailLoading = ref(false)
|
|
const taskSelectionLoading = ref(false)
|
|
const submitLoading = ref(false)
|
|
const previewVisible = ref(false)
|
|
const itemNeedRef = ref()
|
|
const taskTableRef = ref()
|
|
const detailTableRef = ref()
|
|
const previewScheduleList = ref<any[]>([])
|
|
const deviceSelectDialogRef = ref()
|
|
const deviceRelationDialogVisible = ref(false)
|
|
const deviceRelationLoading = ref(false)
|
|
const deviceRelationSaving = ref(false)
|
|
const currentDeviceRelationRow = ref<any>()
|
|
const currentProductData = ref<any>()
|
|
const selectedDeviceRows = ref<any[]>([])
|
|
const deviceRelationForm = reactive({
|
|
productId: undefined as number | undefined,
|
|
devices: [] as { id: number; name: string }[]
|
|
})
|
|
|
|
const deviceColumns = computed(() => [
|
|
{ label: t('ProductionPlan.TaskSummary.deviceColumnCode'), prop: 'deviceCode', minWidth: 140 },
|
|
{ label: t('ProductionPlan.TaskSummary.deviceColumnName'), prop: 'deviceName', minWidth: 160 },
|
|
{ label: t('ProductionPlan.TaskSummary.deviceColumnModel'), prop: 'deviceModel', minWidth: 140 },
|
|
{ label: t('ProductionPlan.TaskSummary.deviceColumnWorkshop'), prop: 'workshop', minWidth: 140 }
|
|
])
|
|
const deviceRelationDisplayText = computed(() => {
|
|
if (!deviceRelationForm.devices.length) return ''
|
|
if (!selectedDeviceRows.value.length) return deviceRelationForm.devices.map((item) => item.name).join('、')
|
|
return selectedDeviceRows.value.map((item) => item.deviceName || item.name || item.code || `ID:${item.id}`).join('、')
|
|
})
|
|
|
|
const scheduleRuleOptions = computed(() => [
|
|
{ label: t('ProductionPlan.TaskSummary.scheduleRulePriority'), value: 2 },
|
|
{ label: t('ProductionPlan.TaskSummary.scheduleRuleCategory'), value: 3 },
|
|
{ label: t('ProductionPlan.TaskSummary.scheduleRuleDelivery'), value: 4 }
|
|
])
|
|
const capacityTypeOptions = computed(() => [
|
|
{ label: t('ProductionPlan.TaskSummary.capacityTypeRated'), value: 1 },
|
|
{ label: t('ProductionPlan.TaskSummary.capacityTypeDailyAvg'), value: 2 },
|
|
{ label: t('ProductionPlan.TaskSummary.capacityTypeDataCollection'), value: 3 }
|
|
])
|
|
|
|
const searchForm = reactive({
|
|
inventoryTaskSchedule: false,
|
|
skipHoliday: false,
|
|
sortRule: 2 as number | undefined,
|
|
capacityType: 1 as number | undefined,
|
|
workTimeRange: ['08:00', '18:00'] as string[],
|
|
code: '',
|
|
orderDate: [] as string[],
|
|
deliveryDate: [] as string[],
|
|
remark: ''
|
|
})
|
|
|
|
const total = ref(0)
|
|
const queryParams = reactive({
|
|
pageNo: 1,
|
|
pageSize: 10
|
|
})
|
|
|
|
const taskList = ref<any[]>([])
|
|
const detailList = ref<any[]>([])
|
|
const currentTask = ref<any>()
|
|
const allDetailsMap = ref<Record<number, any[]>>({}) // store details per taskId
|
|
const taskDetailRequestMap = new Map<number, Promise<any[]>>()
|
|
const currentDetailRequestId = ref(0)
|
|
const taskTableBusy = computed(() => taskLoading.value || taskSelectionLoading.value)
|
|
|
|
const getRelationName = (item: Record<string, any>, nameKeys: string[], fallbackPrefix: string, id: number) => {
|
|
for (const key of nameKeys) {
|
|
const value = item[key]
|
|
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
|
return String(value)
|
|
}
|
|
}
|
|
return `${fallbackPrefix}ID:${id}`
|
|
}
|
|
const normalizeRelationList = (
|
|
value: unknown,
|
|
fallbackRows: any[] | undefined,
|
|
nameKeys: string[],
|
|
fallbackPrefix: string
|
|
): { id: number; name: string }[] => {
|
|
const fallbackMap = new Map<number, string>()
|
|
;(fallbackRows || []).forEach((row) => {
|
|
const id = Number(row?.id)
|
|
if (!Number.isFinite(id)) return
|
|
fallbackMap.set(id, getRelationName(row, nameKeys, fallbackPrefix, id))
|
|
})
|
|
const parseArray = (source: unknown): any[] => {
|
|
if (Array.isArray(source)) return source
|
|
if (typeof source === 'string') {
|
|
const content = source.trim()
|
|
if (!content) return []
|
|
try {
|
|
const parsed = JSON.parse(content.startsWith('[') ? content : `[${content}]`)
|
|
return Array.isArray(parsed) ? parsed : []
|
|
} catch {
|
|
return content.split(',').map((item) => item.trim()).filter(Boolean)
|
|
}
|
|
}
|
|
return []
|
|
}
|
|
return parseArray(value)
|
|
.map((item) => {
|
|
if (typeof item === 'object' && item !== null) {
|
|
const id = Number((item as any).id)
|
|
if (!Number.isFinite(id)) return undefined
|
|
return {
|
|
id,
|
|
name: getRelationName(item as any, nameKeys, fallbackPrefix, id)
|
|
}
|
|
}
|
|
const id = Number(item)
|
|
if (!Number.isFinite(id)) return undefined
|
|
return {
|
|
id,
|
|
name: fallbackMap.get(id) || `${fallbackPrefix}ID:${id}`
|
|
}
|
|
})
|
|
.filter((item): item is { id: number; name: string } => Boolean(item))
|
|
}
|
|
const toDeviceRows = (devices: { id: number; name: string }[]) => {
|
|
return devices.map((item) => ({
|
|
id: item.id,
|
|
deviceName: item.name,
|
|
name: item.name
|
|
}))
|
|
}
|
|
const buildRelationIdList = (list: { id: number; name: string }[]) => {
|
|
return list.map((item) => Number(item.id)).filter((id) => Number.isFinite(id))
|
|
}
|
|
const fetchDeviceLedgerPage = (params: Record<string, any>) => {
|
|
return DeviceLedgerApi.getDeviceLedgerPage({
|
|
...params,
|
|
isScheduled: 1
|
|
})
|
|
}
|
|
|
|
const loadTaskList = async () => {
|
|
taskLoading.value = true
|
|
try {
|
|
const params: any = {
|
|
isScheduled: 0,
|
|
pageNo: queryParams.pageNo,
|
|
pageSize: queryParams.pageSize,
|
|
code: searchForm.code || undefined,
|
|
orderDate: searchForm.orderDate,
|
|
deliveryDate: searchForm.deliveryDate,
|
|
remark: searchForm.remark || undefined,
|
|
taskType: searchForm.inventoryTaskSchedule ? t('ProductionPlan.TaskSummary.inventoryTaskType') : undefined
|
|
}
|
|
const data = await TaskApi.getPlanTaskPage(params)
|
|
taskList.value = data?.list ?? []
|
|
total.value = data?.total ?? 0
|
|
if (taskList.value.length) {
|
|
await handleCurrentTaskChange(taskList.value[0])
|
|
} else {
|
|
currentTask.value = undefined
|
|
detailList.value = []
|
|
}
|
|
} finally {
|
|
taskLoading.value = false
|
|
}
|
|
}
|
|
|
|
const getTaskDetailList = async (taskRow?: any) => {
|
|
const taskId = taskRow?.id
|
|
if (!taskId) return []
|
|
|
|
if (allDetailsMap.value[taskId]) {
|
|
return allDetailsMap.value[taskId]
|
|
}
|
|
|
|
const pendingRequest = taskDetailRequestMap.get(taskId)
|
|
if (pendingRequest) {
|
|
return pendingRequest
|
|
}
|
|
|
|
const requestPromise = TaskApi.getTaskDetailPage({
|
|
pageNo: 1,
|
|
pageSize: 10,
|
|
taskId
|
|
})
|
|
.then((data: any) => {
|
|
const list = (data?.list ?? []).map((item: any) => ({
|
|
...item,
|
|
_parentTaskOrderPriority: taskRow?.isUrgent,
|
|
_parentTaskDeliveryDate: taskRow?.deliveryDate,
|
|
deviceDisplayName: item.deviceDisplayName || item.deviceName || item.feedingPipelineName || ''
|
|
}))
|
|
allDetailsMap.value[taskId] = list
|
|
return list
|
|
})
|
|
.finally(() => {
|
|
taskDetailRequestMap.delete(taskId)
|
|
})
|
|
|
|
taskDetailRequestMap.set(taskId, requestPromise)
|
|
return requestPromise
|
|
}
|
|
|
|
const handleCurrentTaskChange = async (row: any) => {
|
|
if (!row) return
|
|
currentTask.value = row
|
|
const requestId = ++currentDetailRequestId.value
|
|
detailLoading.value = true
|
|
try {
|
|
const list = await getTaskDetailList(row)
|
|
if (requestId !== currentDetailRequestId.value || currentTask.value?.id !== row.id) return
|
|
detailList.value = list
|
|
nextTick(() => {
|
|
const parentIsSelected = taskTableRef.value?.getSelectionRows()?.some((t: any) => t.id === row.id)
|
|
if (parentIsSelected) {
|
|
detailList.value.forEach(child => {
|
|
const unplanNumber = child.number - child.planNumber
|
|
if (unplanNumber > 0) {
|
|
detailTableRef.value?.toggleRowSelection(child, true)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
} finally {
|
|
if (requestId === currentDetailRequestId.value) {
|
|
detailLoading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleTaskSelect = async (selection: any[], row: any) => {
|
|
if (taskSelectionLoading.value) return
|
|
taskSelectionLoading.value = true
|
|
try {
|
|
const isSelected = selection.some((item) => item.id === row.id)
|
|
if (isSelected) {
|
|
await handleCurrentTaskChange(row)
|
|
const childList = allDetailsMap.value[row.id] || []
|
|
if (childList.length === 0) {
|
|
message.warning(t('ProductionPlan.TaskSummary.taskNoDetailWarning', { code: row.code }))
|
|
taskTableRef.value?.toggleRowSelection(row, false)
|
|
return
|
|
}
|
|
detailList.value.forEach((child) => {
|
|
const unplanNumber = child.number - child.planNumber
|
|
if (unplanNumber > 0) {
|
|
detailTableRef.value?.toggleRowSelection(child, true)
|
|
}
|
|
})
|
|
return
|
|
}
|
|
|
|
const childList = allDetailsMap.value[row.id] || []
|
|
if (currentTask.value?.id === row.id) {
|
|
childList.forEach((child) => {
|
|
const rowInView = detailList.value.find(d => d.id === child.id)
|
|
if (rowInView) {
|
|
detailTableRef.value?.toggleRowSelection(rowInView, false)
|
|
}
|
|
})
|
|
}
|
|
} finally {
|
|
taskSelectionLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleTaskSelectAll = async (selection: any[]) => {
|
|
if (taskSelectionLoading.value) return
|
|
taskSelectionLoading.value = true
|
|
try {
|
|
const isAllSelected = selection.length > 0
|
|
if (isAllSelected) {
|
|
let hasEmptyChild = false
|
|
for (const row of selection) {
|
|
const childList = await getTaskDetailList(row)
|
|
if (childList.length === 0) {
|
|
hasEmptyChild = true
|
|
taskTableRef.value?.toggleRowSelection(row, false)
|
|
} else {
|
|
childList.forEach((child) => {
|
|
const unplanNumber = child.number - child.planNumber
|
|
if (unplanNumber > 0) {
|
|
const rowInView = detailList.value.find(d => d.id === child.id)
|
|
if (rowInView) {
|
|
detailTableRef.value?.toggleRowSelection(rowInView, true)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
if (hasEmptyChild) {
|
|
message.warning(t('ProductionPlan.TaskSummary.partialTaskNoDetailWarning'))
|
|
}
|
|
} else {
|
|
detailTableRef.value?.clearSelection()
|
|
}
|
|
} finally {
|
|
taskSelectionLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleDetailSelect = (selection: any[], row: any) => {
|
|
const taskId = row.taskId
|
|
const childList = allDetailsMap.value[taskId] || []
|
|
const selectableChildren = childList.filter(child => {
|
|
const unplanNumber = child.number - child.planNumber
|
|
return unplanNumber > 0
|
|
})
|
|
|
|
const allChildrenSelected = selectableChildren.length > 0 && selectableChildren.every(child => selection.some(sel => sel.id === child.id))
|
|
const noChildrenSelected = selectableChildren.every(child => !selection.some(sel => sel.id === child.id))
|
|
|
|
const parentTask = taskList.value.find(task => task.id === taskId)
|
|
if (parentTask) {
|
|
if (allChildrenSelected) {
|
|
taskTableRef.value?.toggleRowSelection(parentTask, true)
|
|
} else if (noChildrenSelected) {
|
|
taskTableRef.value?.toggleRowSelection(parentTask, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleDetailSelectAll = (selection: any[]) => {
|
|
if (!currentTask.value?.id) return
|
|
|
|
const taskId = currentTask.value.id
|
|
const childList = allDetailsMap.value[taskId] || []
|
|
const selectableChildren = childList.filter(child => {
|
|
const unplanNumber = child.number - child.planNumber
|
|
return unplanNumber > 0
|
|
})
|
|
|
|
const parentTask = taskList.value.find(task => task.id === taskId)
|
|
if (parentTask) {
|
|
const currentViewSelection = selection.filter(sel => sel.taskId === taskId)
|
|
|
|
if (selectableChildren.length > 0 && currentViewSelection.length === selectableChildren.length) {
|
|
taskTableRef.value?.toggleRowSelection(parentTask, true)
|
|
} else if (currentViewSelection.length === 0) {
|
|
taskTableRef.value?.toggleRowSelection(parentTask, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
const isTaskRowSelectable = () => !taskTableBusy.value && !detailLoading.value
|
|
|
|
const isDetailRowSelectable = (row: any) => {
|
|
const unplanNumber = row.number - row.planNumber
|
|
return unplanNumber > 0
|
|
}
|
|
|
|
const handleSearch = async () => {
|
|
queryParams.pageNo = 1
|
|
await loadTaskList()
|
|
}
|
|
|
|
const handleReset = async () => {
|
|
searchForm.inventoryTaskSchedule = false
|
|
searchForm.skipHoliday = false
|
|
searchForm.sortRule = undefined
|
|
searchForm.capacityType = 1
|
|
searchForm.workTimeRange = ['08:00', '18:00']
|
|
searchForm.code = ''
|
|
searchForm.orderDate = []
|
|
searchForm.deliveryDate = []
|
|
searchForm.remark = ''
|
|
queryParams.pageNo = 1
|
|
await loadTaskList()
|
|
}
|
|
|
|
const openTaskItemNeed = (row: any) => {
|
|
if (!row?.id) return
|
|
itemNeedRef.value.open('task', t('ProductionPlan.TaskSummary.taskItemNeedPrefix') + (row?.code ?? ''), row.id)
|
|
}
|
|
|
|
const openProductItemNeed = (row: any) => {
|
|
if (!row?.productId) return
|
|
const number = row.number - row.planNumber > 0 ? row.number - row.planNumber : 0
|
|
itemNeedRef.value.open('product', row.productName, row.productId, number)
|
|
}
|
|
const openDeviceRelationDialog = async (row: any) => {
|
|
if (!row?.productId) {
|
|
message.warning(t('ProductionPlan.TaskSummary.detailNoProductWarning'))
|
|
return
|
|
}
|
|
currentDeviceRelationRow.value = row
|
|
deviceRelationDialogVisible.value = true
|
|
deviceRelationLoading.value = true
|
|
selectedDeviceRows.value = []
|
|
try {
|
|
const productData = await ProductApi.getProduct(Number(row.productId))
|
|
currentProductData.value = productData
|
|
const devices = normalizeRelationList(
|
|
(productData as any).devices ?? (productData as any).deviceIds,
|
|
(productData as any).deviceList,
|
|
['name', 'deviceName', 'code'],
|
|
t('ProductionPlan.TaskSummary.deviceColumnName')
|
|
)
|
|
deviceRelationForm.productId = Number(row.productId)
|
|
deviceRelationForm.devices = devices
|
|
selectedDeviceRows.value = toDeviceRows(devices)
|
|
} finally {
|
|
deviceRelationLoading.value = false
|
|
}
|
|
}
|
|
const openDeviceSelectDialog = () => {
|
|
const rows = selectedDeviceRows.value.length
|
|
? selectedDeviceRows.value.map((item) => ({ ...item, id: Number(item.id) }))
|
|
: toDeviceRows(deviceRelationForm.devices)
|
|
deviceSelectDialogRef.value?.open(rows)
|
|
}
|
|
const handleDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
|
|
deviceRelationForm.devices = payload.rows
|
|
.map((item) => {
|
|
const id = Number(item.id)
|
|
if (!Number.isFinite(id)) return undefined
|
|
return {
|
|
id,
|
|
name: item.deviceName || item.name || item.code || `${t('ProductionPlan.TaskSummary.deviceColumnName')}ID:${id}`
|
|
}
|
|
})
|
|
.filter((item): item is { id: number; name: string } => Boolean(item))
|
|
selectedDeviceRows.value = payload.rows
|
|
}
|
|
const refreshDetailListAfterDeviceSaved = async () => {
|
|
const taskId = Number(currentDeviceRelationRow.value?.taskId)
|
|
if (!Number.isFinite(taskId)) return
|
|
delete allDetailsMap.value[taskId]
|
|
if (Number(currentTask.value?.id) === taskId && currentTask.value) {
|
|
await handleCurrentTaskChange(currentTask.value)
|
|
}
|
|
}
|
|
const submitDeviceRelation = async () => {
|
|
if (!currentProductData.value || !deviceRelationForm.productId) return
|
|
deviceRelationSaving.value = true
|
|
try {
|
|
const currentData = currentProductData.value as any
|
|
const moldIds = normalizeRelationList(
|
|
currentData.molds ?? currentData.moldIds,
|
|
currentData.moldList,
|
|
['name', 'code'],
|
|
t('ProductionPlan.TaskSummary.deviceColumnName')
|
|
)
|
|
const payload: any = {
|
|
...currentData,
|
|
deviceIds: buildRelationIdList(deviceRelationForm.devices),
|
|
moldIds: buildRelationIdList(moldIds)
|
|
}
|
|
delete payload.devices
|
|
delete payload.molds
|
|
await ProductApi.updateProduct(payload)
|
|
await refreshDetailListAfterDeviceSaved()
|
|
message.success(t('ProductionPlan.TaskSummary.deviceRelationSaved'))
|
|
deviceRelationDialogVisible.value = false
|
|
} finally {
|
|
deviceRelationSaving.value = false
|
|
}
|
|
}
|
|
|
|
const handlePreviewSaved = async () => {
|
|
dialogVisible.value = false
|
|
await loadTaskList()
|
|
emit('success')
|
|
}
|
|
|
|
const hasValue = (value: unknown) => value !== null && value !== undefined && value !== ''
|
|
|
|
const handleSubmit = async () => {
|
|
if (searchForm.sortRule === undefined) {
|
|
message.warning(t('ProductionPlan.TaskSummary.selectScheduleRuleWarning'))
|
|
return
|
|
}
|
|
if (searchForm.capacityType === undefined) {
|
|
message.warning(t('ProductionPlan.TaskSummary.selectCapacitySourceWarning'))
|
|
return
|
|
}
|
|
const [workStartTime, workEndTime] = Array.isArray(searchForm.workTimeRange) ? searchForm.workTimeRange : []
|
|
if (!workStartTime || !workEndTime) {
|
|
message.warning(t('ProductionPlan.TaskSummary.selectWorkTimeWarning'))
|
|
return
|
|
}
|
|
|
|
const selectedRows = detailTableRef.value?.getSelectionRows() || []
|
|
const filteredRows = selectedRows.filter((row: any) => {
|
|
const unplanNumber = row.number - row.planNumber
|
|
return unplanNumber > 0
|
|
})
|
|
if (filteredRows.length === 0) {
|
|
message.warning(t('ProductionPlan.TaskSummary.selectDetailWarning'))
|
|
return
|
|
}
|
|
submitLoading.value = true
|
|
try {
|
|
console.log(filteredRows)
|
|
const createReqVO = filteredRows.map((row: any) => {
|
|
const planNumber = row.number - row.planNumber > 0 ? row.number - row.planNumber : 0
|
|
return {
|
|
// PlanForm fields
|
|
productId: row.productId,
|
|
taskId: row.taskId,
|
|
taskDetailId: row.id,
|
|
planNumber: planNumber,
|
|
finishNumber: 0,
|
|
isPreProduction: 0,
|
|
isCode: true,
|
|
planStartTime: new Date().getTime(),
|
|
planEndTime: new Date().getTime(),
|
|
productCode: row.barCode,
|
|
|
|
// Image fields
|
|
orderPriority: row.isUrgent || row._parentTaskOrderPriority,
|
|
workOrderCode: row.taskCode,
|
|
deliveryDate: row._parentTaskDeliveryDate,
|
|
orderDetailDeliveryDate: hasValue(row.finishDate) ? row.finishDate : row._parentTaskDeliveryDate,
|
|
orderDetailId: row.id
|
|
}
|
|
})
|
|
|
|
const scheduleResult = await TaskApi.oneClickSchedule({
|
|
createReqVO,
|
|
sortRule: searchForm.sortRule,
|
|
capacityType: searchForm.capacityType,
|
|
workStartTime,
|
|
workEndTime,
|
|
skipHoliday: searchForm.skipHoliday
|
|
})
|
|
const scheduleData = Array.isArray(scheduleResult)
|
|
? scheduleResult
|
|
: Array.isArray((scheduleResult as any)?.data)
|
|
? (scheduleResult as any).data
|
|
: []
|
|
previewScheduleList.value = scheduleData
|
|
message.success(t('ProductionPlan.TaskSummary.scheduleSubmitted'))
|
|
previewVisible.value = true
|
|
emit('success')
|
|
} finally {
|
|
submitLoading.value = false
|
|
}
|
|
}
|
|
|
|
const open = async () => {
|
|
dialogVisible.value = true
|
|
await loadTaskList()
|
|
}
|
|
|
|
defineExpose({ open })
|
|
</script>
|