/* * Copyright (c) 2011 Denis Solonenko. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v2.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html */ package ru.orangesoftware.financisto2.db; import android.database.Cursor; import android.util.Log; import ru.orangesoftware.financisto2.filter.WhereFilter; import ru.orangesoftware.financisto2.filter.Criteria; import ru.orangesoftware.financisto2.model.Currency; import ru.orangesoftware.financisto2.model.Total; import ru.orangesoftware.financisto2.model.TotalError; import ru.orangesoftware.financisto2.model.TransactionInfo; import ru.orangesoftware.financisto2.rates.ExchangeRate; import ru.orangesoftware.financisto2.rates.ExchangeRateProvider; import ru.orangesoftware.financisto2.utils.CurrencyCache; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static ru.orangesoftware.financisto2.db.DatabaseAdapter.enhanceFilterForAccountBlotter; import static ru.orangesoftware.financisto2.db.DatabaseHelper.V_BLOTTER_FOR_ACCOUNT_WITH_SPLITS; /** * Created by IntelliJ IDEA. * User: Denis Solonenko * Date: 8/1/11 11:54 PM */ public class TransactionsTotalCalculator { public static final String[] BALANCE_PROJECTION = { "from_account_currency_id", "SUM(from_amount)"}; public static final String BALANCE_GROUPBY = "from_account_currency_id"; public static final String[] HOME_CURRENCY_PROJECTION = { "datetime", "from_account_currency_id", "from_amount", "to_account_currency_id", "to_amount", "original_currency_id", "original_from_amount" }; private final DatabaseAdapter db; private final WhereFilter filter; public TransactionsTotalCalculator(DatabaseAdapter db, WhereFilter filter) { this.db = db; this.filter = filter; } public Total[] getTransactionsBalance() { WhereFilter filter = this.filter; if (filter.getAccountId() == -1) { filter = excludeAccountsNotIncludedInTotalsAndSplits(filter); } Cursor c = db.db().query(V_BLOTTER_FOR_ACCOUNT_WITH_SPLITS, BALANCE_PROJECTION, filter.getSelection(), filter.getSelectionArgs(), BALANCE_GROUPBY, null, null); try { int count = c.getCount(); List<Total> totals = new ArrayList<Total>(count); while (c.moveToNext()) { long currencyId = c.getLong(0); long balance = c.getLong(1); Currency currency = CurrencyCache.getCurrency(db, currencyId); Total total = new Total(currency); total.balance = balance; totals.add(total); } return totals.toArray(new Total[totals.size()]); } finally { c.close(); } } public Total getAccountTotal() { Total[] totals = getTransactionsBalance(); return totals.length > 0 ? totals[0] : Total.ZERO; } public Total getBlotterBalanceInHomeCurrency() { Currency homeCurrency = db.getHomeCurrency(); return getBlotterBalance(homeCurrency); } public Total getBlotterBalance(Currency toCurrency) { WhereFilter filter = excludeAccountsNotIncludedInTotalsAndSplits(this.filter); return getBalanceInHomeCurrency(V_BLOTTER_FOR_ACCOUNT_WITH_SPLITS, toCurrency, filter); } public Total getAccountBalance(Currency toCurrency, long accountId) { WhereFilter filter = selectedAccountOnly(this.filter, accountId); return getBalanceInHomeCurrency(V_BLOTTER_FOR_ACCOUNT_WITH_SPLITS, toCurrency, filter); } private WhereFilter selectedAccountOnly(WhereFilter filter, long accountId) { WhereFilter copy = enhanceFilterForAccountBlotter(filter); copy.put(Criteria.eq("from_account_id", String.valueOf(accountId))); return copy; } private Total getBalanceInHomeCurrency(String view, Currency toCurrency, WhereFilter filter) { Log.d("Financisto", "Query balance: "+filter.getSelection()+" => "+ Arrays.toString(filter.getSelectionArgs())); Cursor c = db.db().query(view, HOME_CURRENCY_PROJECTION, filter.getSelection(), filter.getSelectionArgs(), null, null, null); try { try { long balance = calculateTotalFromCursor(db, c, toCurrency); Total total = new Total(toCurrency); total.balance = balance; return total; } catch (UnableToCalculateRateException e) { return new Total(e.toCurrency, TotalError.atDateRateError(e.fromCurrency, e.datetime)); } } finally { c.close(); } } private static long calculateTotalFromCursor(DatabaseAdapter db, Cursor c, Currency toCurrency) throws UnableToCalculateRateException { ExchangeRateProvider rates = db.getHistoryRates(); BigDecimal balance = BigDecimal.ZERO; while (c.moveToNext()) { balance = balance.add(getAmountFromCursor(db, c, toCurrency, rates, 0)); } return balance.longValue(); } public static Total calculateTotalFromListInHomeCurrency(DatabaseAdapter db, List<TransactionInfo> list) { try { Currency toCurrency = db.getHomeCurrency(); long[] balance = calculateTotalFromList(db, list, toCurrency); return Total.asIncomeExpense(toCurrency, balance[0], balance[1]); } catch (UnableToCalculateRateException e) { return new Total(e.toCurrency, TotalError.atDateRateError(e.fromCurrency, e.datetime)); } } public static long[] calculateTotalFromList(DatabaseAdapter db, List<TransactionInfo> list, Currency toCurrency) throws UnableToCalculateRateException { ExchangeRateProvider rates = db.getHistoryRates(); BigDecimal income = BigDecimal.ZERO; BigDecimal expenses = BigDecimal.ZERO; for (TransactionInfo t : list) { BigDecimal amount = getAmountFromTransaction(db, t, toCurrency, rates); if (amount.signum() > 0) { income = income.add(amount); } else { expenses = expenses.add(amount); } } return new long[]{income.longValue(),expenses.longValue()}; } public static BigDecimal getAmountFromCursor(DatabaseAdapter db, Cursor c, Currency toCurrency, ExchangeRateProvider rates, int index) throws UnableToCalculateRateException { long datetime = c.getLong(index++); long fromCurrencyId = c.getLong(index++); long fromAmount = c.getLong(index++); long toCurrencyId = c.getLong(index++); long toAmount = c.getLong(index++); long originalCurrencyId = c.getLong(index++); long originalAmount = c.getLong(index); return getConvertedAmount(db, toCurrency, rates, datetime, fromCurrencyId, fromAmount, toCurrencyId, toAmount, originalCurrencyId, originalAmount); } public static BigDecimal getAmountFromTransaction(DatabaseAdapter db, TransactionInfo ti, Currency toCurrency, ExchangeRateProvider rates) throws UnableToCalculateRateException { long datetime = ti.dateTime; long fromCurrencyId = ti.fromAccount.currency.id; long fromAmount = ti.fromAmount; long toCurrencyId = ti.toAccount != null ? ti.toAccount.currency.id : 0; long toAmount = ti.toAmount; long originalCurrencyId = ti.originalCurrency != null ? ti.originalCurrency.id : 0; long originalAmount = ti.originalFromAmount; return getConvertedAmount(db, toCurrency, rates, datetime, fromCurrencyId, fromAmount, toCurrencyId, toAmount, originalCurrencyId, originalAmount); } private static BigDecimal getConvertedAmount(DatabaseAdapter db, Currency toCurrency, ExchangeRateProvider rates, long datetime, long fromCurrencyId, long fromAmount, long toCurrencyId, long toAmount, long originalCurrencyId, long originalAmount) throws UnableToCalculateRateException { if (fromCurrencyId == toCurrency.id) { return BigDecimal.valueOf(fromAmount); } else if (toCurrencyId > 0 && toCurrencyId == toCurrency.id) { return BigDecimal.valueOf(-toAmount); } else if (originalCurrencyId > 0 && originalCurrencyId == toCurrency.id) { return BigDecimal.valueOf(originalAmount); } else { Currency fromCurrency = CurrencyCache.getCurrency(db, fromCurrencyId); ExchangeRate exchangeRate = rates.getRate(fromCurrency, toCurrency, datetime); if (exchangeRate == ExchangeRate.NA) { throw new UnableToCalculateRateException(fromCurrency, toCurrency, datetime); } else { double rate = exchangeRate.rate; return BigDecimal.valueOf(fromAmount).multiply(BigDecimal.valueOf(rate)); } } } private WhereFilter excludeAccountsNotIncludedInTotalsAndSplits(WhereFilter filter) { WhereFilter copy = WhereFilter.copyOf(filter); copy.eq("from_account_is_include_into_totals", "1"); copy.neq("category_id", "-1"); return copy; } }