Compare commits
No commits in common. "4458a1d82866eaf6a3f9147bdef233fe2465c85c" and "4ea168f17f53c9946a16dfda737dba4bb3fae79c" have entirely different histories.
4458a1d828
...
4ea168f17f
4
.gitignore
vendored
4
.gitignore
vendored
@ -139,6 +139,4 @@ dist
|
|||||||
gocdefile/*
|
gocdefile/*
|
||||||
gcodefile
|
gcodefile
|
||||||
gcodefiles/*
|
gcodefiles/*
|
||||||
gcodefiles
|
gcodefiles
|
||||||
|
|
||||||
test-results.xml
|
|
||||||
@ -19,7 +19,7 @@ const filamentStockSchema = new Schema(
|
|||||||
net: { type: Number, required: true },
|
net: { type: Number, required: true },
|
||||||
gross: { type: Number, required: true },
|
gross: { type: Number, required: true },
|
||||||
},
|
},
|
||||||
filamentSku: { type: mongoose.Schema.Types.ObjectId, ref: 'filamentSku', required: true },
|
filament: { type: mongoose.Schema.Types.ObjectId, ref: 'filament', required: true },
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,12 +2,6 @@ import mongoose from 'mongoose';
|
|||||||
import { purchaseOrderModel } from './purchaseorder.schema.js';
|
import { purchaseOrderModel } from './purchaseorder.schema.js';
|
||||||
import { salesOrderModel } from '../sales/salesorder.schema.js';
|
import { salesOrderModel } from '../sales/salesorder.schema.js';
|
||||||
import { taxRateModel } from '../management/taxrate.schema.js';
|
import { taxRateModel } from '../management/taxrate.schema.js';
|
||||||
import { filamentModel } from '../management/filament.schema.js';
|
|
||||||
import { filamentSkuModel } from '../management/filamentsku.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 {
|
import {
|
||||||
aggregateRollups,
|
aggregateRollups,
|
||||||
aggregateRollupsHistory,
|
aggregateRollupsHistory,
|
||||||
@ -17,18 +11,6 @@ import {
|
|||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
const skuModelsByItemType = {
|
|
||||||
filament: filamentSkuModel,
|
|
||||||
part: partSkuModel,
|
|
||||||
product: productSkuModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
const parentModelsByItemType = {
|
|
||||||
filament: filamentModel,
|
|
||||||
part: partModel,
|
|
||||||
product: productModel,
|
|
||||||
};
|
|
||||||
|
|
||||||
const orderItemSchema = new Schema(
|
const orderItemSchema = new Schema(
|
||||||
{
|
{
|
||||||
_reference: { type: String, default: () => generateId()() },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
@ -39,16 +21,7 @@ const orderItemSchema = new Schema(
|
|||||||
},
|
},
|
||||||
order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true },
|
order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true },
|
||||||
itemType: { type: String, required: true },
|
itemType: { type: String, required: true },
|
||||||
item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: false },
|
item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: true },
|
||||||
sku: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: function () {
|
|
||||||
return ['filament', 'part', 'product'].includes(this.itemType)
|
|
||||||
? this.itemType + 'Sku'
|
|
||||||
: null;
|
|
||||||
},
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
syncAmount: { type: String, required: false, default: null },
|
syncAmount: { type: String, required: false, default: null },
|
||||||
itemAmount: { type: Number, required: true },
|
itemAmount: { type: Number, required: true },
|
||||||
quantity: { type: Number, required: true },
|
quantity: { type: Number, required: true },
|
||||||
@ -115,55 +88,9 @@ orderItemSchema.statics.recalculate = async function (orderItem, user) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If SKU present and syncAmount is set, check if override is on for the price mode and use that price instead
|
var taxRate = orderItem.taxRate;
|
||||||
let effectiveItemAmount = orderItem.itemAmount;
|
|
||||||
const syncAmount = orderItem.syncAmount;
|
|
||||||
const skuId = orderItem.sku?._id || orderItem.sku;
|
|
||||||
const itemType = orderItem.itemType;
|
|
||||||
if (syncAmount && skuId && itemType && ['filament', 'part', 'product'].includes(itemType)) {
|
|
||||||
const skuModel = skuModelsByItemType[itemType];
|
|
||||||
const parentModel = parentModelsByItemType[itemType];
|
|
||||||
if (skuModel && parentModel) {
|
|
||||||
const sku = await getObject({
|
|
||||||
model: skuModel,
|
|
||||||
id: skuId,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
if (sku) {
|
|
||||||
const parentId = sku.part?._id || sku.part || sku.product?._id || sku.product || sku.filament?._id || sku.filament;
|
|
||||||
if (syncAmount === 'itemCost') {
|
|
||||||
if (sku.overrideCost && sku.cost != null) {
|
|
||||||
effectiveItemAmount = sku.cost;
|
|
||||||
} else if (parentId) {
|
|
||||||
const parent = await getObject({
|
|
||||||
model: parentModel,
|
|
||||||
id: parentId,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
if (parent && parent.cost != null) {
|
|
||||||
effectiveItemAmount = parent.cost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (syncAmount === 'itemPrice' && itemType !== 'filament') {
|
|
||||||
if (sku.overridePrice && sku.price != null) {
|
|
||||||
effectiveItemAmount = sku.price;
|
|
||||||
} else if (parentId) {
|
|
||||||
const parent = await getObject({
|
|
||||||
model: parentModel,
|
|
||||||
id: parentId,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
if (parent && parent.price != null) {
|
|
||||||
effectiveItemAmount = parent.price;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let taxRate = orderItem.taxRate;
|
if (orderItem.taxRate?._id && Object.keys(orderItem.taxRate).length == 1) {
|
||||||
if (orderItem.taxRate?._id && Object.keys(orderItem.taxRate).length === 1) {
|
|
||||||
taxRate = await getObject({
|
taxRate = await getObject({
|
||||||
model: taxRateModel,
|
model: taxRateModel,
|
||||||
id: orderItem.taxRate._id,
|
id: orderItem.taxRate._id,
|
||||||
@ -171,25 +98,18 @@ orderItemSchema.statics.recalculate = async function (orderItem, user) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderTotalAmount = effectiveItemAmount * orderItem.quantity;
|
const orderTotalAmount = orderItem.itemAmount * orderItem.quantity;
|
||||||
const orderTotalAmountWithTax = orderTotalAmount * (1 + (taxRate?.rate || 0) / 100);
|
const orderTotalAmountWithTax = orderTotalAmount * (1 + (taxRate?.rate || 0) / 100);
|
||||||
|
|
||||||
const orderItemUpdateData = {
|
|
||||||
totalAmount: orderTotalAmount,
|
|
||||||
totalAmountWithTax: orderTotalAmountWithTax,
|
|
||||||
invoicedAmountRemaining: orderTotalAmount - orderItem.invoicedAmount,
|
|
||||||
invoicedAmountWithTaxRemaining: orderTotalAmountWithTax - orderItem.invoicedAmountWithTax,
|
|
||||||
invoicedQuantityRemaining: orderItem.quantity - orderItem.invoicedQuantity,
|
|
||||||
};
|
|
||||||
if (effectiveItemAmount !== orderItem.itemAmount) {
|
|
||||||
orderItemUpdateData.itemAmount = effectiveItemAmount;
|
|
||||||
orderItem.itemAmount = effectiveItemAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
await editObject({
|
await editObject({
|
||||||
model: this,
|
model: orderItemModel,
|
||||||
id: orderItem._id,
|
id: orderItem._id,
|
||||||
updateData: orderItemUpdateData,
|
updateData: {
|
||||||
|
invoicedAmountRemaining: orderTotalAmount - orderItem.invoicedAmount,
|
||||||
|
invoicedAmountWithTaxRemaining: orderTotalAmountWithTax - orderItem.invoicedAmountWithTax,
|
||||||
|
invoicedQuantityRemaining: orderItem.quantity - orderItem.invoicedQuantity,
|
||||||
|
totalAmount: orderTotalAmount,
|
||||||
|
totalAmountWithTax: orderTotalAmountWithTax,
|
||||||
|
},
|
||||||
user,
|
user,
|
||||||
recalculate: false,
|
recalculate: false,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const partStockSchema = new Schema(
|
|||||||
type: { type: String, required: true },
|
type: { type: String, required: true },
|
||||||
progress: { type: Number, required: false },
|
progress: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
partSku: { type: mongoose.Schema.Types.ObjectId, ref: 'partSku', required: true },
|
part: { type: mongoose.Schema.Types.ObjectId, ref: 'part', required: true },
|
||||||
currentQuantity: { type: Number, required: true },
|
currentQuantity: { type: Number, required: true },
|
||||||
sourceType: { type: String, required: true },
|
sourceType: { type: String, required: true },
|
||||||
source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true },
|
source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true },
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { aggregateRollups, aggregateRollupsHistory } from '../../database.js';
|
|||||||
|
|
||||||
const partStockUsageSchema = new Schema({
|
const partStockUsageSchema = new Schema({
|
||||||
partStock: { type: Schema.Types.ObjectId, ref: 'partStock', required: false },
|
partStock: { type: Schema.Types.ObjectId, ref: 'partStock', required: false },
|
||||||
partSku: { type: Schema.Types.ObjectId, ref: 'partSku', required: true },
|
part: { type: Schema.Types.ObjectId, ref: 'part', required: true },
|
||||||
quantity: { type: Number, required: true },
|
quantity: { type: Number, required: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ const productStockSchema = new Schema(
|
|||||||
progress: { type: Number, required: false },
|
progress: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
postedAt: { type: Date, required: false },
|
postedAt: { type: Date, required: false },
|
||||||
productSku: { type: mongoose.Schema.Types.ObjectId, ref: 'productSku', required: true },
|
product: { type: mongoose.Schema.Types.ObjectId, ref: 'product', required: true },
|
||||||
currentQuantity: { type: Number, required: true },
|
currentQuantity: { type: Number, required: true },
|
||||||
partStocks: [partStockUsageSchema],
|
partStocks: [partStockUsageSchema],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,21 +2,24 @@ import mongoose from 'mongoose';
|
|||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
// Filament base - cost and tax; color and cost override at FilamentSKU
|
|
||||||
const filamentSchema = new mongoose.Schema({
|
const filamentSchema = new mongoose.Schema({
|
||||||
_reference: { type: String, default: () => generateId()() },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
barcode: { required: false, type: String },
|
barcode: { required: false, type: String },
|
||||||
url: { required: false, type: String },
|
url: { required: false, type: String },
|
||||||
image: { required: false, type: Buffer },
|
image: { required: false, type: Buffer },
|
||||||
material: { type: Schema.Types.ObjectId, ref: 'material', required: true },
|
color: { required: true, type: String },
|
||||||
|
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 },
|
diameter: { required: true, type: Number },
|
||||||
density: { required: true, type: Number },
|
density: { required: true, type: Number },
|
||||||
|
createdAt: { required: true, type: Date },
|
||||||
|
updatedAt: { required: true, type: Date },
|
||||||
emptySpoolWeight: { required: true, type: Number },
|
emptySpoolWeight: { required: true, type: Number },
|
||||||
cost: { type: Number, required: false },
|
});
|
||||||
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
|
||||||
costWithTax: { type: Number, required: false },
|
|
||||||
}, { timestamps: true });
|
|
||||||
|
|
||||||
filamentSchema.virtual('id').get(function () {
|
filamentSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id;
|
||||||
@ -24,21 +27,4 @@ filamentSchema.virtual('id').get(function () {
|
|||||||
|
|
||||||
filamentSchema.set('toJSON', { virtuals: true });
|
filamentSchema.set('toJSON', { virtuals: true });
|
||||||
|
|
||||||
filamentSchema.statics.recalculate = async function (filament, user) {
|
|
||||||
const orderItemModel = mongoose.model('orderItem');
|
|
||||||
const itemId = filament._id;
|
|
||||||
const draftOrderItems = await orderItemModel
|
|
||||||
.find({
|
|
||||||
'state.type': 'draft',
|
|
||||||
itemType: 'filament',
|
|
||||||
item: itemId,
|
|
||||||
})
|
|
||||||
.populate('order')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
for (const orderItem of draftOrderItems) {
|
|
||||||
await orderItemModel.recalculate(orderItem, user);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const filamentModel = mongoose.model('filament', filamentSchema);
|
export const filamentModel = mongoose.model('filament', filamentSchema);
|
||||||
|
|||||||
@ -1,48 +0,0 @@
|
|||||||
import mongoose from 'mongoose';
|
|
||||||
import { generateId } from '../../utils.js';
|
|
||||||
const { Schema } = mongoose;
|
|
||||||
|
|
||||||
// Define the main filament SKU schema - color and cost live at SKU level
|
|
||||||
const filamentSkuSchema = new Schema(
|
|
||||||
{
|
|
||||||
_reference: { type: String, default: () => generateId()() },
|
|
||||||
barcode: { type: String, required: false },
|
|
||||||
filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true },
|
|
||||||
name: { type: String, required: true },
|
|
||||||
description: { type: String, required: false },
|
|
||||||
color: { type: String, required: true },
|
|
||||||
cost: { type: Number, required: false },
|
|
||||||
overrideCost: { type: Boolean, default: false },
|
|
||||||
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
|
||||||
costWithTax: { type: Number, required: false },
|
|
||||||
},
|
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add virtual id getter
|
|
||||||
filamentSkuSchema.virtual('id').get(function () {
|
|
||||||
return this._id;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure JSON serialization to include virtuals
|
|
||||||
filamentSkuSchema.set('toJSON', { virtuals: true });
|
|
||||||
|
|
||||||
filamentSkuSchema.statics.recalculate = async function (filamentSku, user) {
|
|
||||||
const orderItemModel = mongoose.model('orderItem');
|
|
||||||
const skuId = filamentSku._id;
|
|
||||||
const draftOrderItems = await orderItemModel
|
|
||||||
.find({
|
|
||||||
'state.type': 'draft',
|
|
||||||
itemType: 'filament',
|
|
||||||
sku: skuId,
|
|
||||||
})
|
|
||||||
.populate('order')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
for (const orderItem of draftOrderItems) {
|
|
||||||
await orderItemModel.recalculate(orderItem, user);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create and export the model
|
|
||||||
export const filamentSkuModel = mongoose.model('filamentSku', filamentSkuSchema);
|
|
||||||
@ -1,15 +1,13 @@
|
|||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
|
|
||||||
const materialSchema = new mongoose.Schema(
|
const materialSchema = new mongoose.Schema({
|
||||||
{
|
_reference: { type: String, default: () => generateId()() },
|
||||||
_reference: { type: String, default: () => generateId()() },
|
name: { required: true, type: String },
|
||||||
name: { required: true, type: String },
|
url: { required: false, type: String },
|
||||||
url: { required: false, type: String },
|
image: { required: false, type: Buffer },
|
||||||
tags: [{ type: String }],
|
tags: [{ type: String }],
|
||||||
},
|
});
|
||||||
{ timestamps: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
materialSchema.virtual('id').get(function () {
|
materialSchema.virtual('id').get(function () {
|
||||||
return this._id;
|
return this._id;
|
||||||
|
|||||||
@ -2,22 +2,22 @@ import mongoose from 'mongoose';
|
|||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
// Define the main part schema - cost/price and tax; override at PartSku
|
// Define the main part schema
|
||||||
const partSchema = new Schema(
|
const partSchema = new Schema(
|
||||||
{
|
{
|
||||||
_reference: { type: String, default: () => generateId()() },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
name: { type: String, required: true },
|
name: { type: String, required: true },
|
||||||
fileName: { type: String, required: false },
|
fileName: { type: String, required: false },
|
||||||
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
|
||||||
cost: { type: Number, required: false },
|
|
||||||
price: { type: Number, required: false },
|
|
||||||
priceMode: { type: String, default: 'margin' },
|
priceMode: { type: String, default: 'margin' },
|
||||||
|
price: { type: Number, required: true },
|
||||||
|
cost: { type: Number, required: true },
|
||||||
margin: { type: Number, required: false },
|
margin: { type: Number, required: false },
|
||||||
amount: { type: Number, required: false },
|
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
||||||
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
||||||
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
||||||
costWithTax: { type: Number, required: false },
|
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
||||||
priceWithTax: { type: Number, required: false },
|
priceWithTax: { type: Number, required: false },
|
||||||
|
costWithTax: { type: Number, required: false },
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
@ -30,22 +30,5 @@ partSchema.virtual('id').get(function () {
|
|||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
partSchema.set('toJSON', { virtuals: true });
|
partSchema.set('toJSON', { virtuals: true });
|
||||||
|
|
||||||
partSchema.statics.recalculate = async function (part, user) {
|
|
||||||
const orderItemModel = mongoose.model('orderItem');
|
|
||||||
const itemId = part._id;
|
|
||||||
const draftOrderItems = await orderItemModel
|
|
||||||
.find({
|
|
||||||
'state.type': 'draft',
|
|
||||||
itemType: 'part',
|
|
||||||
item: itemId,
|
|
||||||
})
|
|
||||||
.populate('order')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
for (const orderItem of draftOrderItems) {
|
|
||||||
await orderItemModel.recalculate(orderItem, user);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create and export the model
|
// Create and export the model
|
||||||
export const partModel = mongoose.model('part', partSchema);
|
export const partModel = mongoose.model('part', partSchema);
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
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()() },
|
|
||||||
barcode: { type: String, required: false },
|
|
||||||
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 },
|
|
||||||
overrideCost: { type: Boolean, default: false },
|
|
||||||
overridePrice: { type: Boolean, default: false },
|
|
||||||
margin: { type: Number, required: false },
|
|
||||||
amount: { type: Number, 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 });
|
|
||||||
|
|
||||||
partSkuSchema.statics.recalculate = async function (partSku, user) {
|
|
||||||
const orderItemModel = mongoose.model('orderItem');
|
|
||||||
const skuId = partSku._id;
|
|
||||||
const draftOrderItems = await orderItemModel
|
|
||||||
.find({
|
|
||||||
'state.type': 'draft',
|
|
||||||
itemType: 'part',
|
|
||||||
sku: skuId,
|
|
||||||
})
|
|
||||||
.populate('order')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
for (const orderItem of draftOrderItems) {
|
|
||||||
await orderItemModel.recalculate(orderItem, user);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create and export the model
|
|
||||||
export const partSkuModel = mongoose.model('partSku', partSkuSchema);
|
|
||||||
@ -2,6 +2,11 @@ import mongoose from 'mongoose';
|
|||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
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
|
// Define the main product schema
|
||||||
const productSchema = new Schema(
|
const productSchema = new Schema(
|
||||||
{
|
{
|
||||||
@ -9,16 +14,13 @@ const productSchema = new Schema(
|
|||||||
name: { type: String, required: true },
|
name: { type: String, required: true },
|
||||||
tags: [{ type: String }],
|
tags: [{ type: String }],
|
||||||
version: { type: String },
|
version: { type: String },
|
||||||
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
|
||||||
cost: { type: Number, required: false },
|
|
||||||
price: { type: Number, required: false },
|
|
||||||
priceMode: { type: String, default: 'margin' },
|
priceMode: { type: String, default: 'margin' },
|
||||||
margin: { type: Number, required: false },
|
margin: { type: Number, required: false },
|
||||||
amount: { type: Number, required: false },
|
amount: { type: Number, required: false },
|
||||||
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
|
||||||
|
parts: [partSchema],
|
||||||
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
||||||
costWithTax: { type: Number, required: false },
|
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
|
||||||
priceWithTax: { type: Number, required: false },
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
@ -30,22 +32,5 @@ productSchema.virtual('id').get(function () {
|
|||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
productSchema.set('toJSON', { virtuals: true });
|
productSchema.set('toJSON', { virtuals: true });
|
||||||
|
|
||||||
productSchema.statics.recalculate = async function (product, user) {
|
|
||||||
const orderItemModel = mongoose.model('orderItem');
|
|
||||||
const itemId = product._id;
|
|
||||||
const draftOrderItems = await orderItemModel
|
|
||||||
.find({
|
|
||||||
'state.type': 'draft',
|
|
||||||
itemType: 'product',
|
|
||||||
item: itemId,
|
|
||||||
})
|
|
||||||
.populate('order')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
for (const orderItem of draftOrderItems) {
|
|
||||||
await orderItemModel.recalculate(orderItem, user);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create and export the model
|
// Create and export the model
|
||||||
export const productModel = mongoose.model('product', productSchema);
|
export const productModel = mongoose.model('product', productSchema);
|
||||||
|
|||||||
@ -2,31 +2,14 @@ import mongoose from 'mongoose';
|
|||||||
import { generateId } from '../../utils.js';
|
import { generateId } from '../../utils.js';
|
||||||
const { Schema } = mongoose;
|
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
|
// Define the main product SKU schema
|
||||||
const productSkuSchema = new Schema(
|
const productSkuSchema = new Schema(
|
||||||
{
|
{
|
||||||
_reference: { type: String, default: () => generateId()() },
|
_reference: { type: String, default: () => generateId()() },
|
||||||
barcode: { type: String, required: false },
|
sku: { type: String, required: true },
|
||||||
product: { type: Schema.Types.ObjectId, ref: 'product', required: true },
|
product: { type: Schema.Types.ObjectId, ref: 'product', required: true },
|
||||||
name: { type: String, required: true },
|
name: { type: String, required: true },
|
||||||
description: { type: String, required: false },
|
description: { type: String, required: false },
|
||||||
priceMode: { type: String, default: 'margin' },
|
|
||||||
price: { type: Number, required: false },
|
|
||||||
cost: { type: Number, required: false },
|
|
||||||
overrideCost: { type: Boolean, default: false },
|
|
||||||
overridePrice: { type: Boolean, default: false },
|
|
||||||
margin: { type: Number, required: false },
|
|
||||||
amount: { type: Number, 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 }
|
{ timestamps: true }
|
||||||
);
|
);
|
||||||
@ -39,22 +22,5 @@ productSkuSchema.virtual('id').get(function () {
|
|||||||
// Configure JSON serialization to include virtuals
|
// Configure JSON serialization to include virtuals
|
||||||
productSkuSchema.set('toJSON', { virtuals: true });
|
productSkuSchema.set('toJSON', { virtuals: true });
|
||||||
|
|
||||||
productSkuSchema.statics.recalculate = async function (productSku, user) {
|
|
||||||
const orderItemModel = mongoose.model('orderItem');
|
|
||||||
const skuId = productSku._id;
|
|
||||||
const draftOrderItems = await orderItemModel
|
|
||||||
.find({
|
|
||||||
'state.type': 'draft',
|
|
||||||
itemType: 'product',
|
|
||||||
sku: skuId,
|
|
||||||
})
|
|
||||||
.populate('order')
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
for (const orderItem of draftOrderItems) {
|
|
||||||
await orderItemModel.recalculate(orderItem, user);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create and export the model
|
// Create and export the model
|
||||||
export const productSkuModel = mongoose.model('productSku', productSkuSchema);
|
export const productSkuModel = mongoose.model('productSku', productSkuSchema);
|
||||||
|
|||||||
@ -2,14 +2,11 @@ import { jobModel } from './production/job.schema.js';
|
|||||||
import { subJobModel } from './production/subjob.schema.js';
|
import { subJobModel } from './production/subjob.schema.js';
|
||||||
import { printerModel } from './production/printer.schema.js';
|
import { printerModel } from './production/printer.schema.js';
|
||||||
import { filamentModel } from './management/filament.schema.js';
|
import { filamentModel } from './management/filament.schema.js';
|
||||||
import { filamentSkuModel } from './management/filamentsku.schema.js';
|
|
||||||
import { gcodeFileModel } from './production/gcodefile.schema.js';
|
import { gcodeFileModel } from './production/gcodefile.schema.js';
|
||||||
import { partModel } from './management/part.schema.js';
|
import { partModel } from './management/part.schema.js';
|
||||||
import { partSkuModel } from './management/partsku.schema.js';
|
|
||||||
import { productModel } from './management/product.schema.js';
|
import { productModel } from './management/product.schema.js';
|
||||||
import { productSkuModel } from './management/productsku.schema.js';
|
import { productSkuModel } from './management/productsku.schema.js';
|
||||||
import { vendorModel } from './management/vendor.schema.js';
|
import { vendorModel } from './management/vendor.schema.js';
|
||||||
import { materialModel } from './management/material.schema.js';
|
|
||||||
import { filamentStockModel } from './inventory/filamentstock.schema.js';
|
import { filamentStockModel } from './inventory/filamentstock.schema.js';
|
||||||
import { purchaseOrderModel } from './inventory/purchaseorder.schema.js';
|
import { purchaseOrderModel } from './inventory/purchaseorder.schema.js';
|
||||||
import { orderItemModel } from './inventory/orderitem.schema.js';
|
import { orderItemModel } from './inventory/orderitem.schema.js';
|
||||||
@ -55,13 +52,6 @@ export const models = {
|
|||||||
referenceField: '_reference',
|
referenceField: '_reference',
|
||||||
label: 'Filament',
|
label: 'Filament',
|
||||||
},
|
},
|
||||||
FSU: {
|
|
||||||
model: filamentSkuModel,
|
|
||||||
idField: '_id',
|
|
||||||
type: 'filamentSku',
|
|
||||||
referenceField: '_reference',
|
|
||||||
label: 'Filament SKU',
|
|
||||||
},
|
|
||||||
GCF: {
|
GCF: {
|
||||||
model: gcodeFileModel,
|
model: gcodeFileModel,
|
||||||
idField: '_id',
|
idField: '_id',
|
||||||
@ -77,13 +67,6 @@ export const models = {
|
|||||||
referenceField: '_reference',
|
referenceField: '_reference',
|
||||||
label: 'Part',
|
label: 'Part',
|
||||||
},
|
},
|
||||||
PSU: {
|
|
||||||
model: partSkuModel,
|
|
||||||
idField: '_id',
|
|
||||||
type: 'partSku',
|
|
||||||
referenceField: '_reference',
|
|
||||||
label: 'Part SKU',
|
|
||||||
},
|
|
||||||
PRD: {
|
PRD: {
|
||||||
model: productModel,
|
model: productModel,
|
||||||
idField: '_id',
|
idField: '_id',
|
||||||
@ -105,13 +88,6 @@ export const models = {
|
|||||||
referenceField: '_reference',
|
referenceField: '_reference',
|
||||||
label: 'Vendor',
|
label: 'Vendor',
|
||||||
},
|
},
|
||||||
MAT: {
|
|
||||||
model: materialModel,
|
|
||||||
idField: '_id',
|
|
||||||
type: 'material',
|
|
||||||
referenceField: '_reference',
|
|
||||||
label: 'Material',
|
|
||||||
},
|
|
||||||
SJB: {
|
SJB: {
|
||||||
model: subJobModel,
|
model: subJobModel,
|
||||||
idField: '_id',
|
idField: '_id',
|
||||||
|
|||||||
@ -13,7 +13,7 @@ const gcodeFileSchema = new mongoose.Schema(
|
|||||||
name: { required: true, type: String },
|
name: { required: true, type: String },
|
||||||
gcodeFileName: { required: false, type: String },
|
gcodeFileName: { required: false, type: String },
|
||||||
size: { type: Number, required: false },
|
size: { type: Number, required: false },
|
||||||
filamentSku: { type: Schema.Types.ObjectId, ref: 'filamentSku', required: true },
|
filament: { type: Schema.Types.ObjectId, ref: 'filament', required: true },
|
||||||
parts: [partSchema],
|
parts: [partSchema],
|
||||||
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
|
||||||
cost: { type: Number, required: false },
|
cost: { type: Number, required: false },
|
||||||
|
|||||||
@ -14,10 +14,8 @@ import {
|
|||||||
subJobRoutes,
|
subJobRoutes,
|
||||||
gcodeFileRoutes,
|
gcodeFileRoutes,
|
||||||
filamentRoutes,
|
filamentRoutes,
|
||||||
filamentSkuRoutes,
|
|
||||||
spotlightRoutes,
|
spotlightRoutes,
|
||||||
partRoutes,
|
partRoutes,
|
||||||
partSkuRoutes,
|
|
||||||
productRoutes,
|
productRoutes,
|
||||||
productSkuRoutes,
|
productSkuRoutes,
|
||||||
vendorRoutes,
|
vendorRoutes,
|
||||||
@ -134,9 +132,7 @@ app.use('/jobs', jobRoutes);
|
|||||||
app.use('/subjobs', subJobRoutes);
|
app.use('/subjobs', subJobRoutes);
|
||||||
app.use('/gcodefiles', gcodeFileRoutes);
|
app.use('/gcodefiles', gcodeFileRoutes);
|
||||||
app.use('/filaments', filamentRoutes);
|
app.use('/filaments', filamentRoutes);
|
||||||
app.use('/filamentskus', filamentSkuRoutes);
|
|
||||||
app.use('/parts', partRoutes);
|
app.use('/parts', partRoutes);
|
||||||
app.use('/partskus', partSkuRoutes);
|
|
||||||
app.use('/products', productRoutes);
|
app.use('/products', productRoutes);
|
||||||
app.use('/productskus', productSkuRoutes);
|
app.use('/productskus', productSkuRoutes);
|
||||||
app.use('/vendors', vendorRoutes);
|
app.use('/vendors', vendorRoutes);
|
||||||
|
|||||||
@ -8,10 +8,8 @@ import jobRoutes from './production/jobs.js';
|
|||||||
import subJobRoutes from './production/subjobs.js';
|
import subJobRoutes from './production/subjobs.js';
|
||||||
import gcodeFileRoutes from './production/gcodefiles.js';
|
import gcodeFileRoutes from './production/gcodefiles.js';
|
||||||
import filamentRoutes from './management/filaments.js';
|
import filamentRoutes from './management/filaments.js';
|
||||||
import filamentSkuRoutes from './management/filamentskus.js';
|
|
||||||
import spotlightRoutes from './misc/spotlight.js';
|
import spotlightRoutes from './misc/spotlight.js';
|
||||||
import partRoutes from './management/parts.js';
|
import partRoutes from './management/parts.js';
|
||||||
import partSkuRoutes from './management/partskus.js';
|
|
||||||
import productRoutes from './management/products.js';
|
import productRoutes from './management/products.js';
|
||||||
import productSkuRoutes from './management/productskus.js';
|
import productSkuRoutes from './management/productskus.js';
|
||||||
import vendorRoutes from './management/vendors.js';
|
import vendorRoutes from './management/vendors.js';
|
||||||
@ -56,10 +54,8 @@ export {
|
|||||||
subJobRoutes,
|
subJobRoutes,
|
||||||
gcodeFileRoutes,
|
gcodeFileRoutes,
|
||||||
filamentRoutes,
|
filamentRoutes,
|
||||||
filamentSkuRoutes,
|
|
||||||
spotlightRoutes,
|
spotlightRoutes,
|
||||||
partRoutes,
|
partRoutes,
|
||||||
partSkuRoutes,
|
|
||||||
productRoutes,
|
productRoutes,
|
||||||
productSkuRoutes,
|
productSkuRoutes,
|
||||||
vendorRoutes,
|
vendorRoutes,
|
||||||
|
|||||||
@ -18,14 +18,14 @@ import {
|
|||||||
// list of filament stocks
|
// list of filament stocks
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
const allowedFilters = ['filamentSku', 'state', 'startingWeight', 'currentWeight', 'filamentSku._id'];
|
const allowedFilters = ['filament', 'state', 'startingWeight', 'currentWeight', 'filament._id'];
|
||||||
const filter = getFilter(req.query, allowedFilters);
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
listFilamentStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
listFilamentStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/properties', isAuthenticated, (req, res) => {
|
router.get('/properties', isAuthenticated, (req, res) => {
|
||||||
let properties = convertPropertiesString(req.query.properties);
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
const allowedFilters = ['filamentSku', 'state.type'];
|
const allowedFilters = ['filament', 'state.type'];
|
||||||
const filter = getFilter(req.query, allowedFilters, false);
|
const filter = getFilter(req.query, allowedFilters, false);
|
||||||
var masterFilter = {};
|
var masterFilter = {};
|
||||||
if (req.query.masterFilter) {
|
if (req.query.masterFilter) {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
// list of part stocks
|
// list of part stocks
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
const allowedFilters = ['partSku', 'state', 'startingQuantity', 'currentQuantity', 'partSku._id'];
|
const allowedFilters = ['part', 'state', 'startingQuantity', 'currentQuantity', 'part._id'];
|
||||||
const filter = getFilter(req.query, allowedFilters);
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
listPartStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
listPartStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,14 +18,14 @@ import {
|
|||||||
|
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
const allowedFilters = ['productSku', 'state', 'currentQuantity', 'productSku._id'];
|
const allowedFilters = ['product', 'state', 'currentQuantity', 'product._id'];
|
||||||
const filter = getFilter(req.query, allowedFilters);
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
listProductStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
listProductStocksRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/properties', isAuthenticated, (req, res) => {
|
router.get('/properties', isAuthenticated, (req, res) => {
|
||||||
let properties = convertPropertiesString(req.query.properties);
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
const allowedFilters = ['productSku', 'state.type'];
|
const allowedFilters = ['product', 'state.type'];
|
||||||
const filter = getFilter(req.query, allowedFilters, false);
|
const filter = getFilter(req.query, allowedFilters, false);
|
||||||
var masterFilter = {};
|
var masterFilter = {};
|
||||||
if (req.query.masterFilter) {
|
if (req.query.masterFilter) {
|
||||||
|
|||||||
@ -20,10 +20,12 @@ router.get('/', isAuthenticated, (req, res) => {
|
|||||||
|
|
||||||
const allowedFilters = [
|
const allowedFilters = [
|
||||||
'_id',
|
'_id',
|
||||||
'material',
|
'type',
|
||||||
'material._id',
|
'vendor.name',
|
||||||
'diameter',
|
'diameter',
|
||||||
|
'color',
|
||||||
'name',
|
'name',
|
||||||
|
'vendor._id',
|
||||||
'cost',
|
'cost',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -42,7 +44,7 @@ router.get('/', isAuthenticated, (req, res) => {
|
|||||||
|
|
||||||
router.get('/properties', isAuthenticated, (req, res) => {
|
router.get('/properties', isAuthenticated, (req, res) => {
|
||||||
let properties = convertPropertiesString(req.query.properties);
|
let properties = convertPropertiesString(req.query.properties);
|
||||||
const allowedFilters = ['diameter', 'material'];
|
const allowedFilters = ['diameter', 'type', 'vendor'];
|
||||||
const filter = getFilter(req.query, allowedFilters, false);
|
const filter = getFilter(req.query, allowedFilters, false);
|
||||||
listFilamentsByPropertiesRouteHandler(req, res, properties, filter);
|
listFilamentsByPropertiesRouteHandler(req, res, properties, filter);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
import express from 'express';
|
|
||||||
import { isAuthenticated } from '../../keycloak.js';
|
|
||||||
import { getFilter, convertPropertiesString } from '../../utils.js';
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
import {
|
|
||||||
listFilamentSkusRouteHandler,
|
|
||||||
getFilamentSkuRouteHandler,
|
|
||||||
editFilamentSkuRouteHandler,
|
|
||||||
newFilamentSkuRouteHandler,
|
|
||||||
deleteFilamentSkuRouteHandler,
|
|
||||||
listFilamentSkusByPropertiesRouteHandler,
|
|
||||||
getFilamentSkuStatsRouteHandler,
|
|
||||||
getFilamentSkuHistoryRouteHandler,
|
|
||||||
} from '../../services/management/filamentskus.js';
|
|
||||||
|
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
|
||||||
const allowedFilters = ['_id', 'barcode', 'filament', 'filament._id', 'name', 'color', 'cost'];
|
|
||||||
const filter = getFilter(req.query, allowedFilters);
|
|
||||||
listFilamentSkusRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/properties', isAuthenticated, (req, res) => {
|
|
||||||
let properties = convertPropertiesString(req.query.properties);
|
|
||||||
const allowedFilters = ['filament', 'filament._id'];
|
|
||||||
const filter = getFilter(req.query, allowedFilters, false);
|
|
||||||
let masterFilter = {};
|
|
||||||
if (req.query.masterFilter) {
|
|
||||||
masterFilter = JSON.parse(req.query.masterFilter);
|
|
||||||
}
|
|
||||||
listFilamentSkusByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/', isAuthenticated, (req, res) => {
|
|
||||||
newFilamentSkuRouteHandler(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/stats', isAuthenticated, (req, res) => {
|
|
||||||
getFilamentSkuStatsRouteHandler(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/history', isAuthenticated, (req, res) => {
|
|
||||||
getFilamentSkuHistoryRouteHandler(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/:id', isAuthenticated, (req, res) => {
|
|
||||||
getFilamentSkuRouteHandler(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
|
||||||
editFilamentSkuRouteHandler(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
router.delete('/:id', isAuthenticated, async (req, res) => {
|
|
||||||
deleteFilamentSkuRouteHandler(req, res);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@ -1,11 +1,10 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { isAuthenticated } from '../../keycloak.js';
|
import { isAuthenticated } from '../../keycloak.js';
|
||||||
import { convertPropertiesString, getFilter, parseFilter } from '../../utils.js';
|
import { parseFilter } from '../../utils.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import {
|
||||||
listMaterialsRouteHandler,
|
listMaterialsRouteHandler,
|
||||||
listMaterialsByPropertiesRouteHandler,
|
|
||||||
getMaterialRouteHandler,
|
getMaterialRouteHandler,
|
||||||
editMaterialRouteHandler,
|
editMaterialRouteHandler,
|
||||||
newMaterialRouteHandler,
|
newMaterialRouteHandler,
|
||||||
@ -15,26 +14,22 @@ import {
|
|||||||
|
|
||||||
// list of materials
|
// list of materials
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property } = req.query;
|
||||||
|
|
||||||
const allowedFilters = ['_id', 'name', 'tags'];
|
const allowedFilters = ['type', 'brand', 'diameter', 'color'];
|
||||||
|
|
||||||
var filter = {};
|
var filter = {};
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(req.query)) {
|
for (const [key, value] of Object.entries(req.query)) {
|
||||||
if (allowedFilters.includes(key)) {
|
for (var i = 0; i < allowedFilters.length; i++) {
|
||||||
filter = { ...filter, ...parseFilter(key, value) };
|
if (key == allowedFilters[i]) {
|
||||||
|
const parsedFilter = parseFilter(key, value);
|
||||||
|
filter = { ...filter, ...parsedFilter };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listMaterialsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
listMaterialsRouteHandler(req, res, page, limit, property, filter);
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/properties', isAuthenticated, (req, res) => {
|
|
||||||
let properties = convertPropertiesString(req.query.properties);
|
|
||||||
const allowedFilters = ['name', 'tags'];
|
|
||||||
const filter = getFilter(req.query, allowedFilters, false);
|
|
||||||
listMaterialsByPropertiesRouteHandler(req, res, properties, filter);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/', isAuthenticated, (req, res) => {
|
router.post('/', isAuthenticated, (req, res) => {
|
||||||
@ -55,7 +50,7 @@ router.get('/:id', isAuthenticated, (req, res) => {
|
|||||||
getMaterialRouteHandler(req, res);
|
getMaterialRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
// update material info
|
// update printer info
|
||||||
router.put('/:id', isAuthenticated, async (req, res) => {
|
router.put('/:id', isAuthenticated, async (req, res) => {
|
||||||
editMaterialRouteHandler(req, res);
|
editMaterialRouteHandler(req, res);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
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', 'barcode', '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;
|
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
|
|
||||||
router.get('/', isAuthenticated, (req, res) => {
|
router.get('/', isAuthenticated, (req, res) => {
|
||||||
const { page, limit, property, search, sort, order } = req.query;
|
const { page, limit, property, search, sort, order } = req.query;
|
||||||
const allowedFilters = ['_id', 'barcode', 'product', 'product._id', 'name', 'cost', 'price'];
|
const allowedFilters = ['_id', 'sku', 'product', 'product._id', 'name'];
|
||||||
const filter = getFilter(req.query, allowedFilters);
|
const filter = getFilter(req.query, allowedFilters);
|
||||||
listProductSkusRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
listProductSkusRouteHandler(req, res, page, limit, property, filter, search, sort, order);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export const listFilamentStocksRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: [{ path: 'filamentSku' }],
|
populate: [{ path: 'filament' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -60,7 +60,7 @@ export const listFilamentStocksByPropertiesRouteHandler = async (
|
|||||||
model: filamentStockModel,
|
model: filamentStockModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: ['filamentSku'],
|
populate: ['filament'],
|
||||||
masterFilter,
|
masterFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export const getFilamentStockRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: filamentStockModel,
|
model: filamentStockModel,
|
||||||
id,
|
id,
|
||||||
populate: [{ path: 'filamentSku' }],
|
populate: [{ path: 'filament' }],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Filament Stock not found with supplied id.`);
|
logger.warn(`Filament Stock not found with supplied id.`);
|
||||||
@ -146,7 +146,7 @@ export const newFilamentStockRouteHandler = async (req, res) => {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
startingWeight: req.body.startingWeight,
|
startingWeight: req.body.startingWeight,
|
||||||
currentWeight: req.body.currentWeight,
|
currentWeight: req.body.currentWeight,
|
||||||
filamentSku: req.body.filamentSku,
|
filament: req.body.filament,
|
||||||
state: req.body.state,
|
state: req.body.state,
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import config from '../../config.js';
|
import config from '../../config.js';
|
||||||
import { orderItemModel } from '../../database/schemas/inventory/orderitem.schema.js';
|
import { orderItemModel } from '../../database/schemas/inventory/orderitem.schema.js';
|
||||||
import { getModelByName } from '../misc/model.js';
|
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import {
|
import {
|
||||||
@ -51,39 +50,11 @@ export const listOrderItemsRouteHandler = async (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'item',
|
path: 'item',
|
||||||
populate: [
|
populate: { path: 'costTaxRate', strictPopulate: false },
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'sku',
|
path: 'item',
|
||||||
strictPopulate: false,
|
populate: { path: 'priceTaxRate', strictPopulate: false },
|
||||||
populate: [
|
|
||||||
{
|
|
||||||
path: 'filament',
|
|
||||||
populate: { path: 'costTaxRate', strictPopulate: false },
|
|
||||||
strictPopulate: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'part',
|
|
||||||
populate: [
|
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
strictPopulate: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'product',
|
|
||||||
populate: [
|
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
strictPopulate: false,
|
|
||||||
},
|
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -138,40 +109,13 @@ export const getOrderItemRouteHandler = async (req, res) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'item',
|
path: 'item',
|
||||||
populate: [
|
populate: { path: 'costTaxRate', strictPopulate: false },
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
strictPopulate: false,
|
strictPopulate: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'sku',
|
path: 'item',
|
||||||
|
populate: { path: 'priceTaxRate', strictPopulate: false },
|
||||||
strictPopulate: false,
|
strictPopulate: false,
|
||||||
populate: [
|
|
||||||
{
|
|
||||||
path: 'filament',
|
|
||||||
populate: { path: 'costTaxRate', strictPopulate: false },
|
|
||||||
strictPopulate: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'part',
|
|
||||||
populate: [
|
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
strictPopulate: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'product',
|
|
||||||
populate: [
|
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
strictPopulate: false,
|
|
||||||
},
|
|
||||||
{ path: 'costTaxRate', strictPopulate: false },
|
|
||||||
{ path: 'priceTaxRate', strictPopulate: false },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -189,39 +133,11 @@ export const editOrderItemRouteHandler = async (req, res) => {
|
|||||||
|
|
||||||
logger.trace(`Order Item with ID: ${id}`);
|
logger.trace(`Order Item with ID: ${id}`);
|
||||||
|
|
||||||
const skuType =
|
|
||||||
req.body.sku && req.body.itemType ? req.body.itemType + 'Sku' : null;
|
|
||||||
|
|
||||||
let name = req.body.name;
|
|
||||||
if (!name && req.body.sku && skuType) {
|
|
||||||
const skuEntry = getModelByName(skuType);
|
|
||||||
if (skuEntry?.model) {
|
|
||||||
const sku = await getObject({
|
|
||||||
model: skuEntry.model,
|
|
||||||
id: req.body.sku,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
name = sku?.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!name && req.body.item && req.body.itemType) {
|
|
||||||
const itemEntry = getModelByName(req.body.itemType);
|
|
||||||
if (itemEntry?.model) {
|
|
||||||
const item = await getObject({
|
|
||||||
model: itemEntry.model,
|
|
||||||
id: req.body.item,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
name = item?.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body.name ?? name,
|
name: req.body.name,
|
||||||
itemType: req.body.itemType,
|
itemType: req.body.itemType,
|
||||||
item: req.body.item,
|
item: req.body.item,
|
||||||
sku: req.body.sku,
|
|
||||||
orderType: req.body.orderType,
|
orderType: req.body.orderType,
|
||||||
order: req.body.order,
|
order: req.body.order,
|
||||||
syncAmount: req.body.syncAmount,
|
syncAmount: req.body.syncAmount,
|
||||||
@ -242,7 +158,7 @@ export const editOrderItemRouteHandler = async (req, res) => {
|
|||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
logger.error('Error editing order item:', result.error);
|
logger.error('Error editing order item:', result.error);
|
||||||
res.status(result.code || 500).send(result);
|
res.status(result).send(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,14 +173,13 @@ export const editMultipleOrderItemsRouteHandler = async (req, res) => {
|
|||||||
name: update.name,
|
name: update.name,
|
||||||
itemType: update.itemType,
|
itemType: update.itemType,
|
||||||
item: update.item,
|
item: update.item,
|
||||||
sku: update.sku,
|
|
||||||
orderType: update.orderType,
|
orderType: update.orderType,
|
||||||
order: update.order,
|
order: update.order,
|
||||||
syncAmount: update.syncAmount,
|
syncAmount: update.syncAmount,
|
||||||
itemAmount: update.itemAmount,
|
itemAmount: update.itemAmount,
|
||||||
quantity: update.quantity,
|
quantity: update.quantity,
|
||||||
totalAmount: update.totalAmount,
|
totalAmount: update.totalAmount,
|
||||||
shipment: update.shipment,
|
shipment: update.shipment,
|
||||||
taxRate: update.taxRate,
|
taxRate: update.taxRate,
|
||||||
totalAmountWithTax: update.totalAmountWithTax,
|
totalAmountWithTax: update.totalAmountWithTax,
|
||||||
}));
|
}));
|
||||||
@ -291,41 +206,13 @@ export const editMultipleOrderItemsRouteHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const newOrderItemRouteHandler = async (req, res) => {
|
export const newOrderItemRouteHandler = async (req, res) => {
|
||||||
const skuType =
|
|
||||||
req.body.sku && req.body.itemType ? req.body.itemType + 'Sku' : null;
|
|
||||||
|
|
||||||
let name = req.body.name;
|
|
||||||
if (!name && req.body.sku && skuType) {
|
|
||||||
const skuEntry = getModelByName(skuType);
|
|
||||||
if (skuEntry?.model) {
|
|
||||||
const sku = await getObject({
|
|
||||||
model: skuEntry.model,
|
|
||||||
id: req.body.sku,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
name = sku?.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!name && req.body.item && req.body.itemType) {
|
|
||||||
const itemEntry = getModelByName(req.body.itemType);
|
|
||||||
if (itemEntry?.model) {
|
|
||||||
const item = await getObject({
|
|
||||||
model: itemEntry.model,
|
|
||||||
id: req.body.item,
|
|
||||||
cached: true,
|
|
||||||
});
|
|
||||||
name = item?.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newData = {
|
const newData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: name || 'Order Item',
|
name: req.body.name,
|
||||||
purchaseOrder: req.body.purchaseOrder,
|
purchaseOrder: req.body.purchaseOrder,
|
||||||
state: { type: 'draft' },
|
state: { type: 'draft' },
|
||||||
itemType: req.body.itemType,
|
itemType: req.body.itemType,
|
||||||
item: req.body.item,
|
item: req.body.item,
|
||||||
sku: req.body.sku,
|
|
||||||
orderType: req.body.orderType,
|
orderType: req.body.orderType,
|
||||||
order: req.body.order,
|
order: req.body.order,
|
||||||
syncAmount: req.body.syncAmount,
|
syncAmount: req.body.syncAmount,
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export const listPartStocksRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: [{ path: 'partSku' }],
|
populate: [{ path: 'part' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -60,7 +60,7 @@ export const listPartStocksByPropertiesRouteHandler = async (
|
|||||||
model: partStockModel,
|
model: partStockModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: ['partSku'],
|
populate: ['part'],
|
||||||
masterFilter,
|
masterFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ export const getPartStockRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: partStockModel,
|
model: partStockModel,
|
||||||
id,
|
id,
|
||||||
populate: [{ path: 'partSku' }],
|
populate: [{ path: 'part' }],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Part Stock not found with supplied id.`);
|
logger.warn(`Part Stock not found with supplied id.`);
|
||||||
@ -146,7 +146,7 @@ export const newPartStockRouteHandler = async (req, res) => {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
startingQuantity: req.body.startingQuantity,
|
startingQuantity: req.body.startingQuantity,
|
||||||
currentQuantity: req.body.currentQuantity,
|
currentQuantity: req.body.currentQuantity,
|
||||||
partSku: req.body.partSku,
|
part: req.body.part,
|
||||||
state: req.body.state,
|
state: req.body.state,
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
getModelHistory,
|
getModelHistory,
|
||||||
checkStates,
|
checkStates,
|
||||||
} from '../../database/database.js';
|
} from '../../database/database.js';
|
||||||
import { productSkuModel } from '../../database/schemas/management/productsku.schema.js';
|
import { productModel } from '../../database/schemas/management/product.schema.js';
|
||||||
const logger = log4js.getLogger('Product Stocks');
|
const logger = log4js.getLogger('Product Stocks');
|
||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ export const listProductStocksRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: [{ path: 'productSku' }, { path: 'partStocks.partStock' }],
|
populate: [{ path: 'product' }, { path: 'partStocks.partStock' }],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -62,7 +62,7 @@ export const listProductStocksByPropertiesRouteHandler = async (
|
|||||||
model: productStockModel,
|
model: productStockModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: ['productSku', 'partStocks.partStock'],
|
populate: ['product', 'partStocks.partStock'],
|
||||||
masterFilter,
|
masterFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ export const getProductStockRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: productStockModel,
|
model: productStockModel,
|
||||||
id,
|
id,
|
||||||
populate: [{ path: 'partStocks.partSku' }, { path: 'partStocks.partStock' }, { path: 'productSku' }],
|
populate: [{ path: 'partStocks.part' }, { path: 'partStocks.partStock' }, { path: 'product' }],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Product Stock not found with supplied id.`);
|
logger.warn(`Product Stock not found with supplied id.`);
|
||||||
@ -114,7 +114,6 @@ export const editProductStockRouteHandler = async (req, res) => {
|
|||||||
partStocks: req.body?.partStocks?.map((partStock) => ({
|
partStocks: req.body?.partStocks?.map((partStock) => ({
|
||||||
quantity: partStock.quantity,
|
quantity: partStock.quantity,
|
||||||
partStock: partStock.partStock,
|
partStock: partStock.partStock,
|
||||||
partSku: partStock.partSku,
|
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -163,18 +162,18 @@ export const editMultipleProductStocksRouteHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const newProductStockRouteHandler = async (req, res) => {
|
export const newProductStockRouteHandler = async (req, res) => {
|
||||||
const productSkuId = new mongoose.Types.ObjectId(req.body.productSku?._id);
|
const productId = new mongoose.Types.ObjectId(req.body.product?._id);
|
||||||
const productSku = await getObject({
|
const product = await getObject({
|
||||||
model: productSkuModel,
|
model: productModel,
|
||||||
id: productSkuId,
|
id: productId,
|
||||||
});
|
});
|
||||||
const newData = {
|
const newData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
currentQuantity: req.body.currentQuantity,
|
currentQuantity: req.body.currentQuantity,
|
||||||
productSku: req.body.productSku,
|
product: req.body.product,
|
||||||
state: req.body.state ?? { type: 'draft' },
|
state: req.body.state ?? { type: 'draft' },
|
||||||
partStocks: (productSku.parts || []).map((part) => ({
|
partStocks: product.parts.map((part) => ({
|
||||||
partSku: part.partSku,
|
part: part.part,
|
||||||
quantity: part.quantity,
|
quantity: part.quantity,
|
||||||
partStock: undefined,
|
partStock: undefined,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export const listFilamentsRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: ['costTaxRate', 'material'],
|
populate: ['vendor', 'costTaxRate'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -59,7 +59,16 @@ export const listFilamentsByPropertiesRouteHandler = async (
|
|||||||
model: filamentModel,
|
model: filamentModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: [],
|
populate: [
|
||||||
|
{
|
||||||
|
path: 'vendor',
|
||||||
|
from: 'vendors',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'costTaxRate',
|
||||||
|
from: 'taxrates',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -77,7 +86,7 @@ export const getFilamentRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: filamentModel,
|
model: filamentModel,
|
||||||
id,
|
id,
|
||||||
populate: ['costTaxRate', 'material'],
|
populate: ['vendor', 'costTaxRate'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Filament not found with supplied id.`);
|
logger.warn(`Filament not found with supplied id.`);
|
||||||
@ -99,13 +108,15 @@ export const editFilamentRouteHandler = async (req, res) => {
|
|||||||
barcode: req.body.barcode,
|
barcode: req.body.barcode,
|
||||||
url: req.body.url,
|
url: req.body.url,
|
||||||
image: req.body.image,
|
image: req.body.image,
|
||||||
material: req.body.material?._id ?? req.body.material,
|
color: req.body.color,
|
||||||
|
vendor: req.body.vendor,
|
||||||
|
type: req.body.type,
|
||||||
|
cost: req.body.cost,
|
||||||
|
costTaxRate: req.body.costTaxRate,
|
||||||
|
costWithTax: req.body.costWithTax,
|
||||||
diameter: req.body.diameter,
|
diameter: req.body.diameter,
|
||||||
density: req.body.density,
|
density: req.body.density,
|
||||||
emptySpoolWeight: req.body.emptySpoolWeight,
|
emptySpoolWeight: req.body.emptySpoolWeight,
|
||||||
cost: req.body?.cost,
|
|
||||||
costTaxRate: req.body?.costTaxRate,
|
|
||||||
costWithTax: req.body?.costWithTax,
|
|
||||||
};
|
};
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
model: filamentModel,
|
model: filamentModel,
|
||||||
@ -132,13 +143,15 @@ export const editMultipleFilamentsRouteHandler = async (req, res) => {
|
|||||||
barcode: update.barcode,
|
barcode: update.barcode,
|
||||||
url: update.url,
|
url: update.url,
|
||||||
image: update.image,
|
image: update.image,
|
||||||
material: update.material?._id ?? update.material,
|
color: update.color,
|
||||||
diameter: update.diameter,
|
vendor: update.vendor,
|
||||||
density: update.density,
|
type: update.type,
|
||||||
emptySpoolWeight: update.emptySpoolWeight,
|
|
||||||
cost: update.cost,
|
cost: update.cost,
|
||||||
costTaxRate: update.costTaxRate,
|
costTaxRate: update.costTaxRate,
|
||||||
costWithTax: update.costWithTax,
|
costWithTax: update.costWithTax,
|
||||||
|
diameter: update.diameter,
|
||||||
|
density: update.density,
|
||||||
|
emptySpoolWeight: update.emptySpoolWeight,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!Array.isArray(updates)) {
|
if (!Array.isArray(updates)) {
|
||||||
@ -170,13 +183,15 @@ export const newFilamentRouteHandler = async (req, res) => {
|
|||||||
barcode: req.body.barcode,
|
barcode: req.body.barcode,
|
||||||
url: req.body.url,
|
url: req.body.url,
|
||||||
image: req.body.image,
|
image: req.body.image,
|
||||||
material: req.body.material?._id ?? req.body.material,
|
color: req.body.color,
|
||||||
|
vendor: req.body.vendor,
|
||||||
|
type: req.body.type,
|
||||||
|
cost: req.body.cost,
|
||||||
|
costTaxRate: req.body.costTaxRate,
|
||||||
|
costWithTax: req.body.costWithTax,
|
||||||
diameter: req.body.diameter,
|
diameter: req.body.diameter,
|
||||||
density: req.body.density,
|
density: req.body.density,
|
||||||
emptySpoolWeight: req.body.emptySpoolWeight,
|
emptySpoolWeight: req.body.emptySpoolWeight,
|
||||||
cost: req.body?.cost,
|
|
||||||
costTaxRate: req.body?.costTaxRate,
|
|
||||||
costWithTax: req.body?.costWithTax,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -1,196 +0,0 @@
|
|||||||
import config from '../../config.js';
|
|
||||||
import { filamentSkuModel } from '../../database/schemas/management/filamentsku.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('Filament SKUs');
|
|
||||||
logger.level = config.server.logLevel;
|
|
||||||
|
|
||||||
export const listFilamentSkusRouteHandler = async (
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
page = 1,
|
|
||||||
limit = 25,
|
|
||||||
property = '',
|
|
||||||
filter = {},
|
|
||||||
search = '',
|
|
||||||
sort = '',
|
|
||||||
order = 'ascend'
|
|
||||||
) => {
|
|
||||||
const result = await listObjects({
|
|
||||||
model: filamentSkuModel,
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
property,
|
|
||||||
filter,
|
|
||||||
search,
|
|
||||||
sort,
|
|
||||||
order,
|
|
||||||
populate: ['costTaxRate'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result?.error) {
|
|
||||||
logger.error('Error listing filament SKUs.');
|
|
||||||
res.status(result.code).send(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`List of filament SKUs (Page ${page}, Limit ${limit}). Count: ${result.length}.`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const listFilamentSkusByPropertiesRouteHandler = async (
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
properties = '',
|
|
||||||
filter = {},
|
|
||||||
masterFilter = {}
|
|
||||||
) => {
|
|
||||||
const result = await listObjectsByProperties({
|
|
||||||
model: filamentSkuModel,
|
|
||||||
properties,
|
|
||||||
filter,
|
|
||||||
populate: ['costTaxRate'],
|
|
||||||
masterFilter,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result?.error) {
|
|
||||||
logger.error('Error listing filament SKUs.');
|
|
||||||
res.status(result.code).send(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`List of filament SKUs. Count: ${result.length}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFilamentSkuRouteHandler = async (req, res) => {
|
|
||||||
const id = req.params.id;
|
|
||||||
const result = await getObject({
|
|
||||||
model: filamentSkuModel,
|
|
||||||
id,
|
|
||||||
populate: [{ path: 'filament', populate: 'costTaxRate' }, 'costTaxRate'],
|
|
||||||
});
|
|
||||||
if (result?.error) {
|
|
||||||
logger.warn(`Filament SKU not found with supplied id.`);
|
|
||||||
return res.status(result.code).send(result);
|
|
||||||
}
|
|
||||||
logger.debug(`Retrieved filament SKU with ID: ${id}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const editFilamentSkuRouteHandler = async (req, res) => {
|
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
|
||||||
|
|
||||||
logger.trace(`Filament SKU with ID: ${id}`);
|
|
||||||
|
|
||||||
const overrideCost = req.body?.overrideCost;
|
|
||||||
|
|
||||||
const updateData = {
|
|
||||||
updatedAt: new Date(),
|
|
||||||
barcode: req.body?.barcode,
|
|
||||||
filament: req.body?.filament,
|
|
||||||
name: req.body?.name,
|
|
||||||
description: req.body?.description,
|
|
||||||
color: req.body?.color,
|
|
||||||
cost: overrideCost ? req.body?.cost : null,
|
|
||||||
overrideCost,
|
|
||||||
costTaxRate: overrideCost ? req.body?.costTaxRate : null,
|
|
||||||
costWithTax: overrideCost ? req.body?.costWithTax : null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await editObject({
|
|
||||||
model: filamentSkuModel,
|
|
||||||
id,
|
|
||||||
updateData,
|
|
||||||
user: req.user,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
logger.error('Error editing filament SKU:', result.error);
|
|
||||||
res.status(result.code || 500).send(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`Edited filament SKU with ID: ${id}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const newFilamentSkuRouteHandler = async (req, res) => {
|
|
||||||
const overrideCost = req.body?.overrideCost;
|
|
||||||
|
|
||||||
const newData = {
|
|
||||||
barcode: req.body?.barcode,
|
|
||||||
filament: req.body?.filament,
|
|
||||||
name: req.body?.name,
|
|
||||||
description: req.body?.description,
|
|
||||||
color: req.body?.color,
|
|
||||||
cost: overrideCost ? req.body?.cost : null,
|
|
||||||
overrideCost,
|
|
||||||
costTaxRate: overrideCost ? req.body?.costTaxRate : null,
|
|
||||||
costWithTax: overrideCost ? req.body?.costWithTax : null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await newObject({
|
|
||||||
model: filamentSkuModel,
|
|
||||||
newData,
|
|
||||||
user: req.user,
|
|
||||||
});
|
|
||||||
if (result.error) {
|
|
||||||
logger.error('No filament SKU created:', result.error);
|
|
||||||
return res.status(result.code).send(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`New filament SKU with ID: ${result._id}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const deleteFilamentSkuRouteHandler = async (req, res) => {
|
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
|
||||||
|
|
||||||
logger.trace(`Filament SKU with ID: ${id}`);
|
|
||||||
|
|
||||||
const result = await deleteObject({
|
|
||||||
model: filamentSkuModel,
|
|
||||||
id,
|
|
||||||
user: req.user,
|
|
||||||
});
|
|
||||||
if (result.error) {
|
|
||||||
logger.error('No filament SKU deleted:', result.error);
|
|
||||||
return res.status(result.code).send(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`Deleted filament SKU with ID: ${id}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFilamentSkuStatsRouteHandler = async (req, res) => {
|
|
||||||
const result = await getModelStats({ model: filamentSkuModel });
|
|
||||||
if (result?.error) {
|
|
||||||
logger.error('Error fetching filament SKU stats:', result.error);
|
|
||||||
return res.status(result.code).send(result);
|
|
||||||
}
|
|
||||||
logger.trace('Filament SKU stats:', result);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getFilamentSkuHistoryRouteHandler = async (req, res) => {
|
|
||||||
const from = req.query.from;
|
|
||||||
const to = req.query.to;
|
|
||||||
const result = await getModelHistory({ model: filamentSkuModel, from, to });
|
|
||||||
if (result?.error) {
|
|
||||||
logger.error('Error fetching filament SKU history:', result.error);
|
|
||||||
return res.status(result.code).send(result);
|
|
||||||
}
|
|
||||||
logger.trace('Filament SKU history:', result);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
@ -2,16 +2,7 @@ import config from '../../config.js';
|
|||||||
import { materialModel } from '../../database/schemas/management/material.schema.js';
|
import { materialModel } from '../../database/schemas/management/material.schema.js';
|
||||||
import log4js from 'log4js';
|
import log4js from 'log4js';
|
||||||
import mongoose from 'mongoose';
|
import mongoose from 'mongoose';
|
||||||
import {
|
import { getModelStats, getModelHistory } from '../../database/database.js';
|
||||||
getObject,
|
|
||||||
listObjects,
|
|
||||||
listObjectsByProperties,
|
|
||||||
editObject,
|
|
||||||
newObject,
|
|
||||||
getModelStats,
|
|
||||||
getModelHistory,
|
|
||||||
} from '../../database/database.js';
|
|
||||||
|
|
||||||
const logger = log4js.getLogger('Materials');
|
const logger = log4js.getLogger('Materials');
|
||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
@ -21,121 +12,118 @@ export const listMaterialsRouteHandler = async (
|
|||||||
page = 1,
|
page = 1,
|
||||||
limit = 25,
|
limit = 25,
|
||||||
property = '',
|
property = '',
|
||||||
filter = {},
|
|
||||||
search = '',
|
|
||||||
sort = '',
|
|
||||||
order = 'ascend'
|
|
||||||
) => {
|
|
||||||
const result = await listObjects({
|
|
||||||
model: materialModel,
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
property,
|
|
||||||
filter,
|
|
||||||
search,
|
|
||||||
sort,
|
|
||||||
order,
|
|
||||||
populate: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result?.error) {
|
|
||||||
logger.error('Error listing materials.');
|
|
||||||
res.status(result.code).send(result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`List of materials (Page ${page}, Limit ${limit}). Count: ${result.length}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const listMaterialsByPropertiesRouteHandler = async (
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
properties = [],
|
|
||||||
filter = {}
|
filter = {}
|
||||||
) => {
|
) => {
|
||||||
const result = await listObjectsByProperties({
|
try {
|
||||||
model: materialModel,
|
// Calculate the skip value based on the page number and limit
|
||||||
properties,
|
const skip = (page - 1) * limit;
|
||||||
filter,
|
|
||||||
populate: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result?.error) {
|
let material;
|
||||||
logger.error('Error listing materials.');
|
let aggregateCommand = [];
|
||||||
res.status(result.code).send(result);
|
|
||||||
return;
|
if (filter != {}) {
|
||||||
|
// use filtering if present
|
||||||
|
aggregateCommand.push({ $match: filter });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property != '') {
|
||||||
|
aggregateCommand.push({ $group: { _id: `$${property}` } }); // group all same properties
|
||||||
|
aggregateCommand.push({ $project: { _id: 0, [property]: '$_id' } }); // rename _id to the property name
|
||||||
|
} else {
|
||||||
|
aggregateCommand.push({ $project: { image: 0, url: 0 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregateCommand.push({ $skip: skip });
|
||||||
|
aggregateCommand.push({ $limit: Number(limit) });
|
||||||
|
|
||||||
|
material = await materialModel.aggregate(aggregateCommand);
|
||||||
|
|
||||||
|
logger.trace(
|
||||||
|
`List of materials (Page ${page}, Limit ${limit}, Property ${property}):`,
|
||||||
|
material
|
||||||
|
);
|
||||||
|
res.send(material);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error listing materials:', error);
|
||||||
|
res.status(500).send({ error: error });
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`List of materials. Count: ${result.length}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMaterialRouteHandler = async (req, res) => {
|
export const getMaterialRouteHandler = async (req, res) => {
|
||||||
const id = req.params.id;
|
try {
|
||||||
const result = await getObject({
|
// Get ID from params
|
||||||
model: materialModel,
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
id,
|
// Fetch the material with the given remote address
|
||||||
populate: [],
|
const material = await materialModel.findOne({
|
||||||
});
|
_id: id,
|
||||||
if (result?.error) {
|
});
|
||||||
logger.warn(`Material not found with supplied id.`);
|
|
||||||
return res.status(result.code).send(result);
|
if (!material) {
|
||||||
|
logger.warn(`Material not found with supplied id.`);
|
||||||
|
return res.status(404).send({ error: 'Print job not found.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace(`Material with ID: ${id}:`, material);
|
||||||
|
res.send(material);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error fetching Material:', error);
|
||||||
|
res.status(500).send({ error: error.message });
|
||||||
}
|
}
|
||||||
logger.debug(`Retrieved material with ID: ${id}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editMaterialRouteHandler = async (req, res) => {
|
export const editMaterialRouteHandler = async (req, res) => {
|
||||||
const id = new mongoose.Types.ObjectId(req.params.id);
|
try {
|
||||||
|
// Get ID from params
|
||||||
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
||||||
|
// Fetch the material with the given remote address
|
||||||
|
const material = await materialModel.findOne({ _id: id });
|
||||||
|
|
||||||
logger.trace(`Material with ID: ${id}`);
|
if (!material) {
|
||||||
|
// Error handling
|
||||||
|
logger.warn(`Material not found with supplied id.`);
|
||||||
|
return res.status(404).send({ error: 'Print job not found.' });
|
||||||
|
}
|
||||||
|
|
||||||
const updateData = {
|
logger.trace(`Material with ID: ${id}:`, material);
|
||||||
updatedAt: new Date(),
|
|
||||||
name: req.body.name,
|
|
||||||
url: req.body.url,
|
|
||||||
tags: req.body.tags,
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = await editObject({
|
try {
|
||||||
model: materialModel,
|
const updateData = req.body;
|
||||||
id,
|
|
||||||
updateData,
|
|
||||||
user: req.user,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
const result = await materialModel.updateOne({ _id: id }, { $set: updateData });
|
||||||
logger.error('Error editing material:', result.error);
|
if (result.nModified === 0) {
|
||||||
res.status(result.code).send(result);
|
logger.error('No Material updated.');
|
||||||
return;
|
res.status(500).send({ error: 'No materials updated.' });
|
||||||
|
}
|
||||||
|
} catch (updateError) {
|
||||||
|
logger.error('Error updating material:', updateError);
|
||||||
|
res.status(500).send({ error: updateError.message });
|
||||||
|
}
|
||||||
|
res.send('OK');
|
||||||
|
} catch (fetchError) {
|
||||||
|
logger.error('Error fetching material:', fetchError);
|
||||||
|
res.status(500).send({ error: fetchError.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`Edited material with ID: ${id}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newMaterialRouteHandler = async (req, res) => {
|
export const newMaterialRouteHandler = async (req, res) => {
|
||||||
const newData = {
|
try {
|
||||||
createdAt: new Date(),
|
let { ...newMaterial } = req.body;
|
||||||
updatedAt: new Date(),
|
newMaterial = {
|
||||||
name: req.body.name,
|
...newMaterial,
|
||||||
url: req.body.url,
|
createdAt: new Date(),
|
||||||
tags: req.body.tags,
|
updatedAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await newObject({
|
const result = await materialModel.create(newMaterial);
|
||||||
model: materialModel,
|
if (result.nCreated === 0) {
|
||||||
newData,
|
logger.error('No material created.');
|
||||||
user: req.user,
|
res.status(500).send({ error: 'No material created.' });
|
||||||
});
|
}
|
||||||
if (result.error) {
|
res.status(200).send({ status: 'ok' });
|
||||||
logger.error('No material created:', result.error);
|
} catch (updateError) {
|
||||||
return res.status(result.code).send(result);
|
logger.error('Error updating material:', updateError);
|
||||||
|
res.status(500).send({ error: updateError.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`New material with ID: ${result._id}`);
|
|
||||||
res.send(result);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMaterialStatsRouteHandler = async (req, res) => {
|
export const getMaterialStatsRouteHandler = async (req, res) => {
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const listPartsRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: ['costTaxRate', 'priceTaxRate'],
|
populate: ['vendor'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -53,7 +53,20 @@ export const listPartsByPropertiesRouteHandler = async (req, res, properties = '
|
|||||||
model: partModel,
|
model: partModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: [],
|
populate: [
|
||||||
|
{
|
||||||
|
path: 'vendor',
|
||||||
|
from: 'vendors',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'priceTaxRate',
|
||||||
|
from: 'taxrates',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'costTaxRate',
|
||||||
|
from: 'taxrates',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -71,7 +84,7 @@ export const getPartRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: partModel,
|
model: partModel,
|
||||||
id,
|
id,
|
||||||
populate: ['costTaxRate', 'priceTaxRate'],
|
populate: ['vendor', 'priceTaxRate', 'costTaxRate'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Part not found with supplied id.`);
|
logger.warn(`Part not found with supplied id.`);
|
||||||
@ -90,16 +103,16 @@ export const editPartRouteHandler = async (req, res) => {
|
|||||||
const updateData = {
|
const updateData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body?.name,
|
name: req.body?.name,
|
||||||
fileName: req.body?.fileName,
|
|
||||||
file: req.body?.file,
|
file: req.body?.file,
|
||||||
cost: req.body?.cost,
|
vendor: req.body?.vendor,
|
||||||
price: req.body?.price,
|
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
margin: req.body?.margin,
|
margin: req.body?.margin,
|
||||||
costTaxRate: req.body?.costTaxRate,
|
price: req.body?.price,
|
||||||
|
cost: req.body?.cost,
|
||||||
|
priceMode: req.body?.priceMode,
|
||||||
priceTaxRate: req.body?.priceTaxRate,
|
priceTaxRate: req.body?.priceTaxRate,
|
||||||
costWithTax: req.body?.costWithTax,
|
costTaxRate: req.body?.costTaxRate,
|
||||||
priceWithTax: req.body?.priceWithTax,
|
priceWithTax: req.body?.priceWithTax,
|
||||||
|
costWithTax: req.body?.costWithTax,
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -124,16 +137,16 @@ export const newPartRouteHandler = async (req, res) => {
|
|||||||
const newData = {
|
const newData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body?.name,
|
name: req.body?.name,
|
||||||
fileName: req.body?.fileName,
|
|
||||||
file: req.body?.file,
|
file: req.body?.file,
|
||||||
cost: req.body?.cost,
|
vendor: req.body?.vendor,
|
||||||
price: req.body?.price,
|
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
margin: req.body?.margin,
|
margin: req.body?.margin,
|
||||||
costTaxRate: req.body?.costTaxRate,
|
price: req.body?.price,
|
||||||
|
cost: req.body?.cost,
|
||||||
|
priceMode: req.body?.priceMode,
|
||||||
priceTaxRate: req.body?.priceTaxRate,
|
priceTaxRate: req.body?.priceTaxRate,
|
||||||
costWithTax: req.body?.costWithTax,
|
costTaxRate: req.body?.costTaxRate,
|
||||||
priceWithTax: req.body?.priceWithTax,
|
priceWithTax: req.body?.priceWithTax,
|
||||||
|
costWithTax: req.body?.costWithTax,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -1,212 +0,0 @@
|
|||||||
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: ['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: ['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: [
|
|
||||||
{ path: 'part', populate: ['costTaxRate', 'priceTaxRate'] },
|
|
||||||
'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 overrideCost = req.body?.overrideCost;
|
|
||||||
const overridePrice = req.body?.overridePrice;
|
|
||||||
|
|
||||||
const updateData = {
|
|
||||||
updatedAt: new Date(),
|
|
||||||
barcode: req.body?.barcode,
|
|
||||||
part: req.body?.part,
|
|
||||||
name: req.body?.name,
|
|
||||||
description: req.body?.description,
|
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
price: overridePrice ? req.body?.price : null,
|
|
||||||
cost: overrideCost ? req.body?.cost : null,
|
|
||||||
overrideCost,
|
|
||||||
overridePrice,
|
|
||||||
margin: overridePrice ? req.body?.margin : null,
|
|
||||||
priceTaxRate: overridePrice ? req.body?.priceTaxRate : null,
|
|
||||||
costTaxRate: overrideCost ? req.body?.costTaxRate : null,
|
|
||||||
priceWithTax: overridePrice ? req.body?.priceWithTax : null,
|
|
||||||
costWithTax: overrideCost ? req.body?.costWithTax : null,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 overrideCost = req.body?.overrideCost;
|
|
||||||
const overridePrice = req.body?.overridePrice;
|
|
||||||
|
|
||||||
const newData = {
|
|
||||||
barcode: req.body?.barcode,
|
|
||||||
part: req.body?.part,
|
|
||||||
name: req.body?.name,
|
|
||||||
description: req.body?.description,
|
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
price: overridePrice ? req.body?.price : null,
|
|
||||||
cost: overrideCost ? req.body?.cost : null,
|
|
||||||
overrideCost,
|
|
||||||
overridePrice,
|
|
||||||
margin: overridePrice ? req.body?.margin : null,
|
|
||||||
priceTaxRate: overridePrice ? req.body?.priceTaxRate : null,
|
|
||||||
costTaxRate: overrideCost ? req.body?.costTaxRate : null,
|
|
||||||
priceWithTax: overridePrice ? req.body?.priceWithTax : null,
|
|
||||||
costWithTax: overrideCost ? req.body?.costWithTax : null,
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
@ -35,7 +35,7 @@ export const listProductsRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: ['vendor', 'costTaxRate', 'priceTaxRate'],
|
populate: ['vendor'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -76,7 +76,7 @@ export const getProductRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: productModel,
|
model: productModel,
|
||||||
id,
|
id,
|
||||||
populate: ['vendor', 'costTaxRate', 'priceTaxRate'],
|
populate: ['vendor', 'parts.part'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Product not found with supplied id.`);
|
logger.warn(`Product not found with supplied id.`);
|
||||||
@ -97,15 +97,11 @@ export const editProductRouteHandler = async (req, res) => {
|
|||||||
name: req.body?.name,
|
name: req.body?.name,
|
||||||
tags: req.body?.tags,
|
tags: req.body?.tags,
|
||||||
version: req.body?.version,
|
version: req.body?.version,
|
||||||
|
margin: req.body.margin,
|
||||||
|
amount: req.body.amount,
|
||||||
|
priceMode: req.body.priceMode,
|
||||||
vendor: req.body.vendor,
|
vendor: req.body.vendor,
|
||||||
cost: req.body?.cost,
|
parts: req.body.parts,
|
||||||
price: req.body?.price,
|
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
margin: req.body?.margin,
|
|
||||||
costTaxRate: req.body?.costTaxRate,
|
|
||||||
priceTaxRate: req.body?.priceTaxRate,
|
|
||||||
costWithTax: req.body?.costWithTax,
|
|
||||||
priceWithTax: req.body?.priceWithTax,
|
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -132,15 +128,11 @@ export const newProductRouteHandler = async (req, res) => {
|
|||||||
name: req.body?.name,
|
name: req.body?.name,
|
||||||
tags: req.body?.tags,
|
tags: req.body?.tags,
|
||||||
version: req.body?.version,
|
version: req.body?.version,
|
||||||
|
margin: req.body.margin,
|
||||||
|
amount: req.body.amount,
|
||||||
|
priceMode: req.body.priceMode,
|
||||||
vendor: req.body.vendor,
|
vendor: req.body.vendor,
|
||||||
cost: req.body?.cost,
|
parts: req.body.parts,
|
||||||
price: req.body?.price,
|
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
margin: req.body?.margin,
|
|
||||||
costTaxRate: req.body?.costTaxRate,
|
|
||||||
priceTaxRate: req.body?.priceTaxRate,
|
|
||||||
costWithTax: req.body?.costWithTax,
|
|
||||||
priceWithTax: req.body?.priceWithTax,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const listProductSkusRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: ['priceTaxRate', 'costTaxRate'],
|
populate: ['product'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -59,7 +59,7 @@ export const listProductSkusByPropertiesRouteHandler = async (
|
|||||||
model: productSkuModel,
|
model: productSkuModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: ['priceTaxRate', 'costTaxRate'],
|
populate: ['product'],
|
||||||
masterFilter,
|
masterFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -78,12 +78,7 @@ export const getProductSkuRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: productSkuModel,
|
model: productSkuModel,
|
||||||
id,
|
id,
|
||||||
populate: [
|
populate: ['product'],
|
||||||
{ path: 'product', populate: ['costTaxRate', 'priceTaxRate'] },
|
|
||||||
'priceTaxRate',
|
|
||||||
'costTaxRate',
|
|
||||||
'parts.partSku',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`Product SKU not found with supplied id.`);
|
logger.warn(`Product SKU not found with supplied id.`);
|
||||||
@ -98,26 +93,12 @@ export const editProductSkuRouteHandler = async (req, res) => {
|
|||||||
|
|
||||||
logger.trace(`Product SKU with ID: ${id}`);
|
logger.trace(`Product SKU with ID: ${id}`);
|
||||||
|
|
||||||
const overrideCost = req.body?.overrideCost;
|
|
||||||
const overridePrice = req.body?.overridePrice;
|
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
barcode: req.body?.barcode,
|
sku: req.body?.sku,
|
||||||
product: req.body?.product,
|
product: req.body?.product,
|
||||||
name: req.body?.name,
|
name: req.body?.name,
|
||||||
description: req.body?.description,
|
description: req.body?.description,
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
price: overridePrice ? req.body?.price : null,
|
|
||||||
cost: overrideCost ? req.body?.cost : null,
|
|
||||||
overrideCost,
|
|
||||||
overridePrice,
|
|
||||||
margin: overridePrice ? req.body?.margin : null,
|
|
||||||
parts: req.body?.parts,
|
|
||||||
priceTaxRate: overridePrice ? req.body?.priceTaxRate : null,
|
|
||||||
costTaxRate: overrideCost ? req.body?.costTaxRate : null,
|
|
||||||
priceWithTax: overridePrice ? req.body?.priceWithTax : null,
|
|
||||||
costWithTax: overrideCost ? req.body?.costWithTax : null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await editObject({
|
const result = await editObject({
|
||||||
@ -138,25 +119,11 @@ export const editProductSkuRouteHandler = async (req, res) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const newProductSkuRouteHandler = async (req, res) => {
|
export const newProductSkuRouteHandler = async (req, res) => {
|
||||||
const overrideCost = req.body?.overrideCost;
|
|
||||||
const overridePrice = req.body?.overridePrice;
|
|
||||||
|
|
||||||
const newData = {
|
const newData = {
|
||||||
barcode: req.body?.barcode,
|
sku: req.body?.sku,
|
||||||
product: req.body?.product,
|
product: req.body?.product,
|
||||||
name: req.body?.name,
|
name: req.body?.name,
|
||||||
description: req.body?.description,
|
description: req.body?.description,
|
||||||
priceMode: req.body?.priceMode,
|
|
||||||
price: overridePrice ? req.body?.price : null,
|
|
||||||
cost: overrideCost ? req.body?.cost : null,
|
|
||||||
overrideCost,
|
|
||||||
overridePrice,
|
|
||||||
margin: overridePrice ? req.body?.margin : null,
|
|
||||||
parts: req.body?.parts,
|
|
||||||
priceTaxRate: overridePrice ? req.body?.priceTaxRate : null,
|
|
||||||
costTaxRate: overrideCost ? req.body?.costTaxRate : null,
|
|
||||||
priceWithTax: overridePrice ? req.body?.priceWithTax : null,
|
|
||||||
costWithTax: overrideCost ? req.body?.costWithTax : null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -4,11 +4,96 @@ import { getModelByName } from './model.js';
|
|||||||
import { listObjectsOData } from '../../database/odata.js';
|
import { listObjectsOData } from '../../database/odata.js';
|
||||||
import { getFilter } from '../../utils.js';
|
import { getFilter } from '../../utils.js';
|
||||||
import { generateCsvTable } from '../../database/csv.js';
|
import { generateCsvTable } from '../../database/csv.js';
|
||||||
import { getModelFilterFields, parseOrderBy, rowToFlat } from './export.js';
|
|
||||||
|
|
||||||
const logger = log4js.getLogger('CSV');
|
const logger = log4js.getLogger('CSV');
|
||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flatten nested objects for CSV display.
|
||||||
|
* Objects become "key.subkey: value" or JSON string; arrays become comma-separated.
|
||||||
|
*/
|
||||||
|
function flattenForCsv(obj, prefix = '') {
|
||||||
|
if (obj === null || obj === undefined) return {};
|
||||||
|
if (typeof obj !== 'object') return { [prefix]: obj };
|
||||||
|
if (obj instanceof Date) return { [prefix]: obj };
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
const str = obj
|
||||||
|
.map((v) => (v && typeof v === 'object' && !(v instanceof Date) ? JSON.stringify(v) : v))
|
||||||
|
.join(', ');
|
||||||
|
return { [prefix]: str };
|
||||||
|
}
|
||||||
|
const result = {};
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
const key = prefix ? `${prefix}.${k}` : k;
|
||||||
|
if (v !== null && typeof v === 'object' && !(v instanceof Date) && !Array.isArray(v)) {
|
||||||
|
Object.assign(result, flattenForCsv(v, key));
|
||||||
|
} else {
|
||||||
|
result[key] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a row to flat key-value for CSV. Nested objects are flattened.
|
||||||
|
*/
|
||||||
|
function rowToFlat(row) {
|
||||||
|
const flat = {};
|
||||||
|
for (const [key, val] of Object.entries(row)) {
|
||||||
|
if (key.startsWith('@')) continue;
|
||||||
|
if (val !== null && typeof val === 'object' && !(val instanceof Date) && !Array.isArray(val)) {
|
||||||
|
Object.assign(flat, flattenForCsv(val, key));
|
||||||
|
} else {
|
||||||
|
flat[key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get allowed filter fields for CSV export (reuse OData logic).
|
||||||
|
*/
|
||||||
|
function getModelFilterFields(objectType) {
|
||||||
|
const base = ['_id'];
|
||||||
|
const byType = {
|
||||||
|
note: ['parent._id', 'noteType', 'user'],
|
||||||
|
notification: ['user'],
|
||||||
|
userNotifier: ['user', 'object', 'objectType'],
|
||||||
|
printer: ['host'],
|
||||||
|
job: ['printer', 'gcodeFile'],
|
||||||
|
subJob: ['job'],
|
||||||
|
filamentStock: ['filament'],
|
||||||
|
partStock: ['part'],
|
||||||
|
productStock: ['product'],
|
||||||
|
productSku: ['product'],
|
||||||
|
purchaseOrder: ['vendor'],
|
||||||
|
orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'shipment._id'],
|
||||||
|
shipment: ['order._id', 'orderType', 'courierService._id'],
|
||||||
|
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||||
|
stockAudit: ['filamentStock._id', 'partStock._id'],
|
||||||
|
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
||||||
|
documentTemplate: ['parent._id', 'documentSize._id'],
|
||||||
|
salesOrder: ['client'],
|
||||||
|
invoice: ['to._id', 'from._id', 'order._id', 'orderType'],
|
||||||
|
auditLog: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||||
|
appPassword: ['name', 'user', 'active'],
|
||||||
|
};
|
||||||
|
const extra = byType[objectType] || [];
|
||||||
|
return [...base, ...extra];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOrderBy(orderby) {
|
||||||
|
if (!orderby || typeof orderby !== 'string') {
|
||||||
|
return { sort: 'createdAt', order: 'ascend' };
|
||||||
|
}
|
||||||
|
const trimmed = orderby.trim();
|
||||||
|
const parts = trimmed.split(/\s+/);
|
||||||
|
const sort = parts[0] || 'createdAt';
|
||||||
|
const dir = (parts[1] || 'asc').toLowerCase();
|
||||||
|
const order = dir === 'desc' ? 'descend' : 'ascend';
|
||||||
|
return { sort, order };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate CSV file for the given object type.
|
* Generate CSV file for the given object type.
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
|
|||||||
@ -10,11 +10,96 @@ import {
|
|||||||
incrementExcelTempRequestCount,
|
incrementExcelTempRequestCount,
|
||||||
deleteExcelTempToken,
|
deleteExcelTempToken,
|
||||||
} from '../../database/excel.js';
|
} from '../../database/excel.js';
|
||||||
import { getModelFilterFields, parseOrderBy, rowToFlat } from './export.js';
|
|
||||||
|
|
||||||
const logger = log4js.getLogger('Excel');
|
const logger = log4js.getLogger('Excel');
|
||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flatten nested objects for Excel display.
|
||||||
|
* Objects become "key.subkey: value" or JSON string; arrays become comma-separated.
|
||||||
|
*/
|
||||||
|
function flattenForExcel(obj, prefix = '') {
|
||||||
|
if (obj === null || obj === undefined) return {};
|
||||||
|
if (typeof obj !== 'object') return { [prefix]: obj };
|
||||||
|
if (obj instanceof Date) return { [prefix]: obj };
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
const str = obj
|
||||||
|
.map((v) => (v && typeof v === 'object' && !(v instanceof Date) ? JSON.stringify(v) : v))
|
||||||
|
.join(', ');
|
||||||
|
return { [prefix]: str };
|
||||||
|
}
|
||||||
|
const result = {};
|
||||||
|
for (const [k, v] of Object.entries(obj)) {
|
||||||
|
const key = prefix ? `${prefix}.${k}` : k;
|
||||||
|
if (v !== null && typeof v === 'object' && !(v instanceof Date) && !Array.isArray(v)) {
|
||||||
|
Object.assign(result, flattenForExcel(v, key));
|
||||||
|
} else {
|
||||||
|
result[key] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a row to flat key-value for Excel. Nested objects are flattened.
|
||||||
|
*/
|
||||||
|
function rowToFlat(row) {
|
||||||
|
const flat = {};
|
||||||
|
for (const [key, val] of Object.entries(row)) {
|
||||||
|
if (key.startsWith('@')) continue;
|
||||||
|
if (val !== null && typeof val === 'object' && !(val instanceof Date) && !Array.isArray(val)) {
|
||||||
|
Object.assign(flat, flattenForExcel(val, key));
|
||||||
|
} else {
|
||||||
|
flat[key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get allowed filter fields for Excel export (reuse OData logic).
|
||||||
|
*/
|
||||||
|
function getModelFilterFields(objectType) {
|
||||||
|
const base = ['_id'];
|
||||||
|
const byType = {
|
||||||
|
note: ['parent._id', 'noteType', 'user'],
|
||||||
|
notification: ['user'],
|
||||||
|
userNotifier: ['user', 'object', 'objectType'],
|
||||||
|
printer: ['host'],
|
||||||
|
job: ['printer', 'gcodeFile'],
|
||||||
|
subJob: ['job'],
|
||||||
|
filamentStock: ['filament'],
|
||||||
|
partStock: ['part'],
|
||||||
|
productStock: ['product'],
|
||||||
|
productSku: ['product'],
|
||||||
|
purchaseOrder: ['vendor'],
|
||||||
|
orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'shipment._id'],
|
||||||
|
shipment: ['order._id', 'orderType', 'courierService._id'],
|
||||||
|
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||||
|
stockAudit: ['filamentStock._id', 'partStock._id'],
|
||||||
|
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
||||||
|
documentTemplate: ['parent._id', 'documentSize._id'],
|
||||||
|
salesOrder: ['client'],
|
||||||
|
invoice: ['to._id', 'from._id', 'order._id', 'orderType'],
|
||||||
|
auditLog: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||||
|
appPassword: ['name', 'user', 'active'],
|
||||||
|
};
|
||||||
|
const extra = byType[objectType] || [];
|
||||||
|
return [...base, ...extra];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOrderBy(orderby) {
|
||||||
|
if (!orderby || typeof orderby !== 'string') {
|
||||||
|
return { sort: 'createdAt', order: 'ascend' };
|
||||||
|
}
|
||||||
|
const trimmed = orderby.trim();
|
||||||
|
const parts = trimmed.split(/\s+/);
|
||||||
|
const sort = parts[0] || 'createdAt';
|
||||||
|
const dir = (parts[1] || 'asc').toLowerCase();
|
||||||
|
const order = dir === 'desc' ? 'descend' : 'ascend';
|
||||||
|
return { sort, order };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Excel file for the given object type.
|
* Generate Excel file for the given object type.
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
|
|||||||
@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
* Shared export utilities for OData, CSV, and Excel.
|
|
||||||
* Centralizes filter fields, order-by parsing, and row flattening.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Allowed filter fields per object type for OData/CSV/Excel exports */
|
|
||||||
export const EXPORT_FILTER_BY_TYPE = {
|
|
||||||
note: ['parent._id', 'noteType', 'user'],
|
|
||||||
notification: ['user'],
|
|
||||||
userNotifier: ['user', 'object', 'objectType'],
|
|
||||||
printer: ['host'],
|
|
||||||
job: ['printer', 'gcodeFile'],
|
|
||||||
subJob: ['job'],
|
|
||||||
filamentStock: ['filamentSku'],
|
|
||||||
filament: ['material', 'material._id', 'name', 'diameter', 'cost'],
|
|
||||||
filamentSku: ['filament', 'vendor', 'costTaxRate'],
|
|
||||||
material: ['name', 'tags'],
|
|
||||||
partStock: ['partSku'],
|
|
||||||
partSku: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'],
|
|
||||||
productStock: ['productSku'],
|
|
||||||
productSku: ['product', 'vendor', 'priceTaxRate', 'costTaxRate'],
|
|
||||||
purchaseOrder: ['vendor'],
|
|
||||||
orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'sku._id', 'shipment._id'],
|
|
||||||
shipment: ['order._id', 'orderType', 'courierService._id'],
|
|
||||||
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
|
||||||
stockAudit: ['filamentStock._id', 'partStock._id'],
|
|
||||||
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
|
||||||
documentTemplate: ['parent._id', 'documentSize._id'],
|
|
||||||
salesOrder: ['client'],
|
|
||||||
invoice: ['to._id', 'from._id', 'order._id', 'orderType'],
|
|
||||||
auditLog: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
|
||||||
appPassword: ['name', 'user', 'active'],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get allowed filter fields for a given object type.
|
|
||||||
* @param {string} objectType - Model type (e.g. 'filament', 'material')
|
|
||||||
* @returns {string[]} Allowed filter field names
|
|
||||||
*/
|
|
||||||
export function getModelFilterFields(objectType) {
|
|
||||||
const base = ['_id'];
|
|
||||||
const extra = EXPORT_FILTER_BY_TYPE[objectType] || [];
|
|
||||||
return [...base, ...extra];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse OData $orderby or orderby string into sort and order.
|
|
||||||
* Supports "field asc", "field desc", or just "field" (defaults asc).
|
|
||||||
* @param {string} [orderby] - Orderby string (e.g. "createdAt desc")
|
|
||||||
* @returns {{ sort: string, order: 'ascend'|'descend' }}
|
|
||||||
*/
|
|
||||||
export function parseOrderBy(orderby) {
|
|
||||||
if (!orderby || typeof orderby !== 'string') {
|
|
||||||
return { sort: 'createdAt', order: 'ascend' };
|
|
||||||
}
|
|
||||||
const trimmed = orderby.trim();
|
|
||||||
const parts = trimmed.split(/\s+/);
|
|
||||||
const sort = parts[0] || 'createdAt';
|
|
||||||
const dir = (parts[1] || 'asc').toLowerCase();
|
|
||||||
const order = dir === 'desc' ? 'descend' : 'ascend';
|
|
||||||
return { sort, order };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flatten nested objects for export display.
|
|
||||||
* Objects become "key.subkey: value"; arrays become comma-separated strings.
|
|
||||||
* @param {*} obj - Value to flatten
|
|
||||||
* @param {string} [prefix=''] - Key prefix for nested values
|
|
||||||
* @returns {Object} Flat key-value object
|
|
||||||
*/
|
|
||||||
export function flattenForExport(obj, prefix = '') {
|
|
||||||
if (obj === null || obj === undefined) return {};
|
|
||||||
if (typeof obj !== 'object') return { [prefix]: obj };
|
|
||||||
if (obj instanceof Date) return { [prefix]: obj };
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
const str = obj
|
|
||||||
.map((v) => (v && typeof v === 'object' && !(v instanceof Date) ? JSON.stringify(v) : v))
|
|
||||||
.join(', ');
|
|
||||||
return { [prefix]: str };
|
|
||||||
}
|
|
||||||
const result = {};
|
|
||||||
for (const [k, v] of Object.entries(obj)) {
|
|
||||||
const key = prefix ? `${prefix}.${k}` : k;
|
|
||||||
if (v !== null && typeof v === 'object' && !(v instanceof Date) && !Array.isArray(v)) {
|
|
||||||
Object.assign(result, flattenForExport(v, key));
|
|
||||||
} else {
|
|
||||||
result[key] = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a row (e.g. OData value item) to flat key-value for CSV/Excel.
|
|
||||||
* Nested objects are flattened; @odata.* keys are skipped.
|
|
||||||
* @param {Object} row - Row object
|
|
||||||
* @returns {Object} Flat key-value object
|
|
||||||
*/
|
|
||||||
export function rowToFlat(row) {
|
|
||||||
const flat = {};
|
|
||||||
for (const [key, val] of Object.entries(row)) {
|
|
||||||
if (key.startsWith('@')) continue;
|
|
||||||
if (val !== null && typeof val === 'object' && !(val instanceof Date) && !Array.isArray(val)) {
|
|
||||||
Object.assign(flat, flattenForExport(val, key));
|
|
||||||
} else {
|
|
||||||
flat[key] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flat;
|
|
||||||
}
|
|
||||||
@ -4,7 +4,6 @@ import mongoose from 'mongoose';
|
|||||||
import { getModelByName, getAllModels } from './model.js';
|
import { getModelByName, getAllModels } from './model.js';
|
||||||
import { listObjectsOData } from '../../database/odata.js';
|
import { listObjectsOData } from '../../database/odata.js';
|
||||||
import { getFilter } from '../../utils.js';
|
import { getFilter } from '../../utils.js';
|
||||||
import { getModelFilterFields, parseOrderBy } from './export.js';
|
|
||||||
|
|
||||||
const logger = log4js.getLogger('OData');
|
const logger = log4js.getLogger('OData');
|
||||||
logger.level = config.server.logLevel;
|
logger.level = config.server.logLevel;
|
||||||
@ -254,6 +253,22 @@ export const metadataODataRouteHandler = (req, res) => {
|
|||||||
res.send(xml);
|
res.send(xml);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse OData $orderby into sort and order.
|
||||||
|
* Supports "field asc", "field desc", or just "field" (defaults asc).
|
||||||
|
*/
|
||||||
|
function parseOrderBy(orderby) {
|
||||||
|
if (!orderby || typeof orderby !== 'string') {
|
||||||
|
return { sort: 'createdAt', order: 'ascend' };
|
||||||
|
}
|
||||||
|
const trimmed = orderby.trim();
|
||||||
|
const parts = trimmed.split(/\s+/);
|
||||||
|
const sort = parts[0] || 'createdAt';
|
||||||
|
const dir = (parts[1] || 'asc').toLowerCase();
|
||||||
|
const order = dir === 'desc' ? 'descend' : 'ascend';
|
||||||
|
return { sort, order };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route handler for GET /odata/:objectType
|
* Route handler for GET /odata/:objectType
|
||||||
* Supports OData query options: $top, $skip, $orderby, $count, $select
|
* Supports OData query options: $top, $skip, $orderby, $count, $select
|
||||||
@ -314,3 +329,35 @@ export const listODataRouteHandler = async (req, res) => {
|
|||||||
res.send(result);
|
res.send(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return allowed filter fields for a given object type.
|
||||||
|
* Extends a base set with type-specific fields.
|
||||||
|
*/
|
||||||
|
function getModelFilterFields(objectType) {
|
||||||
|
const base = ['_id'];
|
||||||
|
const byType = {
|
||||||
|
note: ['parent._id', 'noteType', 'user'],
|
||||||
|
notification: ['user'],
|
||||||
|
userNotifier: ['user', 'object', 'objectType'],
|
||||||
|
printer: ['host'],
|
||||||
|
job: ['printer', 'gcodeFile'],
|
||||||
|
subJob: ['job'],
|
||||||
|
filamentStock: ['filament'],
|
||||||
|
partStock: ['part'],
|
||||||
|
productStock: ['product'],
|
||||||
|
productSku: ['product'],
|
||||||
|
purchaseOrder: ['vendor'],
|
||||||
|
orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'shipment._id'],
|
||||||
|
shipment: ['order._id', 'orderType', 'courierService._id'],
|
||||||
|
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||||
|
stockAudit: ['filamentStock._id', 'partStock._id'],
|
||||||
|
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
|
||||||
|
documentTemplate: ['parent._id', 'documentSize._id'],
|
||||||
|
salesOrder: ['client'],
|
||||||
|
invoice: ['to._id', 'from._id', 'order._id', 'orderType'],
|
||||||
|
auditLog: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
|
||||||
|
appPassword: ['name', 'user', 'active'],
|
||||||
|
};
|
||||||
|
const extra = byType[objectType] || [];
|
||||||
|
return [...base, ...extra];
|
||||||
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const listGCodeFilesRouteHandler = async (
|
|||||||
search,
|
search,
|
||||||
sort,
|
sort,
|
||||||
order,
|
order,
|
||||||
populate: ['filamentSku'],
|
populate: ['filament'],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -58,7 +58,7 @@ export const listGCodeFilesByPropertiesRouteHandler = async (
|
|||||||
model: gcodeFileModel,
|
model: gcodeFileModel,
|
||||||
properties,
|
properties,
|
||||||
filter,
|
filter,
|
||||||
populate: 'filamentSku',
|
populate: 'filament',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
@ -76,7 +76,7 @@ export const getGCodeFileRouteHandler = async (req, res) => {
|
|||||||
const result = await getObject({
|
const result = await getObject({
|
||||||
model: gcodeFileModel,
|
model: gcodeFileModel,
|
||||||
id,
|
id,
|
||||||
populate: ['filamentSku', 'parts.partSku'],
|
populate: ['filament', 'parts.part'],
|
||||||
});
|
});
|
||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
logger.warn(`GCodeFile not found with supplied id.`);
|
logger.warn(`GCodeFile not found with supplied id.`);
|
||||||
@ -113,7 +113,7 @@ export const editGCodeFileRouteHandler = async (req, res) => {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
file: req.body.file,
|
file: req.body.file,
|
||||||
filamentSku: req.body.filamentSku,
|
filament: req.body.filament,
|
||||||
parts: req.body.parts,
|
parts: req.body.parts,
|
||||||
};
|
};
|
||||||
// Create audit log before updating
|
// Create audit log before updating
|
||||||
@ -140,7 +140,7 @@ export const newGCodeFileRouteHandler = async (req, res) => {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
file: req.body.file,
|
file: req.body.file,
|
||||||
filamentSku: req.body.filamentSku,
|
filament: req.body.filament,
|
||||||
parts: req.body.parts,
|
parts: req.body.parts,
|
||||||
};
|
};
|
||||||
const result = await newObject({
|
const result = await newObject({
|
||||||
|
|||||||
@ -858,10 +858,7 @@ function buildDeepPopulateSpec(object, model, populated = new Set()) {
|
|||||||
const ref = directRef || arrayRef;
|
const ref = directRef || arrayRef;
|
||||||
if (!ref) return;
|
if (!ref) return;
|
||||||
|
|
||||||
const refName = typeof ref === 'function' ? ref.call(object) : ref;
|
const refModel = model.db.model(ref);
|
||||||
if (!refName) return;
|
|
||||||
|
|
||||||
const refModel = model.db.model(refName);
|
|
||||||
const childPopulate = buildDeepPopulateSpec(object, refModel, populated);
|
const childPopulate = buildDeepPopulateSpec(object, refModel, populated);
|
||||||
|
|
||||||
const id = object[pathname]?._id || object[pathname];
|
const id = object[pathname]?._id || object[pathname];
|
||||||
@ -869,9 +866,9 @@ function buildDeepPopulateSpec(object, model, populated = new Set()) {
|
|||||||
if (id == null || !id) return;
|
if (id == null || !id) return;
|
||||||
|
|
||||||
if (childPopulate.length > 0) {
|
if (childPopulate.length > 0) {
|
||||||
populateSpec.push({ path: pathname, populate: childPopulate, ref: refName, _id: id });
|
populateSpec.push({ path: pathname, populate: childPopulate, ref: ref, _id: id });
|
||||||
} else {
|
} else {
|
||||||
populateSpec.push({ path: pathname, ref: refName, _id: id });
|
populateSpec.push({ path: pathname, ref: ref, _id: id });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user