package name.abuchen.portfolio.datatransfer.csv;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.text.ParseException;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import name.abuchen.portfolio.Messages;
import name.abuchen.portfolio.datatransfer.csv.CSVImporter.AmountField;
import name.abuchen.portfolio.datatransfer.csv.CSVImporter.Column;
import name.abuchen.portfolio.datatransfer.csv.CSVImporter.DateField;
import name.abuchen.portfolio.datatransfer.csv.CSVImporter.EnumField;
import name.abuchen.portfolio.datatransfer.csv.CSVImporter.Field;
import name.abuchen.portfolio.model.BuySellEntry;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.PortfolioTransaction.Type;
import name.abuchen.portfolio.model.PortfolioTransferEntry;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.Transaction.Unit;
import name.abuchen.portfolio.money.CurrencyUnit;
import name.abuchen.portfolio.money.Money;
/* package */class CSVPortfolioTransactionExtractor extends BaseCSVExtractor
{
/* package */ CSVPortfolioTransactionExtractor(Client client)
{
super(client, Messages.CSVDefPortfolioTransactions);
List<Field> fields = getFields();
fields.add(new DateField(Messages.CSVColumn_Date));
fields.add(new Field(Messages.CSVColumn_ISIN).setOptional(true));
fields.add(new Field(Messages.CSVColumn_TickerSymbol).setOptional(true));
fields.add(new Field(Messages.CSVColumn_WKN).setOptional(true));
fields.add(new Field(Messages.CSVColumn_SecurityName).setOptional(true));
fields.add(new AmountField(Messages.CSVColumn_Value));
fields.add(new Field(Messages.CSVColumn_TransactionCurrency).setOptional(true));
fields.add(new AmountField(Messages.CSVColumn_Fees).setOptional(true));
fields.add(new AmountField(Messages.CSVColumn_Taxes).setOptional(true));
fields.add(new AmountField(Messages.CSVColumn_GrossAmount).setOptional(true));
fields.add(new Field(Messages.CSVColumn_CurrencyGrossAmount).setOptional(true));
fields.add(new AmountField(Messages.CSVColumn_ExchangeRate).setOptional(true));
fields.add(new AmountField(Messages.CSVColumn_Shares));
fields.add(new EnumField<PortfolioTransaction.Type>(Messages.CSVColumn_Type, Type.class).setOptional(true));
fields.add(new Field(Messages.CSVColumn_Note).setOptional(true));
}
@Override
void extract(List<Item> items, String[] rawValues, Map<String, Column> field2column) throws ParseException
{
// if forex gross amount is available then assume that is the currency
// of the security to be created
// check if we have a security
Security security = getSecurity(rawValues, field2column, s -> {
String currency = getText(Messages.CSVColumn_CurrencyGrossAmount, rawValues, field2column);
if (currency == null || currency.isEmpty())
currency = getText(Messages.CSVColumn_TransactionCurrency, rawValues, field2column);
if (currency != null)
{
CurrencyUnit unit = CurrencyUnit.getInstance(currency.trim());
s.setCurrencyCode(unit == null ? getClient().getBaseCurrency() : unit.getCurrencyCode());
}
});
if (security == null)
throw new ParseException(MessageFormat.format(Messages.CSVImportMissingSecurity,
new StringJoiner(", ").add(Messages.CSVColumn_ISIN) //$NON-NLS-1$
.add(Messages.CSVColumn_TickerSymbol).add(Messages.CSVColumn_WKN)
.toString()),
0);
// check for the transaction amount
Money amount = getMoney(rawValues, field2column);
// determine type (if not explicitly given by import)
Type type = inferType(rawValues, field2column, amount);
// determine remaining fields
LocalDate date = getDate(Messages.CSVColumn_Date, rawValues, field2column);
if (date == null)
throw new ParseException(MessageFormat.format(Messages.CSVImportMissingField, Messages.CSVColumn_Date), 0);
Long shares = getShares(Messages.CSVColumn_Shares, rawValues, field2column);
Long fees = getAmount(Messages.CSVColumn_Fees, rawValues, field2column);
Long taxes = getAmount(Messages.CSVColumn_Taxes, rawValues, field2column);
String note = getText(Messages.CSVColumn_Note, rawValues, field2column);
Unit grossAmount = extractGrossAmount(rawValues, field2column, amount);
switch (type)
{
case BUY:
case SELL:
items.add(createBuySell(rawValues, field2column, type, security, amount, fees, taxes, date, note,
shares, grossAmount));
break;
case TRANSFER_IN:
case TRANSFER_OUT:
items.add(createTransfer(security, amount, fees, taxes, date, note, shares, grossAmount));
break;
case DELIVERY_INBOUND:
case DELIVERY_OUTBOUND:
items.add(createDelivery(rawValues, field2column, type, security, amount, fees, taxes, date, note,
shares, grossAmount));
break;
default:
throw new IllegalArgumentException(type.toString());
}
}
private Item createBuySell(String[] rawValues, Map<String, Column> field2column, Type type, Security security,
Money amount, Long fees, Long taxes, LocalDate date, String note, Long shares, Unit grossAmount)
throws ParseException
{
BuySellEntry entry = new BuySellEntry();
entry.setType(type);
entry.setSecurity(security);
entry.setDate(date);
entry.setAmount(Math.abs(amount.getAmount()));
entry.setCurrencyCode(amount.getCurrencyCode());
entry.setShares(shares);
entry.setNote(note);
if (grossAmount != null)
entry.getPortfolioTransaction().addUnit(grossAmount);
if (fees != null && fees.longValue() != 0)
entry.getPortfolioTransaction()
.addUnit(new Unit(Unit.Type.FEE, Money.of(amount.getCurrencyCode(), Math.abs(fees))));
if (taxes != null && taxes.longValue() != 0)
entry.getPortfolioTransaction()
.addUnit(new Unit(Unit.Type.TAX, Money.of(amount.getCurrencyCode(), Math.abs(taxes))));
if (grossAmount == null)
createGrossValueIfNecessary(rawValues, field2column, entry.getPortfolioTransaction());
return new BuySellEntryItem(entry);
}
private void createGrossValueIfNecessary(String[] rawValues, Map<String, Column> field2column,
PortfolioTransaction transaction) throws ParseException
{
if (transaction.getSecurity().getCurrencyCode().equals(transaction.getCurrencyCode()))
return;
BigDecimal exchangeRate = getBigDecimal(Messages.CSVColumn_ExchangeRate, rawValues, field2column);
if (exchangeRate != null && exchangeRate.compareTo(BigDecimal.ZERO) != 0)
{
Money grossValue = transaction.getGrossValue();
Money forex = Money.of(transaction.getSecurity().getCurrencyCode(), Math
.round(exchangeRate.multiply(BigDecimal.valueOf(grossValue.getAmount())).doubleValue()));
exchangeRate = BigDecimal.ONE.divide(exchangeRate, 10, BigDecimal.ROUND_HALF_DOWN);
transaction.addUnit(new Unit(Unit.Type.GROSS_VALUE, grossValue, forex, exchangeRate));
}
}
private Item createTransfer(Security security, Money amount, Long fees, Long taxes, LocalDate date, String note,
Long shares, Unit grossAmount)
{
PortfolioTransferEntry entry = new PortfolioTransferEntry();
entry.setSecurity(security);
entry.setDate(date);
entry.setAmount(Math.abs(amount.getAmount()));
entry.setCurrencyCode(amount.getCurrencyCode());
entry.setShares(shares);
entry.setNote(note);
return new PortfolioTransferItem(entry);
}
private Item createDelivery(String[] rawValues, Map<String, Column> field2column, Type type, Security security,
Money amount, Long fees, Long taxes, LocalDate date, String note, Long shares, Unit grossAmount)
throws ParseException
{
PortfolioTransaction t = new PortfolioTransaction();
t.setType(type);
t.setSecurity(security);
t.setDate(date);
t.setAmount(Math.abs(amount.getAmount()));
t.setCurrencyCode(amount.getCurrencyCode());
t.setShares(shares);
t.setNote(note);
if (grossAmount != null)
t.addUnit(grossAmount);
if (fees != null && fees.longValue() != 0)
t.addUnit(new Unit(Unit.Type.FEE, Money.of(amount.getCurrencyCode(), Math.abs(fees))));
if (taxes != null && taxes.longValue() != 0)
t.addUnit(new Unit(Unit.Type.TAX, Money.of(amount.getCurrencyCode(), Math.abs(taxes))));
if (grossAmount == null)
createGrossValueIfNecessary(rawValues, field2column, t);
return new TransactionItem(t);
}
private Type inferType(String[] rawValues, Map<String, Column> field2column, Money amount) throws ParseException
{
Type type = getEnum(Messages.CSVColumn_Type, PortfolioTransaction.Type.class, rawValues, field2column);
if (type == null)
type = amount.isNegative() ? Type.BUY : Type.SELL;
return type;
}
private Unit extractGrossAmount(String[] rawValues, Map<String, Column> field2column, Money amount)
throws ParseException
{
Long grossAmount = getAmount(Messages.CSVColumn_GrossAmount, rawValues, field2column);
String currencyCode = getCurrencyCode(Messages.CSVColumn_CurrencyGrossAmount, rawValues, field2column);
BigDecimal exchangeRate = getBigDecimal(Messages.CSVColumn_ExchangeRate, rawValues, field2column);
// if no currency code is given, let's assume the gross amount is in the
// same currency as the transaction itself. Either way, if the gross
// amount currency equals the transaction currency, no unit is created
if (currencyCode == null || amount.getCurrencyCode().equals(currencyCode))
return null;
// if no gross amount is given at all, no unit
if (grossAmount == null || grossAmount.longValue() == 0)
return null;
// if no exchange rate is available, not unit to create
if (exchangeRate == null || exchangeRate.compareTo(BigDecimal.ZERO) == 0)
return null;
Money forex = Money.of(currencyCode, Math.abs(grossAmount.longValue()));
BigDecimal grossAmountConverted = exchangeRate.multiply(BigDecimal.valueOf(grossAmount));
Money converted = Money.of(amount.getCurrencyCode(), Math.round(grossAmountConverted.doubleValue()));
return new Unit(Unit.Type.GROSS_VALUE, converted, forex, exchangeRate);
}
}