Add inventory and management schemas for filament, part, and stock management
- Introduced new schemas for managing inventory, including filamentStock, partStock, stockAudit, stockEvent, and their respective models. - Added management schemas for user, vendor, material, and various document types to enhance data structure and organization. - Implemented necessary fields and relationships to support inventory tracking and management functionalities.
This commit is contained in:
parent
5584e61583
commit
ce15d3dbfc
490
src/database/database.js
Normal file
490
src/database/database.js
Normal file
@ -0,0 +1,490 @@
|
||||
import _ from 'lodash';
|
||||
import NodeCache from 'node-cache';
|
||||
import {
|
||||
deleteAuditLog,
|
||||
expandObjectIds,
|
||||
editAuditLog,
|
||||
distributeUpdate,
|
||||
newAuditLog,
|
||||
distributeNew
|
||||
} from './utils.js';
|
||||
import log4js from 'log4js';
|
||||
import { loadConfig } from '../config.js';
|
||||
import { userModel } from './schemas/management/user.schema.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
const logger = log4js.getLogger('Database');
|
||||
const cacheLogger = log4js.getLogger('Local Cache');
|
||||
logger.level = config.server.logLevel;
|
||||
cacheLogger.level = config.server.logLevel;
|
||||
|
||||
const modelCaches = new Map();
|
||||
const listCache = new NodeCache({
|
||||
stdTTL: 30, // 30 sec expiration
|
||||
checkperiod: 600, // 30 sec periodic cleanup
|
||||
useClones: false // Don't clone objects for better performance
|
||||
});
|
||||
|
||||
function getModelCache(model) {
|
||||
const modelName = model.modelName;
|
||||
const modelCache = modelCaches.get(modelName);
|
||||
if (modelCache == undefined) {
|
||||
logger.trace('Creating new model cache...');
|
||||
const newModelCache = new NodeCache({
|
||||
stdTTL: 30, // 30 sec expiration
|
||||
checkperiod: 30, // 30 sec periodic cleanup
|
||||
useClones: false // Don't clone objects for better performance
|
||||
});
|
||||
modelCaches.set(modelName, newModelCache);
|
||||
return newModelCache;
|
||||
}
|
||||
logger.trace('Getting model cache...');
|
||||
return modelCache;
|
||||
}
|
||||
|
||||
export const retrieveObjectCache = ({ model, id }) => {
|
||||
cacheLogger.trace('Retrieving:', {
|
||||
model: model.modelName,
|
||||
id
|
||||
});
|
||||
const modelCache = getModelCache(model);
|
||||
|
||||
const cachedObject = modelCache.get(id);
|
||||
|
||||
if (cachedObject == undefined) {
|
||||
cacheLogger.trace('Miss:', {
|
||||
model: model.modelName,
|
||||
id
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
cacheLogger.trace('Hit:', {
|
||||
model: model.modelName,
|
||||
id
|
||||
});
|
||||
|
||||
return cachedObject;
|
||||
};
|
||||
|
||||
export const retrieveObjectsCache = ({ model }) => {
|
||||
cacheLogger.trace('Retrieving:', {
|
||||
model: model.modelName
|
||||
});
|
||||
const modelCache = getModelCache(model);
|
||||
|
||||
const modelCacheKeys = modelCache.keys();
|
||||
|
||||
const cachedList = listCache.get(model.modelName);
|
||||
|
||||
if (cachedList == true) {
|
||||
const cachedObjects = modelCacheKeys.map(key => modelCache.get(key));
|
||||
|
||||
cacheLogger.trace('Hit:', {
|
||||
model: model.modelName,
|
||||
length: cachedObjects.length
|
||||
});
|
||||
|
||||
return cachedObjects;
|
||||
}
|
||||
|
||||
cacheLogger.trace('Miss:', {
|
||||
model: model.modelName
|
||||
});
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const updateObjectCache = ({ model, id, object }) => {
|
||||
cacheLogger.trace('Updating:', {
|
||||
model: model.modelName,
|
||||
id
|
||||
});
|
||||
const modelCache = getModelCache(model);
|
||||
const cachedObject = modelCache.get(id) || {};
|
||||
const mergedObject = _.merge(cachedObject, object);
|
||||
|
||||
modelCache.set(id, mergedObject);
|
||||
|
||||
cacheLogger.trace('Updated:', {
|
||||
model: model.modelName,
|
||||
id
|
||||
});
|
||||
|
||||
return mergedObject;
|
||||
};
|
||||
|
||||
export const deleteObjectCache = ({ model, id }) => {
|
||||
cacheLogger.trace('Deleting:', {
|
||||
model: model.modelName,
|
||||
id
|
||||
});
|
||||
|
||||
modelCache.del(id);
|
||||
|
||||
cacheLogger.trace('Deleted:', {
|
||||
model: model.modelName,
|
||||
id
|
||||
});
|
||||
|
||||
return mergedObject;
|
||||
};
|
||||
|
||||
export const updateObjectsCache = ({ model, objects }) => {
|
||||
cacheLogger.trace('Updating:', {
|
||||
model: model.modelName,
|
||||
length: objects.length
|
||||
});
|
||||
const modelCache = getModelCache(model);
|
||||
|
||||
objects.forEach(object => {
|
||||
const cachedObject = modelCache.get(object._id) || {};
|
||||
|
||||
const mergedObject = _.merge(cachedObject, object);
|
||||
|
||||
modelCache.set(object._id, mergedObject);
|
||||
});
|
||||
|
||||
listCache.set(model.modelName, true);
|
||||
|
||||
cacheLogger.trace('Updated:', {
|
||||
model: model.modelName,
|
||||
length: objects.length
|
||||
});
|
||||
|
||||
return mergedObject;
|
||||
};
|
||||
|
||||
// Reusable function to list objects with aggregation, filtering, search, sorting, and pagination
|
||||
export const listObjects = async ({
|
||||
model,
|
||||
populate = [],
|
||||
filter = {},
|
||||
sort = '',
|
||||
order = 'ascend',
|
||||
project, // optional: override default projection
|
||||
cached = false
|
||||
}) => {
|
||||
try {
|
||||
logger.trace('Listing objects:', {
|
||||
model,
|
||||
populate,
|
||||
page,
|
||||
limit,
|
||||
filter,
|
||||
sort,
|
||||
order,
|
||||
project,
|
||||
cache
|
||||
});
|
||||
|
||||
var cacheKey = undefined;
|
||||
var modelCache = getModelCache(model);
|
||||
|
||||
if (cached == true) {
|
||||
const objectsCache = retrieveObjectsCache({ model });
|
||||
if (objectsCache != undefined) {
|
||||
return objectsCache;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix: descend should be -1, ascend should be 1
|
||||
const sortOrder = order === 'descend' ? -1 : 1;
|
||||
|
||||
if (!sort || sort === '') {
|
||||
sort = 'createdAt';
|
||||
}
|
||||
// Translate parent._id to parent for Mongoose
|
||||
if (filter['parent._id']) {
|
||||
filter.parent = filter['parent._id'];
|
||||
delete filter['parent._id'];
|
||||
}
|
||||
|
||||
// Translate owner._id to owner for Mongoose
|
||||
if (filter['owner._id']) {
|
||||
filter.owner = filter['owner._id'];
|
||||
delete filter['owner._id'];
|
||||
}
|
||||
|
||||
// Use find with population and filter
|
||||
let query = model.find(filter).sort({ [sort]: sortOrder });
|
||||
|
||||
// Handle populate (array or single value)
|
||||
if (populate) {
|
||||
if (Array.isArray(populate)) {
|
||||
for (const pop of populate) {
|
||||
query = query.populate(pop);
|
||||
}
|
||||
} else if (typeof populate === 'string' || typeof populate === 'object') {
|
||||
query = query.populate(populate);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle select (projection)
|
||||
if (project) {
|
||||
query = query.select(project);
|
||||
}
|
||||
|
||||
query = query.lean();
|
||||
|
||||
const queryResult = await query;
|
||||
|
||||
const finalResult = expandObjectIds(queryResult);
|
||||
|
||||
updateObjectsCache({ model, objects });
|
||||
|
||||
logger.trace('Retreived from database:', {
|
||||
model,
|
||||
populate,
|
||||
page,
|
||||
limit,
|
||||
filter,
|
||||
sort,
|
||||
order,
|
||||
project,
|
||||
cache
|
||||
});
|
||||
return finalResult;
|
||||
} catch (error) {
|
||||
logger.error('Object list error:', error);
|
||||
return { error: error, code: 500 };
|
||||
}
|
||||
};
|
||||
|
||||
// Reusable function to get a single object by ID
|
||||
export const getObject = async ({ model, id, populate, cached = false }) => {
|
||||
try {
|
||||
logger.trace('Getting object:', {
|
||||
model,
|
||||
id,
|
||||
populate
|
||||
});
|
||||
|
||||
if (cached == true) {
|
||||
const cachedObject = retrieveObjectCache({ model, id });
|
||||
if (cachedObject != undefined) {
|
||||
return cachedObject;
|
||||
}
|
||||
}
|
||||
|
||||
let query = model.findById(id).lean();
|
||||
|
||||
// Handle populate (array or single value)
|
||||
if (populate) {
|
||||
if (Array.isArray(populate)) {
|
||||
for (const pop of populate) {
|
||||
query = query.populate(pop);
|
||||
}
|
||||
} else if (typeof populate === 'string' || typeof populate === 'object') {
|
||||
query = query.populate(populate);
|
||||
}
|
||||
}
|
||||
const finalResult = await query;
|
||||
|
||||
if (!finalResult) {
|
||||
logger.warn('Object not found in database:', {
|
||||
model,
|
||||
id,
|
||||
populate
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
logger.trace('Retreived object from database:', {
|
||||
model,
|
||||
id,
|
||||
populate
|
||||
});
|
||||
|
||||
updateObjectCache({
|
||||
model: model,
|
||||
id: finalResult._id.toString(),
|
||||
object: finalResult
|
||||
});
|
||||
|
||||
return finalResult;
|
||||
} catch (error) {
|
||||
logger.error('An error retreiving object:', error.message);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// Reusable function to get a single object by ID
|
||||
export const getObjectByFilter = async ({ model, filter, populate }) => {
|
||||
try {
|
||||
logger.trace('Getting object:', {
|
||||
model,
|
||||
filter,
|
||||
populate
|
||||
});
|
||||
|
||||
let query = model.findOne(filter).lean();
|
||||
|
||||
// Handle populate (array or single value)
|
||||
if (populate) {
|
||||
if (Array.isArray(populate)) {
|
||||
for (const pop of populate) {
|
||||
query = query.populate(pop);
|
||||
}
|
||||
} else if (typeof populate === 'string' || typeof populate === 'object') {
|
||||
query = query.populate(populate);
|
||||
}
|
||||
}
|
||||
const finalResult = await query;
|
||||
|
||||
if (!finalResult) {
|
||||
logger.warn('Object not found in database:', {
|
||||
model,
|
||||
filter,
|
||||
populate
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
logger.trace('Retreived object from database:', {
|
||||
model,
|
||||
filter,
|
||||
populate
|
||||
});
|
||||
|
||||
updateObjectCache({
|
||||
model: model,
|
||||
id: finalResult._id.toString(),
|
||||
object: finalResult
|
||||
});
|
||||
|
||||
return finalResult;
|
||||
} catch (error) {
|
||||
logger.error('An error retreiving object:', error.message);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// Reusable function to edit an object by ID, with audit logging and distribution
|
||||
export const editObject = async ({
|
||||
model,
|
||||
id,
|
||||
updateData,
|
||||
owner = undefined,
|
||||
ownerType = undefined,
|
||||
populate
|
||||
}) => {
|
||||
try {
|
||||
// Determine parentType from model name
|
||||
const parentType = model.modelName ? model.modelName : 'unknown';
|
||||
// Fetch the and update object
|
||||
var query = model.findByIdAndUpdate(id, updateData).lean();
|
||||
|
||||
if (populate) {
|
||||
if (Array.isArray(populate)) {
|
||||
for (const pop of populate) {
|
||||
query = query.populate(pop);
|
||||
}
|
||||
} else if (typeof populate === 'string' || typeof populate === 'object') {
|
||||
query = query.populate(populate);
|
||||
}
|
||||
}
|
||||
|
||||
const previousObject = await query;
|
||||
|
||||
if (!previousObject) {
|
||||
return { error: `${parentType} not found.`, code: 404 };
|
||||
}
|
||||
|
||||
const previousExpandedObject = expandObjectIds(previousObject);
|
||||
|
||||
if (owner != undefined && ownerType != undefined) {
|
||||
// Audit log before update
|
||||
await editAuditLog(
|
||||
previousExpandedObject,
|
||||
{ ...previousExpandedObject, ...updateData },
|
||||
id,
|
||||
parentType,
|
||||
owner,
|
||||
ownerType
|
||||
);
|
||||
}
|
||||
|
||||
// Distribute update
|
||||
await distributeUpdate(updateData, id, parentType);
|
||||
|
||||
updateObjectCache({
|
||||
model: model,
|
||||
id: id.toString(),
|
||||
object: { ...previousExpandedObject, ...updateData }
|
||||
});
|
||||
|
||||
return { ...previousExpandedObject, ...updateData };
|
||||
} catch (error) {
|
||||
logger.error('editObject error:', error);
|
||||
return { error: error.message, code: 500 };
|
||||
}
|
||||
};
|
||||
|
||||
// Reusable function to create a new object
|
||||
export const newObject = async ({
|
||||
model,
|
||||
newData,
|
||||
owner = null,
|
||||
ownerType = undefined
|
||||
}) => {
|
||||
try {
|
||||
const parentType = model.modelName ? model.modelName : 'unknown';
|
||||
|
||||
const result = await model.create(newData);
|
||||
if (!result || result.length === 0) {
|
||||
return { error: 'No object created.', code: 500 };
|
||||
}
|
||||
const created = result;
|
||||
|
||||
if (owner != undefined && ownerType != undefined) {
|
||||
await newAuditLog(newData, created._id, parentType, owner, ownerType);
|
||||
}
|
||||
|
||||
await distributeNew(created._id, parentType);
|
||||
|
||||
updateObjectCache({
|
||||
model: model,
|
||||
id: created._id.toString(),
|
||||
object: { _id: created._id, ...newData }
|
||||
});
|
||||
|
||||
return created;
|
||||
} catch (error) {
|
||||
logger.error('newObject error:', error);
|
||||
return { error: error.message, code: 500 };
|
||||
}
|
||||
};
|
||||
|
||||
// Reusable function to delete an object by ID, with audit logging and distribution
|
||||
export const deleteObject = async ({
|
||||
model,
|
||||
id,
|
||||
owner = null,
|
||||
ownerType = undefined
|
||||
}) => {
|
||||
try {
|
||||
const parentType = model.modelName ? model.modelName : 'unknown';
|
||||
// Delete the object
|
||||
const result = await model.findByIdAndDelete(id);
|
||||
|
||||
if (!result) {
|
||||
return { error: `${parentType} not found.`, code: 404 };
|
||||
}
|
||||
|
||||
if (owner != undefined && ownerType != undefined) {
|
||||
// Audit log the deletion
|
||||
await deleteAuditLog(result, id, parentType, owner, ownerType);
|
||||
}
|
||||
|
||||
deleteObjectCache({ model: model, id: id.toString() });
|
||||
|
||||
// Distribute the deletion event
|
||||
await distributeUpdate({ deleted: true }, id, parentType);
|
||||
|
||||
return { deleted: true, id: id.toString() };
|
||||
} catch (error) {
|
||||
logger.error('deleteObject error:', error);
|
||||
return { error: error.message, code: 500 };
|
||||
}
|
||||
};
|
||||
33
src/database/schemas/inventory/filamentstock.schema.js
Normal file
33
src/database/schemas/inventory/filamentstock.schema.js
Normal file
@ -0,0 +1,33 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
// Define the main filamentStock schema
|
||||
const filamentStockSchema = new Schema(
|
||||
{
|
||||
state: {
|
||||
type: { type: String, required: true },
|
||||
percent: { type: String, required: true },
|
||||
},
|
||||
startingWeight: {
|
||||
net: { type: Number, required: true },
|
||||
gross: { type: Number, required: true },
|
||||
},
|
||||
currentWeight: {
|
||||
net: { type: Number, required: true },
|
||||
gross: { type: Number, required: true },
|
||||
},
|
||||
filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament' },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
filamentStockSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
filamentStockSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const filamentStockModel = mongoose.model('filamentStock', filamentStockSchema);
|
||||
25
src/database/schemas/inventory/partstock.schema.js
Normal file
25
src/database/schemas/inventory/partstock.schema.js
Normal file
@ -0,0 +1,25 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
// Define the main partStock schema
|
||||
const partStockSchema = new Schema(
|
||||
{
|
||||
name: { type: String, required: true },
|
||||
fileName: { type: String, required: false },
|
||||
part: { type: mongoose.Schema.Types.ObjectId, ref: 'part' },
|
||||
startingQuantity: { type: Number, required: true },
|
||||
currentQuantity: { type: Number, required: true },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
partStockSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
partStockSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const partStockModel = mongoose.model('partStock', partStockSchema);
|
||||
38
src/database/schemas/inventory/stockaudit.schema.js
Normal file
38
src/database/schemas/inventory/stockaudit.schema.js
Normal file
@ -0,0 +1,38 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const stockAuditItemSchema = new Schema({
|
||||
type: { type: String, enum: ['filament', 'part'], required: true },
|
||||
stock: { type: Schema.Types.ObjectId, required: true },
|
||||
expectedQuantity: { type: Number, required: true },
|
||||
actualQuantity: { type: Number, required: true },
|
||||
notes: { type: String },
|
||||
});
|
||||
|
||||
const stockAuditSchema = new Schema(
|
||||
{
|
||||
type: { type: String, required: true },
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['pending', 'in_progress', 'completed', 'cancelled'],
|
||||
default: 'pending',
|
||||
required: true,
|
||||
},
|
||||
notes: { type: String },
|
||||
items: [stockAuditItemSchema],
|
||||
createdBy: { type: Schema.Types.ObjectId, ref: 'user', required: true },
|
||||
completedAt: { type: Date },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
stockAuditSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
stockAuditSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const stockAuditModel = mongoose.model('stockAudit', stockAuditSchema);
|
||||
43
src/database/schemas/inventory/stockevent.schema.js
Normal file
43
src/database/schemas/inventory/stockevent.schema.js
Normal file
@ -0,0 +1,43 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const stockEventSchema = new Schema(
|
||||
{
|
||||
value: { type: Number, required: true },
|
||||
current: { type: Number, required: true },
|
||||
unit: { type: String, required: true },
|
||||
parent: {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: 'parentType',
|
||||
required: true,
|
||||
},
|
||||
parentType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['filamentStock', 'partStock', 'productStock'], // Add other models as needed
|
||||
},
|
||||
owner: {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: 'ownerType',
|
||||
required: true,
|
||||
},
|
||||
ownerType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['user', 'subJob', 'stockAudit'],
|
||||
},
|
||||
timestamp: { type: Date, default: Date.now },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
stockEventSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
stockEventSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const stockEventModel = mongoose.model('stockEvent', stockEventSchema);
|
||||
64
src/database/schemas/management/auditlog.schema.js
Normal file
64
src/database/schemas/management/auditlog.schema.js
Normal file
@ -0,0 +1,64 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const auditLogSchema = new Schema(
|
||||
{
|
||||
changes: {
|
||||
old: { type: Object, required: true },
|
||||
new: { type: Object, required: true }
|
||||
},
|
||||
parent: {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: 'parentType',
|
||||
required: true
|
||||
},
|
||||
parentType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [
|
||||
'printer',
|
||||
'job',
|
||||
'subJob',
|
||||
'filamentStock',
|
||||
'stockEvent',
|
||||
'vendor',
|
||||
'part',
|
||||
'product',
|
||||
'material',
|
||||
'filament',
|
||||
'gcodeFile',
|
||||
'noteType',
|
||||
'note',
|
||||
'user',
|
||||
'host'
|
||||
] // Add other models as needed
|
||||
},
|
||||
owner: {
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: 'ownerType',
|
||||
required: true
|
||||
},
|
||||
ownerType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['user', 'printer', 'host']
|
||||
},
|
||||
operation: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['edit', 'new', 'delete']
|
||||
}
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
auditLogSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
auditLogSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const auditLogModel = mongoose.model('auditLog', auditLogSchema);
|
||||
33
src/database/schemas/management/documentsize.schema.js
Normal file
33
src/database/schemas/management/documentsize.schema.js
Normal file
@ -0,0 +1,33 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const documentSizeSchema = new Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
documentSizeSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
documentSizeSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const documentSizeModel = mongoose.model('documentSize', documentSizeSchema);
|
||||
61
src/database/schemas/management/documenttemplate.schema.js
Normal file
61
src/database/schemas/management/documenttemplate.schema.js
Normal file
@ -0,0 +1,61 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const documentTemplateSchema = new Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
objectType: { type: String, required: false },
|
||||
tags: [{ type: String }],
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: true,
|
||||
},
|
||||
global: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
parent: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'documentTemplate',
|
||||
required: false,
|
||||
},
|
||||
documentSize: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'documentSize',
|
||||
required: true,
|
||||
},
|
||||
documentPrinters: [
|
||||
{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'documentPrinter',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
content: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '<Container></Container>',
|
||||
},
|
||||
testObject: {
|
||||
type: Schema.Types.Mixed,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
documentTemplateSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
documentTemplateSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const documentTemplateModel = mongoose.model('documentTemplate', documentTemplateSchema);
|
||||
26
src/database/schemas/management/filament.schema.js
Normal file
26
src/database/schemas/management/filament.schema.js
Normal file
@ -0,0 +1,26 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const filamentSchema = new mongoose.Schema({
|
||||
name: { required: true, type: String },
|
||||
barcode: { required: false, type: String },
|
||||
url: { required: false, type: String },
|
||||
image: { required: false, type: Buffer },
|
||||
color: { required: true, type: String },
|
||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
||||
type: { required: true, type: String },
|
||||
cost: { required: true, type: Number },
|
||||
diameter: { required: true, type: Number },
|
||||
density: { required: true, type: Number },
|
||||
createdAt: { required: true, type: Date },
|
||||
updatedAt: { required: true, type: Date },
|
||||
emptySpoolWeight: { required: true, type: Number },
|
||||
});
|
||||
|
||||
filamentSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
filamentSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const filamentModel = mongoose.model('filament', filamentSchema);
|
||||
66
src/database/schemas/management/host.schema.js
Normal file
66
src/database/schemas/management/host.schema.js
Normal file
@ -0,0 +1,66 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
// Define the device schema
|
||||
const deviceInfoSchema = new mongoose.Schema(
|
||||
{
|
||||
os: {
|
||||
platform: { type: String },
|
||||
type: { type: String },
|
||||
release: { type: String },
|
||||
arch: { type: String },
|
||||
hostname: { type: String },
|
||||
uptime: { type: Number }
|
||||
},
|
||||
cpu: {
|
||||
cores: { type: Number },
|
||||
model: { type: String },
|
||||
speedMHz: { type: Number }
|
||||
},
|
||||
memory: {
|
||||
totalGB: { type: String }, // stored as string from .toFixed(2), could also use Number
|
||||
freeGB: { type: String }
|
||||
},
|
||||
network: {
|
||||
type: mongoose.Schema.Types.Mixed // since it's an object with dynamic interface names
|
||||
},
|
||||
user: {
|
||||
uid: { type: Number },
|
||||
gid: { type: Number },
|
||||
username: { type: String },
|
||||
homedir: { type: String },
|
||||
shell: { type: String }
|
||||
},
|
||||
process: {
|
||||
nodeVersion: { type: String },
|
||||
pid: { type: Number },
|
||||
cwd: { type: String },
|
||||
execPath: { type: String }
|
||||
}
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
const hostSchema = new mongoose.Schema({
|
||||
name: { required: true, type: String },
|
||||
tags: [{ required: false, type: String }],
|
||||
online: { required: true, type: Boolean, default: false },
|
||||
state: {
|
||||
type: { type: String, required: true, default: 'offline' },
|
||||
message: { type: String, required: false },
|
||||
percent: { type: Number, required: false }
|
||||
},
|
||||
active: { required: true, type: Boolean, default: true },
|
||||
connectedAt: { required: false, type: Date },
|
||||
authCode: { required: false, type: String },
|
||||
otp: { required: false, type: String },
|
||||
otpExpiresAt: { required: false, type: Date },
|
||||
deviceInfo: deviceInfoSchema
|
||||
});
|
||||
|
||||
hostSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
hostSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const hostModel = mongoose.model('host', hostSchema);
|
||||
16
src/database/schemas/management/material.schema.js
Normal file
16
src/database/schemas/management/material.schema.js
Normal file
@ -0,0 +1,16 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const materialSchema = new mongoose.Schema({
|
||||
name: { required: true, type: String },
|
||||
url: { required: false, type: String },
|
||||
image: { required: false, type: Buffer },
|
||||
tags: [{ type: String }],
|
||||
});
|
||||
|
||||
materialSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
materialSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const materialModel = mongoose.model('material', materialSchema);
|
||||
32
src/database/schemas/management/notetype.schema.js
Normal file
32
src/database/schemas/management/notetype.schema.js
Normal file
@ -0,0 +1,32 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const noteTypeSchema = new Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
noteTypeSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
noteTypeSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const noteTypeModel = mongoose.model('noteType', noteTypeSchema);
|
||||
27
src/database/schemas/management/part.schema.js
Normal file
27
src/database/schemas/management/part.schema.js
Normal file
@ -0,0 +1,27 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
// Define the main part schema
|
||||
const partSchema = new Schema(
|
||||
{
|
||||
name: { type: String, required: true },
|
||||
fileName: { type: String, required: false },
|
||||
product: { type: mongoose.Schema.Types.ObjectId, ref: 'product' },
|
||||
globalPricing: { type: Boolean, default: true },
|
||||
priceMode: { type: String, default: 'margin' },
|
||||
amount: { type: Number, required: false },
|
||||
margin: { type: Number, required: false },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
partSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
partSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const partModel = mongoose.model('part', partSchema);
|
||||
26
src/database/schemas/management/product.schema.js
Normal file
26
src/database/schemas/management/product.schema.js
Normal file
@ -0,0 +1,26 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
// Define the main product schema
|
||||
const productSchema = new Schema(
|
||||
{
|
||||
name: { type: String, required: true },
|
||||
tags: [{ type: String }],
|
||||
version: { type: String },
|
||||
priceMode: { type: String, default: 'margin' },
|
||||
margin: { type: Number, required: false },
|
||||
amount: { type: Number, required: false },
|
||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
// Add virtual id getter
|
||||
productSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
productSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const productModel = mongoose.model('product', productSchema);
|
||||
20
src/database/schemas/management/user.schema.js
Normal file
20
src/database/schemas/management/user.schema.js
Normal file
@ -0,0 +1,20 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const userSchema = new mongoose.Schema(
|
||||
{
|
||||
username: { required: true, type: String },
|
||||
name: { required: true, type: String },
|
||||
firstName: { required: false, type: String },
|
||||
lastName: { required: false, type: String },
|
||||
email: { required: true, type: String },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
userSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
userSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const userModel = mongoose.model('user', userSchema);
|
||||
21
src/database/schemas/management/vendor.schema.js
Normal file
21
src/database/schemas/management/vendor.schema.js
Normal file
@ -0,0 +1,21 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
const vendorSchema = new mongoose.Schema(
|
||||
{
|
||||
name: { required: true, type: String },
|
||||
website: { required: false, type: String },
|
||||
email: { required: false, type: String },
|
||||
phone: { required: false, type: String },
|
||||
contact: { required: false, type: String },
|
||||
country: { required: false, type: String },
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
vendorSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
vendorSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const vendorModel = mongoose.model('vendor', vendorSchema);
|
||||
41
src/database/schemas/misc/note.schema.js
Normal file
41
src/database/schemas/misc/note.schema.js
Normal file
@ -0,0 +1,41 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const noteSchema = new mongoose.Schema({
|
||||
parent: {
|
||||
type: Schema.Types.ObjectId,
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
noteType: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'noteType',
|
||||
required: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
required: true,
|
||||
default: Date.now,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
required: true,
|
||||
default: Date.now,
|
||||
},
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
noteSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
noteSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const noteModel = mongoose.model('note', noteSchema);
|
||||
24
src/database/schemas/production/gcodefile.schema.js
Normal file
24
src/database/schemas/production/gcodefile.schema.js
Normal file
@ -0,0 +1,24 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const gcodeFileSchema = new mongoose.Schema({
|
||||
name: { required: true, type: String },
|
||||
gcodeFileName: { required: false, type: String },
|
||||
gcodeFileInfo: { required: true, type: Object },
|
||||
size: { type: Number, required: false },
|
||||
filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true },
|
||||
parts: [{ type: Schema.Types.ObjectId, ref: 'part', required: true }],
|
||||
cost: { type: Number, required: false },
|
||||
createdAt: { type: Date },
|
||||
updatedAt: { type: Date },
|
||||
});
|
||||
|
||||
gcodeFileSchema.index({ name: 'text', brand: 'text' });
|
||||
|
||||
gcodeFileSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
gcodeFileSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const gcodeFileModel = mongoose.model('gcodeFile', gcodeFileSchema);
|
||||
34
src/database/schemas/production/job.schema.js
Normal file
34
src/database/schemas/production/job.schema.js
Normal file
@ -0,0 +1,34 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const jobSchema = new mongoose.Schema({
|
||||
state: {
|
||||
type: { required: true, type: String },
|
||||
},
|
||||
printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }],
|
||||
createdAt: { required: true, type: Date },
|
||||
updatedAt: { required: true, type: Date },
|
||||
startedAt: { required: false, type: Date },
|
||||
finishedAt: { required: false, type: Date },
|
||||
gcodeFile: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'gcodeFile',
|
||||
required: false,
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 1,
|
||||
min: 1,
|
||||
},
|
||||
subJobs: [{ type: Schema.Types.ObjectId, ref: 'subJob', required: false }],
|
||||
notes: [{ type: Schema.Types.ObjectId, ref: 'note', required: false }],
|
||||
});
|
||||
|
||||
jobSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
jobSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const jobModel = mongoose.model('job', jobSchema);
|
||||
72
src/database/schemas/production/printer.schema.js
Normal file
72
src/database/schemas/production/printer.schema.js
Normal file
@ -0,0 +1,72 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
// Define the moonraker connection schema
|
||||
const moonrakerSchema = new Schema(
|
||||
{
|
||||
host: { type: String, required: true },
|
||||
port: { type: Number, required: true },
|
||||
protocol: { type: String, required: true },
|
||||
apiKey: { type: String, default: null, required: false }
|
||||
},
|
||||
{ _id: false }
|
||||
);
|
||||
|
||||
// Define the alert schema
|
||||
const alertSchema = new Schema(
|
||||
{
|
||||
priority: { type: String, required: true }, // order to show
|
||||
type: { type: String, required: true } // selectFilament, error, info, message,
|
||||
},
|
||||
{ timestamps: true, _id: false }
|
||||
);
|
||||
|
||||
// Define the main FDM printer schema
|
||||
const printerSchema = new Schema(
|
||||
{
|
||||
name: { type: String, required: true },
|
||||
online: { type: Boolean, required: true, default: false },
|
||||
state: {
|
||||
type: { type: String, required: true, default: 'offline' },
|
||||
progress: { type: Number, required: false, default: 0 }
|
||||
},
|
||||
connectedAt: { type: Date, default: null },
|
||||
loadedFilament: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'filament',
|
||||
default: null
|
||||
},
|
||||
moonraker: { type: moonrakerSchema, required: true },
|
||||
tags: [{ type: String }],
|
||||
firmware: { type: String },
|
||||
currentJob: { type: Schema.Types.ObjectId, ref: 'job' },
|
||||
currentSubJob: { type: Schema.Types.ObjectId, ref: 'subJob' },
|
||||
currentFilamentStock: { type: Schema.Types.ObjectId, ref: 'filamentStock' },
|
||||
subJobs: [{ type: Schema.Types.ObjectId, ref: 'subJob' }],
|
||||
vendor: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'vendor',
|
||||
default: null,
|
||||
required: true
|
||||
},
|
||||
host: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'host',
|
||||
default: null,
|
||||
required: true
|
||||
},
|
||||
alerts: [alertSchema]
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Add virtual id getter
|
||||
printerSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
// Configure JSON serialization to include virtuals
|
||||
printerSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
// Create and export the model
|
||||
export const printerModel = mongoose.model('printer', printerSchema);
|
||||
50
src/database/schemas/production/subjob.schema.js
Normal file
50
src/database/schemas/production/subjob.schema.js
Normal file
@ -0,0 +1,50 @@
|
||||
import mongoose from 'mongoose';
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const subJobSchema = new mongoose.Schema({
|
||||
printer: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'printer',
|
||||
required: true,
|
||||
},
|
||||
job: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'job',
|
||||
required: true,
|
||||
},
|
||||
subJobId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
gcodeFile: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'gcodeFile',
|
||||
required: true,
|
||||
},
|
||||
state: {
|
||||
type: { required: true, type: String },
|
||||
percent: { required: false, type: Number },
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
startedAt: { required: false, type: Date },
|
||||
finishedAt: { required: false, type: Date },
|
||||
});
|
||||
|
||||
subJobSchema.virtual('id').get(function () {
|
||||
return this._id.toHexString();
|
||||
});
|
||||
|
||||
subJobSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
export const subJobModel = mongoose.model('subJob', subJobSchema);
|
||||
90
src/utils.js
Normal file
90
src/utils.js
Normal file
@ -0,0 +1,90 @@
|
||||
import { editObject } from './database/database.js';
|
||||
import { hostModel } from './database/schemas/management/host.schema.js';
|
||||
import crypto from 'crypto';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
import { loadConfig } from './config.js';
|
||||
import { userModel } from './database/schemas/management/user.schema.js';
|
||||
import { documentSizeModel } from './database/schemas/management/documentsize.schema.js';
|
||||
import { documentTemplateModel } from './database/schemas/management/documenttemplate.schema.js';
|
||||
import { printerModel } from './database/schemas/production/printer.schema.js';
|
||||
import { subJobModel } from './database/schemas/production/subjob.schema.js';
|
||||
import { jobModel } from './database/schemas/production/job.schema.js';
|
||||
import { filamentStockModel } from './database/schemas/inventory/filamentstock.schema.js';
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
const authCodeLength = 64;
|
||||
|
||||
const modelList = [
|
||||
hostModel,
|
||||
userModel,
|
||||
documentSizeModel,
|
||||
documentTemplateModel,
|
||||
printerModel,
|
||||
jobModel,
|
||||
subJobModel,
|
||||
filamentStockModel
|
||||
];
|
||||
|
||||
export async function generateHostOTP(id) {
|
||||
const otp = crypto.randomInt(0, 1000000).toString().padStart(6, '0'); // 0 to 999999
|
||||
const expiresAt = new Date(
|
||||
Date.now() + (config.otpExpiryMins || 2) * 60 * 1000
|
||||
); // 2 minutes in ms
|
||||
|
||||
const otpHost = await editObject({
|
||||
model: hostModel,
|
||||
id: id,
|
||||
updateData: { otp: otp, otpExpiresAt: expiresAt }
|
||||
});
|
||||
|
||||
return otpHost;
|
||||
}
|
||||
|
||||
export function generateAuthCode() {
|
||||
return nanoid(authCodeLength);
|
||||
}
|
||||
|
||||
export function generateEtcId() {
|
||||
return nanoid(24);
|
||||
}
|
||||
|
||||
export function getChangedValues(oldObj, newObj, old = false) {
|
||||
const changes = {};
|
||||
|
||||
// Check all keys in the new object
|
||||
for (const key in newObj) {
|
||||
// Skip if the key is _id or timestamps
|
||||
if (key === '_id' || key === 'createdAt' || key === 'updatedAt') continue;
|
||||
|
||||
const oldVal = oldObj ? oldObj[key] : undefined;
|
||||
const newVal = newObj[key];
|
||||
|
||||
// If both values are objects (but not arrays or null), recurse
|
||||
if (
|
||||
oldVal &&
|
||||
newVal &&
|
||||
typeof oldVal === 'object' &&
|
||||
typeof newVal === 'object' &&
|
||||
!Array.isArray(oldVal) &&
|
||||
!Array.isArray(newVal) &&
|
||||
oldVal !== null &&
|
||||
newVal !== null
|
||||
) {
|
||||
const nestedChanges = this.getChangedValues(oldVal, newVal, old);
|
||||
if (Object.keys(nestedChanges).length > 0) {
|
||||
changes[key] = nestedChanges;
|
||||
}
|
||||
} else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
|
||||
// If the old value is different from the new value, include it
|
||||
changes[key] = old ? oldVal : newVal;
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
export function getModelByName(modelName) {
|
||||
return modelList.filter(model => model.modelName == modelName);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user