Implemented Part SKUs.

This commit is contained in:
Tom Butcher 2026-03-07 23:34:12 +00:00
parent 17e46f6aee
commit 73fbb50b34
22 changed files with 392 additions and 98 deletions

View File

@ -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 },

View File

@ -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],
},

View File

@ -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 }
);

View File

@ -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);

View File

@ -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 }
);

View File

@ -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 }
);

View File

@ -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',

View File

@ -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);

View File

@ -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,

View File

@ -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);
});

View File

@ -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) {

View File

@ -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;

View File

@ -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);
});

View File

@ -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({

View File

@ -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,
})),

View File

@ -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({

View File

@ -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);
};

View File

@ -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({

View File

@ -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({

View File

@ -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'],

View File

@ -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'],

View File

@ -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'],