diff --git a/src/database/schemas/inventory/partstock.schema.js b/src/database/schemas/inventory/partstock.schema.js index 772ea61..95d6baf 100644 --- a/src/database/schemas/inventory/partstock.schema.js +++ b/src/database/schemas/inventory/partstock.schema.js @@ -11,7 +11,7 @@ const partStockSchema = new Schema( type: { type: String, required: true }, progress: { type: Number, required: false }, }, - part: { type: mongoose.Schema.Types.ObjectId, ref: 'part', required: true }, + partSku: { type: mongoose.Schema.Types.ObjectId, ref: 'partSku', required: true }, currentQuantity: { type: Number, required: true }, sourceType: { type: String, required: true }, source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true }, diff --git a/src/database/schemas/inventory/productstock.schema.js b/src/database/schemas/inventory/productstock.schema.js index 88bffe0..86afaa4 100644 --- a/src/database/schemas/inventory/productstock.schema.js +++ b/src/database/schemas/inventory/productstock.schema.js @@ -5,7 +5,7 @@ import { aggregateRollups, aggregateRollupsHistory } from '../../database.js'; const partStockUsageSchema = new Schema({ partStock: { type: Schema.Types.ObjectId, ref: 'partStock', required: false }, - part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, + partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true }, quantity: { type: Number, required: true }, }); @@ -18,7 +18,7 @@ const productStockSchema = new Schema( progress: { type: Number, required: false }, }, postedAt: { type: Date, required: false }, - product: { type: mongoose.Schema.Types.ObjectId, ref: 'product', required: true }, + productSku: { type: mongoose.Schema.Types.ObjectId, ref: 'productSku', required: true }, currentQuantity: { type: Number, required: true }, partStocks: [partStockUsageSchema], }, diff --git a/src/database/schemas/management/part.schema.js b/src/database/schemas/management/part.schema.js index 4d84001..c7fdff4 100644 --- a/src/database/schemas/management/part.schema.js +++ b/src/database/schemas/management/part.schema.js @@ -2,22 +2,13 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; -// Define the main part schema +// Define the main part schema - pricing moved to PartSku const partSchema = new Schema( { _reference: { type: String, default: () => generateId()() }, name: { type: String, required: true }, fileName: { type: String, required: false }, - priceMode: { type: String, default: 'margin' }, - price: { type: Number, required: true }, - cost: { type: Number, required: true }, - margin: { type: Number, required: false }, - vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true }, file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false }, - priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, - costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, - priceWithTax: { type: Number, required: false }, - costWithTax: { type: Number, required: false }, }, { timestamps: true } ); diff --git a/src/database/schemas/management/partsku.schema.js b/src/database/schemas/management/partsku.schema.js new file mode 100644 index 0000000..d21e2df --- /dev/null +++ b/src/database/schemas/management/partsku.schema.js @@ -0,0 +1,36 @@ +import mongoose from 'mongoose'; +import { generateId } from '../../utils.js'; +const { Schema } = mongoose; + +// Define the main part SKU schema - pricing lives at SKU level +const partSkuSchema = new Schema( + { + _reference: { type: String, default: () => generateId()() }, + sku: { type: String, required: true }, + part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, + name: { type: String, required: true }, + description: { type: String, required: false }, + priceMode: { type: String, default: 'margin' }, + price: { type: Number, required: false }, + cost: { type: Number, required: false }, + margin: { type: Number, required: false }, + amount: { type: Number, required: false }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false }, + priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + priceWithTax: { type: Number, required: false }, + costWithTax: { type: Number, required: false }, + }, + { timestamps: true } +); + +// Add virtual id getter +partSkuSchema.virtual('id').get(function () { + return this._id; +}); + +// Configure JSON serialization to include virtuals +partSkuSchema.set('toJSON', { virtuals: true }); + +// Create and export the model +export const partSkuModel = mongoose.model('partSku', partSkuSchema); diff --git a/src/database/schemas/management/product.schema.js b/src/database/schemas/management/product.schema.js index 64dee0d..80c7b16 100644 --- a/src/database/schemas/management/product.schema.js +++ b/src/database/schemas/management/product.schema.js @@ -2,11 +2,6 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; -const partSchema = new Schema({ - part: { type: Schema.Types.ObjectId, ref: 'part', required: true }, - quantity: { type: Number, required: true }, -}); - // Define the main product schema const productSchema = new Schema( { @@ -14,13 +9,7 @@ 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 }, - parts: [partSchema], - priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, - costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, }, { timestamps: true } ); diff --git a/src/database/schemas/management/productsku.schema.js b/src/database/schemas/management/productsku.schema.js index 42cfa73..ac0c452 100644 --- a/src/database/schemas/management/productsku.schema.js +++ b/src/database/schemas/management/productsku.schema.js @@ -2,6 +2,11 @@ import mongoose from 'mongoose'; import { generateId } from '../../utils.js'; const { Schema } = mongoose; +const partSkuUsageSchema = new Schema({ + partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true }, + quantity: { type: Number, required: true }, +}); + // Define the main product SKU schema const productSkuSchema = new Schema( { @@ -10,6 +15,17 @@ const productSkuSchema = new Schema( product: { type: Schema.Types.ObjectId, ref: 'product', required: true }, name: { type: String, required: true }, description: { type: String, required: false }, + priceMode: { type: String, default: 'margin' }, + price: { type: Number, required: false }, + cost: { type: Number, required: false }, + margin: { type: Number, required: false }, + amount: { type: Number, required: false }, + vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: false }, + parts: [partSkuUsageSchema], + priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false }, + priceWithTax: { type: Number, required: false }, + costWithTax: { type: Number, required: false }, }, { timestamps: true } ); diff --git a/src/database/schemas/models.js b/src/database/schemas/models.js index a990b06..0130206 100644 --- a/src/database/schemas/models.js +++ b/src/database/schemas/models.js @@ -4,6 +4,7 @@ import { printerModel } from './production/printer.schema.js'; import { filamentModel } from './management/filament.schema.js'; import { gcodeFileModel } from './production/gcodefile.schema.js'; import { partModel } from './management/part.schema.js'; +import { partSkuModel } from './management/partsku.schema.js'; import { productModel } from './management/product.schema.js'; import { productSkuModel } from './management/productsku.schema.js'; import { vendorModel } from './management/vendor.schema.js'; @@ -67,6 +68,13 @@ export const models = { referenceField: '_reference', label: 'Part', }, + PSU: { + model: partSkuModel, + idField: '_id', + type: 'partSku', + referenceField: '_reference', + label: 'Part SKU', + }, PRD: { model: productModel, idField: '_id', diff --git a/src/index.js b/src/index.js index 8f50f05..4e4d6c7 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,7 @@ import { filamentRoutes, spotlightRoutes, partRoutes, + partSkuRoutes, productRoutes, productSkuRoutes, vendorRoutes, @@ -133,6 +134,7 @@ app.use('/subjobs', subJobRoutes); app.use('/gcodefiles', gcodeFileRoutes); app.use('/filaments', filamentRoutes); app.use('/parts', partRoutes); +app.use('/partskus', partSkuRoutes); app.use('/products', productRoutes); app.use('/productskus', productSkuRoutes); app.use('/vendors', vendorRoutes); diff --git a/src/routes/index.js b/src/routes/index.js index 894947e..f37b754 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -10,6 +10,7 @@ import gcodeFileRoutes from './production/gcodefiles.js'; import filamentRoutes from './management/filaments.js'; import spotlightRoutes from './misc/spotlight.js'; import partRoutes from './management/parts.js'; +import partSkuRoutes from './management/partskus.js'; import productRoutes from './management/products.js'; import productSkuRoutes from './management/productskus.js'; import vendorRoutes from './management/vendors.js'; @@ -56,6 +57,7 @@ export { filamentRoutes, spotlightRoutes, partRoutes, + partSkuRoutes, productRoutes, productSkuRoutes, vendorRoutes, diff --git a/src/routes/inventory/partstocks.js b/src/routes/inventory/partstocks.js index 6918316..050f0ff 100644 --- a/src/routes/inventory/partstocks.js +++ b/src/routes/inventory/partstocks.js @@ -18,7 +18,7 @@ import { // list of part stocks router.get('/', isAuthenticated, (req, res) => { const { page, limit, property, search, sort, order } = req.query; - const allowedFilters = ['part', 'state', 'startingQuantity', 'currentQuantity', 'part._id']; + const allowedFilters = ['partSku', 'state', 'startingQuantity', 'currentQuantity', 'partSku._id']; const filter = getFilter(req.query, allowedFilters); listPartStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order); }); diff --git a/src/routes/inventory/productstocks.js b/src/routes/inventory/productstocks.js index bd5c669..b5fc3bb 100644 --- a/src/routes/inventory/productstocks.js +++ b/src/routes/inventory/productstocks.js @@ -18,14 +18,14 @@ import { router.get('/', isAuthenticated, (req, res) => { const { page, limit, property, search, sort, order } = req.query; - const allowedFilters = ['product', 'state', 'currentQuantity', 'product._id']; + const allowedFilters = ['productSku', 'state', 'currentQuantity', 'productSku._id']; const filter = getFilter(req.query, allowedFilters); listProductStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order); }); router.get('/properties', isAuthenticated, (req, res) => { let properties = convertPropertiesString(req.query.properties); - const allowedFilters = ['product', 'state.type']; + const allowedFilters = ['productSku', 'state.type']; const filter = getFilter(req.query, allowedFilters, false); var masterFilter = {}; if (req.query.masterFilter) { diff --git a/src/routes/management/partskus.js b/src/routes/management/partskus.js new file mode 100644 index 0000000..b508a84 --- /dev/null +++ b/src/routes/management/partskus.js @@ -0,0 +1,59 @@ +import express from 'express'; +import { isAuthenticated } from '../../keycloak.js'; +import { getFilter, convertPropertiesString } from '../../utils.js'; + +const router = express.Router(); +import { + listPartSkusRouteHandler, + getPartSkuRouteHandler, + editPartSkuRouteHandler, + newPartSkuRouteHandler, + deletePartSkuRouteHandler, + listPartSkusByPropertiesRouteHandler, + getPartSkuStatsRouteHandler, + getPartSkuHistoryRouteHandler, +} from '../../services/management/partskus.js'; + +router.get('/', isAuthenticated, (req, res) => { + const { page, limit, property, search, sort, order } = req.query; + const allowedFilters = ['_id', 'sku', 'part', 'part._id', 'name', 'cost', 'price']; + const filter = getFilter(req.query, allowedFilters); + listPartSkusRouteHandler(req, res, page, limit, property, filter, search, sort, order); +}); + +router.get('/properties', isAuthenticated, (req, res) => { + let properties = convertPropertiesString(req.query.properties); + const allowedFilters = ['part', 'part._id']; + const filter = getFilter(req.query, allowedFilters, false); + let masterFilter = {}; + if (req.query.masterFilter) { + masterFilter = JSON.parse(req.query.masterFilter); + } + listPartSkusByPropertiesRouteHandler(req, res, properties, filter, masterFilter); +}); + +router.post('/', isAuthenticated, (req, res) => { + newPartSkuRouteHandler(req, res); +}); + +router.get('/stats', isAuthenticated, (req, res) => { + getPartSkuStatsRouteHandler(req, res); +}); + +router.get('/history', isAuthenticated, (req, res) => { + getPartSkuHistoryRouteHandler(req, res); +}); + +router.get('/:id', isAuthenticated, (req, res) => { + getPartSkuRouteHandler(req, res); +}); + +router.put('/:id', isAuthenticated, async (req, res) => { + editPartSkuRouteHandler(req, res); +}); + +router.delete('/:id', isAuthenticated, async (req, res) => { + deletePartSkuRouteHandler(req, res); +}); + +export default router; diff --git a/src/routes/management/productskus.js b/src/routes/management/productskus.js index 126e303..a91b9ec 100644 --- a/src/routes/management/productskus.js +++ b/src/routes/management/productskus.js @@ -16,7 +16,7 @@ import { router.get('/', isAuthenticated, (req, res) => { const { page, limit, property, search, sort, order } = req.query; - const allowedFilters = ['_id', 'sku', 'product', 'product._id', 'name']; + const allowedFilters = ['_id', 'sku', 'product', 'product._id', 'name', 'cost', 'price']; const filter = getFilter(req.query, allowedFilters); listProductSkusRouteHandler(req, res, page, limit, property, filter, search, sort, order); }); diff --git a/src/services/inventory/partstocks.js b/src/services/inventory/partstocks.js index 613c504..61d1fb1 100644 --- a/src/services/inventory/partstocks.js +++ b/src/services/inventory/partstocks.js @@ -36,7 +36,7 @@ export const listPartStocksRouteHandler = async ( search, sort, order, - populate: [{ path: 'part' }], + populate: [{ path: 'partSku' }], }); if (result?.error) { @@ -60,7 +60,7 @@ export const listPartStocksByPropertiesRouteHandler = async ( model: partStockModel, properties, filter, - populate: ['part'], + populate: ['partSku'], masterFilter, }); @@ -79,7 +79,7 @@ export const getPartStockRouteHandler = async (req, res) => { const result = await getObject({ model: partStockModel, id, - populate: [{ path: 'part' }], + populate: [{ path: 'partSku' }], }); if (result?.error) { logger.warn(`Part Stock not found with supplied id.`); @@ -146,7 +146,7 @@ export const newPartStockRouteHandler = async (req, res) => { updatedAt: new Date(), startingQuantity: req.body.startingQuantity, currentQuantity: req.body.currentQuantity, - part: req.body.part, + partSku: req.body.partSku, state: req.body.state, }; const result = await newObject({ diff --git a/src/services/inventory/productstocks.js b/src/services/inventory/productstocks.js index 5587437..9a0d0d5 100644 --- a/src/services/inventory/productstocks.js +++ b/src/services/inventory/productstocks.js @@ -14,7 +14,7 @@ import { getModelHistory, checkStates, } from '../../database/database.js'; -import { productModel } from '../../database/schemas/management/product.schema.js'; +import { productSkuModel } from '../../database/schemas/management/productsku.schema.js'; const logger = log4js.getLogger('Product Stocks'); logger.level = config.server.logLevel; @@ -38,7 +38,7 @@ export const listProductStocksRouteHandler = async ( search, sort, order, - populate: [{ path: 'product' }, { path: 'partStocks.partStock' }], + populate: [{ path: 'productSku' }, { path: 'partStocks.partStock' }], }); if (result?.error) { @@ -62,7 +62,7 @@ export const listProductStocksByPropertiesRouteHandler = async ( model: productStockModel, properties, filter, - populate: ['product', 'partStocks.partStock'], + populate: ['productSku', 'partStocks.partStock'], masterFilter, }); @@ -81,7 +81,7 @@ export const getProductStockRouteHandler = async (req, res) => { const result = await getObject({ model: productStockModel, id, - populate: [{ path: 'partStocks.part' }, { path: 'partStocks.partStock' }, { path: 'product' }], + populate: [{ path: 'partStocks.partSku' }, { path: 'partStocks.partStock' }, { path: 'productSku' }], }); if (result?.error) { logger.warn(`Product Stock not found with supplied id.`); @@ -114,6 +114,7 @@ export const editProductStockRouteHandler = async (req, res) => { partStocks: req.body?.partStocks?.map((partStock) => ({ quantity: partStock.quantity, partStock: partStock.partStock, + partSku: partStock.partSku, })), }; @@ -162,18 +163,18 @@ export const editMultipleProductStocksRouteHandler = async (req, res) => { }; export const newProductStockRouteHandler = async (req, res) => { - const productId = new mongoose.Types.ObjectId(req.body.product?._id); - const product = await getObject({ - model: productModel, - id: productId, + const productSkuId = new mongoose.Types.ObjectId(req.body.productSku?._id); + const productSku = await getObject({ + model: productSkuModel, + id: productSkuId, }); const newData = { updatedAt: new Date(), currentQuantity: req.body.currentQuantity, - product: req.body.product, + productSku: req.body.productSku, state: req.body.state ?? { type: 'draft' }, - partStocks: product.parts.map((part) => ({ - part: part.part, + partStocks: (productSku.parts || []).map((part) => ({ + partSku: part.partSku, quantity: part.quantity, partStock: undefined, })), diff --git a/src/services/management/parts.js b/src/services/management/parts.js index c2e8b00..cde2db5 100644 --- a/src/services/management/parts.js +++ b/src/services/management/parts.js @@ -35,7 +35,7 @@ export const listPartsRouteHandler = async ( search, sort, order, - populate: ['vendor'], + populate: [], }); if (result?.error) { @@ -53,20 +53,7 @@ export const listPartsByPropertiesRouteHandler = async (req, res, properties = ' model: partModel, properties, filter, - populate: [ - { - path: 'vendor', - from: 'vendors', - }, - { - path: 'priceTaxRate', - from: 'taxrates', - }, - { - path: 'costTaxRate', - from: 'taxrates', - }, - ], + populate: [], }); if (result?.error) { @@ -84,7 +71,7 @@ export const getPartRouteHandler = async (req, res) => { const result = await getObject({ model: partModel, id, - populate: ['vendor', 'priceTaxRate', 'costTaxRate'], + populate: [], }); if (result?.error) { logger.warn(`Part not found with supplied id.`); @@ -103,16 +90,8 @@ export const editPartRouteHandler = async (req, res) => { const updateData = { updatedAt: new Date(), name: req.body?.name, + fileName: req.body?.fileName, file: req.body?.file, - vendor: req.body?.vendor, - margin: req.body?.margin, - price: req.body?.price, - cost: req.body?.cost, - priceMode: req.body?.priceMode, - priceTaxRate: req.body?.priceTaxRate, - costTaxRate: req.body?.costTaxRate, - priceWithTax: req.body?.priceWithTax, - costWithTax: req.body?.costWithTax, }; // Create audit log before updating const result = await editObject({ @@ -137,16 +116,8 @@ export const newPartRouteHandler = async (req, res) => { const newData = { updatedAt: new Date(), name: req.body?.name, + fileName: req.body?.fileName, file: req.body?.file, - vendor: req.body?.vendor, - margin: req.body?.margin, - price: req.body?.price, - cost: req.body?.cost, - priceMode: req.body?.priceMode, - priceTaxRate: req.body?.priceTaxRate, - costTaxRate: req.body?.costTaxRate, - priceWithTax: req.body?.priceWithTax, - costWithTax: req.body?.costWithTax, }; const result = await newObject({ diff --git a/src/services/management/partskus.js b/src/services/management/partskus.js new file mode 100644 index 0000000..8d077d8 --- /dev/null +++ b/src/services/management/partskus.js @@ -0,0 +1,202 @@ +import config from '../../config.js'; +import { partSkuModel } from '../../database/schemas/management/partsku.schema.js'; +import log4js from 'log4js'; +import mongoose from 'mongoose'; +import { + deleteObject, + listObjects, + getObject, + editObject, + newObject, + listObjectsByProperties, + getModelStats, + getModelHistory, +} from '../../database/database.js'; +const logger = log4js.getLogger('Part SKUs'); +logger.level = config.server.logLevel; + +export const listPartSkusRouteHandler = async ( + req, + res, + page = 1, + limit = 25, + property = '', + filter = {}, + search = '', + sort = '', + order = 'ascend' +) => { + const result = await listObjects({ + model: partSkuModel, + page, + limit, + property, + filter, + search, + sort, + order, + populate: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'], + }); + + if (result?.error) { + logger.error('Error listing part SKUs.'); + res.status(result.code).send(result); + return; + } + + logger.debug(`List of part SKUs (Page ${page}, Limit ${limit}). Count: ${result.length}.`); + res.send(result); +}; + +export const listPartSkusByPropertiesRouteHandler = async ( + req, + res, + properties = '', + filter = {}, + masterFilter = {} +) => { + const result = await listObjectsByProperties({ + model: partSkuModel, + properties, + filter, + populate: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'], + masterFilter, + }); + + if (result?.error) { + logger.error('Error listing part SKUs.'); + res.status(result.code).send(result); + return; + } + + logger.debug(`List of part SKUs. Count: ${result.length}`); + res.send(result); +}; + +export const getPartSkuRouteHandler = async (req, res) => { + const id = req.params.id; + const result = await getObject({ + model: partSkuModel, + id, + populate: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'], + }); + if (result?.error) { + logger.warn(`Part SKU not found with supplied id.`); + return res.status(result.code).send(result); + } + logger.debug(`Retrieved part SKU with ID: ${id}`); + res.send(result); +}; + +export const editPartSkuRouteHandler = async (req, res) => { + const id = new mongoose.Types.ObjectId(req.params.id); + + logger.trace(`Part SKU with ID: ${id}`); + + const updateData = { + updatedAt: new Date(), + sku: req.body?.sku, + part: req.body?.part, + name: req.body?.name, + description: req.body?.description, + priceMode: req.body?.priceMode, + price: req.body?.price, + cost: req.body?.cost, + margin: req.body?.margin, + amount: req.body?.amount, + vendor: req.body?.vendor, + priceTaxRate: req.body?.priceTaxRate, + costTaxRate: req.body?.costTaxRate, + priceWithTax: req.body?.priceWithTax, + costWithTax: req.body?.costWithTax, + }; + + const result = await editObject({ + model: partSkuModel, + id, + updateData, + user: req.user, + }); + + if (result.error) { + logger.error('Error editing part SKU:', result.error); + res.status(result.code || 500).send(result); + return; + } + + logger.debug(`Edited part SKU with ID: ${id}`); + res.send(result); +}; + +export const newPartSkuRouteHandler = async (req, res) => { + const newData = { + sku: req.body?.sku, + part: req.body?.part, + name: req.body?.name, + description: req.body?.description, + priceMode: req.body?.priceMode, + price: req.body?.price, + cost: req.body?.cost, + margin: req.body?.margin, + amount: req.body?.amount, + vendor: req.body?.vendor, + priceTaxRate: req.body?.priceTaxRate, + costTaxRate: req.body?.costTaxRate, + priceWithTax: req.body?.priceWithTax, + costWithTax: req.body?.costWithTax, + }; + + const result = await newObject({ + model: partSkuModel, + newData, + user: req.user, + }); + if (result.error) { + logger.error('No part SKU created:', result.error); + return res.status(result.code).send(result); + } + + logger.debug(`New part SKU with ID: ${result._id}`); + res.send(result); +}; + +export const deletePartSkuRouteHandler = async (req, res) => { + const id = new mongoose.Types.ObjectId(req.params.id); + + logger.trace(`Part SKU with ID: ${id}`); + + const result = await deleteObject({ + model: partSkuModel, + id, + user: req.user, + }); + if (result.error) { + logger.error('No part SKU deleted:', result.error); + return res.status(result.code).send(result); + } + + logger.debug(`Deleted part SKU with ID: ${id}`); + res.send(result); +}; + +export const getPartSkuStatsRouteHandler = async (req, res) => { + const result = await getModelStats({ model: partSkuModel }); + if (result?.error) { + logger.error('Error fetching part SKU stats:', result.error); + return res.status(result.code).send(result); + } + logger.trace('Part SKU stats:', result); + res.send(result); +}; + +export const getPartSkuHistoryRouteHandler = async (req, res) => { + const from = req.query.from; + const to = req.query.to; + const result = await getModelHistory({ model: partSkuModel, from, to }); + if (result?.error) { + logger.error('Error fetching part SKU history:', result.error); + return res.status(result.code).send(result); + } + logger.trace('Part SKU history:', result); + res.send(result); +}; diff --git a/src/services/management/products.js b/src/services/management/products.js index e4560f6..201471b 100644 --- a/src/services/management/products.js +++ b/src/services/management/products.js @@ -76,7 +76,7 @@ export const getProductRouteHandler = async (req, res) => { const result = await getObject({ model: productModel, id, - populate: ['vendor', 'parts.part'], + populate: ['vendor'], }); if (result?.error) { logger.warn(`Product not found with supplied id.`); @@ -97,11 +97,7 @@ export const editProductRouteHandler = async (req, res) => { name: req.body?.name, tags: req.body?.tags, version: req.body?.version, - margin: req.body.margin, - amount: req.body.amount, - priceMode: req.body.priceMode, vendor: req.body.vendor, - parts: req.body.parts, }; // Create audit log before updating const result = await editObject({ @@ -128,11 +124,7 @@ export const newProductRouteHandler = async (req, res) => { name: req.body?.name, tags: req.body?.tags, version: req.body?.version, - margin: req.body.margin, - amount: req.body.amount, - priceMode: req.body.priceMode, vendor: req.body.vendor, - parts: req.body.parts, }; const result = await newObject({ diff --git a/src/services/management/productskus.js b/src/services/management/productskus.js index 5147901..16e3131 100644 --- a/src/services/management/productskus.js +++ b/src/services/management/productskus.js @@ -35,7 +35,7 @@ export const listProductSkusRouteHandler = async ( search, sort, order, - populate: ['product'], + populate: ['product', 'vendor', 'priceTaxRate', 'costTaxRate', 'parts.partSku'], }); if (result?.error) { @@ -59,7 +59,7 @@ export const listProductSkusByPropertiesRouteHandler = async ( model: productSkuModel, properties, filter, - populate: ['product'], + populate: ['product', 'vendor', 'priceTaxRate', 'costTaxRate', 'parts.partSku'], masterFilter, }); @@ -78,7 +78,7 @@ export const getProductSkuRouteHandler = async (req, res) => { const result = await getObject({ model: productSkuModel, id, - populate: ['product'], + populate: ['product', 'vendor', 'priceTaxRate', 'costTaxRate', 'parts.partSku'], }); if (result?.error) { logger.warn(`Product SKU not found with supplied id.`); @@ -99,6 +99,17 @@ export const editProductSkuRouteHandler = async (req, res) => { product: req.body?.product, name: req.body?.name, description: req.body?.description, + priceMode: req.body?.priceMode, + price: req.body?.price, + cost: req.body?.cost, + margin: req.body?.margin, + amount: req.body?.amount, + vendor: req.body?.vendor, + parts: req.body?.parts, + priceTaxRate: req.body?.priceTaxRate, + costTaxRate: req.body?.costTaxRate, + priceWithTax: req.body?.priceWithTax, + costWithTax: req.body?.costWithTax, }; const result = await editObject({ @@ -124,6 +135,17 @@ export const newProductSkuRouteHandler = async (req, res) => { product: req.body?.product, name: req.body?.name, description: req.body?.description, + priceMode: req.body?.priceMode, + price: req.body?.price, + cost: req.body?.cost, + margin: req.body?.margin, + amount: req.body?.amount, + vendor: req.body?.vendor, + parts: req.body?.parts, + priceTaxRate: req.body?.priceTaxRate, + costTaxRate: req.body?.costTaxRate, + priceWithTax: req.body?.priceWithTax, + costWithTax: req.body?.costWithTax, }; const result = await newObject({ diff --git a/src/services/misc/csv.js b/src/services/misc/csv.js index 140cd74..8549f6f 100644 --- a/src/services/misc/csv.js +++ b/src/services/misc/csv.js @@ -63,9 +63,10 @@ function getModelFilterFields(objectType) { job: ['printer', 'gcodeFile'], subJob: ['job'], filamentStock: ['filament'], - partStock: ['part'], - productStock: ['product'], - productSku: ['product'], + partStock: ['partSku'], + partSku: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'], + productStock: ['productSku'], + productSku: ['product', 'vendor', 'priceTaxRate', 'costTaxRate'], purchaseOrder: ['vendor'], orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'shipment._id'], shipment: ['order._id', 'orderType', 'courierService._id'], diff --git a/src/services/misc/excel.js b/src/services/misc/excel.js index 54795e4..279c777 100644 --- a/src/services/misc/excel.js +++ b/src/services/misc/excel.js @@ -69,9 +69,10 @@ function getModelFilterFields(objectType) { job: ['printer', 'gcodeFile'], subJob: ['job'], filamentStock: ['filament'], - partStock: ['part'], - productStock: ['product'], - productSku: ['product'], + partStock: ['partSku'], + partSku: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'], + productStock: ['productSku'], + productSku: ['product', 'vendor', 'priceTaxRate', 'costTaxRate'], purchaseOrder: ['vendor'], orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'shipment._id'], shipment: ['order._id', 'orderType', 'courierService._id'], diff --git a/src/services/misc/odata.js b/src/services/misc/odata.js index 9f96a64..e462271 100644 --- a/src/services/misc/odata.js +++ b/src/services/misc/odata.js @@ -343,9 +343,10 @@ function getModelFilterFields(objectType) { job: ['printer', 'gcodeFile'], subJob: ['job'], filamentStock: ['filament'], - partStock: ['part'], - productStock: ['product'], - productSku: ['product'], + partStock: ['partSku'], + partSku: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'], + productStock: ['productSku'], + productSku: ['product', 'vendor', 'priceTaxRate', 'costTaxRate'], purchaseOrder: ['vendor'], orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'shipment._id'], shipment: ['order._id', 'orderType', 'courierService._id'],