feat: creates backend for the project
This commit is contained in:
170
backend/src/services/transactions.ts
Normal file
170
backend/src/services/transactions.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { pool } from '../db/pool';
|
||||
import { escapeLike } from '../utils';
|
||||
import type {
|
||||
Transaction,
|
||||
GetTransactionsParams,
|
||||
PaginatedResponse,
|
||||
UpdateTransactionRequest,
|
||||
} from '@family-budget/shared';
|
||||
|
||||
export async function getTransactions(
|
||||
params: GetTransactionsParams,
|
||||
): Promise<PaginatedResponse<Transaction>> {
|
||||
const page = params.page ?? 1;
|
||||
const pageSize = [10, 50, 100].includes(params.pageSize ?? 50) ? (params.pageSize ?? 50) : 50;
|
||||
const sortBy = params.sortBy === 'amount' ? 't.amount_signed' : 't.operation_at';
|
||||
const sortOrder = params.sortOrder === 'asc' ? 'ASC' : 'DESC';
|
||||
|
||||
const conditions: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (params.accountId != null) {
|
||||
conditions.push(`t.account_id = $${idx++}`);
|
||||
values.push(params.accountId);
|
||||
}
|
||||
if (params.from) {
|
||||
conditions.push(`t.operation_at >= $${idx++}::date`);
|
||||
values.push(params.from);
|
||||
}
|
||||
if (params.to) {
|
||||
conditions.push(`t.operation_at < ($${idx++}::date + 1)`);
|
||||
values.push(params.to);
|
||||
}
|
||||
if (params.direction) {
|
||||
const dirs = params.direction.split(',').map((d) => d.trim());
|
||||
conditions.push(`t.direction = ANY($${idx++}::text[])`);
|
||||
values.push(dirs);
|
||||
}
|
||||
if (params.categoryId != null) {
|
||||
conditions.push(`t.category_id = $${idx++}`);
|
||||
values.push(params.categoryId);
|
||||
}
|
||||
if (params.search) {
|
||||
conditions.push(`t.description ILIKE '%' || $${idx++} || '%'`);
|
||||
values.push(escapeLike(params.search));
|
||||
}
|
||||
if (params.amountMin != null) {
|
||||
conditions.push(`t.amount_signed >= $${idx++}`);
|
||||
values.push(params.amountMin);
|
||||
}
|
||||
if (params.amountMax != null) {
|
||||
conditions.push(`t.amount_signed <= $${idx++}`);
|
||||
values.push(params.amountMax);
|
||||
}
|
||||
if (params.onlyUnconfirmed) {
|
||||
conditions.push('t.is_category_confirmed = FALSE');
|
||||
}
|
||||
|
||||
const where = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : '';
|
||||
|
||||
const countResult = await pool.query(
|
||||
`SELECT COUNT(*)::int AS total
|
||||
FROM transactions t ${where}`,
|
||||
values,
|
||||
);
|
||||
const totalItems: number = countResult.rows[0].total;
|
||||
|
||||
const offset = (page - 1) * pageSize;
|
||||
const dataResult = await pool.query(
|
||||
`SELECT
|
||||
t.id,
|
||||
t.operation_at,
|
||||
t.account_id,
|
||||
a.alias AS account_alias,
|
||||
t.amount_signed,
|
||||
t.commission,
|
||||
t.description,
|
||||
t.direction,
|
||||
t.category_id,
|
||||
c.name AS category_name,
|
||||
t.is_category_confirmed,
|
||||
t.comment
|
||||
FROM transactions t
|
||||
JOIN accounts a ON a.id = t.account_id
|
||||
LEFT JOIN categories c ON c.id = t.category_id
|
||||
${where}
|
||||
ORDER BY ${sortBy} ${sortOrder}
|
||||
LIMIT $${idx++} OFFSET $${idx++}`,
|
||||
[...values, pageSize, offset],
|
||||
);
|
||||
|
||||
const items: Transaction[] = dataResult.rows.map((r) => ({
|
||||
id: Number(r.id),
|
||||
operationAt: r.operation_at.toISOString(),
|
||||
accountId: Number(r.account_id),
|
||||
accountAlias: r.account_alias ?? null,
|
||||
amountSigned: Number(r.amount_signed),
|
||||
commission: Number(r.commission),
|
||||
description: r.description,
|
||||
direction: r.direction,
|
||||
categoryId: r.category_id != null ? Number(r.category_id) : null,
|
||||
categoryName: r.category_name ?? null,
|
||||
isCategoryConfirmed: r.is_category_confirmed,
|
||||
comment: r.comment ?? null,
|
||||
}));
|
||||
|
||||
return {
|
||||
items,
|
||||
page,
|
||||
pageSize,
|
||||
totalItems,
|
||||
totalPages: Math.ceil(totalItems / pageSize) || 1,
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateTransaction(
|
||||
id: number,
|
||||
body: UpdateTransactionRequest,
|
||||
): Promise<Transaction | null> {
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (body.categoryId !== undefined) {
|
||||
fields.push(`category_id = $${idx++}`);
|
||||
values.push(body.categoryId);
|
||||
}
|
||||
if (body.comment !== undefined) {
|
||||
fields.push(`comment = $${idx++}`);
|
||||
values.push(body.comment);
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
fields.push('is_category_confirmed = TRUE');
|
||||
fields.push('updated_at = NOW()');
|
||||
|
||||
values.push(id);
|
||||
|
||||
const result = await pool.query(
|
||||
`UPDATE transactions SET ${fields.join(', ')} WHERE id = $${idx}
|
||||
RETURNING *`,
|
||||
values,
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) return null;
|
||||
const r = result.rows[0];
|
||||
|
||||
const accResult = await pool.query('SELECT alias FROM accounts WHERE id = $1', [r.account_id]);
|
||||
const catResult = r.category_id
|
||||
? await pool.query('SELECT name FROM categories WHERE id = $1', [r.category_id])
|
||||
: { rows: [] };
|
||||
|
||||
return {
|
||||
id: Number(r.id),
|
||||
operationAt: r.operation_at.toISOString(),
|
||||
accountId: Number(r.account_id),
|
||||
accountAlias: accResult.rows[0]?.alias ?? null,
|
||||
amountSigned: Number(r.amount_signed),
|
||||
commission: Number(r.commission),
|
||||
description: r.description,
|
||||
direction: r.direction,
|
||||
categoryId: r.category_id != null ? Number(r.category_id) : null,
|
||||
categoryName: catResult.rows[0]?.name ?? null,
|
||||
isCategoryConfirmed: r.is_category_confirmed,
|
||||
comment: r.comment ?? null,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user