Compare commits

..

2 Commits

Author SHA1 Message Date
0fb0493d99 Added tax rates, tax records. 2025-12-03 23:35:40 +00:00
7a947ed2fd Added ref spotlight lookup. 2025-12-03 13:11:53 +00:00
17 changed files with 645 additions and 70 deletions

View File

@ -33,6 +33,8 @@ import {
documentJobsRoutes,
courierRoutes,
courierServiceRoutes,
taxRateRoutes,
taxRecordRoutes,
} from './routes/index.js';
import path from 'path';
import * as fs from 'fs';
@ -137,6 +139,8 @@ app.use('/documentprinters', documentPrintersRoutes);
app.use('/documentjobs', documentJobsRoutes);
app.use('/couriers', courierRoutes);
app.use('/courierservices', courierServiceRoutes);
app.use('/taxrates', taxRateRoutes);
app.use('/taxrecords', taxRecordRoutes);
app.use('/notes', noteRoutes);
if (process.env.SCHEDULE_HOUR) {

View File

@ -25,6 +25,8 @@ import documentPrintersRoutes from './management/documentprinters.js';
import documentJobsRoutes from './management/documentjobs.js';
import courierRoutes from './management/courier.js';
import courierServiceRoutes from './management/courierservice.js';
import taxRateRoutes from './management/taxrates.js';
import taxRecordRoutes from './management/taxrecords.js';
import noteRoutes from './misc/notes.js';
export {
@ -56,4 +58,6 @@ export {
documentJobsRoutes,
courierRoutes,
courierServiceRoutes,
taxRateRoutes,
taxRecordRoutes,
};

View File

@ -0,0 +1,46 @@
import express from 'express';
import { isAuthenticated } from '../../keycloak.js';
import { getFilter, convertPropertiesString } from '../../utils.js';
const router = express.Router();
import {
listTaxRatesRouteHandler,
getTaxRateRouteHandler,
editTaxRateRouteHandler,
newTaxRateRouteHandler,
deleteTaxRateRouteHandler,
listTaxRatesByPropertiesRouteHandler,
} from '../../services/management/taxrates.js';
// list of tax rates
router.get('/', isAuthenticated, (req, res) => {
const { page, limit, property, search, sort, order } = req.query;
const allowedFilters = ['name', 'rate', 'rateType', 'active', 'country'];
const filter = getFilter(req.query, allowedFilters);
listTaxRatesRouteHandler(req, res, page, limit, property, filter, search, sort, order);
});
router.get('/properties', isAuthenticated, (req, res) => {
let properties = convertPropertiesString(req.query.properties);
const allowedFilters = ['rateType', 'country', 'active'];
const filter = getFilter(req.query, allowedFilters, false);
listTaxRatesByPropertiesRouteHandler(req, res, properties, filter);
});
router.post('/', isAuthenticated, (req, res) => {
newTaxRateRouteHandler(req, res);
});
router.get('/:id', isAuthenticated, (req, res) => {
getTaxRateRouteHandler(req, res);
});
router.put('/:id', isAuthenticated, async (req, res) => {
editTaxRateRouteHandler(req, res);
});
router.delete('/:id', isAuthenticated, async (req, res) => {
deleteTaxRateRouteHandler(req, res);
});
export default router;

View File

@ -0,0 +1,46 @@
import express from 'express';
import { isAuthenticated } from '../../keycloak.js';
import { getFilter, convertPropertiesString } from '../../utils.js';
const router = express.Router();
import {
listTaxRecordsRouteHandler,
getTaxRecordRouteHandler,
editTaxRecordRouteHandler,
newTaxRecordRouteHandler,
deleteTaxRecordRouteHandler,
listTaxRecordsByPropertiesRouteHandler,
} from '../../services/management/taxrecords.js';
// list of tax records
router.get('/', isAuthenticated, (req, res) => {
const { page, limit, property, search, sort, order } = req.query;
const allowedFilters = ['taxRate', 'transactionType', 'transaction', 'transactionDate'];
const filter = getFilter(req.query, allowedFilters);
listTaxRecordsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
});
router.get('/properties', isAuthenticated, (req, res) => {
let properties = convertPropertiesString(req.query.properties);
const allowedFilters = ['taxRate', 'transactionType', 'transaction'];
const filter = getFilter(req.query, allowedFilters, false);
listTaxRecordsByPropertiesRouteHandler(req, res, properties, filter);
});
router.post('/', isAuthenticated, (req, res) => {
newTaxRecordRouteHandler(req, res);
});
router.get('/:id', isAuthenticated, (req, res) => {
getTaxRecordRouteHandler(req, res);
});
router.put('/:id', isAuthenticated, async (req, res) => {
editTaxRecordRouteHandler(req, res);
});
router.delete('/:id', isAuthenticated, async (req, res) => {
deleteTaxRecordRouteHandler(req, res);
});
export default router;

View File

@ -6,7 +6,10 @@ const itemSchema = new Schema({
itemType: { type: String, required: true },
item: { type: Schema.Types.ObjectId, refPath: 'itemType', required: true },
quantity: { type: Number, required: true },
price: { type: Number, required: true },
itemCost: { type: Number, required: true },
totalCost: { type: Number, required: true },
totalCostWithTax: { type: Number, required: true },
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
});
const purchaseOrderSchema = new Schema(

View File

@ -14,6 +14,10 @@ const partSchema = new Schema(
margin: { type: Number, required: false },
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
file: { type: mongoose.SchemaTypes.ObjectId, ref: 'file', required: false },
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
priceWithTax: { type: Number, required: false },
costWithTax: { type: Number, required: false },
},
{ timestamps: true }
);

View File

@ -19,6 +19,8 @@ const productSchema = new Schema(
amount: { type: Number, required: false },
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
parts: [partSchema],
priceTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
costTaxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: false },
},
{ timestamps: true }
);

