Skip to content

Commit

Permalink
v1.0.24 - Do rate calculation in SQL (#24)
Browse files Browse the repository at this point in the history
* v1.0.24 - Update and improve language

* v1.0.24 - Fix migrations

* v1.0.24 - Do rate calculations in SQL

* v1.0.24 - Update SQL performance

* v1.0.24 - Add refresh indicators and loading icons

* v1.0.24 - Show unrealised gains/losses
  • Loading branch information
Donnie committed Jan 28, 2024
1 parent 58524c4 commit 04d7712
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 115 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<a href="https://flutter.dev/" style="text-decoration:none" area-label="flutter">
<img src="https://img.shields.io/badge/Platform-Flutter%203.16.5-blue">
</a>
<a href="https://github.com/Donnie/Finease/releases/tag/v1.0.23" style="text-decoration:none" area-label="flutter">
<img src="https://img.shields.io/badge/Version-1.0.23-orange">
<a href="https://github.com/Donnie/Finease/releases/tag/v1.0.24" style="text-decoration:none" area-label="flutter">
<img src="https://img.shields.io/badge/Version-1.0.24-orange">
</a>
<a href="https://github.com/Donnie/Finease/actions/workflows/android_release.yml" style="text-decoration:none" area-label="flutter">
<img src="https://github.com/Donnie/Finease/actions/workflows/android_release.yml/badge.svg">
Expand Down
82 changes: 39 additions & 43 deletions lib/db/accounts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,63 +140,59 @@ class AccountService {
AccountType? type,
}) async {
final dbClient = await _databaseHelper.db;
List<String> conditions = ['hidden = 0'];
String prefCurrency =
await _settingService.getSetting(Setting.prefCurrency);

// Determine if conversion is needed
bool needsConversion = await currencyBoxService.isRequired();
if (needsConversion) {
// updates rates table
await currencyBoxService.init();
await currencyBoxService.updateRatesTable();
currencyBoxService.close();
}

List<String> conditions = ["type NOT IN ('income', 'expense')"];
if (liquid) {
conditions.add('liquid = 1');
}

if (type != null) {
conditions.add("type = '${type.name}'");
}

String whereClause = conditions.join(' AND ');

String sql = '''
SELECT
SUM(balance) as total_balance,
currency
FROM Accounts
WHERE $whereClause
AND type NOT IN ('income', 'expense')
GROUP BY currency;
WITH CurrencyGroups AS (
SELECT
COALESCE(SUM(balance), 0) AS gbalance,
currency AS gcurrency
FROM Accounts
WHERE $whereClause
GROUP BY currency
)
SELECT COALESCE(SUM(
CASE
WHEN gcurrency = ? THEN gbalance
ELSE gbalance / (
SELECT cr.rate FROM rates cr
WHERE cr.currency = gcurrency
) * (
SELECT cr.rate FROM rates cr
WHERE cr.currency = ?
)
END
), 0) AS total_balance
FROM CurrencyGroups
''';

// Execute the query
List<Map<String, dynamic>> result = await dbClient.rawQuery(sql);

List<Map<String, dynamic>> result = await dbClient.rawQuery(
sql,
[prefCurrency, prefCurrency],
);
// result is like:
// [{total_balance: 9880057, currency: EUR}]

int totalBalance = 0;
String prefCurrency =
await _settingService.getSetting(Setting.prefCurrency);

// Determine if conversion is needed and initialize currencyBoxService once if true
bool needsConversion = result.any((row) => row['currency'] != prefCurrency);
if (needsConversion) {
await currencyBoxService.init();
}

// Loop through each currency and convert the balance to preferred currency
for (var row in result) {
String currency = row['currency'];
int balance = row['total_balance'];

// If the currency is not the preferred currency, convert it
if (currency != prefCurrency) {
double rate = await currencyBoxService.getSingleRate(
currency,
prefCurrency,
);
totalBalance += (balance * rate).round();
} else {
// Already in preferred currency, no conversion needed
totalBalance += balance;
}
}

return totalBalance / 100;
num totalBalance = result.first['total_balance'] ?? 0.0;
return (totalBalance / 100);
}
}

