Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Check model for missing cross entries
Issue: #22
- Loading branch information
Showing
19 changed files
with
1,271 additions
and
9 deletions.
There are no files selected for viewing
253 changes: 253 additions & 0 deletions
253
name.abuchen.portfolio.tests/src/name/abuchen/portfolio/checks/impl/CrossEntryCheckTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
package name.abuchen.portfolio.checks.impl; | ||
|
||
import static org.hamcrest.Matchers.hasItem; | ||
import static org.hamcrest.Matchers.instanceOf; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.hamcrest.Matchers.notNullValue; | ||
import static org.junit.Assert.assertThat; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import name.abuchen.portfolio.checks.Issue; | ||
import name.abuchen.portfolio.checks.QuickFix; | ||
import name.abuchen.portfolio.model.Account; | ||
import name.abuchen.portfolio.model.AccountTransaction; | ||
import name.abuchen.portfolio.model.BuySellEntry; | ||
import name.abuchen.portfolio.model.Client; | ||
import name.abuchen.portfolio.model.Portfolio; | ||
import name.abuchen.portfolio.model.PortfolioTransaction; | ||
import name.abuchen.portfolio.model.Security; | ||
import name.abuchen.portfolio.util.Dates; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
public class CrossEntryCheckTest | ||
{ | ||
private Client client; | ||
private Account account; | ||
private Portfolio portfolio; | ||
private Security security; | ||
|
||
@Before | ||
public void setupClient() | ||
{ | ||
client = new Client(); | ||
account = new Account(); | ||
client.addAccount(account); | ||
portfolio = new Portfolio(); | ||
client.addPortfolio(portfolio); | ||
security = new Security(); | ||
client.addSecurity(security); | ||
} | ||
|
||
@Test | ||
public void testEmptyClient() | ||
{ | ||
assertThat(new CrossEntryCheck().execute(client).size(), is(0)); | ||
} | ||
|
||
@Test | ||
public void testMissingSellInAccountIssue() | ||
{ | ||
portfolio.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.BUY, 1, 1, 1)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingBuySellAccountIssue.class)); | ||
assertThat(issues.get(0).getEntity(), is((Object) portfolio)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testMissingBuyInAccountIssue() | ||
{ | ||
portfolio.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.SELL, 1, 1, 1)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingBuySellAccountIssue.class)); | ||
assertThat(issues.get(0).getEntity(), is((Object) portfolio)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testThatCorrectBuySellEntriesAreNotReported() | ||
{ | ||
BuySellEntry entry = new BuySellEntry(portfolio, account); | ||
entry.setType(PortfolioTransaction.Type.BUY); | ||
entry.setDate(Dates.today()); | ||
entry.setSecurity(security); | ||
entry.setShares(1); | ||
entry.setAmount(100); | ||
entry.insert(); | ||
|
||
assertThat(new CrossEntryCheck().execute(client).size(), is(0)); | ||
} | ||
|
||
@Test | ||
public void testThatMatchingBuySellEntriesAreFixed() | ||
{ | ||
portfolio.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.SELL, 1, 1, 1)); | ||
|
||
account.addTransaction(new AccountTransaction(Dates.today(), security, // | ||
AccountTransaction.Type.SELL, 1)); | ||
|
||
assertThat(new CrossEntryCheck().execute(client).size(), is(0)); | ||
assertThat(portfolio.getTransactions().get(0).getCrossEntry(), notNullValue()); | ||
assertThat(account.getTransactions().get(0).getCrossEntry(), notNullValue()); | ||
} | ||
|
||
@Test | ||
public void testThatAlmostMatchingBuySellEntriesAreNotMatched() | ||
{ | ||
portfolio.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.SELL, 1, 1, 1)); | ||
|
||
account.addTransaction(new AccountTransaction(Dates.today(), security, // | ||
AccountTransaction.Type.SELL, 2)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
assertThat(issues.size(), is(2)); | ||
List<Object> objects = new ArrayList<Object>(issues); | ||
assertThat(objects, hasItem(instanceOf(MissingBuySellAccountIssue.class))); | ||
assertThat(objects, hasItem(instanceOf(MissingBuySellPortfolioIssue.class))); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testMissingAccountTransferOutIssue() | ||
{ | ||
account.addTransaction(new AccountTransaction(Dates.today(), security, // | ||
AccountTransaction.Type.TRANSFER_IN, 1)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingAccountTransferIssue.class)); | ||
assertThat(issues.get(0).getEntity(), is((Object) account)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testMissingAccountTransferInIssue() | ||
{ | ||
account.addTransaction(new AccountTransaction(Dates.today(), security, // | ||
AccountTransaction.Type.TRANSFER_OUT, 1)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingAccountTransferIssue.class)); | ||
assertThat(issues.get(0).getEntity(), is((Object) account)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testThatNotTheSameAccountIsMatched() | ||
{ | ||
Account second = new Account(); | ||
client.addAccount(second); | ||
|
||
account.addTransaction(new AccountTransaction(Dates.today(), security, // | ||
AccountTransaction.Type.TRANSFER_IN, 2)); | ||
|
||
AccountTransaction umatched = new AccountTransaction(Dates.today(), security, // | ||
AccountTransaction.Type.TRANSFER_OUT, 2); | ||
account.addTransaction(umatched); | ||
|
||
second.addTransaction(new AccountTransaction(Dates.today(), security, // | ||
AccountTransaction.Type.TRANSFER_OUT, 2)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingAccountTransferIssue.class)); | ||
|
||
assertThat(account.getTransactions(), hasItem(umatched)); | ||
assertThat(second.getTransactions().get(0).getCrossEntry(), notNullValue()); | ||
assertThat(second.getTransactions().get(0).getType(), is(AccountTransaction.Type.TRANSFER_OUT)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testMissingPortfolioTransferOutIssue() | ||
{ | ||
portfolio.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.TRANSFER_IN, 1, 1, 1)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingPortfolioTransferIssue.class)); | ||
assertThat(issues.get(0).getEntity(), is((Object) portfolio)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testMissingPortfolioTransferInIssue() | ||
{ | ||
portfolio.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.TRANSFER_OUT, 1, 1, 1)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingPortfolioTransferIssue.class)); | ||
assertThat(issues.get(0).getEntity(), is((Object) portfolio)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
@Test | ||
public void testThatNotTheSamePortfolioIsMatched() | ||
{ | ||
Portfolio second = new Portfolio(); | ||
client.addPortfolio(second); | ||
|
||
portfolio.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.TRANSFER_IN, 1, 3, 1)); | ||
|
||
PortfolioTransaction umatched = new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.TRANSFER_OUT, 1, 3, 1); | ||
portfolio.addTransaction(umatched); | ||
|
||
second.addTransaction(new PortfolioTransaction(Dates.today(), security, // | ||
PortfolioTransaction.Type.TRANSFER_OUT, 1, 3, 1)); | ||
|
||
List<Issue> issues = new CrossEntryCheck().execute(client); | ||
|
||
assertThat(issues.size(), is(1)); | ||
assertThat(issues.get(0), is(MissingPortfolioTransferIssue.class)); | ||
|
||
assertThat(portfolio.getTransactions(), hasItem(umatched)); | ||
assertThat(second.getTransactions().get(0).getCrossEntry(), notNullValue()); | ||
assertThat(second.getTransactions().get(0).getType(), is(PortfolioTransaction.Type.TRANSFER_OUT)); | ||
|
||
applyFixes(client, issues); | ||
} | ||
|
||
private void applyFixes(Client client, List<Issue> issues) | ||
{ | ||
for (Issue issue : issues) | ||
{ | ||
List<QuickFix> fixes = issue.getAvailableFixes(); | ||
assertThat(fixes.isEmpty(), is(false)); | ||
fixes.get(0).execute(); | ||
} | ||
assertThat(new CrossEntryCheck().execute(client).size(), is(0)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
name.abuchen.portfolio/META-INF/services/name.abuchen.portfolio.checks.Check
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
name.abuchen.portfolio.checks.impl.CrossEntryCheck |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
name.abuchen.portfolio/src/name/abuchen/portfolio/checks/Check.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package name.abuchen.portfolio.checks; | ||
|
||
import java.util.List; | ||
|
||
import name.abuchen.portfolio.model.Client; | ||
|
||
public interface Check | ||
{ | ||
List<Issue> execute(Client client); | ||
} |
31 changes: 31 additions & 0 deletions
31
name.abuchen.portfolio/src/name/abuchen/portfolio/checks/Checker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package name.abuchen.portfolio.checks; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.ServiceLoader; | ||
|
||
import name.abuchen.portfolio.model.Client; | ||
|
||
public class Checker | ||
{ | ||
private static List<Check> CHECKS; | ||
|
||
static | ||
{ | ||
CHECKS = new ArrayList<Check>(); | ||
Iterator<Check> iter = ServiceLoader.load(Check.class).iterator(); | ||
while (iter.hasNext()) | ||
CHECKS.add(iter.next()); | ||
} | ||
|
||
public static final List<Issue> runAll(Client client) | ||
{ | ||
List<Issue> answer = new ArrayList<Issue>(); | ||
|
||
for (Check check : CHECKS) | ||
answer.addAll(check.execute(client)); | ||
|
||
return answer; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
name.abuchen.portfolio/src/name/abuchen/portfolio/checks/Issue.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package name.abuchen.portfolio.checks; | ||
|
||
import java.util.Date; | ||
import java.util.List; | ||
|
||
public interface Issue | ||
{ | ||
Date getDate(); | ||
|
||
Object getEntity(); | ||
|
||
Long getAmount(); | ||
|
||
String getLabel(); | ||
|
||
List<QuickFix> getAvailableFixes(); | ||
} |
10 changes: 10 additions & 0 deletions
10
name.abuchen.portfolio/src/name/abuchen/portfolio/checks/QuickFix.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package name.abuchen.portfolio.checks; | ||
|
||
public interface QuickFix | ||
{ | ||
String getLabel(); | ||
|
||
String getDoneLabel(); | ||
|
||
void execute(); | ||
} |
40 changes: 40 additions & 0 deletions
40
name.abuchen.portfolio/src/name/abuchen/portfolio/checks/impl/AbstractAccountIssue.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package name.abuchen.portfolio.checks.impl; | ||
|
||
import java.util.Date; | ||
|
||
import name.abuchen.portfolio.checks.Issue; | ||
import name.abuchen.portfolio.model.Account; | ||
import name.abuchen.portfolio.model.AccountTransaction; | ||
import name.abuchen.portfolio.model.Client; | ||
|
||
/* package */abstract class AbstractAccountIssue implements Issue | ||
{ | ||
protected Client client; | ||
protected Account account; | ||
protected AccountTransaction transaction; | ||
|
||
public AbstractAccountIssue(Client client, Account account, AccountTransaction transaction) | ||
{ | ||
this.client = client; | ||
this.account = account; | ||
this.transaction = transaction; | ||
} | ||
|
||
@Override | ||
public Date getDate() | ||
{ | ||
return transaction.getDate(); | ||
} | ||
|
||
@Override | ||
public Account getEntity() | ||
{ | ||
return account; | ||
} | ||
|
||
@Override | ||
public Long getAmount() | ||
{ | ||
return transaction.getAmount(); | ||
} | ||
} |
Oops, something went wrong.