diff --git a/src/schemas/inventory/filamentstock.schema.js b/src/database/schemas/inventory/filamentstock.schema.js similarity index 60% rename from src/schemas/inventory/filamentstock.schema.js rename to src/database/schemas/inventory/filamentstock.schema.js index 182e1b3..468c8c4 100644 --- a/src/schemas/inventory/filamentstock.schema.js +++ b/src/database/schemas/inventory/filamentstock.schema.js @@ -1,6 +1,7 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; // Define the main filamentStock schema const filamentStockSchema = new Schema( @@ -23,6 +24,35 @@ const filamentStockSchema = new Schema( { timestamps: true } ); +const rollupConfigs = [ + { + name: 'totalCurrentWeight', + filter: {}, + rollups: [{ name: 'totalCurrentWeight', property: 'currentWeight.net', operation: 'sum' }], + }, +]; + +filamentStockSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +filamentStockSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + // Return time-series data array + return results; +}; + // Add virtual id getter filamentStockSchema.virtual('id').get(function () { return this._id; diff --git a/src/database/schemas/inventory/orderitem.schema.js b/src/database/schemas/inventory/orderitem.schema.js new file mode 100644 index 0000000..b5d0e9b --- /dev/null +++ b/src/database/schemas/inventory/orderitem.schema.js @@ -0,0 +1,82 @@ +import mongoose from 'mongoose'; +import { purchaseOrderModel } from './purchaseorder.schema.js'; +import { aggregateRollups, editObject } from '../../database.js'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +const orderItemSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + orderType: { type: String, required: true }, + order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true }, + itemType: { type: String, required: true }, + item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: true }, + syncAmount: { type: String, required: true, default: null }, + itemAmount: { type: Number, required: true }, + quantity: { type: Number, required: true }, + totalAmount: { type: Number, required: true }, + taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + totalAmountWithTax: { type: Number, required: true }, + timestamp: { type: Date, default: Date.now }, + }, + { timestamps: true } +); + +orderItemSchema.statics.recalculate = async function (orderItem, user) { + // Only purchase orders are supported for now + if (orderItem.orderType !== 'purchaseOrder') { + return; + } + + const orderId = orderItem.order?._id || orderItem.order; + if (!orderId) { + return; + } + + const rollupResults = await aggregateRollups({ + model: this, + baseFilter: { + order: new mongoose.Types.ObjectId(orderId), + orderType: orderItem.orderType, + }, + rollupConfigs: [ + { + name: 'orderTotals', + rollups: [ + { name: 'totalAmount', property: 'totalAmount', operation: 'sum' }, + { + name: 'totalAmountWithTax', + property: 'totalAmountWithTax', + operation: 'sum', + }, + ], + }, + ], + }); + + const totals = rollupResults.orderTotals || {}; + const totalAmount = totals.totalAmount.sum?.toFixed(2) || 0; + const totalAmountWithTax = totals.totalAmountWithTax.sum?.toFixed(2) || 0; + + await editObject({ + model: purchaseOrderModel, + id: orderId, + updateData: { + totalAmount: parseFloat(totalAmount), + totalAmountWithTax: parseFloat(totalAmountWithTax), + totalTaxAmount: parseFloat(totalAmountWithTax - totalAmount), + }, + user, + }); +}; + +// Add virtual id getter +orderItemSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +orderItemSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const orderItemModel = mongoose.model('orderItem', orderItemSchema); diff --git a/src/schemas/inventory/partstock.schema.js b/src/database/schemas/inventory/partstock.schema.js similarity index 58% rename from src/schemas/inventory/partstock.schema.js rename to src/database/schemas/inventory/partstock.schema.js index 584c7a9..772ea61 100644 --- a/src/schemas/inventory/partstock.schema.js +++ b/src/database/schemas/inventory/partstock.schema.js @@ -1,6 +1,7 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; // Define the main partStock schema const partStockSchema = new Schema( @@ -18,6 +19,35 @@ const partStockSchema = new Schema( { timestamps: true } ); +const rollupConfigs = [ + { + name: 'totalCurrentQuantity', + filter: {}, + rollups: [{ name: 'totalCurrentQuantity', property: 'currentQuantity', operation: 'sum' }], + }, +]; + +partStockSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + return results; +}; + +partStockSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + // Return time-series data array + return results; +}; + // Add virtual id getter partStockSchema.virtual('id').get(function () { return this._id; diff --git a/src/schemas/inventory/purchaseorder.schema.js b/src/database/schemas/inventory/purchaseorder.schema.js similarity index 58% rename from src/schemas/inventory/purchaseorder.schema.js rename to src/database/schemas/inventory/purchaseorder.schema.js index 4d08762..e4d8d5d 100644 --- a/src/schemas/inventory/purchaseorder.schema.js +++ b/src/database/schemas/inventory/purchaseorder.schema.js @@ -2,21 +2,12 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; -const itemSchema = new Schema({ - itemType: { type: String, required: true }, - item: { type: Schema.Types.ObjectId, refPath: 'items.itemType', required: true }, - quantity: { type: Number, required: true }, - itemCost: { type: Number, required: true }, - totalCost: { type: Number, required: true }, - totalCostWithTax: { type: Number, required: true }, - taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, -}); - const purchaseOrderSchema = new Schema( { _reference: { type: String, default: () => generateId()() }, - cost: { net: { type: Number, required: true }, gross: { type: Number, required: true } }, - items: [itemSchema], + totalAmount: { type: Number, required: true }, + totalAmountWithTax: { type: Number, required: true }, + totalTaxAmount: { type: Number, required: true }, timestamp: { type: Date, default: Date.now }, vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true }, state: { diff --git a/src/schemas/inventory/shipment.schema.js b/src/database/schemas/inventory/shipment.schema.js similarity index 100% rename from src/schemas/inventory/shipment.schema.js rename to src/database/schemas/inventory/shipment.schema.js diff --git a/src/schemas/inventory/stockaudit.schema.js b/src/database/schemas/inventory/stockaudit.schema.js similarity index 100% rename from src/schemas/inventory/stockaudit.schema.js rename to src/database/schemas/inventory/stockaudit.schema.js diff --git a/src/schemas/inventory/stockevent.schema.js b/src/database/schemas/inventory/stockevent.schema.js similarity index 100% rename from src/schemas/inventory/stockevent.schema.js rename to src/database/schemas/inventory/stockevent.schema.js diff --git a/src/schemas/management/auditlog.schema.js b/src/database/schemas/management/auditlog.schema.js similarity index 100% rename from src/schemas/management/auditlog.schema.js rename to src/database/schemas/management/auditlog.schema.js diff --git a/src/schemas/management/courier.schema.js b/src/database/schemas/management/courier.schema.js similarity index 100% rename from src/schemas/management/courier.schema.js rename to src/database/schemas/management/courier.schema.js diff --git a/src/schemas/management/courierservice.schema.js b/src/database/schemas/management/courierservice.schema.js similarity index 100% rename from src/schemas/management/courierservice.schema.js rename to src/database/schemas/management/courierservice.schema.js diff --git a/src/schemas/management/documentjob.schema.js b/src/database/schemas/management/documentjob.schema.js similarity index 91% rename from src/schemas/management/documentjob.schema.js rename to src/database/schemas/management/documentjob.schema.js index 9d981f6..aba8135 100644 --- a/src/schemas/management/documentjob.schema.js +++ b/src/database/schemas/management/documentjob.schema.js @@ -18,7 +18,8 @@ const documentJobSchema = new Schema( }, state: { type: { type: String, required: true, default: 'queued' }, - percent: { type: Number, required: false }, + progress: { type: Number, required: false }, + message: { type: String, required: false }, }, documentTemplate: { type: Schema.Types.ObjectId, diff --git a/src/schemas/management/documentprinter.schema.js b/src/database/schemas/management/documentprinter.schema.js similarity index 100% rename from src/schemas/management/documentprinter.schema.js rename to src/database/schemas/management/documentprinter.schema.js diff --git a/src/schemas/management/documentsize.schema.js b/src/database/schemas/management/documentsize.schema.js similarity index 100% rename from src/schemas/management/documentsize.schema.js rename to src/database/schemas/management/documentsize.schema.js diff --git a/src/schemas/management/documenttemplate.schema.js b/src/database/schemas/management/documenttemplate.schema.js similarity index 100% rename from src/schemas/management/documenttemplate.schema.js rename to src/database/schemas/management/documenttemplate.schema.js diff --git a/src/schemas/management/filament.schema.js b/src/database/schemas/management/filament.schema.js similarity index 88% rename from src/schemas/management/filament.schema.js rename to src/database/schemas/management/filament.schema.js index 270dfd0..54589c2 100644 --- a/src/schemas/management/filament.schema.js +++ b/src/database/schemas/management/filament.schema.js @@ -12,6 +12,8 @@ const filamentSchema = new mongoose.Schema({ vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true }, type: { required: true, type: String }, cost: { required: true, type: Number }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: true }, + costWithTax: { required: true, type: Number }, diameter: { required: true, type: Number }, density: { required: true, type: Number }, createdAt: { required: true, type: Date }, diff --git a/src/schemas/management/file.schema.js b/src/database/schemas/management/file.schema.js similarity index 100% rename from src/schemas/management/file.schema.js rename to src/database/schemas/management/file.schema.js diff --git a/src/schemas/management/host.schema.js b/src/database/schemas/management/host.schema.js similarity index 100% rename from src/schemas/management/host.schema.js rename to src/database/schemas/management/host.schema.js diff --git a/src/schemas/management/material.schema.js b/src/database/schemas/management/material.schema.js similarity index 100% rename from src/schemas/management/material.schema.js rename to src/database/schemas/management/material.schema.js diff --git a/src/schemas/management/notetype.schema.js b/src/database/schemas/management/notetype.schema.js similarity index 100% rename from src/schemas/management/notetype.schema.js rename to src/database/schemas/management/notetype.schema.js diff --git a/src/schemas/management/part.schema.js b/src/database/schemas/management/part.schema.js similarity index 100% rename from src/schemas/management/part.schema.js rename to src/database/schemas/management/part.schema.js diff --git a/src/schemas/management/product.schema.js b/src/database/schemas/management/product.schema.js similarity index 100% rename from src/schemas/management/product.schema.js rename to src/database/schemas/management/product.schema.js diff --git a/src/schemas/management/taxrates.schema.js b/src/database/schemas/management/taxrates.schema.js similarity index 100% rename from src/schemas/management/taxrates.schema.js rename to src/database/schemas/management/taxrates.schema.js diff --git a/src/schemas/management/taxrecord.schema.js b/src/database/schemas/management/taxrecord.schema.js similarity index 100% rename from src/schemas/management/taxrecord.schema.js rename to src/database/schemas/management/taxrecord.schema.js diff --git a/src/schemas/management/user.schema.js b/src/database/schemas/management/user.schema.js similarity index 100% rename from src/schemas/management/user.schema.js rename to src/database/schemas/management/user.schema.js diff --git a/src/schemas/management/vendor.schema.js b/src/database/schemas/management/vendor.schema.js similarity index 100% rename from src/schemas/management/vendor.schema.js rename to src/database/schemas/management/vendor.schema.js diff --git a/src/schemas/misc/note.schema.js b/src/database/schemas/misc/note.schema.js similarity index 100% rename from src/schemas/misc/note.schema.js rename to src/database/schemas/misc/note.schema.js diff --git a/src/schemas/models.js b/src/database/schemas/models.js similarity index 96% rename from src/schemas/models.js rename to src/database/schemas/models.js index 17de5ac..98c37bf 100644 --- a/src/schemas/models.js +++ b/src/database/schemas/models.js @@ -8,6 +8,7 @@ import { productModel } from './management/product.schema.js'; import { vendorModel } from './management/vendor.schema.js'; import { filamentStockModel } from './inventory/filamentstock.schema.js'; import { purchaseOrderModel } from './inventory/purchaseorder.schema.js'; +import { orderItemModel } from './inventory/orderitem.schema.js'; import { stockEventModel } from './inventory/stockevent.schema.js'; import { stockAuditModel } from './inventory/stockaudit.schema.js'; import { partStockModel } from './inventory/partstock.schema.js'; @@ -82,6 +83,12 @@ export const models = { type: 'purchaseOrder', referenceField: '_reference', }, + ODI: { + model: orderItemModel, + idField: '_id', + type: 'orderItem', + referenceField: '_reference', + }, COS: { model: courierServiceModel, idField: '_id', diff --git a/src/schemas/production/gcodefile.schema.js b/src/database/schemas/production/gcodefile.schema.js similarity index 100% rename from src/schemas/production/gcodefile.schema.js rename to src/database/schemas/production/gcodefile.schema.js diff --git a/src/database/schemas/production/job.schema.js b/src/database/schemas/production/job.schema.js new file mode 100644 index 0000000..2163e29 --- /dev/null +++ b/src/database/schemas/production/job.schema.js @@ -0,0 +1,91 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; + +const jobSchema = new mongoose.Schema( + { + _reference: { type: String, default: () => generateId()() }, + state: { + type: { required: true, type: String }, + progress: { type: Number, required: false }, + }, + printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }], + createdAt: { required: true, type: Date }, + updatedAt: { required: true, type: Date }, + startedAt: { required: false, type: Date, default: null }, + finishedAt: { required: false, type: Date, default: null }, + 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 }], + }, + { timestamps: true } +); + +const rollupConfigs = [ + { + name: 'queued', + filter: { 'state.type': 'queued' }, + rollups: [{ name: 'queued', property: 'state.type', operation: 'count' }], + }, + { + name: 'printing', + filter: { 'state.type': 'printing' }, + rollups: [{ name: 'printing', property: 'state.type', operation: 'count' }], + }, + { + name: 'draft', + filter: { 'state.type': 'draft' }, + rollups: [{ name: 'draft', property: 'state.type', operation: 'count' }], + }, + { + name: 'complete', + filter: { 'state.type': 'complete' }, + rollups: [{ name: 'complete', property: 'state.type', operation: 'count' }], + }, + { + name: 'failed', + filter: { 'state.type': 'failed' }, + rollups: [{ name: 'failed', property: 'state.type', operation: 'count' }], + }, +]; + +jobSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + rollupConfigs: rollupConfigs, + }); + + // Transform the results to match the expected format + return results; +}; + +jobSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + // Return time-series data array + return results; +}; + +jobSchema.virtual('id').get(function () { + return this._id; +}); + +jobSchema.set('toJSON', { virtuals: true }); + +export const jobModel = mongoose.model('job', jobSchema); diff --git a/src/schemas/production/printer.schema.js b/src/database/schemas/production/printer.schema.js similarity index 62% rename from src/schemas/production/printer.schema.js rename to src/database/schemas/production/printer.schema.js index cf035b4..53c5447 100644 --- a/src/schemas/production/printer.schema.js +++ b/src/database/schemas/production/printer.schema.js @@ -1,6 +1,7 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; +import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; // Define the moonraker connection schema const moonrakerSchema = new Schema( @@ -56,6 +57,59 @@ const printerSchema = new Schema( { timestamps: true } ); +const rollupConfigs = [ + { + name: 'standby', + filter: { 'state.type': 'standby' }, + rollups: [{ name: 'standby', property: 'state.type', operation: 'count' }], + }, + { + name: 'complete', + filter: { 'state.type': 'complete' }, + rollups: [{ name: 'complete', property: 'state.type', operation: 'count' }], + }, + { + name: 'printing', + filter: { 'state.type': 'printing' }, + rollups: [{ name: 'printing', property: 'state.type', operation: 'count' }], + }, + { + name: 'error', + filter: { 'state.type': 'error' }, + rollups: [{ name: 'error', property: 'state.type', operation: 'count' }], + }, + { + name: 'offline', + filter: { 'state.type': 'offline' }, + rollups: [{ name: 'offline', property: 'state.type', operation: 'count' }], + }, +]; + +printerSchema.statics.stats = async function () { + const results = await aggregateRollups({ + model: this, + baseFilter: { active: true }, + rollupConfigs: rollupConfigs, + }); + + console.log(results); + + // Transform the results to match the expected format + return results; +}; + +printerSchema.statics.history = async function (from, to) { + const results = await aggregateRollupsHistory({ + model: this, + startDate: from, + endDate: to, + rollupConfigs: rollupConfigs, + }); + + // Return time-series data array + return results; +}; + // Add virtual id getter printerSchema.virtual('id').get(function () { return this._id; diff --git a/src/schemas/production/subjob.schema.js b/src/database/schemas/production/subjob.schema.js similarity index 100% rename from src/schemas/production/subjob.schema.js rename to src/database/schemas/production/subjob.schema.js diff --git a/src/schemas/sales/salesorder.schema.js b/src/database/schemas/sales/salesorder.schema.js similarity index 100% rename from src/schemas/sales/salesorder.schema.js rename to src/database/schemas/sales/salesorder.schema.js diff --git a/src/database/utils.js b/src/database/utils.js new file mode 100644 index 0000000..b295f4a --- /dev/null +++ b/src/database/utils.js @@ -0,0 +1,7 @@ +import { customAlphabet } from 'nanoid'; + +const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; +export const generateId = () => { + // 10 characters + return customAlphabet(ALPHABET, 12); +}; diff --git a/src/schemas/inventory/orderitem.schema.js b/src/schemas/inventory/orderitem.schema.js deleted file mode 100644 index 42a68f8..0000000 --- a/src/schemas/inventory/orderitem.schema.js +++ /dev/null @@ -1,32 +0,0 @@ -import mongoose from 'mongoose'; -import { generateId } from '../../utils.js'; -const { Schema } = mongoose; - -const orderItemSchema = new Schema( - { - _reference: { type: String, default: () => generateId()() }, - orderType: { type: String, required: true }, - order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true }, - itemType: { type: String, required: true }, - item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: true }, - syncAmount: { type: String, required: true, default: null }, - itemAmount: { type: Number, required: true }, - quantity: { type: Number, required: true }, - totalAmount: { type: Number, required: true }, - taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, - totalAmountWithTax: { type: Number, required: true }, - timestamp: { type: Date, default: Date.now }, - }, - { timestamps: true } -); - -// Add virtual id getter -orderItemSchema.virtual('id').get(function () { - return this._id; -}); - -// Configure JSON serialization to include virtuals -orderItemSchema.set('toJSON', { virtuals: true }); - -// Create and export the model -export const orderItemModel = mongoose.model('orderItem', orderItemSchema); diff --git a/src/schemas/production/job.schema.js b/src/schemas/production/job.schema.js deleted file mode 100644 index 78f484a..0000000 --- a/src/schemas/production/job.schema.js +++ /dev/null @@ -1,40 +0,0 @@ -import mongoose from 'mongoose'; -import { generateId } from '../../utils.js'; -const { Schema } = mongoose; - -const jobSchema = new mongoose.Schema( - { - _reference: { type: String, default: () => generateId()() }, - state: { - type: { required: true, type: String }, - progress: { type: Number, required: false }, - }, - printers: [{ type: Schema.Types.ObjectId, ref: 'printer', required: false }], - createdAt: { required: true, type: Date }, - updatedAt: { required: true, type: Date }, - startedAt: { required: false, type: Date, default: null }, - finishedAt: { required: false, type: Date, default: null }, - 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 }], - }, - { timestamps: true } -); - -jobSchema.virtual('id').get(function () { - return this._id; -}); - -jobSchema.set('toJSON', { virtuals: true }); - -export const jobModel = mongoose.model('job', jobSchema);