import config from '../../config.js'; import { paymentModel } from '../../database/schemas/finance/payment.schema.js'; import log4js from 'log4js'; import mongoose from 'mongoose'; import { deleteObject, listObjects, getObject, editObject, editObjects, newObject, listObjectsByProperties, getModelStats, getModelHistory, checkStates, } from '../../database/database.js'; import { invoiceModel } from '../../database/schemas/finance/invoice.schema.js'; const logger = log4js.getLogger('Payments'); logger.level = config.server.logLevel; export const listPaymentsRouteHandler = async ( req, res, page = 1, limit = 25, property = '', filter = {}, search = '', sort = '', order = 'ascend' ) => { const populateFields = ['vendor', 'client', 'invoice']; const result = await listObjects({ model: paymentModel, page, limit, property, filter, search, sort, order, populate: populateFields, }); if (result?.error) { logger.error('Error listing payments.'); res.status(result.code).send(result); return; } logger.debug(`List of payments (Page ${page}, Limit ${limit}). Count: ${result.length}`); res.send(result); }; export const listPaymentsByPropertiesRouteHandler = async ( req, res, properties = '', filter = {}, masterFilter = {} ) => { const populateFields = ['vendor', 'client', 'invoice']; const result = await listObjectsByProperties({ model: paymentModel, properties, filter, populate: populateFields, masterFilter, }); if (result?.error) { logger.error('Error listing payments.'); res.status(result.code).send(result); return; } logger.debug(`List of payments. Count: ${result.length}`); res.send(result); }; export const getPaymentRouteHandler = async (req, res) => { const id = req.params.id; const populateFields = [ { path: 'vendor' }, { path: 'client' }, { path: 'invoice' }, ]; const result = await getObject({ model: paymentModel, id, populate: populateFields, }); if (result?.error) { logger.warn(`Payment not found with supplied id.`); return res.status(result.code).send(result); } logger.debug(`Retrieved payment with ID: ${id}`); res.send(result); }; export const editPaymentRouteHandler = async (req, res) => { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Payment with ID: ${id}`); const checkStatesResult = await checkStates({ model: paymentModel, id, states: ['draft'] }); if (checkStatesResult.error) { logger.error('Error checking payment states:', checkStatesResult.error); res.status(checkStatesResult.code).send(checkStatesResult); return; } if (checkStatesResult === false) { logger.error('Payment is not in draft state.'); res.status(400).send({ error: 'Payment is not in draft state.', code: 400 }); return; } const updateData = { updatedAt: new Date(), vendor: req.body.vendor, client: req.body.client, invoice: req.body.invoice, amount: req.body.amount, paymentDate: req.body.paymentDate, paymentMethod: req.body.paymentMethod, notes: req.body.notes, }; // Create audit log before updating const result = await editObject({ model: paymentModel, id, updateData, user: req.user, }); if (result.error) { logger.error('Error editing payment:', result.error); res.status(result).send(result); return; } logger.debug(`Edited payment with ID: ${id}`); res.send(result); }; export const editMultiplePaymentsRouteHandler = async (req, res) => { const updates = req.body.map((update) => ({ _id: update._id, vendor: update.vendor, client: update.client, invoice: update.invoice, })); if (!Array.isArray(updates)) { return res.status(400).send({ error: 'Body must be an array of updates.', code: 400 }); } const result = await editObjects({ model: paymentModel, updates, user: req.user, }); if (result.error) { logger.error('Error editing payments:', result.error); res.status(result.code || 500).send(result); return; } logger.debug(`Edited ${updates.length} payments`); res.send(result); }; export const newPaymentRouteHandler = async (req, res) => { // Get invoice to populate vendor/client const invoice = await getObject({ model: invoiceModel, id: req.body.invoice, populate: [{ path: 'vendor' }, { path: 'client' }], }); if (invoice.error) { logger.error('Error getting invoice:', invoice.error); return res.status(invoice.code).send(invoice); } const newData = { updatedAt: new Date(), vendor: invoice.vendor?._id || req.body.vendor, client: invoice.client?._id || req.body.client, invoice: req.body.invoice, amount: req.body.amount || 0, paymentDate: req.body.paymentDate || new Date(), paymentMethod: req.body.paymentMethod, notes: req.body.notes, }; const result = await newObject({ model: paymentModel, newData, user: req.user, }); if (result.error) { logger.error('No payment created:', result.error); return res.status(result.code).send(result); } logger.debug(`New payment with ID: ${result._id}`); res.send(result); }; export const deletePaymentRouteHandler = async (req, res) => { // Get ID from params const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Payment with ID: ${id}`); const result = await deleteObject({ model: paymentModel, id, user: req.user, }); if (result.error) { logger.error('No payment deleted:', result.error); return res.status(result.code).send(result); } logger.debug(`Deleted payment with ID: ${result._id}`); res.send(result); }; export const getPaymentStatsRouteHandler = async (req, res) => { const result = await getModelStats({ model: paymentModel }); if (result?.error) { logger.error('Error fetching payment stats:', result.error); return res.status(result.code).send(result); } logger.trace('Payment stats:', result); res.send(result); }; export const getPaymentHistoryRouteHandler = async (req, res) => { const from = req.query.from; const to = req.query.to; const result = await getModelHistory({ model: paymentModel, from, to }); if (result?.error) { logger.error('Error fetching payment history:', result.error); return res.status(result.code).send(result); } logger.trace('Payment history:', result); res.send(result); }; export const postPaymentRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Payment with ID: ${id}`); const checkStatesResult = await checkStates({ model: paymentModel, id, states: ['draft'] }); if (checkStatesResult.error) { logger.error('Error checking payment states:', checkStatesResult.error); res.status(checkStatesResult.code).send(checkStatesResult); return; } if (checkStatesResult === false) { logger.error('Payment is not in draft state.'); res.status(400).send({ error: 'Payment is not in draft state.', code: 400 }); return; } const payment = await getObject({ model: paymentModel, id, populate: [{ path: 'invoice' }], }); if (payment.error) { logger.error('Error getting payment:', payment.error); return res.status(payment.code).send(payment); } // Update invoice paid amounts if needed if (payment.invoice) { const invoice = await getObject({ model: invoiceModel, id: payment.invoice._id || payment.invoice, }); if (!invoice.error) { // You can add logic here to update invoice paid amounts // This is a simplified version - adjust based on your business logic } } const updateData = { updatedAt: new Date(), state: { type: 'posted' }, postedAt: new Date(), }; const result = await editObject({ model: paymentModel, id, updateData, user: req.user, }); if (result.error) { logger.error('Error posting payment:', result.error); res.status(result.code).send(result); return; } logger.debug(`Posted payment with ID: ${id}`); res.send(result); }; export const cancelPaymentRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Payment with ID: ${id}`); const checkStatesResult = await checkStates({ model: paymentModel, id, states: ['draft', 'posted'], }); if (checkStatesResult.error) { logger.error('Error checking payment states:', checkStatesResult.error); res.status(checkStatesResult.code).send(checkStatesResult); return; } if (checkStatesResult === false) { logger.error('Payment is not in a cancellable state.'); res.status(400).send({ error: 'Payment is not in a cancellable state (must be draft or posted).', code: 400, }); return; } const updateData = { updatedAt: new Date(), state: { type: 'cancelled' }, cancelledAt: new Date(), }; const result = await editObject({ model: paymentModel, id, updateData, user: req.user, }); if (result.error) { logger.error('Error cancelling payment:', result.error); res.status(result.code).send(result); return; } logger.debug(`Cancelled payment with ID: ${id}`); res.send(result); };