import dotenv from "dotenv"; import mongoose from "mongoose"; import { jobModel } from "../../schemas/production/job.schema.js"; import { subJobModel } from "../../schemas/production/subjob.schema.js"; import { noteModel } from "../../schemas/misc/note.schema.js"; import jwt from "jsonwebtoken"; import log4js from "log4js"; import { auditLogModel } from "../../schemas/management/auditlog.schema.js"; dotenv.config(); const logger = log4js.getLogger("Jobs"); logger.level = process.env.LOG_LEVEL; export const listJobsRouteHandler = async ( req, res, page = 1, limit = 25, ) => { try { // Calculate the skip value based on the page number and limit const skip = (page - 1) * limit; // Fetch users with pagination const jobs = await jobModel .find() .sort({ createdAt: -1 }) .skip(skip) .limit(limit) .populate("subJobs", "state") .populate("gcodeFile", "name"); logger.trace(`List of print jobs (Page ${page}, Limit ${limit}):`); res.send(jobs); } catch (error) { logger.error("Error listing print jobs:", error); res.status(500).send({ error: error }); } }; export const getJobRouteHandler = async (req, res) => { try { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); // Fetch the job with the given remote address const job = await jobModel .findOne({ _id: id, }) .populate("printers", "name state") .populate("gcodeFile") .populate("subJobs") .populate("notes"); if (!job) { logger.warn(`Job not found with supplied id.`); return res.status(404).send({ error: "Print job not found." }); } logger.trace(`Job with ID: ${id}:`, job); const targetIds = [id, ...job.subJobs.map(subJob => subJob._id)]; const auditLogs = await auditLogModel.find({ target: { $in: targetIds.map(id => new mongoose.Types.ObjectId(id)) } }).populate('owner'); res.send({...job._doc, auditLogs: auditLogs}); } catch (error) { logger.error("Error fetching job:", error); res.status(500).send({ error: error.message }); } }; export const editJobRouteHandler = async (req, res) => { try { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); // Fetch the job with the given remote address const job = await jobModel.findOne({ _id: id }); if (!job) { logger.warn(`Job not found with supplied id.`); return res.status(404).send({ error: "Print job not found." }); } logger.trace(`Job with ID: ${id}:`, job); const { createdAt, updatedAt, started_at, status, ...updateData } = req.body; const result = await jobModel.updateOne( { _id: id }, { $set: updateData }, ); if (result.nModified === 0) { logger.warn("No jobs updated."); return res.status(400).send({ error: "No jobs updated." }); } res.send({ message: "Print job updated successfully" }); } catch (error) { logger.error("Error updating job:", error); res.status(500).send({ error: error.message }); } }; export const createJobRouteHandler = async (req, res) => { try { const { gcodeFile, printers, quantity = 1 } = req.body; if (!printers || printers.length === 0) { return res .status(400) .send({ error: "At least one printer must be specified" }); } // Convert printer IDs to ObjectIds const printerIds = printers.map((id) => new mongoose.Types.ObjectId(id)); // Create new print job const newJob = new jobModel({ state: { type: "draft" }, printers: printerIds, gcodeFile: gcodeFile ? new mongoose.Types.ObjectId(gcodeFile) : null, quantity, subJobs: [], // Initialize empty array for subjob references createdAt: new Date(), updatedAt: new Date(), startedAt: null }); // Save the print job first to get its ID const savedJob = await newJob.save(); // Create subjobs array with sequential numbers based on quantity const subJobs = await Promise.all( Array.from({ length: quantity }, (_, index) => { const subJob = new subJobModel({ printer: printerIds[index % printerIds.length], // Distribute across available printers job: savedJob._id, gcodeFile: gcodeFile ? new mongoose.Types.ObjectId(gcodeFile) : null, subJobId: `subjob-${index + 1}`, state: { type: "draft" }, number: index + 1, createdAt: new Date(), updatedAt: new Date(), }); return subJob.save(); }), ); // Update the print job with the subjob references savedJob.subJobs = subJobs.map((subJob) => subJob._id); await savedJob.save(); logger.trace( `Created new print job with ID: ${savedJob._id} and ${subJobs.length} subjobs`, ); res.status(201).send({ job: savedJob, subJobs }); } catch (error) { logger.error("Error creating print job:", error); res.status(500).send({ error: error.message }); } }; export const getJobStatsRouteHandler = async (req, res) => { try { const stats = await jobModel.aggregate([ { $group: { _id: "$state.type", count: { $sum: 1 } } } ]); // Transform the results into a more readable format const formattedStats = stats.reduce((acc, curr) => { acc[curr._id] = curr.count; return acc; }, {}); logger.trace("Print job stats by state:", formattedStats); res.send(formattedStats); } catch (error) { logger.error("Error fetching print job stats:", error); res.status(500).send({ error: error.message }); } };