/*
* 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.test.db;
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import ru.orangesoftware.financisto2.filter.DateTimeCriteria;
import ru.orangesoftware.financisto2.filter.WhereFilter;
import ru.orangesoftware.financisto2.model.Account;
import ru.orangesoftware.financisto2.model.Category;
import ru.orangesoftware.financisto2.model.Currency;
import ru.orangesoftware.financisto2.model.Total;
import ru.orangesoftware.financisto2.model.TransactionInfo;
import ru.orangesoftware.financisto2.test.builders.AccountBuilder;
import ru.orangesoftware.financisto2.test.builders.CategoryBuilder;
import ru.orangesoftware.financisto2.test.builders.CurrencyBuilder;
import ru.orangesoftware.financisto2.test.builders.DateTime;
import ru.orangesoftware.financisto2.test.builders.RateBuilder;
import ru.orangesoftware.financisto2.test.builders.TransactionBuilder;
import ru.orangesoftware.financisto2.test.builders.TransferBuilder;
import ru.orangesoftware.financisto2.utils.CurrencyCache;
import ru.orangesoftware.financisto2.utils.FuturePlanner;
import ru.orangesoftware.financisto2.utils.MonthlyViewPlanner;
import ru.orangesoftware.financisto2.utils.TransactionList;
import ru.orangesoftware.financisto2.utils.Utils;
import static ru.orangesoftware.financisto2.test.builders.DateTime.date;
public class PlannerTest extends AbstractDbTest {
Currency c1;
Account a1;
Currency c2;
Account a2;
Map<String, Category> categoriesMap;
Currency homeCurrency;
Date from = date(2011, 8, 1).atMidnight().asDate();
Date to = date(2011, 8, 16).atDayEnd().asDate();
Date now = date(2011, 8, 8).at(23, 20, 0, 0).asDate();
@Override
public void setUp() throws Exception {
super.setUp();
c1 = CurrencyBuilder.withDb(db).name("USD").title("Dollar").symbol("$").create();
c2 = CurrencyBuilder.withDb(db).name("SGD").title("Singapore Dollar").symbol("S$").makeDefault().create();
a1 = AccountBuilder.createDefault(db, c1);
a2 = AccountBuilder.createDefault(db, c2);
categoriesMap = CategoryBuilder.createDefaultHierarchy(categoryRepository);
homeCurrency = db.getHomeCurrency();
CurrencyCache.initialize(db);
}
public void test_should_generate_monthly_view_for_account() {
prepareData();
//a1
//0 2011-08-08 +1000 t1
//1 2011-08-09 -100 -> a2 t2
//2 2011-08-09 +40 r2
//3 2011-08-10 -500 t3
//4 2011-08-10 -50 r1
//5 2011-08-11 -100 -> a2 t4
//6 2011-08-12 +200 <- a2 t5-s2
//7 2011-08-12 +52 <- a2 r4
//8 2011-08-12 -50 r1
//9 2011-08-12 +30 <- a2 r6-s2
//10 2011-08-14 -100 t7
//11 2011-08-14 -50 r1
//12 2011-08-15 +400 t6
//13 2011-08-15 -210 -> a2 r3
//14 2011-08-15 -105 -> a2 r5
//15 2011-08-16 -50 r1
//16 2011-08-16 +40 r2
MonthlyViewPlanner planner = new MonthlyViewPlanner(db, a1, false, from, to, now);
TransactionList list = planner.getPlannedTransactionsWithTotals();
logTransactions(list.transactions);
assertTransactions(list.transactions,
date(2011, 8, 8), 1000,
date(2011, 8, 9), -100,
date(2011, 8, 9), 40,
date(2011, 8, 10), -500,
date(2011, 8, 10), -50,
date(2011, 8, 11), -100,
date(2011, 8, 12), 200,
date(2011, 8, 12), 52,
date(2011, 8, 12), -50,
date(2011, 8, 12), 30,
date(2011, 8, 14), -100,
date(2011, 8, 14), -50,
date(2011, 8, 15), 400,
date(2011, 8, 15), -210,
date(2011, 8, 15), -105,
date(2011, 8, 16), -50,
date(2011, 8, 16), 40
);
assertAmount(447, a1.currency, list.totals[0]);
}
public void test_should_generate_credit_card_statement() {
prepareData();
MonthlyViewPlanner planner = new MonthlyViewPlanner(db, a1, true, from, to, now);
TransactionList statement = planner.getCreditCardStatement();
List<TransactionInfo> transactions = statement.transactions;
logTransactions(transactions);
assertTransactions(transactions,
//payments
DateTime.NULL_DATE, 0,
date(2011, 8, 15), 400,
//credits
DateTime.NULL_DATE, 0,
date(2011, 8, 8), 1000,
date(2011, 8, 9), 40,
date(2011, 8, 12), 200,
date(2011, 8, 12), 52,
date(2011, 8, 12), 30,
date(2011, 8, 16), 40,
//expenses
DateTime.NULL_DATE, 0,
date(2011, 8, 9), -100,
date(2011, 8, 10), -500,
date(2011, 8, 10), -50,
date(2011, 8, 11), -100,
date(2011, 8, 12), -50,
date(2011, 8, 14), -100,
date(2011, 8, 14), -50,
date(2011, 8, 15), -210,
date(2011, 8, 15), -105,
date(2011, 8, 16), -50
);
// 400 gets excluded as payment
assertAmount(47, a1.currency, statement.totals[0]);
}
public void test_should_generate_monthly_preview_for_the_next_month_correctly(){
prepareData();
from = date(2011, 9, 1).atMidnight().asDate();
to = date(2011, 9, 16).atDayEnd().asDate();
//2011-09-02 -50 r1
//2011-09-02 +52 <- a2 r4
//2011-09-02 +30 <- a2 r6
//2011-09-04 -50 r1
//2011-09-06 -50 r1
//2011-08-06 +40 r2
//2011-09-08 -50 r1
//2011-09-09 +52 <- a2 r4
//2011-09-09 +30 <- a2 r6
//2011-09-10 -50 r1
//2011-09-12 -50 r1
//2011-09-13 +40 r2
//2011-09-14 -50 r1
//2011-09-16 -50 r1
//2011-09-16 +52 <- a2 r4
//2011-09-16 +30 <- a2 r6
MonthlyViewPlanner planner = new MonthlyViewPlanner(db, a1, false, from, to, now);
List<TransactionInfo> transactions = planner.getPlannedTransactions();
logTransactions(transactions);
assertTransactions(transactions,
date(2011, 9, 1), -50,
date(2011, 9, 2), 52,
date(2011, 9, 2), 30,
date(2011, 9, 3), -50,
date(2011, 9, 5), -50,
date(2011, 9, 6), 40,
date(2011, 9, 7), -50,
date(2011, 9, 9), 52,
date(2011, 9, 9), -50,
date(2011, 9, 9), 30,
date(2011, 9, 11), -50,
date(2011, 9, 13), -50,
date(2011, 9, 13), 40,
date(2011, 9, 15), -50,
date(2011, 9, 16), 52,
date(2011, 9, 16), 30
);
}
public void test_should_generate_monthly_preview_for_the_previous_month_correctly(){
prepareData();
from = date(2011, 7, 1).atMidnight().asDate();
to = date(2011, 7, 16).atDayEnd().asDate();
MonthlyViewPlanner planner = new MonthlyViewPlanner(db, a1, false, from, to, now);
List<TransactionInfo> transactions = planner.getPlannedTransactions();
logTransactions(transactions);
assertTransactions(transactions,
date(2011, 7, 9), 122
);
}
public void test_should_generate_future_preview() {
prepareData();
TransactionList transactions = planTransactions(date(2011, 7, 1), date(2011, 7, 19));
// well, this is going to be impossible to re-verify if something breaks :)
assertTransactions2(transactions.transactions,
0, date(2011, 7, 1), -50, "x1",
1, date(2011, 7, 3), -50, "x1",
2, date(2011, 7, 5), -50, "x1",
3, date(2011, 7, 7), -50, "x1",
4, date(2011, 7, 9), 122, "t0",
5, date(2011, 7, 9), -50, "x1",
6, date(2011, 7, 11), -50, "x1",
7, date(2011, 7, 13), -50, "x1",
8, date(2011, 7, 15), -50, "x1",
9, date(2011, 7, 17), -50, "x1",
10, date(2011, 7, 19), -50, "x1");
assertTrue(transactions.totals[0].isError());
RateBuilder.withDb(db).from(c1).to(c2).at(DateTime.date(2011, 7, 1)).rate(2.0f).create();
transactions = planTransactions(date(2011, 7, 1), date(2011, 7, 19));
assertFalse(transactions.totals[0].isError());
assertAmount(2*122, 10*(-50), homeCurrency, transactions.totals[0]);
transactions = planTransactions(date(2011, 7, 20), date(2011, 8, 4));
assertTransactions2(transactions.transactions,
11, date(2011, 7, 21), -50, "x1",
12, date(2011, 7, 23), -50, "x1",
13, date(2011, 7, 25), -50, "x1",
14, date(2011, 7, 27), -50, "x1",
15, date(2011, 7, 29), -50, "x1",
16, date(2011, 7, 31), -50, "x1",
17, date(2011, 8, 2), -50, "r1",
18, date(2011, 8, 2), -50, "x1",
19, date(2011, 8, 2), 40, "r2",
20, date(2011, 8, 4), -50, "r1",
21, date(2011, 8, 4), -50, "x1");
transactions = planTransactions(date(2011, 8, 5), date(2011, 8, 15));
assertTransactions2(transactions.transactions,
22, date(2011, 8, 5), -600, "r4",
23, date(2011, 8, 5), -120, "r6",
24, date(2011, 8, 6), -50, "r1",
25, date(2011, 8, 6), -50, "x1",
26, date(2011, 8, 8), 1000, "t1",
27, date(2011, 8, 8), -50, "r1",
28, date(2011, 8, 8), -50, "x1",
29, date(2011, 8, 9), -100, "t2",
30, date(2011, 8, 9), 40, "r2",
31, date(2011, 8, 10), -500, "t3",
32, date(2011, 8, 10), -50, "r1",
33, date(2011, 8, 10), -50, "x1",
34, date(2011, 8, 11), -100, "t4",
35, date(2011, 8, 12), -120, "t5",
36, date(2011, 8, 12), -600, "r4",
37, date(2011, 8, 12), -50, "r1",
38, date(2011, 8, 12), -50, "x1",
39, date(2011, 8, 12), -120, "r6",
40, date(2011, 8, 14), -100, "t7",
41, date(2011, 8, 14), -50, "r1",
42, date(2011, 8, 14), -50, "x1",
43, date(2011, 8, 15), 400, "t6",
44, date(2011, 8, 15), -210, "r3",
45, date(2011, 8, 15), -105, "r5"
);
}
private TransactionList planTransactions(DateTime start, DateTime end) {
now = start.atMidnight().asDate();
to = end.atDayEnd().asDate();
WhereFilter filter = WhereFilter.empty();
filter.put(new DateTimeCriteria(now.getTime(), to.getTime()));
FuturePlanner planner = new FuturePlanner(db, filter, now);
TransactionList data = planner.getPlannedTransactionsWithTotals();
logTransactions(data.transactions);
return data;
}
private void prepareData() {
// regular transactions and transfers
//t0
TransactionBuilder.withDb(db).dateTime(date(2011, 7, 9).atNoon())
.account(a1).amount(122).note("t0").create();
//t1
TransactionBuilder.withDb(db).dateTime(date(2011, 8, 8).atNoon())
.account(a1).amount(1000).note("t1").create();
// regular transfer
//t2
TransferBuilder.withDb(db).dateTime(date(2011, 8, 9).atNoon())
.fromAccount(a1).fromAmount(-100).toAccount(a2).toAmount(50).note("t2").create();
// regular split
//t3
TransactionBuilder.withDb(db).dateTime(date(2011, 8, 10).atNoon())
.account(a1).amount(-500)
.withSplit(categoriesMap.get("A1"), -200, "t3-s1")
.withSplit(categoriesMap.get("A1"), -300, "t3-s2")
.note("t3")
.create();
// transfer split
//t4
TransactionBuilder.withDb(db).dateTime(date(2011, 8, 11).atNoon())
.account(a1).amount(-100)
.withTransferSplit(a2, -100, 20, "t4-s1")
.note("t4")
.create();
//t5
TransactionBuilder.withDb(db).dateTime(date(2011, 8, 12).atNoon())
.account(a2).amount(-120)
.withSplit(categoriesMap.get("B"), -20, "t5-s1")
.withTransferSplit(a1, -100, 200, "t5-s2")
.note("t5")
.create();
// payment
//t6
TransactionBuilder.withDb(db).dateTime(date(2011, 8, 15).atNoon()).account(a1).amount(400).ccPayment().note("t6").create();
//scheduled once
//t7
TransactionBuilder.withDb(db).scheduleOnce(date(2011, 8, 14).atNoon()).account(a1).amount(-100).note("t7").create();
//scheduled recur
//r1
TransactionBuilder.withDb(db).scheduleRecur("2011-08-02T21:40:00~DAILY:interval@2#~INDEFINETELY:null")
.account(a1).amount(-50).note("r1").create();
//r2
TransactionBuilder.withDb(db).scheduleRecur("2011-08-02T23:00:00~WEEKLY:days@TUE#interval@1#~INDEFINETELY:null")
.account(a1).amount(+40).note("r2").create();
//this should not be included because the account is differ
TransactionBuilder.withDb(db).scheduleRecur("2011-07-01T21:40:00~DAILY:interval@2#~INDEFINETELY:null")
.account(a2).amount(-50).note("x1").create();
//these should not be included because the date is out of picture
TransactionBuilder.withDb(db).scheduleOnce(date(2011, 10, 14).at(13, 0, 0, 0))
.account(a1).amount(-500).note("x2?").create();
TransactionBuilder.withDb(db).scheduleRecur("2011-10-01T21:40:00~DAILY:interval@2#~INDEFINETELY:null")
.account(a1).amount(-500).note("x3?").create();
//this is a scheduled transfer which should appear in the monthly view
//r3
TransferBuilder.withDb(db).scheduleOnce(date(2011, 8, 15).at(13, 0, 0, 0))
.fromAccount(a1).fromAmount(-210).toAccount(a2).toAmount(51).note("r3").create();
//r4
TransferBuilder.withDb(db).scheduleRecur("2011-08-02T21:20:00~WEEKLY:days@FRI#interval@1#~INDEFINETELY:null")
.fromAccount(a2).fromAmount(-600).toAccount(a1).toAmount(52).note("r4").create();
//this is a scheduled split with a transfer which should appear in the monthly view
//r5
TransactionBuilder.withDb(db).scheduleOnce(date(2011, 8, 15).at(14, 0, 0, 0))
.account(a1).amount(-105)
.withSplit(categoriesMap.get("A1"), -5, "r5-s1")
.withTransferSplit(a2, -100, 22, "r5-s2")
.note("r5")
.create();
//r6
TransactionBuilder.withDb(db).scheduleRecur("2011-08-02T22:30:00~WEEKLY:days@FRI#interval@1#~INDEFINETELY:null")
.account(a2).amount(-120)
.withSplit(categoriesMap.get("B"), -20, "r6-s1")
.withTransferSplit(a1, -88, 30, "r6-s2")
.note("r6")
.create();
}
private void logTransactions(List<TransactionInfo> transactions) {
Log.d("PlannerTest", "===== Planned transactions: " + transactions.size() + " =====");
SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy");
for (TransactionInfo transaction : transactions) {
Log.d("PlannerTest", df.format(new Date(transaction.dateTime))+" "+ Utils.amountToString(Currency.EMPTY, transaction.fromAmount)+" "+transaction.note);
}
Log.d("PlannerTest", "==========");
}
private void assertTransactions(List<TransactionInfo> transactions, Object...data) {
int count = data.length/2;
if (count > transactions.size()) {
fail("Too few transactions. Expected "+count+", Got "+transactions.size());
}
if (count < transactions.size()) {
fail("Too many transactions. Expected "+count+", Got "+transactions.size());
}
for (int i=0; i<count; i++) {
assertTransaction("Row "+i, transactions.get(i), (DateTime)data[i*2], (Integer)data[i*2+1]);
}
}
private void assertTransaction(String row, TransactionInfo t, DateTime date, long expectedAmount) {
assertEquals(row, asDate(date), asDate(t.dateTime));
assertEquals(row, expectedAmount, t.fromAmount);
}
private void assertTransactions2(List<TransactionInfo> transactions, Object...data) {
int count = data.length/4;
if (count > transactions.size()) {
fail("Too few transactions. Expected "+count+", Got "+transactions.size());
}
if (count < transactions.size()) {
fail("Too many transactions. Expected "+count+", Got "+transactions.size());
}
for (int i=0; i<count; i++) {
assertTransaction2("Row " + (i+(Integer)data[i*4]), transactions.get(i), (DateTime) data[i * 4 + 1], (Integer) data[i * 4 + 2], (String) data[i * 4 + 3]);
}
}
private void assertTransaction2(String row, TransactionInfo t, DateTime date, long expectedAmount, String note) {
assertEquals(row+":"+note, asDate(date), asDate(t.dateTime));
assertEquals(row+":"+note, expectedAmount, t.fromAmount);
assertEquals(row+":"+note, note, t.note);
}
private Date asDate(DateTime date) {
return date.atMidnight().asDate();
}
private Date asDate(long date) {
return asDate(DateTime.fromTimestamp(date));
}
private void assertAmount(long expectedIncome, long expectedExpenses, Currency expectedCurrency, Total total) {
assertEquals(expectedCurrency, total.currency);
assertEquals(expectedIncome, total.income);
assertEquals(expectedExpenses, total.expenses);
assertEquals(expectedIncome+expectedExpenses, total.balance);
}
private void assertAmount(long expectedAmount, Currency expectedCurrency, Total total) {
assertEquals(expectedCurrency, total.currency);
assertEquals(expectedAmount, total.balance);
}
}