server/controllers/transactions.ts
Rama main · réplica de lectura del repositorio original.
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');
}
}