package name.abuchen.portfolio.checks.impl;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import name.abuchen.portfolio.Messages;
import name.abuchen.portfolio.checks.Check;
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.AccountTransferEntry;
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.PortfolioTransferEntry;
import name.abuchen.portfolio.model.Transaction;
import name.abuchen.portfolio.model.TransactionOwner;
import name.abuchen.portfolio.model.TransactionPair;
import name.abuchen.portfolio.money.CurrencyUnit;
/**
* Checks if there is at least one account or security without a currency.
*/
public class TransactionCurrencyCheck implements Check
{
public static class TransactionCurrencyQuickFix implements QuickFix
{
private TransactionPair<?> pair;
private String currencyCode;
public TransactionCurrencyQuickFix(Client client, TransactionPair<?> pair)
{
this.pair = pair;
// either take currency from account or from security. Use base
// currency as a fallback
this.currencyCode = pair.getOwner() instanceof Account ? ((Account) pair.getOwner()).getCurrencyCode()
: (pair.getOwner() instanceof Portfolio ? ((PortfolioTransaction) pair.getTransaction())
.getSecurity().getCurrencyCode() : client.getBaseCurrency());
}
@Override
public String getLabel()
{
return CurrencyUnit.getInstance(currencyCode).getLabel();
}
@Override
public String getDoneLabel()
{
return MessageFormat.format(Messages.FixAssignCurrencyCodeDone, currencyCode);
}
@Override
public void execute()
{
pair.getTransaction().setCurrencyCode(currencyCode);
// since currency fixes are only created if the currency is
// identical, we can safely set the currency on both transactions
if (pair.getTransaction().getCrossEntry() != null)
{
pair.getTransaction().getCrossEntry().getCrossTransaction(pair.getTransaction())
.setCurrencyCode(currencyCode);
}
}
}
private static class TransactionMissingCurrencyIssue implements Issue
{
private Client client;
private TransactionPair<Transaction> pair;
private boolean isFixable;
public TransactionMissingCurrencyIssue(Client client, TransactionPair<Transaction> pair)
{
this(client, pair, true);
}
public TransactionMissingCurrencyIssue(Client client, TransactionPair<Transaction> pair, boolean isFixable)
{
this.client = client;
this.pair = pair;
this.isFixable = isFixable;
}
@Override
public LocalDate getDate()
{
return pair.getTransaction().getDate();
}
@Override
public Object getEntity()
{
return pair.getOwner();
}
@Override
public Long getAmount()
{
return pair.getTransaction().getAmount();
}
@Override
public String getLabel()
{
String type = pair.getTransaction() instanceof AccountTransaction ? ((AccountTransaction) pair
.getTransaction()).getType().toString() : ((PortfolioTransaction) pair.getTransaction())
.getType().toString();
return MessageFormat.format(Messages.IssueTransactionMissingCurrencyCode, type);
}
@Override
public List<QuickFix> getAvailableFixes()
{
List<QuickFix> fixes = new ArrayList<QuickFix>();
fixes.add(new DeleteTransactionFix<Transaction>(client, pair.getOwner(), pair.getTransaction()));
if (isFixable)
fixes.add(new TransactionCurrencyQuickFix(client, pair));
return fixes;
}
}
@Override
public List<Issue> execute(Client client)
{
Set<Object> transactions = new HashSet<Object>();
for (Account account : client.getAccounts())
{
account.getTransactions()
.stream()
.filter(t -> t.getCurrencyCode() == null)
.forEach(t -> transactions.add(t.getCrossEntry() != null ? t.getCrossEntry()
: new TransactionPair<AccountTransaction>(account, t)));
}
for (Portfolio portfolio : client.getPortfolios())
{
portfolio.getTransactions()
.stream()
.filter(t -> t.getCurrencyCode() == null)
.forEach(t -> transactions.add(t.getCrossEntry() != null ? t.getCrossEntry()
: new TransactionPair<PortfolioTransaction>(portfolio, t)));
}
List<Issue> issues = new ArrayList<Issue>();
for (Object t : transactions)
{
if (t instanceof TransactionPair<?>)
{
@SuppressWarnings("unchecked")
TransactionPair<Transaction> pair = (TransactionPair<Transaction>) t;
issues.add(new TransactionMissingCurrencyIssue(client, pair));
}
else if (t instanceof BuySellEntry)
{
// attempt to fix it if both currencies are identical. If a fix
// involves currency conversion plus exchange rates, just offer
// to delete the transaction.
BuySellEntry entry = (BuySellEntry) t;
String accountCurrency = entry.getAccount().getCurrencyCode();
String securityCurrency = entry.getPortfolioTransaction().getSecurity().getCurrencyCode();
@SuppressWarnings("unchecked")
TransactionPair<Transaction> pair = new TransactionPair<Transaction>(
(TransactionOwner<Transaction>) entry.getOwner(entry.getAccountTransaction()),
entry.getAccountTransaction());
issues.add(new TransactionMissingCurrencyIssue(client, pair, Objects.equals(accountCurrency,
securityCurrency)));
}
else if (t instanceof AccountTransferEntry)
{
// same story as with purchases: only offer to fix if currencies
// match
AccountTransferEntry entry = (AccountTransferEntry) t;
String sourceCurrency = entry.getSourceAccount().getCurrencyCode();
String targetCurrency = entry.getTargetAccount().getCurrencyCode();
@SuppressWarnings("unchecked")
TransactionPair<Transaction> pair = new TransactionPair<Transaction>(
(TransactionOwner<Transaction>) entry.getOwner(entry.getSourceTransaction()),
entry.getSourceTransaction());
issues.add(new TransactionMissingCurrencyIssue(client, pair, Objects.equals(sourceCurrency,
targetCurrency)));
}
else if (t instanceof PortfolioTransferEntry)
{
// transferring a security involves no currency change because
// the currency is defined the security itself
PortfolioTransferEntry entry = (PortfolioTransferEntry) t;
@SuppressWarnings("unchecked")
TransactionPair<Transaction> pair = new TransactionPair<Transaction>(
(TransactionOwner<Transaction>) entry.getOwner(entry.getSourceTransaction()),
entry.getSourceTransaction());
issues.add(new TransactionMissingCurrencyIssue(client, pair));
}
else
{
throw new IllegalArgumentException();
}
}
return issues;
}
}