Implemented job editing functionality by adding an editJobRouteHandler and associated logic to update draft jobs. Enhanced job schema with a recalculate method to manage subjob creation and updates based on job quantity and printer assignments. Updated tests to cover new editing capabilities and ensure proper state checks.
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
This commit is contained in:
parent
5419b3fbd1
commit
e112ec4386
@ -1,7 +1,15 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { generateId } from '../../utils.js';
|
||||
const { Schema } = mongoose;
|
||||
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
|
||||
import {
|
||||
aggregateRollups,
|
||||
aggregateRollupsHistory,
|
||||
listObjects,
|
||||
editObject,
|
||||
newObject,
|
||||
deleteObject,
|
||||
} from '../../database.js';
|
||||
import { subJobModel } from './subjob.schema.js';
|
||||
|
||||
const jobSchema = new mongoose.Schema(
|
||||
{
|
||||
@ -84,6 +92,95 @@ jobSchema.statics.history = async function (from, to) {
|
||||
return results;
|
||||
};
|
||||
|
||||
const normalizeId = (value) => value?._id ?? value;
|
||||
|
||||
jobSchema.statics.recalculate = async function (job, user) {
|
||||
const jobId = job._id || job;
|
||||
if (!jobId || job?.state?.type !== 'draft') {
|
||||
return;
|
||||
}
|
||||
|
||||
const quantity = job.quantity || 1;
|
||||
const printers = (job.printers || []).map((printer) => normalizeId(printer));
|
||||
const gcodeFile = normalizeId(job.gcodeFile);
|
||||
|
||||
const existingSubJobs = await listObjects({
|
||||
model: subJobModel,
|
||||
filter: { job: jobId },
|
||||
sort: 'number',
|
||||
order: 'ascend',
|
||||
pagination: false,
|
||||
});
|
||||
|
||||
if (existingSubJobs?.error) {
|
||||
throw existingSubJobs;
|
||||
}
|
||||
|
||||
const existingSubJobsByNumber = new Map(
|
||||
existingSubJobs.map((subJob) => [subJob.number, subJob])
|
||||
);
|
||||
|
||||
var printerCount = 0;
|
||||
|
||||
for (let i = 0; i < quantity; i++) {
|
||||
const subJobNumber = i + 1;
|
||||
const printer = printers[printerCount];
|
||||
const subJobUpdateData = {
|
||||
updatedAt: new Date(),
|
||||
printer,
|
||||
gcodeFile,
|
||||
number: subJobNumber,
|
||||
};
|
||||
|
||||
const existingSubJob = existingSubJobsByNumber.get(subJobNumber);
|
||||
|
||||
if (existingSubJob) {
|
||||
const subJobResult = await editObject({
|
||||
model: subJobModel,
|
||||
id: existingSubJob._id,
|
||||
updateData: subJobUpdateData,
|
||||
user,
|
||||
});
|
||||
if (subJobResult.error) {
|
||||
throw subJobResult;
|
||||
}
|
||||
} else {
|
||||
const subJobResult = await newObject({
|
||||
model: subJobModel,
|
||||
newData: {
|
||||
createdAt: new Date(),
|
||||
...subJobUpdateData,
|
||||
job: jobId,
|
||||
state: { type: 'draft' },
|
||||
},
|
||||
user,
|
||||
});
|
||||
if (subJobResult.error) {
|
||||
throw subJobResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (printerCount >= printers.length - 1) {
|
||||
printerCount = 0;
|
||||
} else {
|
||||
printerCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (const existingSubJob of existingSubJobs) {
|
||||
if (existingSubJob.number > quantity) {
|
||||
const deleteResult = await deleteObject({
|
||||
model: subJobModel,
|
||||
id: existingSubJob._id,
|
||||
user,
|
||||
});
|
||||
if (deleteResult.error) {
|
||||
throw deleteResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jobSchema.virtual('id').get(function () {
|
||||
return this._id;
|
||||
});
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
getJobRouteHandler,
|
||||
newJobRouteHandler,
|
||||
deleteJobRouteHandler,
|
||||
editJobRouteHandler,
|
||||
getJobStatsRouteHandler,
|
||||
getJobHistoryRouteHandler,
|
||||
searchJobsRouteHandler
|
||||
@ -52,6 +53,10 @@ router.get('/:id', isAuthenticated, (req, res) => {
|
||||
getJobRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||
editJobRouteHandler(req, res);
|
||||
});
|
||||
|
||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
||||
deleteJobRouteHandler(req, res);
|
||||
});
|
||||
|
||||
@ -6,10 +6,12 @@ jest.unstable_mockModule('../../../database/database.js', () => ({
|
||||
listObjects: jest.fn(),
|
||||
getObject: jest.fn(),
|
||||
newObject: jest.fn(),
|
||||
editObject: jest.fn(),
|
||||
deleteObject: jest.fn(),
|
||||
listObjectsByProperties: jest.fn(),
|
||||
getModelStats: jest.fn(),
|
||||
getModelHistory: jest.fn(),
|
||||
checkStates: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../../../database/schemas/production/job.schema.js', () => ({
|
||||
@ -33,10 +35,15 @@ jest.unstable_mockModule('log4js', () => ({
|
||||
}));
|
||||
|
||||
// Import handlers after mocking
|
||||
const { listJobsRouteHandler, getJobRouteHandler, newJobRouteHandler, deleteJobRouteHandler } =
|
||||
await import('../jobs.js');
|
||||
const {
|
||||
listJobsRouteHandler,
|
||||
getJobRouteHandler,
|
||||
newJobRouteHandler,
|
||||
editJobRouteHandler,
|
||||
deleteJobRouteHandler,
|
||||
} = await import('../jobs.js');
|
||||
|
||||
const { listObjects, getObject, newObject, deleteObject } = await import(
|
||||
const { listObjects, getObject, newObject, editObject, deleteObject, checkStates } = await import(
|
||||
'../../../database/database.js'
|
||||
);
|
||||
const { jobModel } = await import('../../../database/schemas/production/job.schema.js');
|
||||
@ -72,7 +79,7 @@ describe('Job Service Route Handlers', () => {
|
||||
});
|
||||
|
||||
describe('newJobRouteHandler', () => {
|
||||
it('should create a new job and corresponding subjobs', async () => {
|
||||
it('should create a new job', async () => {
|
||||
req.body = {
|
||||
quantity: 2,
|
||||
printers: ['p1', 'p2'],
|
||||
@ -80,12 +87,11 @@ describe('Job Service Route Handlers', () => {
|
||||
};
|
||||
|
||||
const mockJob = { _id: 'job123' };
|
||||
newObject.mockResolvedValueOnce(mockJob); // For Job
|
||||
newObject.mockResolvedValue({ _id: 'subjob' }); // For SubJobs
|
||||
newObject.mockResolvedValueOnce(mockJob);
|
||||
|
||||
await newJobRouteHandler(req, res);
|
||||
|
||||
expect(newObject).toHaveBeenCalledTimes(3); // 1 Job + 2 SubJobs
|
||||
expect(newObject).toHaveBeenCalledTimes(1);
|
||||
expect(res.send).toHaveBeenCalledWith(mockJob);
|
||||
});
|
||||
|
||||
@ -100,6 +106,40 @@ describe('Job Service Route Handlers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('editJobRouteHandler', () => {
|
||||
it('should update a draft job', async () => {
|
||||
req.params.id = '507f1f77bcf86cd799439011';
|
||||
req.body = {
|
||||
quantity: 2,
|
||||
printers: ['p1', 'p2'],
|
||||
gcodeFile: 'file123',
|
||||
};
|
||||
|
||||
checkStates.mockResolvedValue(true);
|
||||
editObject.mockResolvedValue({ _id: '507f1f77bcf86cd799439011', ...req.body });
|
||||
|
||||
await editJobRouteHandler(req, res);
|
||||
|
||||
expect(checkStates).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ states: ['draft'] })
|
||||
);
|
||||
expect(editObject).toHaveBeenCalled();
|
||||
expect(res.send).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fail if job is not in draft state', async () => {
|
||||
req.params.id = '507f1f77bcf86cd799439011';
|
||||
checkStates.mockResolvedValue(false);
|
||||
|
||||
await editJobRouteHandler(req, res);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.send).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ error: 'Job is not in draft state.' })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteJobRouteHandler', () => {
|
||||
it('should delete a job', async () => {
|
||||
req.params.id = '507f1f77bcf86cd799439011';
|
||||
|
||||
@ -8,11 +8,12 @@ import {
|
||||
listObjects,
|
||||
listObjectsByProperties,
|
||||
newObject,
|
||||
editObject,
|
||||
getModelStats,
|
||||
getModelHistory,
|
||||
searchObjects
|
||||
searchObjects,
|
||||
checkStates
|
||||
} from '../../database/database.js';
|
||||
import { subJobModel } from '../../database/schemas/production/subjob.schema.js';
|
||||
const logger = log4js.getLogger('Jobs');
|
||||
logger.level = config.server.logLevel;
|
||||
|
||||
@ -117,34 +118,51 @@ export const newJobRouteHandler = async (req, res) => {
|
||||
|
||||
logger.debug(`New job with ID: ${result._id}`);
|
||||
|
||||
var printerCount = 0;
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
for (let i = 0; i < newData.quantity; i++) {
|
||||
const newSubJobData = {
|
||||
createdAt: new Date(),
|
||||
export const editJobRouteHandler = async (req, res) => {
|
||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||
|
||||
logger.trace(`Job with ID: ${id}`);
|
||||
|
||||
const checkStatesResult = await checkStates({ model: jobModel, id, states: ['draft'] });
|
||||
|
||||
if (checkStatesResult.error) {
|
||||
logger.error('Error checking job states:', checkStatesResult.error);
|
||||
res.status(checkStatesResult.code).send(checkStatesResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkStatesResult === false) {
|
||||
logger.error('Job is not in draft state.');
|
||||
res.status(400).send({ error: 'Job is not in draft state.', code: 400 });
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
updatedAt: new Date(),
|
||||
printer: newData.printers[printerCount],
|
||||
quantity: req.body.quantity,
|
||||
printers: req.body.printers,
|
||||
gcodeFile: req.body.gcodeFile,
|
||||
number: i + 1,
|
||||
job: result._id,
|
||||
state: { type: 'draft' },
|
||||
};
|
||||
const subJobResult = await newObject({
|
||||
model: subJobModel,
|
||||
newData: newSubJobData,
|
||||
|
||||
const result = await editObject({
|
||||
model: jobModel,
|
||||
id,
|
||||
updateData,
|
||||
user: req.user,
|
||||
populate: ['gcodeFile', 'printers'],
|
||||
});
|
||||
if (subJobResult.error) {
|
||||
logger.error('No sub job created:', result.error);
|
||||
return res.status(result.code).send(result);
|
||||
}
|
||||
if (printerCount >= newData.printers.length - 1) {
|
||||
printerCount = 0;
|
||||
} else {
|
||||
printerCount += 1;
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
logger.error('Error editing job:', result.error);
|
||||
res.status(result.code).send(result);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug(`Edited job with ID: ${id}`);
|
||||
|
||||
res.send(result);
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user