View File

@ -0,0 +1,25 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const taxRateSchema = new mongoose.Schema(
{
_reference: { type: String, default: () => generateId()() },
name: { required: true, type: String },
rate: { required: true, type: Number },
rateType: { required: true, type: String, enum: ['percentage', 'fixed'] },
active: { required: true, type: Boolean, default: true },
description: { required: false, type: String },
country: { required: false, type: String },
effectiveFrom: { required: false, type: Date },
effectiveTo: { required: false, type: Date },
},
{ timestamps: true }
);
taxRateSchema.virtual('id').get(function () {
return this._id;
});
taxRateSchema.set('toJSON', { virtuals: true });
export const taxRateModel = mongoose.model('taxRate', taxRateSchema);

View File

@ -0,0 +1,28 @@
import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const taxRecordSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
taxRate: { type: Schema.Types.ObjectId, ref: 'taxRate', required: true },
transactionType: {
type: String,
required: true,
enum: ['purchaseOrder', 'salesOrder', 'other'],
},
transaction: { type: Schema.Types.ObjectId, refPath: 'transactionType', required: true },
amount: { type: Number, required: true },
taxAmount: { type: Number, required: true },
transactionDate: { required: true, type: Date, default: Date.now },
},
{ timestamps: true }
);
taxRecordSchema.virtual('id').get(function () {
return this._id;
});
taxRecordSchema.set('toJSON', { virtuals: true });
export const taxRecordModel = mongoose.model('taxRecord', taxRecordSchema);

94
src/schemas/models.js Normal file
View File