Expand Down
19 changes: 19 additions & 0 deletions lib/db/currency.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ class CurrencyBoxService {
late String prefCurrency;
final ExchangeService _exchangeService = ExchangeService();

Future<bool> isRequired() async {
final dbClient = await _databaseHelper.db;
const String sql = '''
SELECT COALESCE(COUNT(DISTINCT currency), 0) AS total_count
FROM (
SELECT currency FROM Accounts
UNION
SELECT value FROM settings WHERE key = 'prefCurrency'
) AS combined_currencies;
''';
final List<Map<String, dynamic>> result = await dbClient.rawQuery(sql);

if (result.isNotEmpty) {
final num count = result.first['total_count'];
return count > 1;
}
return false;
}

Future<void> init() async {
_box = await Hive.openBox(_boxName);
final currentDate = DateTime.now();
Expand Down
2 changes: 2 additions & 0 deletions lib/db/db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class DatabaseHelper {

Future<void> _onCreate(Database db, int version) async {
await aInitialMigration(db);
await bAddIndices(db);
await cAddRates(db);
}

Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
Expand Down
75 changes: 49 additions & 26 deletions lib/db/months.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ class MonthService {
String prefCurrency =
await SettingService().getSetting(Setting.prefCurrency);

await currencyBoxService.init();
await currencyBoxService.updateRatesTable();
// Determine if conversion is needed
bool needsConversion = await currencyBoxService.isRequired();
if (needsConversion) {
// updates rates table
await currencyBoxService.init();
await currencyBoxService.updateRatesTable();
currencyBoxService.close();
}

final dbClient = await _databaseHelper.db;
String query = '''
Expand All @@ -26,37 +32,45 @@ class MonthService {
WHERE DATETIME(monthDate, '+1 month') < CURRENT_DATE
),
ForexPairs AS (
SELECT
MIN(e.id) AS forex_debit_id,
MAX(e.id) AS forex_credit_id
FROM
entries e
INNER JOIN accounts a ON e.credit_account_id = a.id OR e.debit_account_id = a.id
WHERE
a.name = 'Forex'
GROUP BY
e.date
SELECT
ed.id AS debit_entry_id,
ec.id AS credit_entry_id,
ad.currency AS debit_currency,
ac.currency AS credit_currency
FROM entries ed
JOIN entries ec ON ed.date = ec.date AND ed.id <> ec.id
JOIN accounts ad ON ed.debit_account_id = ad.id
JOIN accounts ac ON ec.credit_account_id = ac.id
JOIN accounts adc ON ed.credit_account_id = adc.id AND adc.name = 'Forex'
JOIN accounts acd ON ec.debit_account_id = acd.id AND acd.name = 'Forex'
),
ConsolidatedForex AS (
SELECT
f.forex_debit_id AS id,
f.debit_entry_id AS id,
e1.created_at,
e1.updated_at,
e1.debit_account_id,
e2.credit_account_id,
e2.amount,
(CASE
WHEN f.debit_currency = ? THEN e1.amount
ELSE e2.amount
END) AS amount,
e1.date,
e2.notes
e2.notes,
(CASE
WHEN f.debit_currency = ? THEN ?
ELSE f.credit_currency
END) AS currency
FROM
ForexPairs f
JOIN entries e1 ON f.forex_debit_id = e1.id
JOIN entries e2 ON f.forex_credit_id = e2.id
JOIN entries e1 ON f.debit_entry_id = e1.id
JOIN entries e2 ON f.credit_entry_id = e2.id
),
NonForexEntries AS (
SELECT *
SELECT *, ? AS currency
FROM entries
WHERE id NOT IN (SELECT forex_debit_id FROM ForexPairs)
AND id NOT IN (SELECT forex_credit_id FROM ForexPairs)
WHERE id NOT IN (SELECT debit_entry_id FROM ForexPairs)
AND id NOT IN (SELECT credit_entry_id FROM ForexPairs)
),
ConsolidatedEntries AS (
SELECT *
Expand All @@ -71,10 +85,10 @@ class MonthService {
months.endDate,
COALESCE(SUM(
CASE
WHEN ac.currency = ? THEN e.amount
WHEN e.currency = ? THEN e.amount
ELSE e.amount / (
SELECT cr.rate FROM rates cr
WHERE cr.currency = ac.currency
WHERE cr.currency = e.currency
) * (
SELECT cr.rate FROM rates cr
WHERE cr.currency = ?
Expand All @@ -85,10 +99,10 @@ class MonthService {
), 0) AS income,
COALESCE(SUM(
CASE
WHEN ac.currency = ? THEN e.amount
WHEN e.currency = ? THEN e.amount
ELSE e.amount / (
SELECT cr.rate FROM rates cr
WHERE cr.currency = ac.currency
WHERE cr.currency = e.currency
) * (
SELECT cr.rate FROM rates cr
WHERE cr.currency = ?
Expand Down Expand Up @@ -131,9 +145,18 @@ class MonthService {

final results = await dbClient.rawQuery(
query,
[prefCurrency, prefCurrency, prefCurrency, prefCurrency, prefCurrency],
[
prefCurrency,
prefCurrency,
prefCurrency,
prefCurrency,
prefCurrency,
prefCurrency,
prefCurrency,
prefCurrency,
prefCurrency
],
);
currencyBoxService.close();

try {
return results.map((json) => Month.fromJson(json)).toList();
Expand Down
4 changes: 2 additions & 2 deletions lib/pages/add_entry/entry_body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ class AddEntryBodyState extends State<AddEntryBody> {
key: const Key('entry_notes'),
controller: widget.entryNotes,
decoration: const InputDecoration(
hintText: 'Enter entry notes',
label: Text('Enter entry notes'),
hintText: 'Enter transaction notes',
label: Text('Enter transaction notes'),
),
keyboardType: TextInputType.text,
),
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/add_entry/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class AddEntryScreenState extends State<AddEntryScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Add Entry')),
appBar: AppBar(title: const Text('add a transaction')),
body: AddEntryBody(
accounts: _accounts,
creditAccount: _creditAccount,
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/add_info/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class _AddInfoPageState extends State<AddInfoPage> {
balance: 0,
currency: _currency.text,
liquid: false,
name: _name.text,
name: "Past",
hidden: true,
type: AccountType.income,
));
Expand Down
18 changes: 17 additions & 1 deletion lib/pages/home/accounts/account_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ class BankAccounts extends StatelessWidget {

return ListView(
children: [
const Padding(
padding: EdgeInsets.all(16.0),
child: Text("Assets and Liabilities"),
),
Visibility(
visible: mainAccounts.isEmpty,
child: const Center(
child: Text('Accounts yet to be set up'),
),
),
...mainAccounts.map(
(a) => BankAccountCardClickable(
account: a,
Expand All @@ -52,7 +62,13 @@ class BankAccounts extends StatelessWidget {
const Divider(),
const Padding(
padding: EdgeInsets.all(16.0),
child: Text("Income and Expense Accounts"),
child: Text("Incomes and Expenses"),
),
Visibility(
visible: extAccounts.isEmpty,
child: const Center(
child: Text('Accounts yet to be set up'),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
Expand Down
9 changes: 6 additions & 3 deletions lib/pages/home/accounts/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ class _AccountsPageState extends State<AccountsPage> {
return Scaffold(
key: scaffoldStateKey,
appBar: appBar(context, "accounts"),
body: BankAccounts(
accounts: accounts,
onEdit: loadAccounts,
body: RefreshIndicator(
onRefresh: loadAccounts,
child: BankAccounts(
accounts: accounts,
onEdit: loadAccounts,
),
),
drawer: AppDrawer(
onRefresh: loadAccounts,
Expand Down
9 changes: 6 additions & 3 deletions lib/pages/home/entries/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,12 @@ class EntriesPageState extends State<EntriesPage> {
context,
"transactions",
),
body: EntriesListView(
entries: entries,
onDelete: entryOnDelete,
body: RefreshIndicator(
onRefresh: loadEntries,
child: EntriesListView(
entries: entries,
onDelete: entryOnDelete,
),
),
drawer: AppDrawer(
onRefresh: loadEntries,
Expand Down
Loading

0 comments on commit 04d7712

Please sign in to comment.