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> { 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 { 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, }; } export async function clearAllTransactions(): Promise<{ deleted: number }> { const result = await pool.query('DELETE FROM transactions RETURNING id'); return { deleted: result.rowCount ?? 0 }; }