From 0fb0493d99252773cb7452c64de4851c07117333 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Wed, 3 Dec 2025 23:35:40 +0000 Subject: [PATCH] Added tax rates, tax records. --- src/index.js | 4 + src/routes/index.js | 4 + src/routes/management/taxrates.js | 46 +++++ src/routes/management/taxrecords.js | 46 +++++ src/schemas/inventory/purchaseorder.schema.js | 5 +- src/schemas/management/part.schema.js | 4 + src/schemas/management/product.schema.js | 2 + src/schemas/management/taxrates.schema.js | 25 +++ src/schemas/management/taxrecord.schema.js | 28 +++ src/schemas/models.js | 94 ++++++++++ src/services/inventory/purchaseorders.js | 2 +- src/services/management/parts.js | 12 +- src/services/management/taxrates.js | 168 ++++++++++++++++++ src/services/management/taxrecords.js | 164 +++++++++++++++++ src/services/misc/model.js | 99 +---------- src/utils.js | 25 ++- 16 files changed, 627 insertions(+), 101 deletions(-) create mode 100644 src/routes/management/taxrates.js create mode 100644 src/routes/management/taxrecords.js create mode 100644 src/schemas/management/taxrates.schema.js create mode 100644 src/schemas/management/taxrecord.schema.js create mode 100644 src/schemas/models.js create mode 100644 src/services/management/taxrates.js create mode 100644 src/services/management/taxrecords.js diff --git a/src/index.js b/src/index.js index aaf2ca9..c098fd3 100644 --- a/src/index.js +++ b/src/index.js @@ -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) { diff --git a/src/routes/index.js b/src/routes/index.js index d6a7423..d8acf75 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -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, }; diff --git a/src/routes/management/taxrates.js b/src/routes/management/taxrates.js new file mode 100644 index 0000000..38b6553 --- /dev/null +++ b/src/routes/management/taxrates.js @@ -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; diff --git a/src/routes/management/taxrecords.js b/src/routes/management/taxrecords.js new file mode 100644 index 0000000..a078ac1 --- /dev/null +++ b/src/routes/management/taxrecords.js @@ -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; diff --git a/src/schemas/inventory/purchaseorder.schema.js b/src/schemas/inventory/purchaseorder.schema.js index 7c96150..d88fe3e 100644 --- a/src/schemas/inventory/purchaseorder.schema.js +++ b/src/schemas/inventory/purchaseorder.schema.js @@ -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( diff --git a/src/schemas/management/part.schema.js b/src/schemas/management/part.schema.js index 0fca23a..4d84001 100644 --- a/src/schemas/management/part.schema.js +++ b/src/schemas/management/part.schema.js @@ -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 } ); diff --git a/src/schemas/management/product.schema.js b/src/schemas/management/product.schema.js index 004c451..64dee0d 100644 --- a/src/schemas/management/product.schema.js +++ b/src/schemas/management/product.schema.js @@ -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 } ); diff --git a/src/schemas/management/taxrates.schema.js b/src/schemas/management/taxrates.schema.js new file mode 100644 index 0000000..898c3ca --- /dev/null +++ b/src/schemas/management/taxrates.schema.js @@ -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); diff --git a/src/schemas/management/taxrecord.schema.js b/src/schemas/management/taxrecord.schema.js new file mode 100644 index 0000000..3680196 --- /dev/null +++ b/src/schemas/management/taxrecord.schema.js @@ -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); diff --git a/src/schemas/models.js b/src/schemas/models.js new file mode 100644 index 0000000..17de5ac --- /dev/null +++ b/src/schemas/models.js @@ -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' }, +}; diff --git a/src/services/inventory/purchaseorders.js b/src/services/inventory/purchaseorders.js index 2734c0b..cc28868 100644 --- a/src/services/inventory/purchaseorders.js +++ b/src/services/inventory/purchaseorders.js @@ -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.`); diff --git a/src/services/management/parts.js b/src/services/management/parts.js index ecc43af..6bc8ad5 100644 --- a/src/services/management/parts.js +++ b/src/services/management/parts.js @@ -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({ diff --git a/src/services/management/taxrates.js b/src/services/management/taxrates.js new file mode 100644 index 0000000..418733b --- /dev/null +++ b/src/services/management/taxrates.js @@ -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); +}; diff --git a/src/services/management/taxrecords.js b/src/services/management/taxrecords.js new file mode 100644 index 0000000..d122b75 --- /dev/null +++ b/src/services/management/taxrecords.js @@ -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); +}; diff --git a/src/services/misc/model.js b/src/services/misc/model.js index 482a5fb..b42bb5e 100644 --- a/src/services/misc/model.js +++ b/src/services/misc/model.js @@ -1,100 +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', 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' }, -}; +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); }; /** @@ -103,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; }; @@ -113,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 }; diff --git a/src/utils.js b/src/utils.js index 72b59c0..82c059f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -329,9 +329,28 @@ function getChangedValues(oldObj, newObj, old = false) { changes[key] = nestedChanges; } } - } else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) { - // If the old value is different from the new value, include it - changes[key] = old ? oldVal : 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; + } } }