@ -0,0 +1,94 @@
import { jobModel } from './production/job.schema.js';
import { subJobModel } from './production/subjob.schema.js';
import { printerModel } from './production/printer.schema.js';
import { filamentModel } from './management/filament.schema.js';
import { gcodeFileModel } from './production/gcodefile.schema.js';
import { partModel } from './management/part.schema.js';
import { productModel } from './management/product.schema.js';
import { vendorModel } from './management/vendor.schema.js';
import { filamentStockModel } from './inventory/filamentstock.schema.js';
import { purchaseOrderModel } from './inventory/purchaseorder.schema.js';
import { stockEventModel } from './inventory/stockevent.schema.js';
import { stockAuditModel } from './inventory/stockaudit.schema.js';
import { partStockModel } from './inventory/partstock.schema.js';
import { auditLogModel } from './management/auditlog.schema.js';
import { userModel } from './management/user.schema.js';
import { noteTypeModel } from './management/notetype.schema.js';
import { noteModel } from './misc/note.schema.js';
import { documentSizeModel } from './management/documentsize.schema.js';
import { documentTemplateModel } from './management/documenttemplate.schema.js';
import { hostModel } from './management/host.schema.js';
import { documentPrinterModel } from './management/documentprinter.schema.js';
import { documentJobModel } from './management/documentjob.schema.js';
import { fileModel } from './management/file.schema.js';
import { courierServiceModel } from './management/courierservice.schema.js';
import { courierModel } from './management/courier.schema.js';
import { taxRateModel } from './management/taxrates.schema.js';
import { taxRecordModel } from './management/taxrecord.schema.js';
// Map prefixes to models and id fields
export const models = {
PRN: { model: printerModel, idField: '_id', type: 'printer', referenceField: '_reference' },
FIL: { model: filamentModel, idField: '_id', type: 'filament', referenceField: '_reference' },
GCF: { model: gcodeFileModel, idField: '_id', type: 'gcodeFile', referenceField: '_reference' },
JOB: { model: jobModel, idField: '_id', type: 'job', referenceField: '_reference' },
PRT: { model: partModel, idField: '_id', type: 'part', referenceField: '_reference' },
PRD: { model: productModel, idField: '_id', type: 'product', referenceField: '_reference' },
VEN: { model: vendorModel, idField: '_id', type: 'vendor', referenceField: '_reference' },
SJB: { model: subJobModel, idField: '_id', type: 'subJob', referenceField: '_reference' },
FLS: {
model: filamentStockModel,
idField: '_id',
type: 'filamentStock',
referenceField: '_reference',
},
SEV: { model: stockEventModel, idField: '_id', type: 'stockEvent', referenceField: '_reference' },
SAU: { model: stockAuditModel, idField: '_id', type: 'stockAudit', referenceField: '_reference' },
PTS: { model: partStockModel, idField: '_id', type: 'partStock', referenceField: '_reference' },
PDS: { model: null, idField: '_id', type: 'productStock', referenceField: '_reference' }, // No productStockModel found
ADL: { model: auditLogModel, idField: '_id', type: 'auditLog', referenceField: '_reference' },
USR: { model: userModel, idField: '_id', type: 'user', referenceField: '_reference' },
NTY: { model: noteTypeModel, idField: '_id', type: 'noteType', referenceField: '_reference' },
NTE: { model: noteModel, idField: '_id', type: 'note', referenceField: '_reference' },
DSZ: {
model: documentSizeModel,
idField: '_id',
type: 'documentSize',
referenceField: '_reference',
},
DTP: {
model: documentTemplateModel,
idField: '_id',
type: 'documentTemplate',
referenceField: '_reference',
},
DPR: {
model: documentPrinterModel,
idField: '_id',
type: 'documentPrinter',
referenceField: '_reference',
},
DJB: {
model: documentJobModel,
idField: '_id',
type: 'documentJob',
referenceField: '_reference',
},
HST: { model: hostModel, idField: '_id', type: 'host', referenceField: '_reference' },
FLE: { model: fileModel, idField: '_id', type: 'file', referenceField: '_reference' },
POR: {
model: purchaseOrderModel,
idField: '_id',
type: 'purchaseOrder',
referenceField: '_reference',
},
COS: {
model: courierServiceModel,
idField: '_id',
type: 'courierService',
referenceField: '_reference',
},
COR: { model: courierModel, idField: '_id', type: 'courier', referenceField: '_reference' },
TXR: { model: taxRateModel, idField: '_id', type: 'taxRate', referenceField: '_reference' },
TXD: { model: taxRecordModel, idField: '_id', type: 'taxRecord', referenceField: '_reference' },
};

