mirror of
https://github.com/arkorty/Expensso.git
synced 2026-03-18 00:47:11 +00:00
init
This commit is contained in:
167
src/db/database.ts
Normal file
167
src/db/database.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import SQLite, {
|
||||
SQLiteDatabase,
|
||||
Transaction as SQLTransaction,
|
||||
ResultSet,
|
||||
} from 'react-native-sqlite-storage';
|
||||
|
||||
// Enable promise-based API
|
||||
SQLite.enablePromise(true);
|
||||
|
||||
const DATABASE_NAME = 'expensso.db';
|
||||
const DATABASE_VERSION = '1.0';
|
||||
const DATABASE_DISPLAY_NAME = 'Expensso Database';
|
||||
const DATABASE_SIZE = 200000;
|
||||
|
||||
let db: SQLiteDatabase | null = null;
|
||||
|
||||
// ─── Open / Get Database ─────────────────────────────────────────────
|
||||
|
||||
export async function getDatabase(): Promise<SQLiteDatabase> {
|
||||
if (db) {
|
||||
return db;
|
||||
}
|
||||
|
||||
db = await SQLite.openDatabase({
|
||||
name: DATABASE_NAME,
|
||||
location: 'default',
|
||||
});
|
||||
|
||||
await createTables(db);
|
||||
return db;
|
||||
}
|
||||
|
||||
// ─── Schema Creation ─────────────────────────────────────────────────
|
||||
|
||||
async function createTables(database: SQLiteDatabase): Promise<void> {
|
||||
await database.transaction(async (tx: SQLTransaction) => {
|
||||
// Categories table
|
||||
tx.executeSql(`
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
icon TEXT NOT NULL DEFAULT 'dots-horizontal',
|
||||
color TEXT NOT NULL DEFAULT '#95A5A6',
|
||||
type TEXT NOT NULL CHECK(type IN ('income', 'expense')),
|
||||
is_default INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
|
||||
// Transactions table (the ledger)
|
||||
tx.executeSql(`
|
||||
CREATE TABLE IF NOT EXISTS transactions (
|
||||
id TEXT PRIMARY KEY,
|
||||
amount REAL NOT NULL,
|
||||
currency TEXT NOT NULL DEFAULT 'INR',
|
||||
type TEXT NOT NULL CHECK(type IN ('income', 'expense')),
|
||||
category_id TEXT NOT NULL,
|
||||
payment_method TEXT NOT NULL DEFAULT 'UPI',
|
||||
note TEXT DEFAULT '',
|
||||
date TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// Transaction impacts table (links transactions to net-worth entries)
|
||||
tx.executeSql(`
|
||||
CREATE TABLE IF NOT EXISTS transaction_impacts (
|
||||
transaction_id TEXT PRIMARY KEY,
|
||||
target_type TEXT NOT NULL CHECK(target_type IN ('asset', 'liability')),
|
||||
target_id TEXT NOT NULL,
|
||||
operation TEXT NOT NULL CHECK(operation IN ('add', 'subtract')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
||||
// Assets table
|
||||
tx.executeSql(`
|
||||
CREATE TABLE IF NOT EXISTS assets (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
current_value REAL NOT NULL DEFAULT 0,
|
||||
currency TEXT NOT NULL DEFAULT 'INR',
|
||||
note TEXT DEFAULT '',
|
||||
last_updated TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
|
||||
// Liabilities table
|
||||
tx.executeSql(`
|
||||
CREATE TABLE IF NOT EXISTS liabilities (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
outstanding_amount REAL NOT NULL DEFAULT 0,
|
||||
currency TEXT NOT NULL DEFAULT 'INR',
|
||||
interest_rate REAL NOT NULL DEFAULT 0,
|
||||
emi_amount REAL NOT NULL DEFAULT 0,
|
||||
note TEXT DEFAULT '',
|
||||
last_updated TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
|
||||
// Net-worth snapshots for historical tracking
|
||||
tx.executeSql(`
|
||||
CREATE TABLE IF NOT EXISTS net_worth_snapshots (
|
||||
id TEXT PRIMARY KEY,
|
||||
total_assets REAL NOT NULL DEFAULT 0,
|
||||
total_liabilities REAL NOT NULL DEFAULT 0,
|
||||
net_worth REAL NOT NULL DEFAULT 0,
|
||||
currency TEXT NOT NULL DEFAULT 'INR',
|
||||
snapshot_date TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
|
||||
// Indexes for performance
|
||||
tx.executeSql(`
|
||||
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(date);
|
||||
`);
|
||||
tx.executeSql(`
|
||||
CREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(type);
|
||||
`);
|
||||
tx.executeSql(`
|
||||
CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category_id);
|
||||
`);
|
||||
tx.executeSql(`
|
||||
CREATE INDEX IF NOT EXISTS idx_transaction_impacts_target ON transaction_impacts(target_type, target_id);
|
||||
`);
|
||||
tx.executeSql(`
|
||||
CREATE INDEX IF NOT EXISTS idx_snapshots_date ON net_worth_snapshots(snapshot_date);
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Generic Helpers ─────────────────────────────────────────────────
|
||||
|
||||
export async function executeSql(
|
||||
sql: string,
|
||||
params: any[] = [],
|
||||
): Promise<ResultSet> {
|
||||
const database = await getDatabase();
|
||||
const [result] = await database.executeSql(sql, params);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function rowsToArray<T>(result: ResultSet): T[] {
|
||||
const rows: T[] = [];
|
||||
for (let i = 0; i < result.rows.length; i++) {
|
||||
rows.push(result.rows.item(i) as T);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
// ─── Close Database ──────────────────────────────────────────────────
|
||||
|
||||
export async function closeDatabase(): Promise<void> {
|
||||
if (db) {
|
||||
await db.close();
|
||||
db = null;
|
||||
}
|
||||
}
|
||||
2
src/db/index.ts
Normal file
2
src/db/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export {getDatabase, executeSql, rowsToArray, closeDatabase} from './database';
|
||||
export * from './queries';
|
||||
445
src/db/queries.ts
Normal file
445
src/db/queries.ts
Normal file
@@ -0,0 +1,445 @@
|
||||
import {executeSql, rowsToArray} from './database';
|
||||
import {
|
||||
Category,
|
||||
Transaction,
|
||||
Asset,
|
||||
Liability,
|
||||
NetWorthSnapshot,
|
||||
TransactionImpact,
|
||||
NetWorthTargetType,
|
||||
ImpactOperation,
|
||||
} from '../types';
|
||||
import {generateId} from '../utils';
|
||||
import {DEFAULT_EXPENSE_CATEGORIES, DEFAULT_INCOME_CATEGORIES} from '../constants';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// CATEGORIES
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
export async function seedDefaultCategories(): Promise<void> {
|
||||
const result = await executeSql('SELECT COUNT(*) as count FROM categories');
|
||||
const count = result.rows.item(0).count;
|
||||
|
||||
if (count > 0) {return;}
|
||||
|
||||
const allCategories = [...DEFAULT_EXPENSE_CATEGORIES, ...DEFAULT_INCOME_CATEGORIES];
|
||||
|
||||
for (const cat of allCategories) {
|
||||
await executeSql(
|
||||
'INSERT INTO categories (id, name, icon, color, type, is_default) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[generateId(), cat.name, cat.icon, cat.color, cat.type, cat.isDefault ? 1 : 0],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCategories(type?: 'income' | 'expense'): Promise<Category[]> {
|
||||
let sql = 'SELECT id, name, icon, color, type, is_default as isDefault, created_at as createdAt FROM categories';
|
||||
const params: any[] = [];
|
||||
|
||||
if (type) {
|
||||
sql += ' WHERE type = ?';
|
||||
params.push(type);
|
||||
}
|
||||
|
||||
sql += ' ORDER BY is_default DESC, name ASC';
|
||||
const result = await executeSql(sql, params);
|
||||
return rowsToArray<Category>(result);
|
||||
}
|
||||
|
||||
export async function insertCategory(cat: Omit<Category, 'id' | 'createdAt'>): Promise<string> {
|
||||
const id = generateId();
|
||||
await executeSql(
|
||||
'INSERT INTO categories (id, name, icon, color, type, is_default) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[id, cat.name, cat.icon, cat.color, cat.type, cat.isDefault ? 1 : 0],
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// TRANSACTIONS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
export async function getTransactions(options?: {
|
||||
type?: 'income' | 'expense';
|
||||
fromDate?: string;
|
||||
toDate?: string;
|
||||
categoryId?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}): Promise<Transaction[]> {
|
||||
let sql = `
|
||||
SELECT
|
||||
t.id, t.amount, t.currency, t.type, t.category_id as categoryId,
|
||||
t.payment_method as paymentMethod, t.note, t.date,
|
||||
t.created_at as createdAt, t.updated_at as updatedAt,
|
||||
c.name as categoryName, c.icon as categoryIcon, c.color as categoryColor
|
||||
FROM transactions t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params: any[] = [];
|
||||
|
||||
if (options?.type) {
|
||||
sql += ' AND t.type = ?';
|
||||
params.push(options.type);
|
||||
}
|
||||
if (options?.fromDate) {
|
||||
sql += ' AND t.date >= ?';
|
||||
params.push(options.fromDate);
|
||||
}
|
||||
if (options?.toDate) {
|
||||
sql += ' AND t.date <= ?';
|
||||
params.push(options.toDate);
|
||||
}
|
||||
if (options?.categoryId) {
|
||||
sql += ' AND t.category_id = ?';
|
||||
params.push(options.categoryId);
|
||||
}
|
||||
|
||||
sql += ' ORDER BY t.date DESC, t.created_at DESC';
|
||||
|
||||
if (options?.limit) {
|
||||
sql += ' LIMIT ?';
|
||||
params.push(options.limit);
|
||||
}
|
||||
if (options?.offset) {
|
||||
sql += ' OFFSET ?';
|
||||
params.push(options.offset);
|
||||
}
|
||||
|
||||
const result = await executeSql(sql, params);
|
||||
return rowsToArray<Transaction>(result);
|
||||
}
|
||||
|
||||
export async function getTransactionById(id: string): Promise<Transaction | null> {
|
||||
const result = await executeSql(
|
||||
`SELECT
|
||||
t.id, t.amount, t.currency, t.type, t.category_id as categoryId,
|
||||
t.payment_method as paymentMethod, t.note, t.date,
|
||||
t.created_at as createdAt, t.updated_at as updatedAt,
|
||||
c.name as categoryName, c.icon as categoryIcon, c.color as categoryColor
|
||||
FROM transactions t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
WHERE t.id = ?`,
|
||||
[id],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.rows.item(0) as Transaction;
|
||||
}
|
||||
|
||||
export async function insertTransaction(
|
||||
txn: Omit<Transaction, 'id' | 'createdAt' | 'updatedAt' | 'categoryName' | 'categoryIcon' | 'categoryColor'>,
|
||||
): Promise<string> {
|
||||
const id = generateId();
|
||||
await executeSql(
|
||||
`INSERT INTO transactions (id, amount, currency, type, category_id, payment_method, note, date)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[id, txn.amount, txn.currency, txn.type, txn.categoryId, txn.paymentMethod, txn.note, txn.date],
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
export async function updateTransaction(
|
||||
id: string,
|
||||
txn: Partial<Omit<Transaction, 'id' | 'createdAt'>>,
|
||||
): Promise<void> {
|
||||
const fields: string[] = [];
|
||||
const params: any[] = [];
|
||||
|
||||
if (txn.amount !== undefined) { fields.push('amount = ?'); params.push(txn.amount); }
|
||||
if (txn.currency) { fields.push('currency = ?'); params.push(txn.currency); }
|
||||
if (txn.type) { fields.push('type = ?'); params.push(txn.type); }
|
||||
if (txn.categoryId) { fields.push('category_id = ?'); params.push(txn.categoryId); }
|
||||
if (txn.paymentMethod) { fields.push('payment_method = ?'); params.push(txn.paymentMethod); }
|
||||
if (txn.note !== undefined) { fields.push('note = ?'); params.push(txn.note); }
|
||||
if (txn.date) { fields.push('date = ?'); params.push(txn.date); }
|
||||
|
||||
fields.push("updated_at = datetime('now')");
|
||||
params.push(id);
|
||||
|
||||
await executeSql(`UPDATE transactions SET ${fields.join(', ')} WHERE id = ?`, params);
|
||||
}
|
||||
|
||||
export async function deleteTransaction(id: string): Promise<void> {
|
||||
await executeSql('DELETE FROM transactions WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
export async function saveTransactionImpact(input: TransactionImpact): Promise<void> {
|
||||
await executeSql(
|
||||
`INSERT OR REPLACE INTO transaction_impacts (transaction_id, target_type, target_id, operation)
|
||||
VALUES (?, ?, ?, ?)`,
|
||||
[input.transactionId, input.targetType, input.targetId, input.operation],
|
||||
);
|
||||
}
|
||||
|
||||
export async function getTransactionImpact(transactionId: string): Promise<TransactionImpact | null> {
|
||||
const result = await executeSql(
|
||||
`SELECT transaction_id as transactionId, target_type as targetType, target_id as targetId, operation
|
||||
FROM transaction_impacts
|
||||
WHERE transaction_id = ?`,
|
||||
[transactionId],
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.rows.item(0) as TransactionImpact;
|
||||
}
|
||||
|
||||
export async function deleteTransactionImpact(transactionId: string): Promise<void> {
|
||||
await executeSql('DELETE FROM transaction_impacts WHERE transaction_id = ?', [transactionId]);
|
||||
}
|
||||
|
||||
export async function applyTargetImpact(
|
||||
targetType: NetWorthTargetType,
|
||||
targetId: string,
|
||||
operation: ImpactOperation,
|
||||
amount: number,
|
||||
): Promise<void> {
|
||||
if (amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetType === 'asset') {
|
||||
if (operation === 'add') {
|
||||
await executeSql(
|
||||
`UPDATE assets
|
||||
SET current_value = current_value + ?,
|
||||
last_updated = datetime('now')
|
||||
WHERE id = ?`,
|
||||
[amount, targetId],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await executeSql(
|
||||
`UPDATE assets
|
||||
SET current_value = MAX(current_value - ?, 0),
|
||||
last_updated = datetime('now')
|
||||
WHERE id = ?`,
|
||||
[amount, targetId],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation === 'add') {
|
||||
await executeSql(
|
||||
`UPDATE liabilities
|
||||
SET outstanding_amount = outstanding_amount + ?,
|
||||
last_updated = datetime('now')
|
||||
WHERE id = ?`,
|
||||
[amount, targetId],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await executeSql(
|
||||
`UPDATE liabilities
|
||||
SET outstanding_amount = MAX(outstanding_amount - ?, 0),
|
||||
last_updated = datetime('now')
|
||||
WHERE id = ?`,
|
||||
[amount, targetId],
|
||||
);
|
||||
}
|
||||
|
||||
export async function reverseTargetImpact(impact: TransactionImpact, amount: number): Promise<void> {
|
||||
const reverseOperation: ImpactOperation = impact.operation === 'add' ? 'subtract' : 'add';
|
||||
await applyTargetImpact(impact.targetType, impact.targetId, reverseOperation, amount);
|
||||
}
|
||||
|
||||
export async function getMonthlyTotals(
|
||||
type: 'income' | 'expense',
|
||||
year: number,
|
||||
month: number,
|
||||
): Promise<number> {
|
||||
const startDate = `${year}-${String(month).padStart(2, '0')}-01`;
|
||||
const endDate =
|
||||
month === 12
|
||||
? `${year + 1}-01-01`
|
||||
: `${year}-${String(month + 1).padStart(2, '0')}-01`;
|
||||
|
||||
const result = await executeSql(
|
||||
'SELECT COALESCE(SUM(amount), 0) as total FROM transactions WHERE type = ? AND date >= ? AND date < ?',
|
||||
[type, startDate, endDate],
|
||||
);
|
||||
return result.rows.item(0).total;
|
||||
}
|
||||
|
||||
export async function getSpendingByCategory(
|
||||
fromDate: string,
|
||||
toDate: string,
|
||||
): Promise<{categoryName: string; categoryColor: string; categoryIcon: string; total: number}[]> {
|
||||
const result = await executeSql(
|
||||
`SELECT c.name as categoryName, c.color as categoryColor, c.icon as categoryIcon,
|
||||
SUM(t.amount) as total
|
||||
FROM transactions t
|
||||
LEFT JOIN categories c ON t.category_id = c.id
|
||||
WHERE t.type = 'expense' AND t.date >= ? AND t.date < ?
|
||||
GROUP BY t.category_id
|
||||
ORDER BY total DESC`,
|
||||
[fromDate, toDate],
|
||||
);
|
||||
return rowsToArray(result);
|
||||
}
|
||||
|
||||
export async function getMonthlySpendingTrend(months: number = 6): Promise<{month: string; total: number}[]> {
|
||||
const result = await executeSql(
|
||||
`SELECT strftime('%Y-%m', date) as month, SUM(amount) as total
|
||||
FROM transactions
|
||||
WHERE type = 'expense'
|
||||
AND date >= date('now', '-' || ? || ' months')
|
||||
GROUP BY strftime('%Y-%m', date)
|
||||
ORDER BY month ASC`,
|
||||
[months],
|
||||
);
|
||||
return rowsToArray(result);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// ASSETS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
export async function getAssets(): Promise<Asset[]> {
|
||||
const result = await executeSql(
|
||||
`SELECT id, name, type, current_value as currentValue, currency,
|
||||
note, last_updated as lastUpdated, created_at as createdAt
|
||||
FROM assets
|
||||
ORDER BY current_value DESC`,
|
||||
);
|
||||
return rowsToArray<Asset>(result);
|
||||
}
|
||||
|
||||
export async function insertAsset(asset: Omit<Asset, 'id' | 'createdAt' | 'lastUpdated'>): Promise<string> {
|
||||
const id = generateId();
|
||||
await executeSql(
|
||||
'INSERT INTO assets (id, name, type, current_value, currency, note) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[id, asset.name, asset.type, asset.currentValue, asset.currency, asset.note],
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
export async function updateAsset(id: string, asset: Partial<Omit<Asset, 'id' | 'createdAt'>>): Promise<void> {
|
||||
const fields: string[] = [];
|
||||
const params: any[] = [];
|
||||
|
||||
if (asset.name) { fields.push('name = ?'); params.push(asset.name); }
|
||||
if (asset.type) { fields.push('type = ?'); params.push(asset.type); }
|
||||
if (asset.currentValue !== undefined) { fields.push('current_value = ?'); params.push(asset.currentValue); }
|
||||
if (asset.currency) { fields.push('currency = ?'); params.push(asset.currency); }
|
||||
if (asset.note !== undefined) { fields.push('note = ?'); params.push(asset.note); }
|
||||
|
||||
fields.push("last_updated = datetime('now')");
|
||||
params.push(id);
|
||||
|
||||
await executeSql(`UPDATE assets SET ${fields.join(', ')} WHERE id = ?`, params);
|
||||
}
|
||||
|
||||
export async function deleteAsset(id: string): Promise<void> {
|
||||
await executeSql('DELETE FROM assets WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
export async function getTotalAssets(): Promise<number> {
|
||||
const result = await executeSql('SELECT COALESCE(SUM(current_value), 0) as total FROM assets');
|
||||
return result.rows.item(0).total;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// LIABILITIES
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
export async function getLiabilities(): Promise<Liability[]> {
|
||||
const result = await executeSql(
|
||||
`SELECT id, name, type, outstanding_amount as outstandingAmount, currency,
|
||||
interest_rate as interestRate, emi_amount as emiAmount,
|
||||
note, last_updated as lastUpdated, created_at as createdAt
|
||||
FROM liabilities
|
||||
ORDER BY outstanding_amount DESC`,
|
||||
);
|
||||
return rowsToArray<Liability>(result);
|
||||
}
|
||||
|
||||
export async function insertLiability(
|
||||
liability: Omit<Liability, 'id' | 'createdAt' | 'lastUpdated'>,
|
||||
): Promise<string> {
|
||||
const id = generateId();
|
||||
await executeSql(
|
||||
`INSERT INTO liabilities (id, name, type, outstanding_amount, currency, interest_rate, emi_amount, note)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
id, liability.name, liability.type, liability.outstandingAmount,
|
||||
liability.currency, liability.interestRate, liability.emiAmount, liability.note,
|
||||
],
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
export async function updateLiability(
|
||||
id: string,
|
||||
liability: Partial<Omit<Liability, 'id' | 'createdAt'>>,
|
||||
): Promise<void> {
|
||||
const fields: string[] = [];
|
||||
const params: any[] = [];
|
||||
|
||||
if (liability.name) { fields.push('name = ?'); params.push(liability.name); }
|
||||
if (liability.type) { fields.push('type = ?'); params.push(liability.type); }
|
||||
if (liability.outstandingAmount !== undefined) { fields.push('outstanding_amount = ?'); params.push(liability.outstandingAmount); }
|
||||
if (liability.currency) { fields.push('currency = ?'); params.push(liability.currency); }
|
||||
if (liability.interestRate !== undefined) { fields.push('interest_rate = ?'); params.push(liability.interestRate); }
|
||||
if (liability.emiAmount !== undefined) { fields.push('emi_amount = ?'); params.push(liability.emiAmount); }
|
||||
if (liability.note !== undefined) { fields.push('note = ?'); params.push(liability.note); }
|
||||
|
||||
fields.push("last_updated = datetime('now')");
|
||||
params.push(id);
|
||||
|
||||
await executeSql(`UPDATE liabilities SET ${fields.join(', ')} WHERE id = ?`, params);
|
||||
}
|
||||
|
||||
export async function deleteLiability(id: string): Promise<void> {
|
||||
await executeSql('DELETE FROM liabilities WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
export async function getTotalLiabilities(): Promise<number> {
|
||||
const result = await executeSql('SELECT COALESCE(SUM(outstanding_amount), 0) as total FROM liabilities');
|
||||
return result.rows.item(0).total;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// NET WORTH SNAPSHOTS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
export async function saveNetWorthSnapshot(
|
||||
totalAssets: number,
|
||||
totalLiabilities: number,
|
||||
currency: string,
|
||||
): Promise<string> {
|
||||
const id = generateId();
|
||||
const netWorth = totalAssets - totalLiabilities;
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
// Upsert: delete any existing snapshot for today then insert
|
||||
await executeSql('DELETE FROM net_worth_snapshots WHERE snapshot_date = ?', [today]);
|
||||
await executeSql(
|
||||
`INSERT INTO net_worth_snapshots (id, total_assets, total_liabilities, net_worth, currency, snapshot_date)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[id, totalAssets, totalLiabilities, netWorth, currency, today],
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
export async function getNetWorthHistory(months: number = 12): Promise<NetWorthSnapshot[]> {
|
||||
const result = await executeSql(
|
||||
`SELECT id, total_assets as totalAssets, total_liabilities as totalLiabilities,
|
||||
net_worth as netWorth, currency, snapshot_date as snapshotDate,
|
||||
created_at as createdAt
|
||||
FROM net_worth_snapshots
|
||||
WHERE snapshot_date >= date('now', '-' || ? || ' months')
|
||||
ORDER BY snapshot_date ASC`,
|
||||
[months],
|
||||
);
|
||||
return rowsToArray<NetWorthSnapshot>(result);
|
||||
}
|
||||
Reference in New Issue
Block a user