Refactor inventory and filament schemas for improved consistency and readability
All checks were successful
farmcontrol/farmcontrol-ws/pipeline/head This commit looks good

- Enhanced the stock event schema by ensuring consistent formatting and removing unnecessary line breaks for better maintainability.
- Updated the filament schema to improve code readability and maintain required fields while maintaining functionality.
- Introduced a new `recalculate` method in the printer schema to manage printer state changes effectively.
- Added logic to handle host disconnection in the socket host, ensuring proper state management and logging.
This commit is contained in:
Tom Butcher 2026-07-26 22:41:09 +01:00
parent f3a3d3914a
commit ef5cfd34e4
4 changed files with 88 additions and 67 deletions

View File

@ -6,13 +6,13 @@ const { Schema } = mongoose;
const parentStockModelNames = { const parentStockModelNames = {
filamentStock: 'filamentStock', filamentStock: 'filamentStock',
partStock: 'partStock', partStock: 'partStock',
productStock: 'productStock' productStock: 'productStock',
}; };
const initialStockStates = { const initialStockStates = {
filamentStock: 'unconsumed', filamentStock: 'unconsumed',
partStock: 'new', partStock: 'new',
productStock: 'posted' productStock: 'posted',
}; };
const getStartingAmount = (parentType, parentStock) => { const getStartingAmount = (parentType, parentStock) => {
@ -23,12 +23,7 @@ const getStartingAmount = (parentType, parentStock) => {
return parentStock.startingQuantity ?? 0; return parentStock.startingQuantity ?? 0;
}; };
const buildParentState = ( const buildParentState = (parentType, parentStock, currentAmount, startingAmount) => {
parentType,
parentStock,
currentAmount,
startingAmount
) => {
if (parentStock.state?.type === 'draft') { if (parentStock.state?.type === 'draft') {
return undefined; return undefined;
} }
@ -48,6 +43,8 @@ const buildParentState = (
const progress = currentAmount / startingAmount; const progress = currentAmount / startingAmount;
console.log('progress', progress);
if (currentAmount === startingAmount) { if (currentAmount === startingAmount) {
return { ...parentStock.state, type: fullState, progress: 1 }; return { ...parentStock.state, type: fullState, progress: 1 };
} }
@ -63,20 +60,18 @@ const getStockEventTotal = async (parentId, parentType) => {
if (!parentId) return null; if (!parentId) return null;
const objectId = const objectId =
parentId instanceof mongoose.Types.ObjectId parentId instanceof mongoose.Types.ObjectId ? parentId : new mongoose.Types.ObjectId(parentId);
? parentId
: new mongoose.Types.ObjectId(parentId);
const [result] = await mongoose const [result] = await mongoose
.model('stockEvent') .model('stockEvent')
.aggregate([ .aggregate([
{ $match: { parent: objectId, parentType } }, { $match: { parent: objectId, parentType } },
{ $group: { _id: null, total: { $sum: '$value' }, count: { $sum: 1 } } } { $group: { _id: null, total: { $sum: '$value' }, count: { $sum: 1 } } },
]); ]);
return { return {
total: result?.total ?? 0, total: result?.total ?? 0,
count: result?.count ?? 0 count: result?.count ?? 0,
}; };
}; };
@ -113,8 +108,7 @@ const HISTORY_RATE_LIMIT_MS = 3000;
const isWithinHistoryRateLimit = (lastEntry, timestamp = new Date()) => { const isWithinHistoryRateLimit = (lastEntry, timestamp = new Date()) => {
if (!lastEntry?.timestamp) return false; if (!lastEntry?.timestamp) return false;
const elapsed = const elapsed = new Date(timestamp).getTime() - new Date(lastEntry.timestamp).getTime();
new Date(timestamp).getTime() - new Date(lastEntry.timestamp).getTime();
return elapsed < HISTORY_RATE_LIMIT_MS; return elapsed < HISTORY_RATE_LIMIT_MS;
}; };
@ -122,9 +116,7 @@ const getLastParentHistoryValue = (parentType, history = []) => {
const lastEntry = history.at(-1); const lastEntry = history.at(-1);
if (!lastEntry) return undefined; if (!lastEntry) return undefined;
return parentType === 'filamentStock' return parentType === 'filamentStock' ? lastEntry.currentWeight : lastEntry.currentQuantity;
? lastEntry.currentWeight
: lastEntry.currentQuantity;
}; };
const parentValuesEqual = (parentType, a, b) => { const parentValuesEqual = (parentType, a, b) => {
@ -155,9 +147,7 @@ const appendParentHistoryIfChanged = (
const history = parentStock.history || []; const history = parentStock.history || [];
const lastEntry = history.at(-1); const lastEntry = history.at(-1);
const currentValue = const currentValue =
parentType === 'filamentStock' parentType === 'filamentStock' ? updateData.currentWeight : updateData.currentQuantity;
? updateData.currentWeight
: updateData.currentQuantity;
const lastHistoryValue = getLastParentHistoryValue(parentType, history); const lastHistoryValue = getLastParentHistoryValue(parentType, history);
if (parentValuesEqual(parentType, currentValue, lastHistoryValue)) { if (parentValuesEqual(parentType, currentValue, lastHistoryValue)) {
@ -170,10 +160,7 @@ const appendParentHistoryIfChanged = (
return { return {
...updateData, ...updateData,
history: [ history: [...history, buildParentHistoryEntry(parentType, currentValue, timestamp)],
...history,
buildParentHistoryEntry(parentType, currentValue, timestamp)
]
}; };
}; };
@ -187,7 +174,7 @@ const recalculateParentStock = async (parentType, parentId, user) => {
const parentStock = await getObject({ const parentStock = await getObject({
model: parentModel, model: parentModel,
id: parentId, id: parentId,
cached: true cached: true,
}); });
if (!parentStock || parentStock.error) return; if (!parentStock || parentStock.error) return;
@ -203,7 +190,7 @@ const recalculateParentStock = async (parentType, parentId, user) => {
buildParentUpdateData(parentType, parentStock, events) buildParentUpdateData(parentType, parentStock, events)
), ),
user, user,
recalculate: false recalculate: false,
}); });
}; };
@ -215,30 +202,30 @@ const stockEventSchema = new Schema(
parent: { parent: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
refPath: 'parentType', refPath: 'parentType',
required: true required: true,
}, },
parentType: { parentType: {
type: String, type: String,
required: true, required: true,
enum: ['filamentStock', 'partStock', 'productStock'] // Add other models as needed enum: ['filamentStock', 'partStock', 'productStock'], // Add other models as needed
}, },
owner: { owner: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
refPath: 'ownerType', refPath: 'ownerType',
required: true required: true,
}, },
ownerType: { ownerType: {
type: String, type: String,
required: true, required: true,
enum: ['user', 'subJob', 'stockAudit', 'stockTransfer'] enum: ['user', 'subJob', 'stockAudit', 'stockTransfer'],
}, },
history: [ history: [
{ {
value: { type: Number, required: true }, value: { type: Number, required: true },
timestamp: { type: Date, default: Date.now } timestamp: { type: Date, default: Date.now },
} },
], ],
timestamp: { type: Date, default: Date.now } timestamp: { type: Date, default: Date.now },
}, },
{ timestamps: true } { timestamps: true }
); );
@ -252,18 +239,15 @@ stockEventSchema.statics.recalculate = async function (stockEvent, user) {
const currentValue = stockEvent.value; const currentValue = stockEvent.value;
const timestamp = stockEvent.timestamp || new Date(); const timestamp = stockEvent.timestamp || new Date();
if ( if (currentValue !== lastHistoryValue && !isWithinHistoryRateLimit(lastEntry, timestamp)) {
currentValue !== lastHistoryValue &&
!isWithinHistoryRateLimit(lastEntry, timestamp)
) {
await editObject({ await editObject({
model: this, model: this,
id: stockEvent._id, id: stockEvent._id,
updateData: { updateData: {
history: [...history, { value: currentValue, timestamp }] history: [...history, { value: currentValue, timestamp }],
}, },
user, user,
recalculate: false recalculate: false,
}); });
} }

View File

@ -3,28 +3,21 @@ import { generateId } from '../../utils.js';
const { Schema } = mongoose; const { Schema } = mongoose;
// Filament base - cost and tax; color and cost override at FilamentSKU // Filament base - cost and tax; color and cost override at FilamentSKU
const filamentSchema = new mongoose.Schema( const filamentSchema = new mongoose.Schema({
{ _reference: { type: String, default: () => generateId()() },
_reference: { type: String, default: () => generateId()() }, name: { required: true, type: String },
name: { required: true, type: String }, vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false },
vendor: { required: false, type: Schema.Types.ObjectId, ref: 'vendor' }, barcode: { required: false, type: String },
barcode: { required: false, type: String }, url: { required: false, type: String },
url: { required: false, type: String }, image: { required: false, type: Buffer },
image: { required: false, type: Buffer }, material: { type: Schema.Types.ObjectId, ref: 'material', required: true },
material: { type: Schema.Types.ObjectId, ref: 'material', required: true }, diameter: { required: true, type: Number },
diameter: { required: true, type: Number }, density: { required: true, type: Number },
density: { required: true, type: Number }, emptySpoolWeight: { required: true, type: Number },
emptySpoolWeight: { required: true, type: Number }, cost: { type: Number, required: false },
cost: { type: Number, required: false }, costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
costTaxRate: { costWithTax: { type: Number, required: false },
type: Schema.Types.ObjectId, }, { timestamps: true });
ref: 'taxRate',
required: false
},
costWithTax: { type: Number, required: false }
},
{ timestamps: true }
);
filamentSchema.index({ name: 'text', barcode: 'text', url: 'text' }); filamentSchema.index({ name: 'text', barcode: 'text', url: 'text' });
@ -36,10 +29,7 @@ filamentSchema.set('toJSON', { virtuals: true });
filamentSchema.statics.recalculate = async function (filament, user) { filamentSchema.statics.recalculate = async function (filament, user) {
const filamentSkuModel = mongoose.model('filamentSku'); const filamentSkuModel = mongoose.model('filamentSku');
const skus = await filamentSkuModel const skus = await filamentSkuModel.find({ filament: filament._id }).select('_id').lean();
.find({ filament: filament._id })
.select('_id')
.lean();
for (const sku of skus) { for (const sku of skus) {
await filamentSkuModel.recalculate(sku, user); await filamentSkuModel.recalculate(sku, user);
} }

View File

@ -1,7 +1,7 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { generateId } from '../../utils.js'; import { generateId } from '../../utils.js';
const { Schema } = mongoose; const { Schema } = mongoose;
import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; import { aggregateRollups, aggregateRollupsHistory, editObject } from '../../database.js';
// Define the moonraker connection schema // Define the moonraker connection schema
const moonrakerSchema = new Schema( const moonrakerSchema = new Schema(
@ -124,6 +124,28 @@ printerSchema.statics.history = async function (from, to) {
return results; return results;
}; };
printerSchema.statics.recalculate = async function (printer, user) {
if (printer.active === false && printer.state?.type !== 'inactive') {
await editObject({
model: this,
id: printer._id,
updateData: { state: { type: 'inactive' } },
user,
recalculate: false,
});
return;
}
if (printer.active === true && printer.state?.type === 'inactive' && printer.online === false) {
await editObject({
model: this,
id: printer._id,
updateData: { state: { type: 'offline' } },
user,
recalculate: false,
});
}
};
// Add virtual id getter // Add virtual id getter
printerSchema.virtual('id').get(function () { printerSchema.virtual('id').get(function () {
return this._id; return this._id;

View File

@ -94,6 +94,25 @@ export class SocketHost {
ownerType: 'host' ownerType: 'host'
}); });
}; };
const setHostOffline = async () => {
logger.info('Host disconnected.');
await editObject({
model: hostModel,
id: this.host._id,
updateData: {
online: false,
state: { type: 'offline' },
connectedAt: null
},
owner: this.host,
ownerType: 'host'
});
this.host = null;
this.id = null;
this.authenticated = false;
};
logger.trace('handleAuthenticateEvent'); logger.trace('handleAuthenticateEvent');
const id = data.id || undefined; const id = data.id || undefined;
const authCode = data.authCode || undefined; const authCode = data.authCode || undefined;
@ -103,6 +122,9 @@ export class SocketHost {
logger.info('Authenticating host with id + authCode...'); logger.info('Authenticating host with id + authCode...');
const verifyResult = await this.codeAuth.verifyCode(id, authCode); const verifyResult = await this.codeAuth.verifyCode(id, authCode);
if (verifyResult.valid == true) { if (verifyResult.valid == true) {
if (this.host?._id) {
await setHostOffline();
}
await setHostOnline(verifyResult); await setHostOnline(verifyResult);
await this.initializeHost(); await this.initializeHost();
} }
@ -115,6 +137,9 @@ export class SocketHost {
const verifyResult = await this.codeAuth.verifyOtp(otp); const verifyResult = await this.codeAuth.verifyOtp(otp);
if (verifyResult.valid == true) { if (verifyResult.valid == true) {
logger.info('Host authenticated and valid.'); logger.info('Host authenticated and valid.');
if (this.host?._id) {
await setHostOffline();
}
await setHostOnline(verifyResult); await setHostOnline(verifyResult);
await this.initializeHost(); await this.initializeHost();
} }