From 964e95019dd4ab774048ab12fbdfc4471bdcf9a3 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 8 Nov 2026 23:40:37 +0000 Subject: [PATCH] Enhance job and subjob schemas with name field and text indexing This commit adds a `name` field to both the job and subjob schemas, allowing for optional naming of jobs and subjobs. It updates the text indexing to include the new `name` field, improving search capabilities. Additionally, a utility function is introduced to retrieve the GCode file name based on its reference, ensuring that job names are dynamically generated based on associated GCode files. These changes enhance the overall functionality and usability of the job management system. --- src/database/schemas/production/job.schema.js | 58 ++++++++++++++++++- .../schemas/production/subjob.schema.js | 24 +++++++- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/database/schemas/production/job.schema.js b/src/database/schemas/production/job.schema.js index 71a2f02..3070e62 100644 --- a/src/database/schemas/production/job.schema.js +++ b/src/database/schemas/production/job.schema.js @@ -10,10 +10,12 @@ import { deleteObject, } from '../../database.js'; import { subJobModel } from './subjob.schema.js'; +import { gcodeFileModel } from './gcodefile.schema.js'; const jobSchema = new mongoose.Schema( { _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: false }, state: { type: { required: true, type: String }, progress: { type: Number, required: false }, @@ -40,7 +42,7 @@ const jobSchema = new mongoose.Schema( { timestamps: true } ); -jobSchema.index({ 'state.type': 'text' }); +jobSchema.index({ name: 'text', 'state.type': 'text' }); const rollupConfigs = [ { @@ -94,6 +96,19 @@ jobSchema.statics.history = async function (from, to) { const normalizeId = (value) => value?._id ?? value; +const gcodeFileNameFrom = async (gcodeFileRef) => { + if (gcodeFileRef && typeof gcodeFileRef === 'object' && typeof gcodeFileRef.name === 'string') { + const populatedName = gcodeFileRef.name.trim(); + if (populatedName) return populatedName; + } + + const gcodeFileId = normalizeId(gcodeFileRef); + if (!gcodeFileId) return ''; + + const gcodeFile = await gcodeFileModel.findById(gcodeFileId).select('name').lean(); + return typeof gcodeFile?.name === 'string' ? gcodeFile.name.trim() : ''; +}; + jobSchema.statics.recalculate = async function (job, user) { const jobId = job._id || job; if (!jobId || job?.state?.type !== 'draft') { @@ -103,6 +118,25 @@ jobSchema.statics.recalculate = async function (job, user) { const quantity = job.quantity || 1; const printers = (job.printers || []).map((printer) => normalizeId(printer)); const gcodeFile = normalizeId(job.gcodeFile); + const gcodeFileName = await gcodeFileNameFrom(job.gcodeFile); + const jobName = gcodeFileName ? `${quantity} ${gcodeFileName}` : null; + const subJobName = gcodeFileName || null; + + if ((job.name ?? null) !== jobName) { + const jobResult = await editObject({ + model: this, + id: jobId, + updateData: { + name: jobName, + updatedAt: new Date(), + }, + user, + recalculate: false, + }); + if (jobResult?.error) { + throw jobResult; + } + } const existingSubJobs = await listObjects({ model: subJobModel, @@ -127,6 +161,7 @@ jobSchema.statics.recalculate = async function (job, user) { const printer = printers[printerCount]; const subJobUpdateData = { updatedAt: new Date(), + name: subJobName, printer, gcodeFile, number: subJobNumber, @@ -188,3 +223,24 @@ jobSchema.virtual('id').get(function () { jobSchema.set('toJSON', { virtuals: true }); export const jobModel = mongoose.model('job', jobSchema); + +const replaceTextIndex = async (model, field) => { + try { + const indexes = await model.collection.indexes(); + const textIndex = indexes.find((index) => Object.values(index.key || {}).includes('text')); + const weights = textIndex?.weights; + const includesField = weights != null && Object.prototype.hasOwnProperty.call(weights, field); + if (textIndex && !includesField) { + await model.collection.dropIndex(textIndex.name); + } + await model.createIndexes(); + } catch { + // Collection may not exist until Mongo is connected. + } +}; + +if (mongoose.connection.readyState === 1) { + replaceTextIndex(jobModel, 'name'); +} else { + mongoose.connection.once('open', () => replaceTextIndex(jobModel, 'name')); +} diff --git a/src/database/schemas/production/subjob.schema.js b/src/database/schemas/production/subjob.schema.js index 02d2522..3058393 100644 --- a/src/database/schemas/production/subjob.schema.js +++ b/src/database/schemas/production/subjob.schema.js @@ -4,6 +4,7 @@ const { Schema } = mongoose; const subJobSchema = new mongoose.Schema({ _reference: { type: String, default: () => generateId()() }, + name: { type: String, required: false }, printer: { type: Schema.Types.ObjectId, ref: 'printer', @@ -43,7 +44,7 @@ const subJobSchema = new mongoose.Schema({ finishedAt: { required: false, type: Date, default: null }, }); -subJobSchema.index({ moonrakerJobId: 'text', 'state.type': 'text' }); +subJobSchema.index({ name: 'text', moonrakerJobId: 'text', 'state.type': 'text' }); subJobSchema.virtual('id').get(function () { return this._id; @@ -52,3 +53,24 @@ subJobSchema.virtual('id').get(function () { subJobSchema.set('toJSON', { virtuals: true }); export const subJobModel = mongoose.model('subJob', subJobSchema); + +const replaceTextIndex = async (model, field) => { + try { + const indexes = await model.collection.indexes(); + const textIndex = indexes.find((index) => Object.values(index.key || {}).includes('text')); + const weights = textIndex?.weights; + const includesField = weights != null && Object.prototype.hasOwnProperty.call(weights, field); + if (textIndex && !includesField) { + await model.collection.dropIndex(textIndex.name); + } + await model.createIndexes(); + } catch { + // Collection may not exist until Mongo is connected. + } +}; + +if (mongoose.connection.readyState === 1) { + replaceTextIndex(subJobModel, 'name'); +} else { + mongoose.connection.once('open', () => replaceTextIndex(subJobModel, 'name')); +}