Added order items, shipments etc

This commit is contained in:
Tom Butcher 2025-12-07 02:36:55 +00:00
parent 9f2341c613
commit d88da6939d
9 changed files with 603 additions and 17 deletions

View File

@ -0,0 +1,50 @@
import express from 'express';
import { isAuthenticated } from '../../keycloak.js';
import { getFilter, convertPropertiesString } from '../../utils.js';
const router = express.Router();
import {
listOrderItemsRouteHandler,
getOrderItemRouteHandler,
editOrderItemRouteHandler,
newOrderItemRouteHandler,
deleteOrderItemRouteHandler,
listOrderItemsByPropertiesRouteHandler,
} from '../../services/inventory/orderitems.js';
// list of order items
router.get('/', isAuthenticated, (req, res) => {
const { page, limit, property, search, sort, order } = req.query;
const allowedFilters = ['itemType', 'item', 'item._id', 'order', 'order._id', 'orderType'];
const filter = getFilter(req.query, allowedFilters);
listOrderItemsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
});
router.get('/properties', isAuthenticated, (req, res) => {
let properties = convertPropertiesString(req.query.properties);
const allowedFilters = ['itemType', 'item', 'item._id', 'order', 'order._id', 'orderType'];
const filter = getFilter(req.query, allowedFilters, false);
var masterFilter = {};
if (req.query.masterFilter) {
masterFilter = JSON.parse(req.query.masterFilter);
}
listOrderItemsByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
});
router.post('/', isAuthenticated, (req, res) => {
newOrderItemRouteHandler(req, res);
});
router.get('/:id', isAuthenticated, (req, res) => {
getOrderItemRouteHandler(req, res);
});
router.put('/:id', isAuthenticated, async (req, res) => {
editOrderItemRouteHandler(req, res);
});
router.delete('/:id', isAuthenticated, async (req, res) => {
deleteOrderItemRouteHandler(req, res);
});
export default router;

View File

@ -0,0 +1,64 @@
import express from 'express';
import { isAuthenticated } from '../../keycloak.js';
import { getFilter, convertPropertiesString } from '../../utils.js';
const router = express.Router();
import {
listShipmentsRouteHandler,
getShipmentRouteHandler,
editShipmentRouteHandler,
newShipmentRouteHandler,
deleteShipmentRouteHandler,
listShipmentsByPropertiesRouteHandler,
} from '../../services/inventory/shipments.js';
// list of shipments
router.get('/', isAuthenticated, (req, res) => {
const { page, limit, property, search, sort, order } = req.query;
const allowedFilters = [
'vendor',
'purchaseOrder',
'state',
'courierService',
'vendor._id',
'purchaseOrder._id',
];
const filter = getFilter(req.query, allowedFilters);
listShipmentsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
});
router.get('/properties', isAuthenticated, (req, res) => {
let properties = convertPropertiesString(req.query.properties);
const allowedFilters = [
'vendor',
'purchaseOrder',
'state.type',
'courierService',
'vendor._id',
'purchaseOrder._id',
];
const filter = getFilter(req.query, allowedFilters, false);
var masterFilter = {};
if (req.query.masterFilter) {
masterFilter = JSON.parse(req.query.masterFilter);
}
listShipmentsByPropertiesRouteHandler(req, res, properties, filter, masterFilter);
});
router.post('/', isAuthenticated, (req, res) => {
newShipmentRouteHandler(req, res);
});
router.get('/:id', isAuthenticated, (req, res) => {
getShipmentRouteHandler(req, res);
});
router.put('/:id', isAuthenticated, async (req, res) => {
editShipmentRouteHandler(req, res);
});
router.delete('/:id', isAuthenticated, async (req, res) => {
deleteShipmentRouteHandler(req, res);
});
export default router;

View File