View File

@ -78,7 +78,7 @@ export const getPurchaseOrderRouteHandler = async (req, res) => {
const result = await getObject({
model: purchaseOrderModel,
id,
populate: ['vendor', 'items.item'],
populate: ['vendor', 'items.item', 'items.taxRate'],
});
if (result?.error) {
logger.warn(`Purchase Order not found with supplied id.`);

View File

@ -53,7 +53,7 @@ export const listPartsByPropertiesRouteHandler = async (req, res, properties = '
model: partModel,
properties,
filter,
populate: ['vendor'],
populate: ['vendor', 'priceTaxRate', 'costTaxRate'],
});
if (result?.error) {
@ -71,7 +71,7 @@ export const getPartRouteHandler = async (req, res) => {
const result = await getObject({
model: partModel,
id,
populate: ['vendor'],
populate: ['vendor', 'priceTaxRate', 'costTaxRate'],
});
if (result?.error) {
logger.warn(`Part not found with supplied id.`);
@ -96,6 +96,10 @@ export const editPartRouteHandler = async (req, res) => {
price: req.body?.price,
cost: req.body?.cost,
priceMode: req.body?.priceMode,
priceTaxRate: req.body?.priceTaxRate,
costTaxRate: req.body?.costTaxRate,
priceWithTax: req.body?.priceWithTax,
costWithTax: req.body?.costWithTax,
};
// Create audit log before updating
const result = await editObject({
@ -126,6 +130,10 @@ export const newPartRouteHandler = async (req, res) => {
price: req.body?.price,
cost: req.body?.cost,
priceMode: req.body?.priceMode,
priceTaxRate: req.body?.priceTaxRate,
costTaxRate: req.body?.costTaxRate,
priceWithTax: req.body?.priceWithTax,
costWithTax: req.body?.costWithTax,
};
const result = await newObject({

View File

@ -0,0 +1,168 @@
import dotenv from 'dotenv';
import { taxRateModel } from '../../schemas/management/taxrates.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('TaxRates');
logger.level = process.env.LOG_LEVEL;
export const listTaxRatesRouteHandler = async (
req,
res,
page = 1,
limit = 25,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend'
) => {
const result = await listObjects({
model: taxRateModel,
page,
limit,
property,
filter,
search,
sort,
order,
});
if (result?.error) {
logger.error('Error listing tax rates.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of tax rates (Page ${page}, Limit ${limit}). Count: ${result.length}.`);
res.send(result);
};
export const listTaxRatesByPropertiesRouteHandler = async (
req,
res,
properties = '',
filter = {}
) => {
const result = await listObjectsByProperties({
model: taxRateModel,
properties,
filter,
});
if (result?.error) {
logger.error('Error listing tax rates.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of tax rates. Count: ${result.length}`);
res.send(result);
};
export const getTaxRateRouteHandler = async (req, res) => {
const id = req.params.id;
const result = await getObject({
model: taxRateModel,
id,
});
if (result?.error) {
logger.warn(`Tax rate not found with supplied id.`);
return res.status(result.code).send(result);
}
logger.debug(`Retreived tax rate with ID: ${id}`);
res.send(result);
};
export const editTaxRateRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Tax rate with ID: ${id}`);
const updateData = {
updatedAt: new Date(),
name: req.body.name,
rate: req.body.rate,
rateType: req.body.rateType,
active: req.body.active,
description: req.body.description,
country: req.body.country,
effectiveFrom: req.body.effectiveFrom,
effectiveTo: req.body.effectiveTo,
};
// Create audit log before updating
const result = await editObject({
model: taxRateModel,
id,
updateData,
user: req.user,
});
if (result.error) {
logger.error('Error editing tax rate:', result.error);
res.status(result).send(result);
return;
}
logger.debug(`Edited tax rate with ID: ${id}`);
res.send(result);
};
export const newTaxRateRouteHandler = async (req, res) => {
const newData = {
updatedAt: new Date(),
name: req.body.name,
rate: req.body.rate,
rateType: req.body.rateType,
active: req.body.active,
description: req.body.description,
country: req.body.country,
effectiveFrom: req.body.effectiveFrom,
effectiveTo: req.body.effectiveTo,
};
const result = await newObject({
model: taxRateModel,
newData,
user: req.user,
});
if (result.error) {
logger.error('No tax rate created:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`New tax rate with ID: ${result._id}`);
res.send(result);
};
export const deleteTaxRateRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Tax rate with ID: ${id}`);
const result = await deleteObject({
model: taxRateModel,
id,
user: req.user,
});
if (result.error) {
logger.error('No tax rate deleted:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`Deleted tax rate with ID: ${result._id}`);
res.send(result);
};

View File

@ -0,0 +1,164 @@
import dotenv from 'dotenv';
import { taxRecordModel } from '../../schemas/management/taxrecord.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('TaxRecords');
logger.level = process.env.LOG_LEVEL;
export const listTaxRecordsRouteHandler = async (
req,
res,
page = 1,
limit = 25,
property = '',
filter = {},
search = '',
sort = '',
order = 'ascend'
) => {
const result = await listObjects({
model: taxRecordModel,
page,
limit,
property,
filter,
search,
sort,
order,
});
if (result?.error) {
logger.error('Error listing tax records.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of tax records (Page ${page}, Limit ${limit}). Count: ${result.length}.`);
res.send(result);
};
export const listTaxRecordsByPropertiesRouteHandler = async (
req,
res,
properties = '',
filter = {}
) => {
const result = await listObjectsByProperties({
model: taxRecordModel,
properties,
filter,
});
if (result?.error) {
logger.error('Error listing tax records.');
res.status(result.code).send(result);
return;
}
logger.debug(`List of tax records. Count: ${result.length}`);
res.send(result);
};
export const getTaxRecordRouteHandler = async (req, res) => {
const id = req.params.id;
const result = await getObject({
model: taxRecordModel,
id,
});
if (result?.error) {
logger.warn(`Tax record not found with supplied id.`);
return res.status(result.code).send(result);
}
logger.debug(`Retreived tax record with ID: ${id}`);
res.send(result);
};
export const editTaxRecordRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Tax record with ID: ${id}`);
const updateData = {
updatedAt: new Date(),
taxRate: req.body.taxRate,
transactionType: req.body.transactionType,
transaction: req.body.transaction,
amount: req.body.amount,
taxAmount: req.body.taxAmount,
transactionDate: req.body.transactionDate,
};
// Create audit log before updating
const result = await editObject({
model: taxRecordModel,
id,
updateData,
user: req.user,
});
if (result.error) {
logger.error('Error editing tax record:', result.error);
res.status(result).send(result);
return;
}
logger.debug(`Edited tax record with ID: ${id}`);
res.send(result);
};
export const newTaxRecordRouteHandler = async (req, res) => {
const newData = {
updatedAt: new Date(),
taxRate: req.body.taxRate,
transactionType: req.body.transactionType,
transaction: req.body.transaction,
amount: req.body.amount,
taxAmount: req.body.taxAmount,
transactionDate: req.body.transactionDate,
};
const result = await newObject({
model: taxRecordModel,
newData,
user: req.user,
});
if (result.error) {
logger.error('No tax record created:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`New tax record with ID: ${result._id}`);
res.send(result);
};
export const deleteTaxRecordRouteHandler = async (req, res) => {
// Get ID from params
const id = new mongoose.Types.ObjectId(req.params.id);
logger.trace(`Tax record with ID: ${id}`);
const result = await deleteObject({
model: taxRecordModel,
id,
user: req.user,
});
if (result.error) {
logger.error('No tax record deleted:', result.error);
return res.status(result.code).send(result);
}
logger.debug(`Deleted tax record with ID: ${result._id}`);
res.send(result);
};

View File

@ -1,65 +1,11 @@
import { jobModel } from '../../schemas/production/job.schema.js';
import { subJobModel } from '../../schemas/production/subjob.schema.js';
import { printerModel } from '../../schemas/production/printer.schema.js';
import { filamentModel } from '../../schemas/management/filament.schema.js';
import { gcodeFileModel } from '../../schemas/production/gcodefile.schema.js';
import { partModel } from '../../schemas/management/part.schema.js';
import { productModel } from '../../schemas/management/product.schema.js';
import { vendorModel } from '../../schemas/management/vendor.schema.js';
import { filamentStockModel } from '../../schemas/inventory/filamentstock.schema.js';
import { purchaseOrderModel } from '../../schemas/inventory/purchaseorder.schema.js';
import { stockEventModel } from '../../schemas/inventory/stockevent.schema.js';
import { stockAuditModel } from '../../schemas/inventory/stockaudit.schema.js';
import { partStockModel } from '../../schemas/inventory/partstock.schema.js';
import { auditLogModel } from '../../schemas/management/auditlog.schema.js';
import { userModel } from '../../schemas/management/user.schema.js';
import { noteTypeModel } from '../../schemas/management/notetype.schema.js';
import { noteModel } from '../../schemas/misc/note.schema.js';
import { documentSizeModel } from '../../schemas/management/documentsize.schema.js';
import { documentTemplateModel } from '../../schemas/management/documenttemplate.schema.js';
import { hostModel } from '../../schemas/management/host.schema.js';
import { documentPrinterModel } from '../../schemas/management/documentprinter.schema.js';
import { documentJobModel } from '../../schemas/management/documentjob.schema.js';
import { fileModel } from '../../schemas/management/file.schema.js';
import { courierServiceModel } from '../../schemas/management/courierservice.schema.js';
import { courierModel } from '../../schemas/management/courier.schema.js';
// Map prefixes to models and id fields
const PREFIX_MODEL_MAP = {
PRN: { model: printerModel, idField: '_id', type: 'printer' },
FIL: { model: filamentModel, idField: '_id', type: 'filament' },
GCF: { model: gcodeFileModel, idField: '_id', type: 'gcodeFile' },
JOB: { model: jobModel, idField: '_id', type: 'job' },
PRT: { model: partModel, idField: '_id', type: 'part' },
PRD: { model: productModel, idField: '_id', type: 'product' },
VEN: { model: vendorModel, idField: '_id', type: 'vendor' },
SJB: { model: subJobModel, idField: '_id', type: 'subJob' },
FLS: { model: filamentStockModel, idField: '_id', type: 'filamentStock' },
SEV: { model: stockEventModel, idField: '_id', type: 'stockEvent' },
SAU: { model: stockAuditModel, idField: '_id', type: 'stockAudit' },
PTS: { model: partStockModel, idField: '_id', type: 'partStock' },
PDS: { model: null, idField: '_id', type: 'productStock' }, // No productStockModel found
ADL: { model: auditLogModel, idField: '_id', type: 'auditLog' },
USR: { model: userModel, idField: '_id', type: 'user' },
NTY: { model: noteTypeModel, idField: '_id', type: 'noteType' },
NTE: { model: noteModel, idField: '_id', type: 'note' },
DSZ: { model: documentSizeModel, idField: '_id', type: 'documentSize' },
DTP: { model: documentTemplateModel, idField: '_id', type: 'documentTemplate' },
DPR: { model: documentPrinterModel, idField: '_id', type: 'documentPrinter' },
DJB: { model: documentJobModel, idField: '_id', type: 'documentJob' },
HST: { model: hostModel, idField: '_id', type: 'host' },
FLE: { model: fileModel, idField: '_id', type: 'file' },
POR: { model: purchaseOrderModel, idField: '_id', type: 'purchaseOrder' },
COS: { model: courierServiceModel, idField: '_id', type: 'courierService' },
COR: { model: courierModel, idField: '_id', type: 'courier' },
};
import { models } from '../../schemas/models.js';
/**
* Get all models from the PREFIX_MODEL_MAP
* @returns {Array} Array of model entries with model, idField, and type properties
*/
export const getAllModels = () => {
return Object.values(PREFIX_MODEL_MAP);
return Object.values(models);
};
/**
@ -68,7 +14,7 @@ export const getAllModels = () => {
* @returns {Object|null} The model entry or null if not found
*/
export const getModelByName = (name) => {
const entry = Object.values(PREFIX_MODEL_MAP).find((entry) => entry.type === name);
const entry = Object.values(models).find((entry) => entry.type === name);
return entry || null;
};
@ -78,8 +24,8 @@ export const getModelByName = (name) => {
* @returns {Object|null} The model entry or null if not found
*/
export const getModelByPrefix = (prefix) => {
return PREFIX_MODEL_MAP[prefix] || null;
return models[prefix] || null;
};
// Export the PREFIX_MODEL_MAP for backward compatibility
export { PREFIX_MODEL_MAP };
export { models };

View File

@ -60,7 +60,7 @@ export const getSpotlightRouteHandler = async (req, res) => {
res.status(200).send([]);
return;
}
const prefix = query.substring(0, 3);
const prefix = query.substring(0, 3).toUpperCase();
const delimiter = query.substring(3, 4);
const suffix = query.substring(4);
@ -72,18 +72,32 @@ export const getSpotlightRouteHandler = async (req, res) => {
res.status(400).send({ error: 'Invalid or unsupported prefix' });
return;
}
const { model, idField, type } = prefixEntry;
const { model, idField, type, referenceField } = prefixEntry;
const suffixLength = suffix.length;
// Validate ObjectId if the idField is '_id'
if (idField === '_id' && !mongoose.Types.ObjectId.isValid(suffix)) {
if (
idField === '_id' &&
referenceField === '_reference' &&
!mongoose.Types.ObjectId.isValid(suffix) &&
suffixLength != 12
) {
res.status(200).send([]);
return;
}
// Find the object by the correct field
const queryObj = {};
if (suffixLength == 12) {
queryObj[referenceField] = suffix.toUpperCase();
} else {
queryObj[idField] = suffix.toLowerCase();
}
let doc = await model.findOne(queryObj).lean();
if (!doc) {
res.status(200).send([]);
return;

View File

@ -329,11 +329,30 @@ function getChangedValues(oldObj, newObj, old = false) {
changes[key] = nestedChanges;
}
}
} else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
} else {
// Check if both values are numbers (or can be converted to numbers)
const oldIsNumber =
typeof oldVal === 'number' ||
(oldVal !== null && oldVal !== undefined && !isNaN(Number(oldVal)) && oldVal !== '');
const newIsNumber =
typeof newVal === 'number' ||
(newVal !== null && newVal !== undefined && !isNaN(Number(newVal)) && newVal !== '');
let valuesDiffer;
if (oldIsNumber && newIsNumber) {
// Compare numbers directly (this normalizes 7.50 to 7.5)
valuesDiffer = Number(oldVal) !== Number(newVal);
} else {
// Use JSON.stringify for non-number comparisons
valuesDiffer = JSON.stringify(oldVal) !== JSON.stringify(newVal);
}
if (valuesDiffer) {
// If the old value is different from the new value, include it
changes[key] = old ? oldVal : newVal;
}
}
}
return changes;
}