| | |
| | | <el-dialog v-model="reportDialogVisible" |
| | | :title="`报工(工单编号:${currentReportRowData?.workOrderNo || '-'})`" |
| | | width="1000px"> |
| | | <el-form |
| | | ref="reportFormRef" |
| | | <el-form ref="reportFormRef" |
| | | :model="reportForm" |
| | | :rules="reportFormRules" |
| | | label-width="120px" |
| | | > |
| | | label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | |
| | | <el-col :span="12"> |
| | | <el-form-item label="待生产数量"> |
| | | <el-input v-model="reportForm.planQuantity" readonly disabled/> |
| | | <el-input v-model="reportForm.planQuantity" |
| | | readonly |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-form-item label="本次生产数量" prop="quantity"> |
| | | <el-input |
| | | v-model.number="reportForm.quantity" |
| | | <el-form-item label="本次生产数量" |
| | | prop="quantity"> |
| | | <el-input v-model.number="reportForm.quantity" |
| | | type="number" |
| | | min="1" |
| | | step="1" |
| | | placeholder="请输入本次生产数量" |
| | | style="width: 100%" |
| | | :class="{ 'over-limit': reportForm.quantity > reportForm.planQuantity }" |
| | | @input="handleQuantityInput" |
| | | /> |
| | | @input="handleQuantityInput" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-form-item label="补产数量" prop="replenishQty"> |
| | | <el-input |
| | | v-model.number="reportForm.replenishQty" |
| | | <el-form-item label="补产数量" |
| | | prop="replenishQty"> |
| | | <el-input v-model.number="reportForm.replenishQty" |
| | | type="number" |
| | | min="0" |
| | | step="1" |
| | | placeholder="请输入补产数量" |
| | | /> |
| | | placeholder="请输入补产数量" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-form-item label="报废数量" prop="scrapQty"> |
| | | <el-input |
| | | v-model.number="reportForm.scrapQty" |
| | | <el-form-item label="报废数量" |
| | | prop="scrapQty"> |
| | | <el-input v-model.number="reportForm.scrapQty" |
| | | type="number" |
| | | min="0" |
| | | step="1" |
| | | placeholder="请输入报废数量" |
| | | @input="handleScrapQtyInput" |
| | | /> |
| | | @input="handleScrapQtyInput" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-form-item label="加放数" prop="addQty"> |
| | | <el-input |
| | | v-model.number="reportForm.addQty" |
| | | <el-form-item label="加放数" |
| | | prop="addQty"> |
| | | <el-input v-model.number="reportForm.addQty" |
| | | type="number" |
| | | min="0" |
| | | step="1" |
| | | placeholder="请输入加放数" |
| | | /> |
| | | placeholder="请输入加放数" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="12"> |
| | | <el-form-item label="班组信息" prop="teamList"> |
| | | <el-select |
| | | v-model="reportForm.teamList" |
| | | <el-form-item label="班组信息" |
| | | prop="teamList"> |
| | | <el-select v-model="reportForm.teamList" |
| | | multiple |
| | | filterable |
| | | allow-create |
| | | default-first-option |
| | | clearable |
| | | collapse-tags |
| | | value-key="userId" |
| | | placeholder="请选择班组成员" |
| | | > |
| | | <el-option |
| | | v-for="user in reportForm.userIdsList" |
| | | value-key="userName" |
| | | placeholder="请选择或输入班组成员" |
| | | @change="handleTeamListChange"> |
| | | <el-option v-for="user in reportForm.userIdsList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="{ userId: user.userId, userName: user.nickName }" |
| | | /> |
| | | :value="{ userId: user.userId, userName: user.nickName }" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <el-form-item label="机台" prop="deviceId">--> |
| | | <!-- <el-select--> |
| | |
| | | <!-- </el-select>--> |
| | | <!-- </el-form-item>--> |
| | | <!-- </el-col>--> |
| | | |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <el-form-item label="审核人" prop="auditUserId">--> |
| | | <!-- <el-select--> |
| | |
| | | <!-- </el-select>--> |
| | | <!-- </el-form-item>--> |
| | | <!-- </el-col>--> |
| | | |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog |
| | | v-model="auditDialogVisible" |
| | | <el-dialog v-model="auditDialogVisible" |
| | | title="审核" |
| | | width="1000px" |
| | | :close-on-click-modal="false" |
| | | > |
| | | <el-table :data="auditTableData" border style="width: 100%" v-loading="auditLoading"> |
| | | <el-table-column label="产品名称" prop="productName" min-width="140" show-overflow-tooltip/> |
| | | <el-table-column label="规格" prop="model" min-width="120" show-overflow-tooltip/> |
| | | <el-table-column label="单位" prop="unit" width="80"/> |
| | | <el-table-column label="工序名称" prop="processName" min-width="120" show-overflow-tooltip/> |
| | | <el-table-column label="需求数量" prop="planQuantity" width="110"/> |
| | | <el-table-column label="完成数量" prop="completeQuantity" width="110"/> |
| | | <el-table-column label="完成进度" prop="completionStatus" width="140"> |
| | | :close-on-click-modal="false"> |
| | | <el-table :data="auditTableData" |
| | | border |
| | | style="width: 100%" |
| | | v-loading="auditLoading"> |
| | | <el-table-column label="产品名称" |
| | | prop="productName" |
| | | min-width="140" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="规格" |
| | | prop="model" |
| | | min-width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="单位" |
| | | prop="unit" |
| | | width="80" /> |
| | | <el-table-column label="工序名称" |
| | | prop="processName" |
| | | min-width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="需求数量" |
| | | prop="planQuantity" |
| | | width="110" /> |
| | | <el-table-column label="完成数量" |
| | | prop="completeQuantity" |
| | | width="110" /> |
| | | <el-table-column label="完成进度" |
| | | prop="completionStatus" |
| | | width="140"> |
| | | <template #default="{ row }"> |
| | | <el-progress |
| | | :percentage="toProgressPercentage(row?.completionStatus)" |
| | | <el-progress :percentage="toProgressPercentage(row?.completionStatus)" |
| | | :color="progressColor(toProgressPercentage(row?.completionStatus))" |
| | | :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" |
| | | /> |
| | | :status="toProgressPercentage(row?.completionStatus) >= 100 ? 'success' : ''" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="计划开始时间" prop="planStartTime" width="140"/> |
| | | <el-table-column label="计划结束时间" prop="planEndTime" width="140"/> |
| | | <el-table-column label="计划开始时间" |
| | | prop="planStartTime" |
| | | width="140" /> |
| | | <el-table-column label="计划结束时间" |
| | | prop="planEndTime" |
| | | width="140" /> |
| | | </el-table> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" :loading="auditLoading" @click="submitAudit(1)">通过</el-button> |
| | | <el-button type="danger" :loading="auditLoading" @click="submitAudit(2)">不通过</el-button> |
| | | <el-button :disabled="auditLoading" @click="auditDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" |
| | | :loading="auditLoading" |
| | | @click="submitAudit(1)">通过</el-button> |
| | | <el-button type="danger" |
| | | :loading="auditLoading" |
| | | @click="submitAudit(2)">不通过</el-button> |
| | | <el-button :disabled="auditLoading" |
| | | @click="auditDialogVisible = false">取消</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | width="1000px" |
| | | :close-on-click-modal="false"> |
| | | <div class="schedule-panel"> |
| | | <el-row v-if="!isScheduleHistoryMode" style="margin-bottom: 12px;"> |
| | | <el-row v-if="!isScheduleHistoryMode" |
| | | style="margin-bottom: 12px;"> |
| | | <el-col> |
| | | <el-button type="primary" plain :disabled="scheduleLoading || scheduleSaving" @click="addScheduleRow"> |
| | | <el-button type="primary" |
| | | plain |
| | | :disabled="scheduleLoading || scheduleSaving" |
| | | @click="addScheduleRow"> |
| | | 新增一行 |
| | | </el-button> |
| | | </el-col> |
| | | </el-row> |
| | | <el-table :data="scheduleRows" border style="width: 100%" v-loading="scheduleLoading"> |
| | | <el-table-column type="index" label="序号" width="70" align="center" :index="indexMethod" /> |
| | | <el-table-column label="本次上机机台" min-width="220"> |
| | | <el-table :data="scheduleRows" |
| | | border |
| | | style="width: 100%" |
| | | v-loading="scheduleLoading"> |
| | | <el-table-column type="index" |
| | | label="序号" |
| | | width="70" |
| | | align="center" |
| | | :index="indexMethod" /> |
| | | <el-table-column label="本次上机机台" |
| | | min-width="220"> |
| | | <template #default="{ row }"> |
| | | <el-select |
| | | v-model="row.deviceId" |
| | | <el-select v-model="row.deviceId" |
| | | placeholder="请选择机台" |
| | | filterable |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="scheduleSaving || isScheduleHistoryMode" |
| | | @change="val => handleScheduleDeviceChange(val, row)" |
| | | > |
| | | <el-option |
| | | v-for="item in deviceOptions" |
| | | @change="val => handleScheduleDeviceChange(val, row)"> |
| | | <el-option v-for="item in deviceOptions" |
| | | :key="item.id" |
| | | :label="item.deviceName" |
| | | :value="String(item.id)" |
| | | /> |
| | | :value="String(item.id)" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="本次上机人" min-width="220"> |
| | | <el-table-column label="本次上机人" |
| | | min-width="220"> |
| | | <template #default="{ row }"> |
| | | <div v-if="isScheduleHistoryMode" class="schedule-user-tags"> |
| | | <el-tag |
| | | v-for="(userId, index) in row.userIds" |
| | | <div v-if="isScheduleHistoryMode" |
| | | class="schedule-user-tags"> |
| | | <el-tag v-for="(userId, index) in row.userIds" |
| | | :key="`${userId}-${index}`" |
| | | type="primary" |
| | | > |
| | | type="primary"> |
| | | {{ resolveScheduleUserName(userId) }} |
| | | </el-tag> |
| | | <span v-if="!row.userIds?.length">-</span> |
| | | </div> |
| | | <el-select |
| | | v-else |
| | | <el-select v-else |
| | | v-model="row.userIds" |
| | | placeholder="请选择上机人" |
| | | filterable |
| | |
| | | collapse-tags |
| | | style="width: 100%" |
| | | :disabled="scheduleSaving" |
| | | @change="val => handleScheduleUserChange(val, row)" |
| | | > |
| | | <el-option |
| | | v-for="user in row.userOptions" |
| | | @change="val => handleScheduleUserChange(val, row)"> |
| | | <el-option v-for="user in row.userOptions" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="String(user.userId)" |
| | | /> |
| | | :value="String(user.userId)" /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column label="本次上机时间" min-width="240"> |
| | | <el-table-column label="本次上机时间" |
| | | min-width="240"> |
| | | <template #default="{ row }"> |
| | | <el-date-picker |
| | | v-model="row.startTime" |
| | | <el-date-picker v-model="row.startTime" |
| | | type="datetime" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | placeholder="请选择上机时间" |
| | | style="width: 100%" |
| | | :disabled="scheduleSaving || isScheduleHistoryMode" |
| | | /> |
| | | :disabled="scheduleSaving || isScheduleHistoryMode" /> |
| | | </template> |
| | | </el-table-column> |
| | | |
| | | <el-table-column v-if="!isScheduleHistoryMode" label="操作" width="110" align="center"> |
| | | <el-table-column v-if="!isScheduleHistoryMode" |
| | | label="操作" |
| | | width="110" |
| | | align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | | link |
| | | <el-button link |
| | | type="danger" |
| | | :loading="row.deleting" |
| | | :disabled="scheduleSaving" |
| | | @click="removeScheduleRow(row)" |
| | | > |
| | | @click="removeScheduleRow(row)"> |
| | | 删除 |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <Pagination |
| | | v-show="isScheduleHistoryMode && schedulePage.total > 0" |
| | | <Pagination v-show="isScheduleHistoryMode && schedulePage.total > 0" |
| | | style="margin-top: 12px" |
| | | :total="schedulePage.total" |
| | | :page="schedulePage.current" |
| | | :limit="schedulePage.size" |
| | | @pagination="handleSchedulePagination" |
| | | /> |
| | | @pagination="handleSchedulePagination" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <template v-if="isScheduleHistoryMode"> |
| | | <el-button @click="scheduleDialogVisible = false">关闭</el-button> |
| | | </template> |
| | | <template v-else> |
| | | <el-button type="primary" :loading="scheduleSaving" @click="handleSaveSchedule">保存排产</el-button> |
| | | <el-button :disabled="scheduleSaving" @click="scheduleDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" |
| | | :loading="scheduleSaving" |
| | | @click="handleSaveSchedule">保存排产</el-button> |
| | | <el-button :disabled="scheduleSaving" |
| | | @click="scheduleDialogVisible = false">取消</el-button> |
| | | </template> |
| | | </span> |
| | | </template> |
| | |
| | | |
| | | <script setup> |
| | | import {onMounted, ref, nextTick, computed} from "vue"; |
| | | import {deepClone} from "@/utils/index.js" |
| | | import { deepClone } from "@/utils/index.js"; |
| | | import {ElMessageBox, ElMessage} from "element-plus"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import dayjs from "dayjs"; |
| | | import { processList } from '@/api/productionManagement/productionProcess.js' |
| | | import { processList } from "@/api/productionManagement/productionProcess.js"; |
| | | import { |
| | | productWorkOrderPage, |
| | | updateProductWorkOrder, |
| | |
| | | downProductWorkOrder, |
| | | addProductionMachineRecord, |
| | | productionMachineRecordListPage, |
| | | deleteProductionMachineRecord |
| | | deleteProductionMachineRecord, |
| | | } from "@/api/productionManagement/workOrder.js"; |
| | | import {getUserProfile, userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import QRCode from "qrcode"; |
| | |
| | | const {proxy} = getCurrentInstance(); |
| | | |
| | | const currentUserId = ref(""); |
| | | const deviceOptions = ref([]) |
| | | const deviceOptions = ref([]); |
| | | const currentUserName = ref(""); |
| | | |
| | | const ensureCurrentUser = async () => { |
| | |
| | | // 机台获取 |
| | | const getDeviceList = () => { |
| | | getDeviceLedger().then(res => { |
| | | deviceOptions.value = Array.isArray(res?.data) ? res.data : [] |
| | | }) |
| | | } |
| | | deviceOptions.value = Array.isArray(res?.data) ? res.data : []; |
| | | }); |
| | | }; |
| | | |
| | | const handleDeviceChange = (val) => { |
| | | const device = deviceOptions.value.find(item => item.id === val) |
| | | reportForm.deviceName = device?.deviceName || "" |
| | | reportForm.deviceId = val || "" |
| | | } |
| | | const handleDeviceChange = val => { |
| | | const device = deviceOptions.value.find(item => item.id === val); |
| | | reportForm.deviceName = device?.deviceName || ""; |
| | | reportForm.deviceId = val || ""; |
| | | }; |
| | | |
| | | const normalizeArray = (val) => { |
| | | const normalizeArray = val => { |
| | | if (val === null || val === undefined) return []; |
| | | if (Array.isArray(val)) return val; |
| | | if (typeof val === "string") { |
| | | return val |
| | | .split(/[,,;;\s]+/g) |
| | | .map((s) => s.trim()) |
| | | .map(s => s.trim()) |
| | | .filter(Boolean); |
| | | } |
| | | return [val]; |
| | | }; |
| | | |
| | | const isCurrentUserInUserIds = (row) => { |
| | | const isCurrentUserInUserIds = row => { |
| | | const uid = String(currentUserId.value || ""); |
| | | if (!uid) return false; |
| | | |
| | |
| | | }; |
| | | |
| | | // 判断当前用户是否在工序报工人中 |
| | | const isCurrentUserInProcessUserIds = (row) => { |
| | | const isCurrentUserInProcessUserIds = row => { |
| | | const uid = String(currentUserId.value || ""); |
| | | if (!uid) return false; |
| | | |
| | |
| | | }; |
| | | |
| | | // 判断当前用户是否可以报工(工单报工人 或 工序报工人) |
| | | const canCurrentUserReport = (row) => { |
| | | const canCurrentUserReport = row => { |
| | | return isCurrentUserInUserIds(row) || isCurrentUserInProcessUserIds(row); |
| | | }; |
| | | |
| | | const canOperateByReportWorker = computed(() => { |
| | | return (row) => isCurrentUserReportWorker(row); |
| | | return row => isCurrentUserReportWorker(row); |
| | | }); |
| | | |
| | | const isRowScheduled = (row) => { |
| | | const isRowScheduled = row => { |
| | | const ids = normalizeArray(row?.userIds) |
| | | .map((val) => String(val)) |
| | | .map(val => String(val)) |
| | | .filter(Boolean); |
| | | if (!ids.length) return false; |
| | | return ids.some((val) => val !== "0"); |
| | | return ids.some(val => val !== "0"); |
| | | }; |
| | | |
| | | const buildBaseScheduleUsersByRow = (row) => { |
| | | const buildBaseScheduleUsersByRow = row => { |
| | | if (!row) return []; |
| | | |
| | | if (Array.isArray(row?.reportWorkerList) && row.reportWorkerList.length > 0) { |
| | | const mapped = row.reportWorkerList |
| | | .map((item) => { |
| | | .map(item => { |
| | | const userId = String(item?.userId ?? item?.id ?? "").trim(); |
| | | const nickName = String(item?.userName ?? item?.nickName ?? "").trim(); |
| | | return { userId, nickName: nickName || userId }; |
| | | }) |
| | | .filter((item) => item.userId); |
| | | .filter(item => item.userId); |
| | | const uniq = new Map(); |
| | | mapped.forEach((item) => uniq.set(String(item.userId), item)); |
| | | mapped.forEach(item => uniq.set(String(item.userId), item)); |
| | | return Array.from(uniq.values()); |
| | | } |
| | | |
| | |
| | | row.reportUserId, |
| | | row.userId, |
| | | ] |
| | | .flatMap((v) => normalizeArray(v)) |
| | | .map((v) => String(v).trim()) |
| | | .flatMap(v => normalizeArray(v)) |
| | | .map(v => String(v).trim()) |
| | | .filter(Boolean); |
| | | |
| | | if (configuredIds.length > 0) { |
| | | const uniqIds = Array.from(new Set(configuredIds)); |
| | | return uniqIds.map((id) => { |
| | | const user = userTeamOptions.value.find((u) => String(u.userId) === String(id)); |
| | | return uniqIds.map(id => { |
| | | const user = userTeamOptions.value.find( |
| | | u => String(u.userId) === String(id) |
| | | ); |
| | | return { userId: String(id), nickName: user?.nickName || String(id) }; |
| | | }); |
| | | } |
| | | |
| | | return userTeamOptions.value.map((u) => ({ |
| | | return userTeamOptions.value.map(u => ({ |
| | | userId: String(u.userId), |
| | | nickName: u.nickName, |
| | | })); |
| | | }; |
| | | |
| | | const resolveScheduleUserName = (userId) => { |
| | | const resolveScheduleUserName = userId => { |
| | | const uid = String(userId ?? "").trim(); |
| | | if (!uid) return ""; |
| | | const inBase = baseScheduleUsers.value.find((u) => String(u.userId) === uid); |
| | | const inBase = baseScheduleUsers.value.find(u => String(u.userId) === uid); |
| | | if (inBase?.nickName) return inBase.nickName; |
| | | const inTeam = userTeamOptions.value.find((u) => String(u.userId) === uid); |
| | | const inTeam = userTeamOptions.value.find(u => String(u.userId) === uid); |
| | | return inTeam?.nickName || uid; |
| | | }; |
| | | |
| | | const buildScheduleUserOptionsByDeviceId = (deviceId) => { |
| | | const device = deviceOptions.value.find((item) => String(item.id) === String(deviceId)); |
| | | const buildScheduleUserOptionsByDeviceId = deviceId => { |
| | | const device = deviceOptions.value.find( |
| | | item => String(item.id) === String(deviceId) |
| | | ); |
| | | |
| | | const operatorIds = device?.operatorId |
| | | ? String(device.operatorId) |
| | | .split(/[,,;;\s]+/g) |
| | | .map((id) => id.trim()) |
| | | .map(id => id.trim()) |
| | | .filter(Boolean) |
| | | : []; |
| | | |
| | |
| | | return [...baseScheduleUsers.value]; |
| | | } |
| | | |
| | | return baseScheduleUsers.value.filter((user) => |
| | | return baseScheduleUsers.value.filter(user => |
| | | operatorIds.includes(String(user.userId)) |
| | | ); |
| | | }; |
| | |
| | | : [...baseScheduleUsers.value]; |
| | | |
| | | const userIds = normalizeArray(preset?.userIds) |
| | | .map((val) => String(val)) |
| | | .map(val => String(val)) |
| | | .filter(Boolean) |
| | | .filter((uid) => userOptions.some((user) => String(user.userId) === String(uid))); |
| | | .filter(uid => |
| | | userOptions.some(user => String(user.userId) === String(uid)) |
| | | ); |
| | | |
| | | return { |
| | | id: preset?.id ?? "", |
| | |
| | | }; |
| | | }; |
| | | |
| | | const addScheduleRow = (preset) => { |
| | | const addScheduleRow = preset => { |
| | | if (preset) { |
| | | scheduleRows.value.push(createScheduleRow(preset)); |
| | | return; |
| | |
| | | const records = Array.isArray(res?.data?.records) ? res.data.records : []; |
| | | const apiTotal = Number(res?.data?.total); |
| | | schedulePage.total = |
| | | Number.isFinite(apiTotal) && apiTotal > 0 |
| | | ? apiTotal |
| | | : records.length; |
| | | Number.isFinite(apiTotal) && apiTotal > 0 ? apiTotal : records.length; |
| | | |
| | | const lastPage = Math.max(1, Math.ceil((schedulePage.total || 0) / schedulePage.size)); |
| | | const lastPage = Math.max( |
| | | 1, |
| | | Math.ceil((schedulePage.total || 0) / schedulePage.size) |
| | | ); |
| | | if (schedulePage.current > lastPage) { |
| | | schedulePage.current = lastPage; |
| | | await refreshScheduleRows(); |
| | |
| | | } |
| | | }; |
| | | |
| | | const removeScheduleRow = async (row) => { |
| | | const removeScheduleRow = async row => { |
| | | if (!row || isScheduleHistoryMode.value) return; |
| | | |
| | | if (!row.id) { |
| | | scheduleRows.value = scheduleRows.value.filter((item) => item !== row); |
| | | scheduleRows.value = scheduleRows.value.filter(item => item !== row); |
| | | if (!scheduleRows.value.length) { |
| | | addScheduleRow(); |
| | | } |
| | |
| | | }; |
| | | |
| | | const handleScheduleUserChange = (userIds, row) => { |
| | | row.userIds = normalizeArray(userIds).map((val) => String(val)).filter(Boolean); |
| | | row.userIds = normalizeArray(userIds) |
| | | .map(val => String(val)) |
| | | .filter(Boolean); |
| | | }; |
| | | |
| | | const handleScheduleDeviceChange = (deviceId, row) => { |
| | | const device = deviceOptions.value.find((item) => String(item.id) === String(deviceId)); |
| | | const device = deviceOptions.value.find( |
| | | item => String(item.id) === String(deviceId) |
| | | ); |
| | | |
| | | row.deviceId = deviceId === null || deviceId === undefined ? "" : String(deviceId); |
| | | row.deviceId = |
| | | deviceId === null || deviceId === undefined ? "" : String(deviceId); |
| | | row.deviceName = device?.deviceName || ""; |
| | | row.userOptions = row.deviceId ? buildScheduleUserOptionsByDeviceId(row.deviceId) : [...baseScheduleUsers.value]; |
| | | row.userOptions = row.deviceId |
| | | ? buildScheduleUserOptionsByDeviceId(row.deviceId) |
| | | : [...baseScheduleUsers.value]; |
| | | |
| | | row.userIds = normalizeArray(row.userIds) |
| | | .map((uid) => String(uid)) |
| | | .filter((uid) => row.userOptions.some((user) => String(user.userId) === String(uid))); |
| | | .map(uid => String(uid)) |
| | | .filter(uid => |
| | | row.userOptions.some(user => String(user.userId) === String(uid)) |
| | | ); |
| | | }; |
| | | |
| | | const handleSchedulePagination = ({page, limit}) => { |
| | |
| | | } |
| | | } |
| | | |
| | | |
| | | return true; |
| | | }; |
| | | |
| | | const buildMachineRecordPayload = (workOrderRow, scheduleRow, sortIndex = 0) => { |
| | | const buildMachineRecordPayload = ( |
| | | workOrderRow, |
| | | scheduleRow, |
| | | sortIndex = 0 |
| | | ) => { |
| | | const processId = |
| | | workOrderRow?.processId ?? |
| | | workOrderRow?.productProcessRouteItemId ?? |
| | | reportForm.productProcessRouteItemId; |
| | | |
| | | const operatorIds = normalizeArray(scheduleRow?.userIds) |
| | | .map((val) => String(val).trim()) |
| | | .map(val => String(val).trim()) |
| | | .filter(Boolean) |
| | | .join(","); |
| | | |
| | | const nickName = normalizeArray(scheduleRow?.userIds) |
| | | .map((uid) => resolveScheduleUserName(uid)) |
| | | .map(uid => resolveScheduleUserName(uid)) |
| | | .filter(Boolean) |
| | | .join(","); |
| | | |
| | |
| | | return payload; |
| | | }; |
| | | |
| | | const indexMethod = (index) => { |
| | | const indexMethod = index => { |
| | | return (schedulePage.current - 1) * schedulePage.size + index + 1; |
| | | }; |
| | | |
| | | const scheduleDialogMode = ref("create"); |
| | | const isScheduleHistoryMode = computed(() => scheduleDialogMode.value === "history"); |
| | | const scheduleDialogTitle = computed(() => |
| | | `${isScheduleHistoryMode.value ? "排产记录" : "生产排产"}(工单编号:${currentReportRowData.value?.workOrderNo || "-"})` |
| | | const isScheduleHistoryMode = computed( |
| | | () => scheduleDialogMode.value === "history" |
| | | ); |
| | | const scheduleDialogTitle = computed( |
| | | () => |
| | | `${isScheduleHistoryMode.value ? "排产记录" : "生产排产"}(工单编号:${ |
| | | currentReportRowData.value?.workOrderNo || "-" |
| | | })` |
| | | ); |
| | | |
| | | const mapMachineRecordToScheduleRow = (record) => { |
| | | const mapMachineRecordToScheduleRow = record => { |
| | | const id = record?.id ?? ""; |
| | | const deviceId = record?.machineId ?? record?.deviceId ?? ""; |
| | | const deviceName = record?.deviceName ?? record?.machineName ?? ""; |
| | | const startTime = record?.machineStartTime ?? record?.startTime ?? ""; |
| | | const userIds = normalizeArray(record?.operatorId ?? record?.operatorIds ?? record?.userId) |
| | | .map((val) => String(val)) |
| | | const userIds = normalizeArray( |
| | | record?.operatorId ?? record?.operatorIds ?? record?.userId |
| | | ) |
| | | .map(val => String(val)) |
| | | .filter(Boolean); |
| | | |
| | | return createScheduleRow({ |
| | | id, |
| | | deviceId: deviceId === null || deviceId === undefined ? "" : String(deviceId), |
| | | deviceId: |
| | | deviceId === null || deviceId === undefined ? "" : String(deviceId), |
| | | deviceName, |
| | | userIds, |
| | | startTime, |
| | | }); |
| | | }; |
| | | |
| | | const buildScheduleRowsFromRecords = (records) => { |
| | | const buildScheduleRowsFromRecords = records => { |
| | | const list = Array.isArray(records) ? records : []; |
| | | const grouped = new Map(); |
| | | |
| | | list.forEach((record) => { |
| | | list.forEach(record => { |
| | | const row = mapMachineRecordToScheduleRow(record); |
| | | const key = `${row.deviceId}__${row.startTime}__${row.deviceName}`; |
| | | |
| | |
| | | const existing = grouped.get(key); |
| | | existing.ids = Array.from(new Set([existing.id, row.id].filter(Boolean))); |
| | | existing.userIds = Array.from( |
| | | new Set([...(existing?.userIds || []), ...(row?.userIds || [])].map((v) => String(v))) |
| | | new Set( |
| | | [...(existing?.userIds || []), ...(row?.userIds || [])].map(v => |
| | | String(v) |
| | | ) |
| | | ) |
| | | ).filter(Boolean); |
| | | |
| | | if (!existing.deviceName && row.deviceName) existing.deviceName = row.deviceName; |
| | | if (!existing.deviceName && row.deviceName) |
| | | existing.deviceName = row.deviceName; |
| | | }); |
| | | |
| | | return Array.from(grouped.values()).sort( |
| | |
| | | addScheduleRow(); |
| | | }; |
| | | |
| | | const openScheduleDialog = async (row) => { |
| | | const openScheduleDialog = async row => { |
| | | scheduleDialogMode.value = "create"; |
| | | currentReportRowData.value = row; |
| | | baseScheduleUsers.value = buildBaseScheduleUsersByRow(row); |
| | |
| | | resetCreateScheduleRows(); |
| | | }; |
| | | |
| | | const openHistoryTimelineDialog = async (row) => { |
| | | const openHistoryTimelineDialog = async row => { |
| | | scheduleDialogMode.value = "history"; |
| | | currentReportRowData.value = row; |
| | | baseScheduleUsers.value = buildBaseScheduleUsersByRow(row); |
| | |
| | | return; |
| | | } |
| | | |
| | | const sortedRows = [...scheduleRows.value].sort((a, b) => dayjs(a.startTime).valueOf() - dayjs(b.startTime).valueOf()); |
| | | const sortedRows = [...scheduleRows.value].sort( |
| | | (a, b) => dayjs(a.startTime).valueOf() - dayjs(b.startTime).valueOf() |
| | | ); |
| | | |
| | | scheduleSaving.value = true; |
| | | try { |
| | |
| | | label: "成品名称", |
| | | prop: "finalProductModel", |
| | | minWidth: 200, |
| | | overHidden: false |
| | | overHidden: false, |
| | | }, |
| | | // { |
| | | // label: "加工品名称", |
| | |
| | | showReportDialog(row); |
| | | }, |
| | | // 用户当前id在工单报工人或工序报工人中 |
| | | disabled: row => row.completeQuantity >= row.planQuantity || |
| | | !canCurrentUserReport(row) || row.hasUnreportedMachine |
| | | disabled: row => |
| | | row.completeQuantity >= row.planQuantity || |
| | | !canCurrentUserReport(row) || |
| | | row.hasUnreportedMachine, |
| | | }, |
| | | { |
| | | name: "生产排产", |
| | |
| | | } |
| | | openScheduleDialog(row); |
| | | }, |
| | | disabled: row => !row.canSchedule || row.completeQuantity >= row.planQuantity |
| | | disabled: row => |
| | | !row.canSchedule || row.completeQuantity >= row.planQuantity, |
| | | }, |
| | | { |
| | | name: "排产记录", |
| | |
| | | } |
| | | openHistoryTimelineDialog(row); |
| | | }, |
| | | disabled: row => !row.canSchedule |
| | | } |
| | | disabled: row => !row.canSchedule, |
| | | }, |
| | | // { |
| | | // name:"审核", |
| | | // color: "#f56c6c", |
| | |
| | | deviceId: null, |
| | | }); |
| | | function removeLastFour(str) { |
| | | if (!str) return ''; // 空值保护 |
| | | if (!str) return ""; // 空值保护 |
| | | return str.toString().slice(0, -4); // 核心:截取 0 到 倒数第4位 |
| | | } |
| | | // 本次生产数量验证规则 |
| | |
| | | }; |
| | | |
| | | // 审核 |
| | | const handleAudit = (row) => { |
| | | const handleAudit = row => { |
| | | if (Number(row?.auditStatus) === 1) { |
| | | ElMessage.warning("该工单已审核"); |
| | | return; |
| | |
| | | auditDialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitAudit = async (result) => { |
| | | const submitAudit = async result => { |
| | | const current = auditRowData.value; |
| | | if (!current) return; |
| | | if (auditLoading.value) return; |
| | |
| | | }; |
| | | |
| | | // 查看详情 |
| | | const handleView = (row) => { |
| | | const handleView = row => { |
| | | const {workOrderId} = row; |
| | | router.push({ |
| | | path: "/productionManagement/workOrderDetail", |
| | | query: {workOrderId}, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 验证规则 |
| | | const reportFormRules = { |
| | |
| | | |
| | | tableData.value = records.map(row => ({ |
| | | ...row, |
| | | canSchedule: canScheduleByWorkOrderNo(row) |
| | | canSchedule: canScheduleByWorkOrderNo(row), |
| | | })); |
| | | |
| | | page.total = res?.data?.total || 0; |
| | |
| | | }; |
| | | |
| | | const showReportDialog = row => { |
| | | |
| | | currentReportRowData.value = row; |
| | | reportForm.planQuantity = row.planQuantity - row.completeQuantity; |
| | | reportForm.quantity = row.planQuantity - row.completeQuantity; |
| | |
| | | .filter(item => allowedUserIds.includes(String(item.userId))) |
| | | .map(item => ({ |
| | | userId: item.userId, |
| | | nickName: item.nickName |
| | | nickName: item.nickName, |
| | | })); |
| | | |
| | | |
| | | nextTick(() => { |
| | | reportFormRef.value?.clearValidate(); |
| | |
| | | userListNoPageByTenantId() |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | const list = Array.isArray(res.data) ? res.data : [] |
| | | const list = Array.isArray(res.data) ? res.data : []; |
| | | userOptions.value = [ |
| | | {nickName: "任意用户", userId: "-1"}, |
| | | ...deepClone(list) |
| | | ] |
| | | userTeamOptions.value = deepClone(list) |
| | | ...deepClone(list), |
| | | ]; |
| | | userTeamOptions.value = deepClone(list); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | |
| | | reportForm.userName = ""; |
| | | } |
| | | }; |
| | | |
| | | const handleTeamListChange = val => { |
| | | if (!Array.isArray(val)) return; |
| | | if (!val.some(item => typeof item === "string")) return; |
| | | reportForm.teamList = val.map(item => { |
| | | if (typeof item === "string") { |
| | | return { userName: item }; |
| | | } |
| | | return item; |
| | | }); |
| | | }; |
| | | // 审核人 |
| | | const handleReviewerIdChange = userId => { |
| | | if (userId) { |
| | |
| | | } else { |
| | | reportForm.auditUserName = ""; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const processData = ref([]); |
| | | |
| | | // 查询所有工序 |
| | | const processLists = async () => { |
| | | console.log(processData.value) |
| | | console.log(processData.value); |
| | | if (processData.value.length > 0) { |
| | | return processData.value; |
| | | } |
| | |
| | | }; |
| | | |
| | | // 判断当前用户是否能排产 |
| | | const canScheduleByWorkOrderNo = (row) => { |
| | | const canScheduleByWorkOrderNo = row => { |
| | | if (!row) return false; |
| | | |
| | | const uid = String(currentUserId.value || ""); |
| | | if (!uid) return false; |
| | | |
| | | const currentProcess = processData.value.find(item => |
| | | String(item.id) === String(row.processId) |
| | | const currentProcess = processData.value.find( |
| | | item => String(item.id) === String(row.processId) |
| | | ); |
| | | |
| | | if (!currentProcess) return false; |