@ -0,0 +1,32 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const orderItemSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
orderType: { type: String, required: true },
order: { type: Schema.Types.ObjectId, refPath: 'orderType', required: true },
itemType: { type: String, required: true },
item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: true },
syncAmount: { type: String, required: true, default: null },
itemAmount: { type: Number, required: true },
quantity: { type: Number, required: true },
totalAmount: { type: Number, required: true },
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
totalAmountWithTax: { type: Number, required: true },
timestamp: { type: Date, default: Date.now },
},
{ timestamps: true }
);
// Add virtual id getter
orderItemSchema.virtual('id').get(function () {
return this._id;
});
// Configure JSON serialization to include virtuals
orderItemSchema.set('toJSON', { virtuals: true });
// Create and export the model
export const orderItemModel = mongoose.model('orderItem', orderItemSchema);

View File

@ -11,8 +11,9 @@ const partStockSchema = new Schema(
progress: { type: Number, required: false }, progress: { type: Number, required: false },
}, },
part: { type: mongoose.Schema.Types.ObjectId, ref: 'part', required: true }, part: { type: mongoose.Schema.Types.ObjectId, ref: 'part', required: true },
startingQuantity: { type: Number, required: true },
currentQuantity: { type: Number, required: true }, currentQuantity: { type: Number, required: true },
sourceType: { type: String, required: true },
source: { type: Schema.Types.ObjectId, refPath: 'sourceType', required: true },
}, },
{ timestamps: true } { timestamps: true }
); );

View File

@ -4,7 +4,7 @@ const { Schema } = mongoose;
const itemSchema = new Schema({ const itemSchema = new Schema({
itemType: { type: String, required: true }, itemType: { type: String, required: true },
item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: true }, item: { type: Schema.Types.ObjectId, refPath: 'items.itemType', required: true },
quantity: { type: Number, required: true }, quantity: { type: Number, required: true },
itemCost: { type: Number, required: true }, itemCost: { type: Number, required: true },
totalCost: { type: Number, required: true }, totalCost: { type: Number, required: true },

View File

@ -0,0 +1,50 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const shipmentItemSchema = new Schema({
itemType: { type: String, required: true },
item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: true },
quantity: { type: Number, required: true },
itemCost: { type: Number, required: true },
totalCost: { type: Number, required: true },
totalCostWithTax: { type: Number, required: true },
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
});
const shipmentSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
purchaseOrder: { type: Schema.Types.ObjectId, ref: 'purchaseOrder', required: true },
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
courierService: { type: Schema.Types.ObjectId, ref: 'courierService', required: false },
trackingNumber: { type: String, required: false },
items: [shipmentItemSchema],
cost: { net: { type: Number, required: true }, gross: { type: Number, required: true } },
shippedDate: { type: Date, required: false },
expectedDeliveryDate: { type: Date, required: false },
actualDeliveryDate: { type: Date, required: false },
state: {
type: {
type: String,
required: true,
default: 'pending',
enum: ['pending', 'shipped', 'in_transit', 'delivered', 'cancelled'],
},
},
notes: { type: String },
timestamp: { type: Date, default: Date.now },
},
{ timestamps: true }
);
// Add virtual id getter
shipmentSchema.virtual('id').get(function () {
return this._id;
});
// Configure JSON serialization to include virtuals
shipmentSchema.set('toJSON', { virtuals: true });
// Create and export the model
export const shipmentModel = mongoose.model('shipment', shipmentSchema);

View File

