Ctrl K
ComunidadGuías…

server/controllers/transactions.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import type express from 'express';
import { asyncErr, KError, UNKNOWN_TRANSACTION_TYPE } from '../helpers';
import { isKnownTransactionTypeName } from '../lib/transaction-types';
import { Account, Category, Transaction } from '../models';
import type { IdentifiedRequest, PreloadedRequest } from './routes';

async function preload(
    varName: string,
    req: IdentifiedRequest<Transaction>,
    res: express.Response,
    nextHandler: () => void,
    transactionId: number
) {
    const { id: userId } = req.user;
    try {
        const transaction = await Transaction.find(userId, transactionId);
        if (!transaction) {
            throw new KError('bank transaction not found', 404);
        }
        req.preloaded = req.preloaded || {};
        req.preloaded[varName] = transaction;
        nextHandler();
    } catch (err) {
        asyncErr(res, err, 'when preloading a transaction');
    }
}

export async function preloadTransaction(
    req: IdentifiedRequest<Transaction>,
    res: express.Response,
    nextHandler: () => void,
    transactionId: number
) {
    await preload('transaction', req, res, nextHandler, transactionId);
}

export async function preloadOtherTransaction(
    req: IdentifiedRequest<Transaction>,
    res: express.Response,
    nextHandler: () => void,
    othertransactionId: number
) {
    await preload('otherTransaction', req, res, nextHandler, othertransactionId);
}

export async function update(req: PreloadedRequest<Transaction>, res: express.Response) {
    try {
        const { id: userId } = req.user;

        const attr = req.body;

        // We can only update the category id, transaction type, custom label, budget date
        // or date (only if it was created by the user) of a transaction.
        if (
            typeof attr.categoryId === 'undefined' &&
            typeof attr.type === 'undefined' &&
            typeof attr.customLabel === 'undefined' &&
            typeof attr.budgetDate === 'undefined' &&
            (typeof attr.date === 'undefined' || !req.preloaded.transaction.createdByUser) &&
            (typeof attr.amount === 'undefined' || !req.preloaded.transaction.createdByUser)
        ) {
            throw new KError('Missing parameter', 400);
        }

        const transactionUpdate: Partial<Transaction> = {};
        if (typeof attr.categoryId !== 'undefined') {
            if (attr.categoryId !== null) {
                const found = await Category.find(userId, attr.categoryId);
                if (!found) {
                    throw new KError('Category not found', 404);
                }
            }
            transactionUpdate.categoryId = attr.categoryId;
        }

        if (typeof attr.type !== 'undefined') {
            if (isKnownTransactionTypeName(attr.type)) {
                transactionUpdate.type = attr.type;
            } else {
                transactionUpdate.type = UNKNOWN_TRANSACTION_TYPE;
            }
        }

        if (typeof transactionUpdate.type !== 'undefined') {
            transactionUpdate.isUserDefinedType = true;
        }

        if (typeof attr.customLabel !== 'undefined') {
            if (attr.customLabel === '') {
                transactionUpdate.customLabel = null;
            } else {
                transactionUpdate.customLabel = attr.customLabel;
            }
        }

        if (typeof attr.budgetDate !== 'undefined') {
            if (attr.budgetDate === null) {
                transactionUpdate.budgetDate = null;
            } else {
                transactionUpdate.budgetDate = new Date(attr.budgetDate);
            }
        }

        if (typeof attr.date !== 'undefined') {
            transactionUpdate.date = new Date(attr.date);

            if (typeof attr.debitDate !== 'undefined') {
                transactionUpdate.debitDate = new Date(attr.debitDate);
            }
        }

        if (typeof attr.amount === 'number') {
            transactionUpdate.amount = attr.amount;
        }

        const updatedTransaction = await Transaction.update(
            userId,
            req.preloaded.transaction.id,
            transactionUpdate
        );

        // Send back the transaction as well as the (possibly) updated account balance.
        const account = await Account.find(userId, updatedTransaction.accountId);
        if (!account) {
            throw new KError('bank account not found', 404);
        }

        res.status(201).json({
            transaction: updatedTransaction,
            accountBalance: account.balance,
            accountId: updatedTransaction.accountId,
        });
    } catch (err) {
        asyncErr(res, err, 'when updating attributes of transaction');
    }
}

export async function merge(req: PreloadedRequest<Transaction>, res: express.Response) {
    try {
        const { id: userId } = req.user;

        // @transaction is the one to keep, @otherTransaction is the one to delete.
        const otherTr = req.preloaded.otherTransaction;
        let tr = req.preloaded.transaction;

        // Check that both transactions belong to the same account.
        if (tr.accountId !== otherTr.accountId) {
            throw new KError(
                'transactions merge is only possible for transactions of a same account',
                400
            );
        }

        // Transfer various fields upon deletion
        const newFields = tr.mergeWith(otherTr);

        tr = await Transaction.update(userId, tr.id, newFields);

        await Transaction.destroy(userId, otherTr.id);

        const account = await Account.find(userId, otherTr.accountId);
        if (!account) {
            throw new KError('bank account not found', 404);
        }

        res.status(200).json({
            transaction: tr,
            accountBalance: account.balance,
            accountId: otherTr.accountId,
        });
    } catch (err) {
        asyncErr(res, err, 'when merging two transactions');
    }
}

// Create a new transaction.
export async function create(req: IdentifiedRequest<Transaction>, res: express.Response) {
    try {
        const { id: userId } = req.user;
        const transaction = req.body;
        if (!Transaction.isTransaction(transaction)) {
            throw new KError('Not a transaction', 400);
        }

        if (typeof transaction.categoryId !== 'undefined' && transaction.categoryId !== null) {
            const found = await Category.find(userId, transaction.categoryId);
            if (!found) {
                throw new KError('Category not found', 404);
            }
        }

        // We fill potentially missing fields.
        transaction.rawLabel = transaction.rawLabel || transaction.label;
        transaction.customLabel =
            transaction.customLabel || transaction.label || transaction.rawLabel;
        transaction.importDate = transaction.importDate || new Date();
        transaction.debitDate = transaction.debitDate || transaction.date;
        transaction.createdByUser = true;
        if (
            typeof transaction.type !== 'undefined' &&
            transaction.type !== UNKNOWN_TRANSACTION_TYPE
        ) {
            transaction.isUserDefinedType = true;
        }
        const newTransaction = await Transaction.create(userId, transaction);

        // Send back the transaction as well as the (possibly) updated account balance.
        const account = await Account.find(userId, newTransaction.accountId);
        if (!account) {
            throw new KError('bank account not found', 404);
        }

        res.status(201).json({
            transaction: newTransaction,
            accountBalance: account.balance,
            accountId: newTransaction.accountId,
        });
    } catch (err) {
        asyncErr(res, err, 'when creating transaction for a bank account');
    }
}

// Delete a transaction
export async function destroy(req: PreloadedRequest<Transaction>, res: express.Response) {
    try {
        const { id: userId } = req.user;
        const tr = req.preloaded.transaction;

        await Transaction.destroy(userId, tr.id);

        // Send back the transaction as well as the (possibly) updated account balance.
        const account = await Account.find(userId, tr.accountId);
        if (!account) {
            throw new KError('bank account not found', 404);
        }

        res.status(200).json({
            accountBalance: account.balance,
            accountId: tr.accountId,
        });
    } catch (err) {
        asyncErr(res, err, 'when deleting transaction');
    }
}