@ -41,22 +41,25 @@ const deviceInfoSchema = new mongoose.Schema(
{ _id: false } { _id: false }
); );
const hostSchema = new mongoose.Schema({ const hostSchema = new mongoose.Schema(
_reference: { type: String, default: () => generateId()() }, {
name: { required: true, type: String }, _reference: { type: String, default: () => generateId()() },
tags: [{ required: false, type: String }], name: { required: true, type: String },
online: { required: true, type: Boolean, default: false }, tags: [{ required: false, type: String }],
state: { online: { required: true, type: Boolean, default: false },
type: { type: String, required: true, default: 'offline' }, state: {
message: { type: String, required: false }, type: { type: String, required: true, default: 'offline' },
percent: { type: Number, required: false }, message: { type: String, required: false },
percent: { type: Number, required: false },
},
active: { required: true, type: Boolean, default: true },
connectedAt: { required: false, type: Date },
authCode: { type: { required: false, type: String } },
deviceInfo: { deviceInfoSchema },
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
}, },
active: { required: true, type: Boolean, default: true }, { timestamps: true }
connectedAt: { required: false, type: Date }, );
authCode: { type: { required: false, type: String } },
deviceInfo: { deviceInfoSchema },
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
});
hostSchema.virtual('id').get(function () { hostSchema.virtual('id').get(function () {
return this._id; return this._id;

View File

@ -0,0 +1,209 @@
import dotenv from 'dotenv';
import { orderItemModel } from '../../schemas/inventory/orderitem.schema.js';
import log4js from 'log4js';
import mongoose from 'mongoose';
import {
deleteObject,
listObjects,
getObject,
editObject,
newObject,
listObjectsByProperties,
} from '../../database/database.js';
dotenv.config();
const logger = log4js.getLogger('Order Items');
logger.level = process.env.LOG_LEVEL;
export const listOrderItemsRouteHandler = async (
req,
res,
page = 1,
limit = 25,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend'
) => {
const result = await listObjects({
model: orderItemModel,
page,
limit,
property,
filter,
search,
sort,
order,
populate: [
{
path: 'order',
},
{
path: 'taxRate',
strictPopulate: false,
},
{
path: 'item',
populate: { path: 'costTaxRate' },
},
{
path: 'item',
populate: { path: 'priceTaxRate' },
},
],
});
if (result?.error) {
logger.error('Error listing order items.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of order items (Page ${page}, Limit ${limit}). Count: ${result.length}`);
res.send(result);
};
export const listOrderItemsByPropertiesRouteHandler = async (
req,
res,
properties = '',
filter = {},
masterFilter = {}
) => {
const result = await listObjectsByProperties({
model: orderItemModel,
properties,
filter,
populate: [],
masterFilter,
});
if (result?.error) {
logger.error('Error listing order items.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of order items. Count: ${result.length}`);
res.send(result);
};
export const getOrderItemRouteHandler = async (req, res) => {
const id = req.params.id;
const result = await getObject({
model: orderItemModel,
id,
populate: [
{
path: 'order',
},
{
path: 'taxRate',
strictPopulate: false,
},
{
path: 'item',
populate: { path: 'costTaxRate' },
},
{
path: 'item',
populate: { path: 'priceTaxRate' },
},
],
});
if (result?.error) {
logger.warn(`Order Item not found with supplied id.`);
return res.status(result.code).send(result);
}
logger.debug(`Retreived order item with ID: ${id}`);
res.send(result);
};
export const editOrderItemRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Order Item with ID: ${id}`);
const updateData = {
updatedAt: new Date(),
purchaseOrder: req.body.purchaseOrder,
itemType: req.body.itemType,
item: req.body.item,
syncAmount: req.body.syncAmount,
itemAmount: req.body.itemAmount,
quantity: req.body.quantity,
totalAmount: req.body.totalAmount,
taxRate: req.body.taxRate,
totalAmountWithTax: req.body.totalAmountWithTax,
};
// Create audit log before updating
const result = await editObject({
model: orderItemModel,
id,
updateData,
user: req.user,
});
if (result.error) {
logger.error('Error editing order item:', result.error);
res.status(result).send(result);
return;
}
logger.debug(`Edited order item with ID: ${id}`);
res.send(result);
};
export const newOrderItemRouteHandler = async (req, res) => {
const newData = {
updatedAt: new Date(),
purchaseOrder: req.body.purchaseOrder,
itemType: req.body.itemType,
item: req.body.item,
orderType: req.body.orderType,
order: req.body.order,
syncAmount: req.body.syncAmount,
itemAmount: req.body.itemAmount,
quantity: req.body.quantity,
totalAmount: req.body.totalAmount,
taxRate: req.body.taxRate,
totalAmountWithTax: req.body.totalAmountWithTax,
};
const result = await newObject({
model: orderItemModel,
newData,
user: req.user,
});
if (result.error) {
logger.error('No order item created:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`New order item with ID: ${result._id}`);
res.send(result);
};
export const deleteOrderItemRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Order Item with ID: ${id}`);
const result = await deleteObject({
model: orderItemModel,
id,
user: req.user,
});
if (result.error) {
logger.error('No order item deleted:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`Deleted order item with ID: ${result._id}`);
res.send(result);
};

View File

@ -0,0 +1,177 @@
import dotenv from 'dotenv';
import { shipmentModel } from '../../schemas/inventory/shipment.schema.js';
import log4js from 'log4js';
import mongoose from 'mongoose';
import {
deleteObject,
listObjects,
getObject,
editObject,
newObject,
listObjectsByProperties,
} from '../../database/database.js';
dotenv.config();
const logger = log4js.getLogger('Shipments');
logger.level = process.env.LOG_LEVEL;
export const listShipmentsRouteHandler = async (
req,
res,
page = 1,
limit = 25,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend'
) => {
const result = await listObjects({
model: shipmentModel,
page,
limit,
property,
filter,
search,
sort,
order,
populate: ['purchaseOrder', 'vendor', 'courierService'],
});
if (result?.error) {
logger.error('Error listing shipments.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of shipments (Page ${page}, Limit ${limit}). Count: ${result.length}`);
res.send(result);
};
export const listShipmentsByPropertiesRouteHandler = async (
req,
res,
properties = '',
filter = {},
masterFilter = {}
) => {
const result = await listObjectsByProperties({
model: shipmentModel,
properties,
filter,
populate: ['purchaseOrder', 'vendor', 'courierService'],
masterFilter,
});
if (result?.error) {
logger.error('Error listing shipments.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of shipments. Count: ${result.length}`);
res.send(result);
};
export const getShipmentRouteHandler = async (req, res) => {
const id = req.params.id;
const result = await getObject({
model: shipmentModel,
id,
populate: ['purchaseOrder', 'vendor', 'courierService', 'items.item', 'items.taxRate'],
});
if (result?.error) {
logger.warn(`Shipment not found with supplied id.`);
return res.status(result.code).send(result);
}
logger.debug(`Retreived shipment with ID: ${id}`);
res.send(result);
};
export const editShipmentRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Shipment with ID: ${id}`);
const updateData = {
updatedAt: new Date(),
purchaseOrder: req.body.purchaseOrder,
vendor: req.body.vendor,
courierService: req.body.courierService,
trackingNumber: req.body.trackingNumber,
shippedDate: req.body.shippedDate,
expectedDeliveryDate: req.body.expectedDeliveryDate,
actualDeliveryDate: req.body.actualDeliveryDate,
state: req.body.state,
notes: req.body.notes,
};
// Create audit log before updating
const result = await editObject({
model: shipmentModel,
id,
updateData,
user: req.user,
});
if (result.error) {
logger.error('Error editing shipment:', result.error);
res.status(result).send(result);
return;
}
logger.debug(`Edited shipment with ID: ${id}`);
res.send(result);
};
export const newShipmentRouteHandler = async (req, res) => {
const newData = {
updatedAt: new Date(),
purchaseOrder: req.body.purchaseOrder,
vendor: req.body.vendor,
courierService: req.body.courierService,
trackingNumber: req.body.trackingNumber,
items: req.body.items,
cost: req.body.cost,
shippedDate: req.body.shippedDate,
expectedDeliveryDate: req.body.expectedDeliveryDate,
actualDeliveryDate: req.body.actualDeliveryDate,
state: req.body.state,
notes: req.body.notes,
};
const result = await newObject({
model: shipmentModel,
newData,
user: req.user,
});
if (result.error) {
logger.error('No shipment created:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`New shipment with ID: ${result._id}`);
res.send(result);
};
export const deleteShipmentRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Shipment with ID: ${id}`);
const result = await deleteObject({
model: shipmentModel,
id,
user: req.user,
});
if (result.error) {
logger.error('No shipment deleted:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`Deleted shipment with ID: ${result._id}`);
res.send(result);
};